feat(benzinga): add benzinga#1406
Conversation
|
@greptileai review Auto-nudge from |
Greptile SummaryThis PR adds a complete Benzinga financial-data CLI (
Confidence Score: 5/5Safe to merge; novel commands, store layer, SQL gate, and shellout security are all well-constructed, and previous thread issues are resolved in the current diff. Novel commands open the store read-only, zero-timestamp rows are correctly skipped in --since/--window filters, the SQL injection gate is carefully layered (noise-strip + allowlist + multi-statement check + driver mode=ro), and the MCP shellout passes args as an exec slice with flag-injection blocking. The one new finding — the search tool overstating FTS5 operator support — is a documentation mismatch that misleads agents composing OR/NOT queries but does not break functionality or expose data. The search tool parameter description in internal/mcp/tools.go and the ftsMatchQuery implementation in internal/store/store.go are the only area warranting a follow-up correction. Important Files Changed
|
Add two read-only commands to the Benzinga CLI: - `wiims` -> GET /api/v1/wiims (Why Is It Moving): structured explanations of why a security is moving, tagged to a security. NOTE: endpoint is not in Benzinga's official OpenAPI specs; reverse-engineered and verified live (see patch entry's deferred_to_upstream note). - `news-quantified` -> GET /api/v2/newsquantified (Quantitative News Analytics): sentiment, volume ratios, per-ticker price reactions at 30s-120m horizons. Endpoint was in the captured spec but the generator never emitted a command for it. Both verified live (200, source: live). Registered in root.go, documented in README/SKILL, added to tools-manifest.json (auto- exposed via the curated MCP server's generic executor). Patch records under .printing-press-patches/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@greptileai review Auto-nudge from |
WIIM is not a standalone endpoint. The dedicated /api/v2/wiims path
404s on the gateway and /api/v1/wiims (previously used) is a dead
legacy route returning a single stale item. Benzinga delivers "Why
Is It Moving" as the `WIIM` channel of the news feed, verified
against a live production integration.
Repoint the wiims command to GET /api/v2/news with channels=WIIM
preset, mirroring news.get's camelCase query params (pageSize,
dateFrom, dateTo, updatedSince) and its bare-array response. Drop
the {"wiims":[...]} envelope unwrap (news returns a plain array).
WIIM requires a Benzinga News token entitled to the WIIM channel;
the market-data tokens return no items. Documented in the command
help, README, SKILL, and the patch record.
Verified live with a WIIM-entitled token: `wiims --date 2026-07-01
--page-size 50 --json` returns 50 items, all on the WIIM channel.
go build/vet/test pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@greptileai review Auto-nudge from |
The WIIM feed carries no numeric price move (only prose in the
title). Add --with-points to wiims: it collects the tickers from
the returned WIIM items, fetches GET /api/v1/quoteDelayed in
batches of 50, and attaches price_move:[{ticker, points, percent,
last}] to each item (the day's move in points vs previous close).
Both calls use the SAME credential — a Benzinga Pro token entitles
the WIIM news channel and delayed quotes alike — so no second token
slot is needed. Verified live with a Pro token: each WIIM item is
enriched with its delayed-quote move. go build/vet/test pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@greptileai review Auto-nudge from |
Benzinga entitlements are split and no single token covers everything: the news/Pro token serves news, WIIM, fundamentals, signals, and delayed quotes but 401s on market data; the market/ super-token serves movers, bars, short interest, logos, and the calendar but returns nothing for the WIIM channel. With one credential slot, users had to swap tokens between commands. Add a distinct market token (MarketApiKey, env BENZINGA_MARKET_API_KEY, `auth set-token --market`) and resolve the request token per path via Config.TokenForPath: market-data and calendar paths use the market token when set, everything else uses the default. Unset product tokens fall back to the default, so single-token setups are unchanged. This also fixes the duplicate `toml:"api_key"` tag that CalendarApiKey shared with BenzingaApiKey (silent credential loss / invalid TOML on round-trip) in both config.go and cliutil/credentials.go — CalendarApiKey now uses toml:"calendar_api_key". No migration needed: CalendarApiKey was never independently persisted, and existing api_key files read unchanged. Verified live: with api_key=<Pro> and market_api_key=<Market> stored as distinct TOML keys, `wiims` (Pro) and `market` movers (Market) both work in one session with no swap. go build/vet/test pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@greptileai review Auto-nudge from |
| if binaryResponse { | ||
| encoded := base64.StdEncoding.EncodeToString(data) | ||
| out, err := json.Marshal(map[string]any{ | ||
| "content_encoding": "base64", | ||
| "data_base64": encoded, | ||
| "byte_count": len(data), | ||
| }) | ||
| if err != nil { | ||
| return mcplib.NewToolResultError(fmt.Sprintf("encoding binary result: %v", err)), nil | ||
| } | ||
| if len(out) > bound.MaxBytes { | ||
| return mcplib.NewToolResultError(fmt.Sprintf("binary response is too large for MCP text output: %d response bytes encode to %d base64 bytes and %d MCP result bytes, exceeding the %d byte budget. Use the companion CLI command with --output <file> to save the payload locally.", len(data), len(encoded), len(out), bound.MaxBytes)), nil | ||
| } | ||
| return mcplib.NewToolResultText(string(out)), nil |
There was a problem hiding this comment.
MCP binary response is double-encoded
When binaryResponse=true, data is already the JSON envelope produced by client.wrapBinaryResponse — {"_pp_binary":true,"content_type":"...","encoding":"base64","bytes":N,"data":"<base64 of raw bytes>"}. The handler then base64-encodes the entire JSON envelope string a second time and places it in data_base64. To recover the actual binary, a caller would have to: (1) base64-decode data_base64 → JSON string, (2) JSON-parse that envelope, (3) base64-decode the inner data field — three layers instead of one.
Additionally, byte_count is set to len(data) (length of the JSON envelope bytes, which includes the base64 overhead and JSON syntax) rather than the raw binary byte count that lives in the envelope's bytes field.
The wrapBinaryResponse envelope is already a valid JSON result; the MCP handler should return it directly via mcpToolResultText rather than adding another encoding layer.
Two correctness fixes for the hand-built novel commands: - novelStore now opens the mirror with OpenReadOnlyContext instead of OpenWithContext. All six novel commands (watch/why/catalysts/ analyst-accuracy/earnings-season/insider-cluster) are read-only and only SELECT, so opening read-only enforces the contract at the driver level, skips the schema-migration write path, and lets a concurrent `sync` proceed without blocking on the WAL write lock. - watch and why filtered with `!when.IsZero() && when.Before( cutoff)`, letting rows whose timestamp doesn't parse bypass the always-active --since/--window cutoff and surface as if recent. Zero-time rows are now skipped. (catalysts already drops them.) Verified read-only why/watch against a 1.6GB synced mirror (exit 0, results returned). go build/vet/test pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@greptileai review Auto-nudge from |
benzinga
Every Benzinga calendar, news, fundamentals, and signal endpoint as a typed command — plus an offline SQLite store, full-text search, cross-entity queries, and the first Benzinga MCP server.
API: benzinga | Category: other | Press version: 4.27.0
Spec: Official Benzinga OpenAPI specs (Benzinga/benzinga-docs), all on
api.benzinga.comwith?token=authPublication Path
New print
CLI Shape
Novel Commands
watchwhycatalystsanalyst-accuracyearnings-seasoninsider-clusterWhat This CLI Does
Benzinga's licensed financial-data API is powerful but fragmented across ~60 endpoints and has only one complete client — a Python library with no CLI, no offline store, and no agent surface. This CLI covers the full documented REST surface as first-class commands across news, 16 calendar products, signals, analyst intelligence, congressional/insider trades, fundamentals, market data, logos, and ticker trends. It delta-syncs the calendar/news/signal families into a local SQLite database via the API's own
updatedcursors (offline FTS5 search), and adds six cross-entity commands the REST API cannot express in a single call:watch,why,catalysts,analyst-accuracy,earnings-season, andinsider-cluster. Ships the first-ever Benzinga MCP server (Cloudflare search+execute pattern for the 60+ tool surface).Manuscripts
Validation Results
Publish Live Gate
Full live dogfood reran at publish time and passed (220/220, status: pass) against the real Benzinga API. Proof: phase5 acceptance marker.
Known Gaps
Two Benzinga endpoints were upstream-down at build time and were intentionally omitted (documented in the CLI README
## Known Gaps): the standaloneearnings-call-transcriptsendpoints (HTTP 503 across all tokens — delivery-service outage) and the deprecatedfundamentals operationRatios-v2path (HTTP 500; the working v2.1 variant ships). Benzinga tokens are product-scoped, so a valid token can return 403 on a product not in your plan — a licensing boundary the CLI surfaces clearly.