mirror of
https://github.com/jackwener/OpenCLI.git
synced 2026-09-14 18:25:42 +08:00
fix/external-cli-alias-display
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6f597a2a4b |
feat: 8 read adapters across 5 sites (arxiv / SO / coingecko / wikipedia / hf) (#1338)
* feat: add 13 read adapters across 6 sites (github / arxiv / SO / coingecko / wikipedia / hf)
New site:
- github: user, repo, search-repos, user-repos, releases (unauth REST API; 60 req/h IP limit)
Existing sites — gap-fill for high-traffic verticals:
- arxiv author (papers by author, newest first; au:"name" phrase match on the public Atom API)
- stackoverflow user / tag (Stack Exchange API 2.3, with HTML-entity decode for display names / titles)
- coingecko coin / trending (single-coin market detail; 24h trending search-volume)
- wikipedia page (full plain-text article extract; opt-in --paragraphs cap, no silent truncation)
- hf models / datasets (downloads/likes/trending/freshness sorted lists)
All adapters use Node-side func + typed errors per the post-#1332 convention:
- ArgumentError for invalid limit / bad enum / empty positional / malformed owner-repo
- EmptyResultError for genuinely-empty results (no silent return [])
- CommandExecutionError for upstream HTTP/JSON failures (rate limit / 5xx / parse)
- AuthRequiredError reserved for endpoints that genuinely refuse anonymous traffic
- No silent clamp on --limit; no sentinel rows; no scalar 'unknown' / '-' fallbacks
Audit gates locally green:
- check:typed-error-lint 196/196 (no new)
- check:silent-column-drop 103/103 (no new)
- check:doc-coverage --strict 113/113 (added github.md, extended 5 existing pages)
- advise:listing-id-pairing advisory only (+2 wikipedia entries: title is the
round-trippable key into wikipedia/page; not a gate)
* chore: drop github adapter set per WAWQAQ directive
WAWQAQ (#opencli-pr-review): "我们不需要GitHub的adapter,因为已经有GH了"
Removes the 5 github commands + utils + docs added in 664ed1aa
(github/user, github/repo, github/search-repos, github/releases,
github/user-repos). The remaining 8 read commands across 5 sites
(arxiv author, stackoverflow user/tag, coingecko coin/trending,
wikipedia page, hf models/datasets) are unaffected.
Audit gates re-checked:
- check:typed-error-lint: 196/196 (baseline unchanged)
- check:silent-column-drop: 103/103 (baseline unchanged)
- doc-coverage: 112/112 (one less site documented)
- advise:listing-id-pairing: 12 advisory (unchanged)
* fix(adapter-expansion): tighten id and currency contracts
|
||
|
|
c1a4bd3b7e |
feat(stackoverflow): surface question_id on listings + new read <id> (#1293)
* feat(stackoverflow): surface question_id + metadata on listings, add `read <id>`
Agent-native gap: all 4 stackoverflow listings (`hot`, `search`,
`unanswered`, `bounties`) only emitted `[title, score, answers, url]`,
which means an agent could see a hot question but had no `id` to round-
trip into a body read, no `tags` to filter by topic, no `views` to gauge
demand, and no `is_answered` / `creation_date` / `author` to triage.
There also wasn't a `read` adapter, so reading a SO question through
opencli was impossible.
Listings (`hot` / `search` / `bounties` / `unanswered`):
- Add `rank`, `id` (question_id), `views`, `is_answered` (skipped on
`unanswered` since always false), `tags` (joined), `author`
(owner.display_name), `creation_date` columns.
- Pass `pagesize` to the upstream API instead of fetching the default
page and trimming locally.
New `stackoverflow read <id>`:
- 4-call fan-out against the public Stack Exchange API
(`/questions/{id}` + `/questions/{id}/comments` +
`/questions/{id}/answers` + batched `/answers/a;b;c/comments`).
- Returns `POST` + `Q-COMMENT` + `ANSWER` + `A-COMMENT` rows mirroring
the `hackernews read` and `lobsters read` shape.
- Accepted answer is always surfaced first and tagged `accepted='true'`;
remaining answers follow in descending vote order, capped by
`--answers-limit`.
- HTML body cleanup: tags stripped, `<pre><code>` preserved, `<code>`
inline-fenced, `<li>` rendered as `- `, comments indented with `> `.
- Entity decoding: a shared `decodeEntities` handles named (incl.
`…`/`©`/etc), decimal (`ö`), and hex (`'`)
forms, applied to both bodies AND `display_name` (otherwise users
like `Jonas Kölker` come through mojibaked).
- Typed fail-fast: `ArgumentError` for non-numeric id and
`--max-length < 100` (with no-fetch assertion); `EmptyResultError`
when `items` is empty; `CommandExecutionError` for HTTP non-2xx and
for Stack Exchange's in-band `error_id` envelopes (throttle / quota).
No silent clamps anywhere.
Tests: 14 vitest assertions
- 4 listing column-shape (incl. `unanswered` skipping `is_answered` and
`bounties` keeping its `bounty` column position)
- 10 read-adapter cases: registration / args / strategy + 3 typed-error
fail-fast paths (with no-fetch assertion on the pre-fetch ones) + the
full POST/Q-COMMENT/ANSWER/A-COMMENT row order with accepted-first +
the answer-comments fetch verified to batch ids semicolon-joined +
HTML entity decoding (named/decimal/hex) on both body and display_name
+ answers-limit honored when there are more answers than the cap.
Live verification:
- `stackoverflow hot --limit 2` → `id`/`tags`/`views`/`is_answered`/
`author` populated.
- `stackoverflow search "async await" --limit 1`,
`stackoverflow unanswered --limit 1` → same shape.
- `stackoverflow read 79935770` and the very-long classic question
`stackoverflow read 11227809 --answers-limit 1 --comments-limit 2`
→ produces the threaded POST/Q-COMMENT/ANSWER/A-COMMENT structure
with proper entity decoding (`Jonas Kölker` reads correctly).
- `stackoverflow read not-numeric` → exits with `ARGUMENT`.
- `stackoverflow read 999999999` → exits with `EMPTY_RESULT`.
* fix(stackoverflow): wrap fetch/json/coerce paths in typed errors
Apply the 3 lessons from PR #1292 (devto) review at merge time, before
B-group hits this PR:
1. CLI args may arrive as strings (e.g. `--max-length 50` → `'50'`).
The bare `Number.isInteger(value)` in `requirePositiveInt` /
`requireMinInt` would accept negative-but-coerced numbers and reject
string-form integers. Now the helpers `coerceInt` first then validate,
and the rejection message echoes the raw input via `JSON.stringify`.
2. `await fetch(url)` and `await res.json()` were not wrapped — a network
blip would surface as a raw `TypeError` and a maintenance HTML page
would surface as a raw `SyntaxError`. Both are now caught and rethrown
as `CommandExecutionError` with hints, matching the in-band error_id
path.
Tests: +3 cases (17 total)
- fetch network failure → CommandExecutionError
- malformed JSON body → CommandExecutionError
- string-form max-length "50" / "abc" rejected with ArgumentError before
fetching
* fix(stackoverflow): avoid partial read fanout
|