From aaa6b256e31dbb3294eac84dac3c4f39e375c25d Mon Sep 17 00:00:00 2001 From: Richie Caputo Date: Tue, 4 Aug 2026 13:14:55 -0400 Subject: [PATCH] Sync the quartr skill with the CLI's new behaviour The skill is what agents read at runtime, and the stack changed enough that parts of it were actively misleading. Recipe 3 told the reader to run `reports list --sort-by date`, which now exits 2. Recipe 9's `document-types list | grep 8-K` was searching a silently truncated page. Updated across SKILL.md, references/commands.md and references/recipes.md: - sorting: events-only, exit 2 elsewhere, and what to do instead - ticker collisions: `companies resolve`, EXCHANGE:TICKER, no name search - `--expand company` as a client-side join, and why attribution matters - downloads: `--output -`, status line on stderr - lookup tables return whole catalogs, so grepping them is trustworthy - 403 is entitlement and 401 is auth; stop debugging the key on a 403 - exit codes, including what a 2 means and that retrying will not help Co-Authored-By: Claude Opus 5 (1M context) --- .claude/skills/quartr/SKILL.md | 31 ++++++- .claude/skills/quartr/references/commands.md | 34 ++++++-- .claude/skills/quartr/references/recipes.md | 85 ++++++++++++++++---- 3 files changed, 124 insertions(+), 26 deletions(-) diff --git a/.claude/skills/quartr/SKILL.md b/.claude/skills/quartr/SKILL.md index d052ffd..e37c6b0 100644 --- a/.claude/skills/quartr/SKILL.md +++ b/.claude/skills/quartr/SKILL.md @@ -79,16 +79,19 @@ explicitly passed, `--all` raises it to 500 to minimize round-trips. ```bash # Companies quartr companies list --tickers AAPL,MSFT --fields id,name,country +quartr companies resolve CE # every company using that ticker quartr companies get 4742 --format json # Events (earnings calls, AGMs, etc.) quartr events list --tickers AAPL --sort-by date --direction desc --limit 10 +quartr events list --tickers NYSE:BLD --expand company --limit 5 quartr events get 406161 --format json # Transcripts -quartr transcripts list --tickers AAPL --expand event --limit 10 +quartr transcripts list --tickers AAPL --expand event,company --limit 10 quartr transcripts get --format json quartr transcripts download --output transcript.json +quartr transcripts download --output - | jq . # stream, no file quartr transcripts chapters --levels 1,2 # Reports (10-K, 10-Q, 8-K, etc.) @@ -111,9 +114,9 @@ quartr live list --states live,willBeLive quartr live transcripts list --states live quartr live transcripts stream --transcript-version 1.7 -# Lookup tables (use these to map names → IDs before filtering) +# Lookup tables (whole catalog, not a first page; map names → IDs before filtering) quartr event-types list --format csv # Q1=26, Q2=27, Q3=28, Q4=29 … -quartr document-types list --format csv # 10-K=11, 10-Q=7, 8-K=10, 20-F=13 … +quartr document-types list --format csv # 10-K=11, 10-Q=7, 8-K=10, 25=shareholder letter … ``` ## Escape hatch @@ -132,9 +135,29 @@ quartr request get /events --query tickers=AAPL --query limit=3 --format json - **Companies use `ids`, not `companyIds`.** The CLI auto-maps `--company-ids` to `ids` for the `companies` resource. Other resources use `companyIds`. This only matters when reading raw API responses or using `request get`. +- **Only `events list` can sort.** `--sort-by` (fields `id`, `date`) exists + nowhere else and is rejected with exit 2, because those endpoints return rows + in insertion order — the newest filing or call is often *not* on the first + page. To get the latest documents: sort events, then filter by `--event-ids`, + or pull with `--all` and sort locally on `createdAt`. `--direction` works + everywhere but reverses insertion order, not date order. +- **Tickers collide across exchanges.** `--tickers CE` returns Celanese, + Credito Emiliano and Cortus Energy. Run `quartr companies resolve ` + when a symbol might be shared, then either use `--company-ids` or qualify the + ticker as `NYSE:BLD` (the CLI resolves it to a companyId before querying). + There is no name search in the API — tickers and CIKs only. +- **`--expand company` is a client-side join.** The API rejects + `expand=company`; the CLI strips it and batch-fetches `/companies` instead. + Use it whenever rows need to be attributable — otherwise they carry only a + bare `companyId` and a collision is invisible. - **Tier-restricted endpoints** return `403 Forbidden` on the user's API tier. Observed restrictions: `events summary`, `audio list`, `live transcripts list`. - Surface the error verbatim — do not retry, hide, or silently fall back. + The CLI prints a `hint:` line clarifying that 403 is entitlement, not + authentication. Surface the error verbatim — do not retry, hide, silently + fall back, or start debugging the API key. A rejected key returns 401. +- **Downloads always write a file** named `-.` unless + `--output` says otherwise; the `Saved ` line goes to stderr. To pipe or + redirect the document itself, use `--output -`. - **Downloads don't send the API key by default.** The metadata response contains a public file URL. Add `--with-api-key` only if a 401/403 occurs fetching the file URL itself. diff --git a/.claude/skills/quartr/references/commands.md b/.claude/skills/quartr/references/commands.md index e5ffa16..92e9bd5 100644 --- a/.claude/skills/quartr/references/commands.md +++ b/.claude/skills/quartr/references/commands.md @@ -8,7 +8,7 @@ specific flag or endpoint at hand. | Command | Operations | Base path | Notes | |--------------------|---------------------------------------------|----------------------------|--------------------------------| | `auth` | `login`, `show`, `logout` | (local) | Manages `~/.config/quartr/config.json` | -| `companies` | `list`, `get` | `/companies` | Uses `ids` API param, not `companyIds` | +| `companies` | `list`, `get`, `resolve` | `/companies` | Uses `ids` API param, not `companyIds`; `resolve ` lists collision candidates | | `events` | `list`, `get`, `summary` | `/events` | `summary` is tier-restricted | | `documents` | `list`, `get`, `download` | `/documents` | Generic parent; prefer typed resources | | `reports` | `list`, `get`, `summary`, `pages`, `download` | `/documents/reports` | `fileUrl` is the download field | @@ -18,8 +18,8 @@ specific flag or endpoint at hand. | `live` | `list`, `get` | `/live` | Honors `transcriptVersion` | | `live audio` | `list`, `get`, `download` | `/live/audio` | Download field is `audio` | | `live transcripts` | `list`, `get`, `stream` | `/live/transcripts` | `list` may be tier-restricted; stream field is `transcript` | -| `event-types` | `list`, `get` | `/event-types` | Lookup table | -| `document-types` | `list`, `get` | `/document-types` | Lookup table | +| `event-types` | `list`, `get` | `/event-types` | Lookup table; `list` returns the whole catalog | +| `document-types` | `list`, `get` | `/document-types` | Lookup table; `list` returns the whole catalog | | `request` | `get` | (any path) | Escape hatch; `--query k=v --paginate` | `live audio` and `live transcripts` accept either `quartr live audio …` or @@ -50,7 +50,8 @@ Auth precedence: flags > env > config file > defaults. --all follow pagination.nextCursor (auto-bumps limit to 500 if not set) --fields a,b,c output columns; supports dotted paths ---tickers AAPL,MSFT comma-separated tickers +--tickers AAPL,MSFT comma-separated tickers; EXCHANGE:TICKER (NYSE:BLD) is + resolved to a companyId first, deduped case-insensitively --company-ids 4742 maps to "ids" for companies, "companyIds" elsewhere --countries US,GB ISO country codes --exchanges NYSE,NASDAQ exchange symbols @@ -61,19 +62,23 @@ Auth precedence: flags > env > config file > defaults. --end-date 2024-12-31 ISO 8601 --updated-after 2024-01-01 incremental sync lower bound --updated-before 2024-12-31 ---expand event merge related objects into response +--expand event,company event is expanded by the API; company is joined client-side --type-ids 1,2,3 events / documents* / transcripts* / reports* / slides* / audio* --event-ids 128301 documents* / transcripts* / reports* / slides* / audio* / live* --document-group-ids foo documents* / transcripts* / reports* / slides* --states live,willBeLive live, live-transcripts, live-audio --transcript-version 1.7 live, live-transcripts, transcripts (get only), audio (get only) ---sort-by date events list only +--sort-by id|date events list ONLY; rejected with exit 2 everywhere else --levels 1,2 chapters subcommand on reports/slides/transcripts/audio ``` Filter flags that aren't allowed for a resource are silently dropped; the allowed set is enforced by `paramSet` in `internal/cli/resources.go`. +Two exceptions are errors rather than silent drops, because silence produced +wrong answers: `--sort-by` on anything but `events list`, and `--expand +company` on rows that carry no companyId. Both exit 2. + ## Per-operation flags beyond list | Operation | Flag | Notes | @@ -86,7 +91,8 @@ allowed set is enforced by `paramSet` in `internal/cli/resources.go`. | ` summary` | `--fields` | Output columns | | ` pages` | list flags | reports, slides only | | ` chapters` | list flags + `--levels` | transcripts, audio only | -| ` download` | `--output PATH` | Defaults to `-.` in cwd | +| ` download` | `--output PATH` | Defaults to `-.` in cwd; `-` streams to stdout | +| ` download` | (status line) | `Saved ` goes to **stderr**, never stdout | | ` download` | `--url-field NAME` | Defaults to `fileUrl` (or resource-specific) | | ` download` | `--with-api-key` | Send `x-api-key` when fetching the file URL | | ` download` | `--expand` | On the metadata request | @@ -141,6 +147,16 @@ with `-`. - Up to 3 attempts with exponential backoff (250ms, 500ms) - Errors surface as `quartr api error: : ` on final failure +## Exit codes + +| Code | Meaning | +|------|---------| +| 0 | Success | +| 1 | The request failed (API error, network, missing key) | +| 2 | The command line was wrong: unsupported `--sort-by`, `--expand company` on rows without a company, an exchange-qualified ticker matching nothing, a bad global flag | + +A `2` is never transient. The message names the supported alternative. + ## Lookup tables (run these once, then reference in flags) ```bash @@ -174,6 +190,10 @@ quartr document-types list --format csv # … ``` +Both commands return the entire catalog (46 document types, 34 event types), +not a first page, so grepping their output is safe. Pass an explicit `--limit` +to page instead. + When the user names a filing form (10-K, 8-K, etc.), look up the `id` first and pass it via `--type-ids`. diff --git a/.claude/skills/quartr/references/recipes.md b/.claude/skills/quartr/references/recipes.md index e6efc21..8408e2c 100644 --- a/.claude/skills/quartr/references/recipes.md +++ b/.claude/skills/quartr/references/recipes.md @@ -30,6 +30,33 @@ For multiple tickers in one call: quartr companies list --tickers AAPL,MSFT,NVDA --fields id,name,country ``` +**Always check for a ticker collision first.** Quartr matches a ticker across +every exchange, so common US symbols also return foreign namesakes: + +```bash +quartr companies resolve CE +# id name country matchedTickers +# 5977 Celanese Corporation US NYSE:CE +# 16679 Credito Emiliano S.p.A. IT BIT:CE +# 16930 Cortus Energy SE OM:CE +``` + +`resolve` takes a ticker, an `EXCHANGE:TICKER` pair, or a CIK, and lists every +candidate with the exchange pairs that matched. There is no name search — the +API has no `search`/`query`/`name` parameter — so never try to look a company +up by name. + +Once the exchange is known, qualify the ticker anywhere `--tickers` is +accepted and the CLI resolves it to a companyId before querying: + +```bash +quartr events list --tickers NYSE:BLD --limit 5 +``` + +Known collisions seen in practice: `ACA` (Arcosa / Crédit Agricole), `BLD` +(TopBuild / Boral), `CE` (Celanese / Credito Emiliano), `WM`, `COST`, `TEL`, +`AVNT`. + When parsing programmatically, prefer: ```bash @@ -78,27 +105,22 @@ typeId, companyId, language, …}`. Three steps: -```bash -# 1. Get company ID (or use --tickers directly in step 2 if the API accepts it) -COMPANY_ID=$(quartr companies list --tickers AAPL --format json \ - | jq -r '.data[0].id') - -# 2. Find the latest 10-K (document type id 11) -quartr reports list --tickers AAPL --type-ids 11 \ - --sort-by date --direction desc --limit 1 \ - --fields id,fileUrl,eventId,createdAt -``` +**`reports list` cannot sort.** `--sort-by` exists only on `events list`; +everywhere else the CLI rejects it with exit 2, because the endpoint returns +rows in insertion order and the newest filing is often not on the first page. +Do not reach for `--sort-by` here, and do not trust the first page. -The `reports list` endpoint may not accept `--sort-by` (it's events-only); if -so, list and pick the highest-`createdAt`: +Pull a wide page and sort locally: ```bash -quartr reports list --tickers AAPL --type-ids 11 --limit 5 --format json \ +quartr reports list --tickers AAPL --type-ids 11 --all --format json \ | jq '.data | sort_by(.createdAt) | reverse | .[0]' ``` +`--all` matters: with a small `--limit` the newest report may simply be +absent. Then download it: + ```bash -# 3. Download quartr reports download --output apple-10k.pdf ``` @@ -137,6 +159,18 @@ quartr transcripts list --tickers AAPL --expand event --all --format json \ `--all` follows `pagination.nextCursor` and auto-bumps `--limit` to 500. `--expand event` merges the parent event object into each transcript row. +Add `company` to the expansion when the rows need to be attributable — rows +otherwise carry a bare `companyId`, which is how a ticker collision goes +unnoticed: + +```bash +quartr transcripts list --tickers AAPL --expand event,company --all --format json +``` + +`event` is expanded by the API; `company` is joined client-side by the CLI +(the API rejects `expand=company`), batching the distinct companyIds into +`/companies` lookups. It works on `list` and `get`. + Resulting shape: ```json @@ -173,6 +207,17 @@ quartr transcripts list --tickers AAPL --all --format json \ done ``` +To pipe one document straight through without touching disk, use +`--output -`; it streams the document to stdout and nothing else: + +```bash +quartr transcripts download 432907 --output - | jq -r '.transcript.text' +``` + +Without `--output -` a download always writes a file (the `Saved ` +confirmation goes to stderr), so `download > f.json` produces an empty +`f.json` and a file you did not name. + --- ## 5. Stream a live earnings transcript @@ -272,6 +317,10 @@ For event types (Q1/Q2/Q3/Q4 earnings calls, AGM, Investor Day): quartr event-types list --format csv ``` +Both lookup commands return the **whole** catalog (46 document types, 34 event +types) rather than one page, so a `grep` over them is trustworthy. Pass an +explicit `--limit` only if paging is wanted. + --- ## 10. Empty results vs errors — how to tell the user @@ -279,10 +328,16 @@ quartr event-types list --format csv | Output | Meaning | What to say | |-------------------------------------------|------------------------------------------------------|-------------| | `No rows` | Valid API response with empty `data` array | "No matches for those filters." | -| `quartr api error: 403 Forbidden: …` | API tier doesn't include this endpoint | Quote the error; suggest narrowing scope or contacting Quartr to upgrade. | +| `quartr api error: 403 Forbidden: …` | API tier doesn't include this endpoint (the CLI prints a `hint:` line saying so) | Quote the error; it is entitlement, not auth — do not re-check the key. Suggest contacting Quartr to upgrade. | +| `quartr api error: 401 Unauthorized: …` | The key itself was rejected | Check `quartr auth show` and `QUARTR_API_KEY`. | | `quartr api error: 400 Bad Request: …` | Bad parameter (e.g. unsupported `expand` value) | Check the message body — it usually names the offending field. | | `quartr api error: 404 Not Found: …` | ID doesn't exist | Verify the ID via a list query first. | | `missing API key; set QUARTR_API_KEY …` | No reachable credential | Ask user to set `QUARTR_API_KEY` or run `quartr auth login`. | Don't retry 403/400/404 — they're not transient. The CLI already handles 429/5xx with backoff. + +**Exit codes:** `0` success, `1` request failed, `2` the command line itself +was wrong (unsupported `--sort-by`, `--expand company` where rows have no +company, an exchange-qualified ticker that matches nothing). A `2` will never +be fixed by retrying — read the message, it names the alternative.