@trawlme/cli 3.11.0 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/commands/create.d.ts +0 -28
- package/dist/commands/create.js +0 -89
- package/dist/commands/doctor.d.ts +0 -79
- package/dist/commands/doctor.js +1 -187
- package/dist/commands/login.js +0 -67
- package/dist/commands/ping.d.ts +0 -15
- package/dist/commands/ping.js +0 -15
- package/dist/commands/scraps.d.ts +0 -120
- package/dist/commands/scraps.js +142 -656
- package/dist/commands/skills.js +0 -22
- package/dist/commands/spec.d.ts +0 -85
- package/dist/commands/spec.js +0 -67
- package/dist/commands/telemetry.js +0 -4
- package/dist/commands/token.js +0 -28
- package/dist/commands/upgrade.js +0 -22
- package/dist/commands/whoami.d.ts +0 -12
- package/dist/commands/whoami.js +0 -6
- package/dist/index.d.ts +0 -188
- package/dist/index.js +0 -349
- package/dist/lib/api.d.ts +0 -78
- package/dist/lib/api.js +1 -320
- package/dist/lib/cdp-pipe.d.ts +31 -0
- package/dist/lib/cdp-pipe.js +141 -0
- package/dist/lib/chrome-discovery.d.ts +1 -0
- package/dist/lib/chrome-discovery.js +30 -0
- package/dist/lib/chrome-launch.d.ts +8 -0
- package/dist/lib/chrome-launch.js +53 -0
- package/dist/lib/config.d.ts +0 -53
- package/dist/lib/config.js +0 -55
- package/dist/lib/confirm.d.ts +0 -55
- package/dist/lib/confirm.js +0 -47
- package/dist/lib/docs.d.ts +0 -123
- package/dist/lib/docs.js +0 -169
- package/dist/lib/errors.d.ts +0 -134
- package/dist/lib/errors.js +0 -151
- package/dist/lib/format.d.ts +0 -6
- package/dist/lib/format.js +0 -6
- package/dist/lib/json.d.ts +0 -35
- package/dist/lib/json.js +0 -48
- package/dist/lib/jwt.d.ts +0 -7
- package/dist/lib/jwt.js +0 -7
- package/dist/lib/pinch.d.ts +0 -53
- package/dist/lib/pinch.js +6 -112
- package/dist/lib/pinchAnimation.d.ts +0 -16
- package/dist/lib/pinchAnimation.js +8 -29
- package/dist/lib/posthog.d.ts +0 -9
- package/dist/lib/posthog.js +0 -23
- package/dist/lib/prompt.js +1 -20
- package/dist/lib/secure-transport.d.ts +1 -0
- package/dist/lib/secure-transport.js +15 -0
- package/dist/lib/session-capture-guard.d.ts +6 -0
- package/dist/lib/session-capture-guard.js +9 -0
- package/dist/lib/session-capture.d.ts +55 -0
- package/dist/lib/session-capture.js +319 -0
- package/dist/lib/skills.d.ts +0 -175
- package/dist/lib/skills.js +1 -216
- package/dist/lib/skillsNudge.d.ts +0 -17
- package/dist/lib/skillsNudge.js +0 -83
- package/dist/lib/spinner.d.ts +0 -39
- package/dist/lib/spinner.js +0 -40
- package/dist/lib/storage-state.d.ts +55 -0
- package/dist/lib/storage-state.js +96 -0
- package/dist/lib/tips.d.ts +0 -38
- package/dist/lib/tips.js +0 -77
- package/dist/lib/updateCheckWorker.js +0 -14
- package/dist/lib/updateNotifier.d.ts +0 -17
- package/dist/lib/updateNotifier.js +0 -53
- package/dist/lib/validate.d.ts +0 -8
- package/dist/lib/validate.js +0 -8
- package/dist/lib/version.d.ts +0 -12
- package/dist/lib/version.js +1 -13
- package/package.json +2 -2
package/dist/lib/docs.js
CHANGED
|
@@ -1,78 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* trawl_cli#185 — ONE source of truth for the docs URL(s) an agent (or
|
|
3
|
-
* human) can be pointed at from three surfaces: `spec --json` (top-level
|
|
4
|
-
* `docsUrl`/`llmsUrl` + a per-command `docs` deep link), a run's JSON
|
|
5
|
-
* payload (`doctor`/`data --errors`/`run-info`, keyed on `failureKind` —
|
|
6
|
-
* NOT errors.ts's unrelated `ErrorEnvelope.kind`, see the doc comment on
|
|
7
|
-
* `FAILURE_KIND_DOC_PATHS` below), and the `--help` footer. Three hardcoded
|
|
8
|
-
* lists here would diverge within months — that exact "same fact computed
|
|
9
|
-
* in two places" defect has recurred repeatedly across this epic — so every
|
|
10
|
-
* surface reads these same tables/functions, never its own copy.
|
|
11
|
-
*
|
|
12
|
-
* Resolution ladder (issue #185 — all three rungs REQUIRED, in this order,
|
|
13
|
-
* evaluated PER FIELD, never as one bundled decision — see `resolveDocsUrls`):
|
|
14
|
-
*
|
|
15
|
-
* 1. Prefer the server. `externalDocs.url` on the OpenAPI document at
|
|
16
|
-
* `<apiBase>/api/spec.json` is the standard OpenAPI field for exactly
|
|
17
|
-
* this, and reading it makes a self-hosted install work with ZERO CLI
|
|
18
|
-
* change. It is unset (null) server-side today, so this is
|
|
19
|
-
* forward-looking — build it anyway, and it must win outright over rung
|
|
20
|
-
* 2 when present. The one caller allowed to fetch it is `spec.ts`'s own
|
|
21
|
-
* action, bounded and swallowed-on-failure (mirrors lib/tips.ts's
|
|
22
|
-
* `isReferralProgramUserFacing`) — a deliberate, once-per-invocation
|
|
23
|
-
* agent probe, not "every command". Every other surface (an error
|
|
24
|
-
* payload on an arbitrary failing command, the `--help` footer) must
|
|
25
|
-
* never add a network call or a failure mode to a command nobody asked
|
|
26
|
-
* to hit the docs host for — see `resolveDocsUrls`'s `externalDocsUrl`
|
|
27
|
-
* parameter, which only ever arrives pre-fetched.
|
|
28
|
-
* 2. Else derive, by stripping a leading `api.` host label from the
|
|
29
|
-
* configured API base — but ONLY for a KNOWN first-party `trawl.me`
|
|
30
|
-
* host. `api.trawl.me` -> `trawl.me` (prod: API and docs are genuinely
|
|
31
|
-
* on different hosts); `dev.trawl.me` (no `api.` prefix) -> unchanged
|
|
32
|
-
* (dev serves docs on the SAME host as its API). A generic, unscoped
|
|
33
|
-
* `api.`-strip applied to ANY host is itself a guess — it assumes a
|
|
34
|
-
* self-hosted `api.acme.internal` serves docs at `acme.internal`, which
|
|
35
|
-
* nothing here can know. Scoping the strip to `trawl.me` is what makes
|
|
36
|
-
* rung 3 (below) ever actually fire for a self-hosted base.
|
|
37
|
-
* 3. Else OMIT the field entirely. Never emit a guessed URL (issue's Rule
|
|
38
|
-
* 3): a wrong URL sends an agent to a 404 WITH CONFIDENCE, worse than no
|
|
39
|
-
* URL at all — a self-hosted install with no server-declared
|
|
40
|
-
* `externalDocs` and a base outside `trawl.me` gets no `docsUrl`/
|
|
41
|
-
* `llmsUrl`, not a hopeful default pointed at OUR docs host.
|
|
42
|
-
*
|
|
43
|
-
* `llmsUrl` has no OpenAPI-standard field to read (rung 1 contributes
|
|
44
|
-
* nothing to it) — deriving it from the ORIGIN of a server-declared
|
|
45
|
-
* `externalDocs.url` would itself be a guess for a self-hosted install
|
|
46
|
-
* (rule 3 again), so `llmsUrl` resolves ONLY via rung 2 (the known-host
|
|
47
|
-
* derivation) or omission. It never rides along with a rung-1 `docsUrl`.
|
|
48
|
-
*
|
|
49
|
-
* PROVENANCE (defect fix, reviewer repro against a live mock server): the
|
|
50
|
-
* top-level `docsUrl` above is deliberately the FLATTENED "whichever rung
|
|
51
|
-
* won" value — right for a human reading `spec --json`'s top-level field,
|
|
52
|
-
* WRONG as an input to `resolveCommandDocsUrl`/`resolveFailureKindDocsUrl`
|
|
53
|
-
* below. Those two append one of THIS CLI's own hardcoded guide slugs
|
|
54
|
-
* (`COMMAND_DOC_PATHS`/`FAILURE_KIND_DOC_PATHS`) onto whatever `docsUrl`
|
|
55
|
-
* resolved — safe onto a rung-2 root we derived ourselves (we know
|
|
56
|
-
* trawl.me's guide tree), never safe onto a rung-1 root (an arbitrary
|
|
57
|
-
* third party's own docs site, self-hosted, with no reason to carry our
|
|
58
|
-
* slugs). A mock server declaring `externalDocs.url: 'http://h/guide'`
|
|
59
|
-
* used to get `http://h/guide/build-your-scrap/account-sessions` appended
|
|
60
|
-
* — a confidently-wrong 404. So `DocsUrls.docsUrlIsDerived` carries rung
|
|
61
|
-
* provenance ALONGSIDE the string (never flattened to a bare string again)
|
|
62
|
-
* and both deep-link functions now take the whole `DocsUrls`-shaped object
|
|
63
|
-
* and gate construction on that flag — see its own doc comment below.
|
|
64
|
-
*/
|
|
65
|
-
/** The one first-party domain this CLI knows to serve docs — see rung 2 in
|
|
66
|
-
* the module doc comment above. Deliberately not "any host", so an
|
|
67
|
-
* unrecognized (self-hosted/custom) base falls through to omission. */
|
|
68
1
|
const KNOWN_DOCS_DOMAIN = 'trawl.me';
|
|
69
|
-
/**
|
|
70
|
-
* Rung 2 — see the module doc comment. Returns `{ protocol, host }` for a
|
|
71
|
-
* KNOWN first-party API base (preserving the base's own protocol rather
|
|
72
|
-
* than assuming `https:`), or `null` when the base isn't recognized
|
|
73
|
-
* (self-hosted, a custom domain, `localhost`, an unparseable string, …) —
|
|
74
|
-
* `null` must propagate to omission (rung 3), never to a guessed host.
|
|
75
|
-
*/
|
|
76
2
|
export function deriveDocsOrigin(apiBaseUrl) {
|
|
77
3
|
let url;
|
|
78
4
|
try {
|
|
@@ -88,22 +14,9 @@ export function deriveDocsOrigin(apiBaseUrl) {
|
|
|
88
14
|
}
|
|
89
15
|
return null;
|
|
90
16
|
}
|
|
91
|
-
/** Same rung as `deriveDocsOrigin`, collapsed to just the host string —
|
|
92
|
-
* convenience for a caller that only needs the derived host, not the
|
|
93
|
-
* protocol (kept as its own export since `docs.test.ts` exercises the host
|
|
94
|
-
* derivation independently of protocol handling). */
|
|
95
17
|
export function deriveDocsHost(apiBaseUrl) {
|
|
96
18
|
return deriveDocsOrigin(apiBaseUrl)?.host ?? null;
|
|
97
19
|
}
|
|
98
|
-
/**
|
|
99
|
-
* The full ladder, PER FIELD (see module doc comment for why `llmsUrl`
|
|
100
|
-
* cannot ride along with a rung-1 `docsUrl`). Pure and synchronous — no
|
|
101
|
-
* network, safe to call from any surface (an error payload, the `--help`
|
|
102
|
-
* footer) without adding latency or a new failure mode. `externalDocsUrl`
|
|
103
|
-
* is rung 1's input: only ever supplied by `spec.ts`'s action, after its
|
|
104
|
-
* own bounded, swallowed-on-failure fetch — every other caller omits it and
|
|
105
|
-
* gets rungs 2/3 only.
|
|
106
|
-
*/
|
|
107
20
|
export function resolveDocsUrls(opts) {
|
|
108
21
|
const origin = deriveDocsOrigin(opts.apiBaseUrl);
|
|
109
22
|
const derived = origin
|
|
@@ -114,95 +27,29 @@ export function resolveDocsUrls(opts) {
|
|
|
114
27
|
}
|
|
115
28
|
: {};
|
|
116
29
|
if (opts.externalDocsUrl) {
|
|
117
|
-
// Rung 1 wins for docsUrl outright; llmsUrl stays rung-2-only (see doc
|
|
118
|
-
// comment) — a self-hosted `externalDocsUrl` outside `trawl.me` yields
|
|
119
|
-
// `{ docsUrl: <server's own> }` with `llmsUrl` omitted, never guessed.
|
|
120
|
-
// `docsUrlIsDerived: false` regardless of whether rung 2 ALSO resolved
|
|
121
|
-
// (e.g. a `trawl.me`-hosted server declaring its own externalDocs) — a
|
|
122
|
-
// server-declared root is never "derived" by THIS CLI, and never a safe
|
|
123
|
-
// base for our own guide slugs (see DocsUrls.docsUrlIsDerived).
|
|
124
30
|
return derived.llmsUrl
|
|
125
31
|
? { docsUrl: opts.externalDocsUrl, llmsUrl: derived.llmsUrl, docsUrlIsDerived: false }
|
|
126
32
|
: { docsUrl: opts.externalDocsUrl, docsUrlIsDerived: false };
|
|
127
33
|
}
|
|
128
34
|
return derived;
|
|
129
35
|
}
|
|
130
|
-
/** Join a docs root (e.g. `https://trawl.me/docs`) with a guide path
|
|
131
|
-
* suffix (e.g. `build-your-scrap/account-sessions`) — plain string
|
|
132
|
-
* concatenation, deliberately NOT `new URL(suffix, docsUrl)`: a
|
|
133
|
-
* leading-slash suffix resolved against a URL with its own path component
|
|
134
|
-
* (`/docs`) would resolve relative to the ORIGIN, silently dropping
|
|
135
|
-
* `/docs` from the result (`new URL('/x', 'https://h/docs')` ==
|
|
136
|
-
* `https://h/x`, not `https://h/docs/x`). */
|
|
137
36
|
function joinDocsPath(docsUrl, suffix) {
|
|
138
37
|
return `${docsUrl.replace(/\/+$/, '')}/${suffix.replace(/^\/+/, '')}`;
|
|
139
38
|
}
|
|
140
|
-
/**
|
|
141
|
-
* The single source for the account-sessions guide slug — referenced by
|
|
142
|
-
* BOTH `COMMAND_DOC_PATHS` and `FAILURE_KIND_DOC_PATHS` below. Those two
|
|
143
|
-
* tables key on different fields (a CLI command's full path name vs a run's
|
|
144
|
-
* `failureKind`) and legitimately stay separate, but they name the SAME
|
|
145
|
-
* guide, so the path literal itself must exist exactly once: this is the
|
|
146
|
-
* "same fact computed in two places" defect class this module's own doc
|
|
147
|
-
* comment says it exists to prevent, and it has recurred repeatedly across
|
|
148
|
-
* this epic. Renaming the guide used to require editing two literals in
|
|
149
|
-
* lockstep — miss one and one surface (a run's `docs` field, or `spec
|
|
150
|
-
* --json`'s per-command deep link) silently 404s while the other still
|
|
151
|
-
* works (trawl_cli#185 review).
|
|
152
|
-
*/
|
|
153
39
|
const ACCOUNT_SESSIONS_GUIDE_PATH = 'build-your-scrap/account-sessions';
|
|
154
|
-
/**
|
|
155
|
-
* Per-command deep links (issue #185 scope item 1) — a command's FULL path
|
|
156
|
-
* name (matches `CliSpecCommand.name` in spec.ts, e.g.
|
|
157
|
-
* `"scraps account session set"`) is matched against `prefix` either
|
|
158
|
-
* exactly or as a whole path SEGMENT prefix (`startsWith(prefix + ' ')`) —
|
|
159
|
-
* never a bare substring, so a hypothetical future `scraps accounting`
|
|
160
|
-
* command could never false-match the `scraps account` entry below.
|
|
161
|
-
*/
|
|
162
40
|
const COMMAND_DOC_PATHS = Object.freeze([
|
|
163
|
-
// Every account/session-management command — set, delete, clear-session,
|
|
164
|
-
// status, and the nested `session set` — is covered by one prefix entry
|
|
165
|
-
// rather than one row per leaf, so a new leaf added under `scraps account`
|
|
166
|
-
// later (e.g. a future `session capture`, trawl_cli#183) inherits the
|
|
167
|
-
// link for free instead of needing its own row.
|
|
168
41
|
{ prefix: 'scraps account', path: ACCOUNT_SESSIONS_GUIDE_PATH },
|
|
169
42
|
]);
|
|
170
43
|
function docsPathForCommand(commandName) {
|
|
171
44
|
const match = COMMAND_DOC_PATHS.find((e) => commandName === e.prefix || commandName.startsWith(`${e.prefix} `));
|
|
172
45
|
return match ? match.path : null;
|
|
173
46
|
}
|
|
174
|
-
/**
|
|
175
|
-
* `resolveCommandDocsUrl` returns `undefined` (never a bare path or a
|
|
176
|
-
* relative link) whenever any of three things is missing — `docsUrl`
|
|
177
|
-
* unresolved (rung 3 already fired), `docsUrl` resolved but NOT derived
|
|
178
|
-
* (rung 1 — a server-declared root; see `DocsUrls.docsUrlIsDerived`'s doc
|
|
179
|
-
* comment for why appending our own guide slug onto a third party's root is
|
|
180
|
-
* exactly the confidently-wrong-404 defect this gate closes), or no guide is
|
|
181
|
-
* mapped for this command — so a spec consumer never has to special-case a
|
|
182
|
-
* partial value. Takes the whole resolved `DocsUrls` object (never a bare
|
|
183
|
-
* string) specifically so this provenance can never again be flattened away
|
|
184
|
-
* before it reaches here.
|
|
185
|
-
*/
|
|
186
47
|
export function resolveCommandDocsUrl(commandName, docs) {
|
|
187
48
|
if (!docs.docsUrl || !docs.docsUrlIsDerived)
|
|
188
49
|
return undefined;
|
|
189
50
|
const path = docsPathForCommand(commandName);
|
|
190
51
|
return path ? joinDocsPath(docs.docsUrl, path) : undefined;
|
|
191
52
|
}
|
|
192
|
-
/**
|
|
193
|
-
* failureKind -> guide path (issue #185 scope item 2). Keyed on trawl_node's
|
|
194
|
-
* run-level `failureKind` field (trawl_node#1975, surfaced by this CLI's
|
|
195
|
-
* own `doctor`/`data --errors`/`run-info` JSON payloads since cli#182) —
|
|
196
|
-
* this is a DIFFERENT axis from errors.ts's `ErrorEnvelope.kind` (a CLI
|
|
197
|
-
* transport/execution outcome like `"auth"` meaning "you're not logged
|
|
198
|
-
* into trawl", `"network"`, `"usage"`, …). Both happen to use the string
|
|
199
|
-
* `"auth"` for unrelated things: this table's `'auth'` means "the SCRAPED
|
|
200
|
-
* TARGET site showed a login wall", errors.ts's `'auth'` means "the TRAWL
|
|
201
|
-
* API rejected your OWN credentials". Never merge these two tables — they
|
|
202
|
-
* are keyed off different fields on different objects, and conflating them
|
|
203
|
-
* would either miss the run-level guide or wrongly attach it to an
|
|
204
|
-
* unrelated CLI auth failure.
|
|
205
|
-
*/
|
|
206
53
|
const FAILURE_KIND_DOC_PATHS = Object.freeze({
|
|
207
54
|
auth: ACCOUNT_SESSIONS_GUIDE_PATH,
|
|
208
55
|
});
|
|
@@ -211,28 +58,12 @@ function docsPathForFailureKind(kind) {
|
|
|
211
58
|
return null;
|
|
212
59
|
return FAILURE_KIND_DOC_PATHS[kind] ?? null;
|
|
213
60
|
}
|
|
214
|
-
/** Same "undefined unless docsUrl is resolved AND derived (rung 2)" gate as
|
|
215
|
-
* `resolveCommandDocsUrl` above, same reason — never append this CLI's own
|
|
216
|
-
* guide slug onto a rung-1 server-declared root. Callers MUST gate the call
|
|
217
|
-
* on their own staleness rule first (e.g. `doctor.ts`'s `isAuthWall`) — this
|
|
218
|
-
* function only knows the string-to-path mapping, not whether a stamped
|
|
219
|
-
* `failureKind` is still live on a run patched afterward. */
|
|
220
61
|
export function resolveFailureKindDocsUrl(kind, docs) {
|
|
221
62
|
if (!docs.docsUrl || !docs.docsUrlIsDerived)
|
|
222
63
|
return undefined;
|
|
223
64
|
const path = docsPathForFailureKind(kind);
|
|
224
65
|
return path ? joinDocsPath(docs.docsUrl, path) : undefined;
|
|
225
66
|
}
|
|
226
|
-
/**
|
|
227
|
-
* `--help` footer text (issue #185 scope item 3) — a single dim line, for
|
|
228
|
-
* humans, e.g. "Docs: https://trawl.me/docs". Returns `undefined` (never an
|
|
229
|
-
* empty or guessed line) when no `docsUrl` resolved — mirrors
|
|
230
|
-
* lib/tips.ts's shape (a pure function separated from IO, so it's testable
|
|
231
|
-
* without mocking chalk/console) but NOT its TTY gate: tips.ts suppresses a
|
|
232
|
-
* promotional nudge from piped output, but a docs line inside `--help |
|
|
233
|
-
* less` is exactly the kind of thing worth keeping. Un-colored here — the
|
|
234
|
-
* caller (index.ts) applies `chalk.dim` so this stays trivially testable.
|
|
235
|
-
*/
|
|
236
67
|
export function docsFooterLine(docsUrl) {
|
|
237
68
|
return docsUrl ? `Docs: ${docsUrl}` : undefined;
|
|
238
69
|
}
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -1,58 +1,21 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thrown for CLI usage / input-validation failures (bad flag value, malformed
|
|
3
|
-
* JSON, invalid ObjectId, missing required prompt input, …). Distinguished
|
|
4
|
-
* from ApiError/NetworkError so the top-level handler can map it to its own
|
|
5
|
-
* exit code (2) instead of the generic uniform 1 every other bug collapses
|
|
6
|
-
* into. (#71)
|
|
7
|
-
*/
|
|
8
1
|
export declare class UsageError extends Error {
|
|
9
2
|
constructor(message: string);
|
|
10
3
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Thrown for a business-logic REFUSAL the server explicitly reported back
|
|
13
|
-
* (e.g. a tier-ceiling override the registry cap rejected) — distinct from
|
|
14
|
-
* an arbitrary unmapped bug. Before this, `reportTierRefusal` routed a bare
|
|
15
|
-
* `new Error(message)` through here, which fell through to the generic
|
|
16
|
-
* `kind:"unknown"` bucket — indistinguishable from a genuine crash, even
|
|
17
|
-
* though the README sells `kind` as the machine discriminant an agent
|
|
18
|
-
* branches on. Same exit code (1: a business-logic refusal, not a usage
|
|
19
|
-
* error) as before — only the `kind` differs. (#107 review F3)
|
|
20
|
-
*/
|
|
21
4
|
export declare class RefusalError extends Error {
|
|
22
5
|
constructor(message: string);
|
|
23
6
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Commander prefixes every one of its own usage-error messages with the
|
|
26
|
-
* literal `"error: "` (see `missingArgument`/`unknownOption`/`unknownCommand`
|
|
27
|
-
* etc. in commander's `command.js`) — harmless for its own plain default
|
|
28
|
-
* line, but redundant once this CLI reuses that message itself: (1) baked
|
|
29
|
-
* into a `--json` envelope's `message` field, a human-facing "error: " prefix
|
|
30
|
-
* is dead weight for a machine parser that already reads `kind:"usage"`; (2)
|
|
31
|
-
* reformatted through the app's own `chalk.red('✗ ' + message)` convention it
|
|
32
|
-
* would double up as "✗ error: unknown option …". Idempotent — a message
|
|
33
|
-
* that never had the prefix passes through unchanged. (#149 items 1/2)
|
|
34
|
-
*/
|
|
35
7
|
export declare function stripCommanderErrorPrefix(message: string): string;
|
|
36
8
|
export interface ErrorEnvelope {
|
|
37
9
|
message: string;
|
|
38
10
|
status?: number;
|
|
39
11
|
kind: string;
|
|
40
|
-
/** Is retrying the SAME command, unchanged, worth it? */
|
|
41
12
|
retryable: boolean;
|
|
42
|
-
/** Commands worth running next, most useful first. Empty when there is
|
|
43
|
-
* nothing honest to suggest — never filled to look helpful. */
|
|
44
13
|
next?: string[];
|
|
45
14
|
}
|
|
46
15
|
export interface ClassifiedError {
|
|
47
16
|
exitCode: number;
|
|
48
17
|
envelope: ErrorEnvelope;
|
|
49
18
|
}
|
|
50
|
-
/**
|
|
51
|
-
* The exit-code taxonomy (#71 findings 13/14/60), named once so
|
|
52
|
-
* `classifyError` below and `spec.ts`'s `buildSpec()` (#170) read the SAME
|
|
53
|
-
* numbers instead of `spec.ts` hand-listing its own copy — exactly the kind
|
|
54
|
-
* of second source of truth `tests/contracts/` exists to catch drifting.
|
|
55
|
-
*/
|
|
56
19
|
export declare const EXIT_CODES: Readonly<{
|
|
57
20
|
SUCCESS: 0;
|
|
58
21
|
UNKNOWN: 1;
|
|
@@ -61,116 +24,19 @@ export declare const EXIT_CODES: Readonly<{
|
|
|
61
24
|
NOT_FOUND: 4;
|
|
62
25
|
NETWORK: 5;
|
|
63
26
|
}>;
|
|
64
|
-
/** `trawl spec --json`'s `exitCodes` field — a short label per code. Code
|
|
65
|
-
* `1` is shared by several `kind`s (`api`, `refused`, `unknown`); the label
|
|
66
|
-
* names the generic/unmapped bucket it represents, not an exhaustive list. */
|
|
67
27
|
export declare const EXIT_CODE_LABELS: Readonly<Record<string, string>>;
|
|
68
|
-
/**
|
|
69
|
-
* #170 — ONE frozen map, keyed by the existing `kind`, driving the
|
|
70
|
-
* envelope's new `retryable`/`next` fields. This is also the exhaustive set
|
|
71
|
-
* of `kind`s `classifyError` can produce — `ERROR_KINDS` below derives its
|
|
72
|
-
* list from these keys rather than hand-listing them a second time, and
|
|
73
|
-
* `spec.ts`'s `buildSpec()` reads `ERROR_KINDS`, never its own copy.
|
|
74
|
-
*
|
|
75
|
-
* `network` is the one kind worth retrying unchanged. `auth` isn't
|
|
76
|
-
* retryable but has an honest next step (`trawl login --token <jwt>`).
|
|
77
|
-
* Everything else (`usage`/`not_found`/`api`/`refused`/`unknown`) is neither
|
|
78
|
-
* — retrying a bad flag, a missing resource, or an unmapped bug with no new
|
|
79
|
-
* information just repeats the same failure.
|
|
80
|
-
*
|
|
81
|
-
* #170 review F8 — `next` must be an EXECUTABLE next command, not just a verb
|
|
82
|
-
* name: bare `trawl login` still blocks on an interactive prompt (email then
|
|
83
|
-
* password) — for a non-interactive caller (stdin closed, the exact
|
|
84
|
-
* situation an auth failure implies) it fails immediately with its OWN usage
|
|
85
|
-
* error instead of the login the agent was told to run. `trawl login --token
|
|
86
|
-
* <jwt>` is the one form of `login` that never prompts.
|
|
87
|
-
*/
|
|
88
28
|
export declare const RETRY_POLICY: Readonly<Record<string, {
|
|
89
29
|
retryable: boolean;
|
|
90
30
|
next?: readonly string[];
|
|
91
31
|
}>>;
|
|
92
|
-
/** Every `kind` string `classifyError` can produce — derived from
|
|
93
|
-
* `RETRY_POLICY`'s keys (see its doc comment), never a second hand list. */
|
|
94
32
|
export declare const ERROR_KINDS: readonly string[];
|
|
95
|
-
/**
|
|
96
|
-
* Every `kind` a real `--json` error envelope can carry — NOT the same set
|
|
97
|
-
* as `ERROR_KINDS`. `ERROR_KINDS` is deliberately narrow (exactly what
|
|
98
|
-
* `classifyError` produces, see its own doc comment — `RETRY_POLICY` must
|
|
99
|
-
* keep meaning that, `retryFieldsFor` relies on it), but three more kinds
|
|
100
|
-
* reach a real envelope from hand-built emitters that never go through
|
|
101
|
-
* `classifyError` at all:
|
|
102
|
-
* - `in_progress` — `src/commands/scraps.ts`, `data <id>`'s `reportDataState`
|
|
103
|
-
* call for a run still in flight
|
|
104
|
-
* - `run_failed` — same file, same helper, for a last run that failed
|
|
105
|
-
* - `upgrade_failed` — `src/commands/upgrade.ts`, when `npm install -g` itself fails
|
|
106
|
-
*
|
|
107
|
-
* `spec.ts`'s `errorKinds` field publishes THIS constant, never
|
|
108
|
-
* `ERROR_KINDS` — an agent that builds its allow-list from the published
|
|
109
|
-
* spec must not reject a perfectly valid `{"error":{"kind":"in_progress",…}}`
|
|
110
|
-
* envelope just because it was hand-built instead of classified. Built as a
|
|
111
|
-
* union (spread `ERROR_KINDS` + list the hand-built kinds) rather than a
|
|
112
|
-
* second hand-copy of the first group, so the classifyError kinds are
|
|
113
|
-
* PROVABLY a subset — see errors.test.ts's exhaustiveness check.
|
|
114
|
-
*
|
|
115
|
-
* Adding a new hand-built `kind:` literal anywhere in `src/` means
|
|
116
|
-
* registering it here too, or the published spec will lie about it exactly
|
|
117
|
-
* like this constant exists to prevent.
|
|
118
|
-
*/
|
|
119
33
|
export declare const ENVELOPE_KINDS: readonly string[];
|
|
120
|
-
/**
|
|
121
|
-
* #170 review F7 — `spec --json`'s flat `exitCodes` map (`EXIT_CODE_LABELS`)
|
|
122
|
-
* publishes ONE label per code, which is honest about code `1` being a
|
|
123
|
-
* generic/unmapped bucket but says nothing about which `kind`s actually land
|
|
124
|
-
* there. An agent building an `exitCode -> kind` table off `exitCodes` alone
|
|
125
|
-
* reads `"1":"unknown"` and treats every other kind sharing that code
|
|
126
|
-
* (`api`/`refused`/`in_progress`/`run_failed`/`upgrade_failed`) as an
|
|
127
|
-
* unmapped bug it should give up on — instead of honouring the very
|
|
128
|
-
* `retryable`/`next` fields those envelopes correctly carry.
|
|
129
|
-
*
|
|
130
|
-
* This is the inverse direction, `kind -> exitCode`, one entry per
|
|
131
|
-
* `ENVELOPE_KINDS` member — additive (published as a SIBLING field,
|
|
132
|
-
* `kindExitCodes`, never replacing `exitCodes`) and keyed off the same
|
|
133
|
-
* `EXIT_CODES` constants every real call site already uses, so a future exit
|
|
134
|
-
* code change here can't silently drift from `classifyError`/
|
|
135
|
-
* `reportDataState`/`upgrade.ts`'s actual behaviour without also changing the
|
|
136
|
-
* single source those all draw from. errors.test.ts asserts every entry here
|
|
137
|
-
* against classifyError's REAL returned exitCode for that kind — the closest
|
|
138
|
-
* an exhaustive hand-map can get to "derived", short of `classifyError`
|
|
139
|
-
* itself being rewritten to loop over a shared table (out of scope here).
|
|
140
|
-
*/
|
|
141
34
|
export declare const KIND_EXIT_CODES: Readonly<Record<string, number>>;
|
|
142
|
-
/**
|
|
143
|
-
* Look up the frozen default `retryable`/`next` for a `kind`. Total (never
|
|
144
|
-
* throws) — a `kind` outside `RETRY_POLICY` (e.g. `scraps data`'s own
|
|
145
|
-
* `run_failed`/`in_progress` states, which aren't part of `classifyError`'s
|
|
146
|
-
* taxonomy) falls back to the conservative "not retryable, nothing to
|
|
147
|
-
* suggest" default. Callers with a genuine kind-is-wrong override (an
|
|
148
|
-
* `ApiError` 429, `scraps data`'s `in_progress` refusal) pass their own
|
|
149
|
-
* `retryable`/`next` instead of trusting this lookup — see classifyError and
|
|
150
|
-
* scraps.ts's `reportDataState`.
|
|
151
|
-
*/
|
|
152
35
|
export declare function retryFieldsFor(kind: string): {
|
|
153
36
|
retryable: boolean;
|
|
154
37
|
next?: string[];
|
|
155
38
|
};
|
|
156
|
-
/**
|
|
157
|
-
* Central status → exit-code map (#71 findings 13/14/60). Agents driving this
|
|
158
|
-
* CLI unattended need to tell "you're not logged in" (3) from "that id
|
|
159
|
-
* doesn't exist" (4) from "the network/API is unreachable" (5) from "you
|
|
160
|
-
* passed a bad flag" (2) — a uniform exit 1 collapses all of these into one
|
|
161
|
-
* undifferentiable signal.
|
|
162
|
-
*/
|
|
163
39
|
export declare function classifyError(err: unknown): ClassifiedError;
|
|
164
|
-
/**
|
|
165
|
-
* Print a classified error to the correct stream and return its exit code.
|
|
166
|
-
* stdout is reserved for payload — under --json the error itself IS the
|
|
167
|
-
* payload (`{"error":{message,status,kind,retryable,next?}}`); otherwise the
|
|
168
|
-
* human-readable line goes to stderr, never stdout. (#71 findings 13/14/60)
|
|
169
|
-
*
|
|
170
|
-
* `quiet` skips the human-readable stderr line (used when the caller already
|
|
171
|
-
* printed a fuller diagnostic, e.g. a raw stack trace under --debug) while
|
|
172
|
-
* still emitting the --json payload when requested.
|
|
173
|
-
*/
|
|
174
40
|
export declare function reportError(err: unknown, opts?: {
|
|
175
41
|
json?: boolean;
|
|
176
42
|
quiet?: boolean;
|
package/dist/lib/errors.js
CHANGED
|
@@ -1,54 +1,20 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { ApiError, AuthError, NetworkError } from './api.js';
|
|
3
|
-
/**
|
|
4
|
-
* Thrown for CLI usage / input-validation failures (bad flag value, malformed
|
|
5
|
-
* JSON, invalid ObjectId, missing required prompt input, …). Distinguished
|
|
6
|
-
* from ApiError/NetworkError so the top-level handler can map it to its own
|
|
7
|
-
* exit code (2) instead of the generic uniform 1 every other bug collapses
|
|
8
|
-
* into. (#71)
|
|
9
|
-
*/
|
|
10
3
|
export class UsageError extends Error {
|
|
11
4
|
constructor(message) {
|
|
12
5
|
super(message);
|
|
13
6
|
this.name = 'UsageError';
|
|
14
7
|
}
|
|
15
8
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Thrown for a business-logic REFUSAL the server explicitly reported back
|
|
18
|
-
* (e.g. a tier-ceiling override the registry cap rejected) — distinct from
|
|
19
|
-
* an arbitrary unmapped bug. Before this, `reportTierRefusal` routed a bare
|
|
20
|
-
* `new Error(message)` through here, which fell through to the generic
|
|
21
|
-
* `kind:"unknown"` bucket — indistinguishable from a genuine crash, even
|
|
22
|
-
* though the README sells `kind` as the machine discriminant an agent
|
|
23
|
-
* branches on. Same exit code (1: a business-logic refusal, not a usage
|
|
24
|
-
* error) as before — only the `kind` differs. (#107 review F3)
|
|
25
|
-
*/
|
|
26
9
|
export class RefusalError extends Error {
|
|
27
10
|
constructor(message) {
|
|
28
11
|
super(message);
|
|
29
12
|
this.name = 'RefusalError';
|
|
30
13
|
}
|
|
31
14
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Commander prefixes every one of its own usage-error messages with the
|
|
34
|
-
* literal `"error: "` (see `missingArgument`/`unknownOption`/`unknownCommand`
|
|
35
|
-
* etc. in commander's `command.js`) — harmless for its own plain default
|
|
36
|
-
* line, but redundant once this CLI reuses that message itself: (1) baked
|
|
37
|
-
* into a `--json` envelope's `message` field, a human-facing "error: " prefix
|
|
38
|
-
* is dead weight for a machine parser that already reads `kind:"usage"`; (2)
|
|
39
|
-
* reformatted through the app's own `chalk.red('✗ ' + message)` convention it
|
|
40
|
-
* would double up as "✗ error: unknown option …". Idempotent — a message
|
|
41
|
-
* that never had the prefix passes through unchanged. (#149 items 1/2)
|
|
42
|
-
*/
|
|
43
15
|
export function stripCommanderErrorPrefix(message) {
|
|
44
16
|
return message.startsWith('error: ') ? message.slice('error: '.length) : message;
|
|
45
17
|
}
|
|
46
|
-
/**
|
|
47
|
-
* The exit-code taxonomy (#71 findings 13/14/60), named once so
|
|
48
|
-
* `classifyError` below and `spec.ts`'s `buildSpec()` (#170) read the SAME
|
|
49
|
-
* numbers instead of `spec.ts` hand-listing its own copy — exactly the kind
|
|
50
|
-
* of second source of truth `tests/contracts/` exists to catch drifting.
|
|
51
|
-
*/
|
|
52
18
|
export const EXIT_CODES = Object.freeze({
|
|
53
19
|
SUCCESS: 0,
|
|
54
20
|
UNKNOWN: 1,
|
|
@@ -57,9 +23,6 @@ export const EXIT_CODES = Object.freeze({
|
|
|
57
23
|
NOT_FOUND: 4,
|
|
58
24
|
NETWORK: 5,
|
|
59
25
|
});
|
|
60
|
-
/** `trawl spec --json`'s `exitCodes` field — a short label per code. Code
|
|
61
|
-
* `1` is shared by several `kind`s (`api`, `refused`, `unknown`); the label
|
|
62
|
-
* names the generic/unmapped bucket it represents, not an exhaustive list. */
|
|
63
26
|
export const EXIT_CODE_LABELS = Object.freeze({
|
|
64
27
|
[EXIT_CODES.SUCCESS]: 'success',
|
|
65
28
|
[EXIT_CODES.UNKNOWN]: 'unknown',
|
|
@@ -68,26 +31,6 @@ export const EXIT_CODE_LABELS = Object.freeze({
|
|
|
68
31
|
[EXIT_CODES.NOT_FOUND]: 'not_found',
|
|
69
32
|
[EXIT_CODES.NETWORK]: 'network',
|
|
70
33
|
});
|
|
71
|
-
/**
|
|
72
|
-
* #170 — ONE frozen map, keyed by the existing `kind`, driving the
|
|
73
|
-
* envelope's new `retryable`/`next` fields. This is also the exhaustive set
|
|
74
|
-
* of `kind`s `classifyError` can produce — `ERROR_KINDS` below derives its
|
|
75
|
-
* list from these keys rather than hand-listing them a second time, and
|
|
76
|
-
* `spec.ts`'s `buildSpec()` reads `ERROR_KINDS`, never its own copy.
|
|
77
|
-
*
|
|
78
|
-
* `network` is the one kind worth retrying unchanged. `auth` isn't
|
|
79
|
-
* retryable but has an honest next step (`trawl login --token <jwt>`).
|
|
80
|
-
* Everything else (`usage`/`not_found`/`api`/`refused`/`unknown`) is neither
|
|
81
|
-
* — retrying a bad flag, a missing resource, or an unmapped bug with no new
|
|
82
|
-
* information just repeats the same failure.
|
|
83
|
-
*
|
|
84
|
-
* #170 review F8 — `next` must be an EXECUTABLE next command, not just a verb
|
|
85
|
-
* name: bare `trawl login` still blocks on an interactive prompt (email then
|
|
86
|
-
* password) — for a non-interactive caller (stdin closed, the exact
|
|
87
|
-
* situation an auth failure implies) it fails immediately with its OWN usage
|
|
88
|
-
* error instead of the login the agent was told to run. `trawl login --token
|
|
89
|
-
* <jwt>` is the one form of `login` that never prompts.
|
|
90
|
-
*/
|
|
91
34
|
export const RETRY_POLICY = Object.freeze({
|
|
92
35
|
auth: { retryable: false, next: ['trawl login --token <jwt>'] },
|
|
93
36
|
network: { retryable: true },
|
|
@@ -97,60 +40,13 @@ export const RETRY_POLICY = Object.freeze({
|
|
|
97
40
|
refused: { retryable: false },
|
|
98
41
|
unknown: { retryable: false },
|
|
99
42
|
});
|
|
100
|
-
/** Every `kind` string `classifyError` can produce — derived from
|
|
101
|
-
* `RETRY_POLICY`'s keys (see its doc comment), never a second hand list. */
|
|
102
43
|
export const ERROR_KINDS = Object.freeze(Object.keys(RETRY_POLICY));
|
|
103
|
-
/**
|
|
104
|
-
* Every `kind` a real `--json` error envelope can carry — NOT the same set
|
|
105
|
-
* as `ERROR_KINDS`. `ERROR_KINDS` is deliberately narrow (exactly what
|
|
106
|
-
* `classifyError` produces, see its own doc comment — `RETRY_POLICY` must
|
|
107
|
-
* keep meaning that, `retryFieldsFor` relies on it), but three more kinds
|
|
108
|
-
* reach a real envelope from hand-built emitters that never go through
|
|
109
|
-
* `classifyError` at all:
|
|
110
|
-
* - `in_progress` — `src/commands/scraps.ts`, `data <id>`'s `reportDataState`
|
|
111
|
-
* call for a run still in flight
|
|
112
|
-
* - `run_failed` — same file, same helper, for a last run that failed
|
|
113
|
-
* - `upgrade_failed` — `src/commands/upgrade.ts`, when `npm install -g` itself fails
|
|
114
|
-
*
|
|
115
|
-
* `spec.ts`'s `errorKinds` field publishes THIS constant, never
|
|
116
|
-
* `ERROR_KINDS` — an agent that builds its allow-list from the published
|
|
117
|
-
* spec must not reject a perfectly valid `{"error":{"kind":"in_progress",…}}`
|
|
118
|
-
* envelope just because it was hand-built instead of classified. Built as a
|
|
119
|
-
* union (spread `ERROR_KINDS` + list the hand-built kinds) rather than a
|
|
120
|
-
* second hand-copy of the first group, so the classifyError kinds are
|
|
121
|
-
* PROVABLY a subset — see errors.test.ts's exhaustiveness check.
|
|
122
|
-
*
|
|
123
|
-
* Adding a new hand-built `kind:` literal anywhere in `src/` means
|
|
124
|
-
* registering it here too, or the published spec will lie about it exactly
|
|
125
|
-
* like this constant exists to prevent.
|
|
126
|
-
*/
|
|
127
44
|
export const ENVELOPE_KINDS = Object.freeze([
|
|
128
45
|
...ERROR_KINDS,
|
|
129
46
|
'in_progress',
|
|
130
47
|
'run_failed',
|
|
131
48
|
'upgrade_failed',
|
|
132
49
|
]);
|
|
133
|
-
/**
|
|
134
|
-
* #170 review F7 — `spec --json`'s flat `exitCodes` map (`EXIT_CODE_LABELS`)
|
|
135
|
-
* publishes ONE label per code, which is honest about code `1` being a
|
|
136
|
-
* generic/unmapped bucket but says nothing about which `kind`s actually land
|
|
137
|
-
* there. An agent building an `exitCode -> kind` table off `exitCodes` alone
|
|
138
|
-
* reads `"1":"unknown"` and treats every other kind sharing that code
|
|
139
|
-
* (`api`/`refused`/`in_progress`/`run_failed`/`upgrade_failed`) as an
|
|
140
|
-
* unmapped bug it should give up on — instead of honouring the very
|
|
141
|
-
* `retryable`/`next` fields those envelopes correctly carry.
|
|
142
|
-
*
|
|
143
|
-
* This is the inverse direction, `kind -> exitCode`, one entry per
|
|
144
|
-
* `ENVELOPE_KINDS` member — additive (published as a SIBLING field,
|
|
145
|
-
* `kindExitCodes`, never replacing `exitCodes`) and keyed off the same
|
|
146
|
-
* `EXIT_CODES` constants every real call site already uses, so a future exit
|
|
147
|
-
* code change here can't silently drift from `classifyError`/
|
|
148
|
-
* `reportDataState`/`upgrade.ts`'s actual behaviour without also changing the
|
|
149
|
-
* single source those all draw from. errors.test.ts asserts every entry here
|
|
150
|
-
* against classifyError's REAL returned exitCode for that kind — the closest
|
|
151
|
-
* an exhaustive hand-map can get to "derived", short of `classifyError`
|
|
152
|
-
* itself being rewritten to loop over a shared table (out of scope here).
|
|
153
|
-
*/
|
|
154
50
|
export const KIND_EXIT_CODES = Object.freeze({
|
|
155
51
|
auth: EXIT_CODES.AUTH,
|
|
156
52
|
network: EXIT_CODES.NETWORK,
|
|
@@ -159,51 +55,18 @@ export const KIND_EXIT_CODES = Object.freeze({
|
|
|
159
55
|
api: EXIT_CODES.UNKNOWN,
|
|
160
56
|
refused: EXIT_CODES.UNKNOWN,
|
|
161
57
|
unknown: EXIT_CODES.UNKNOWN,
|
|
162
|
-
// Hand-built kinds (never routed through classifyError) — in_progress/
|
|
163
|
-
// run_failed both set via reportDataState's literal `1` (scraps.ts),
|
|
164
|
-
// upgrade_failed via its own literal `process.exitCode = 1` (upgrade.ts).
|
|
165
58
|
in_progress: EXIT_CODES.UNKNOWN,
|
|
166
59
|
run_failed: EXIT_CODES.UNKNOWN,
|
|
167
60
|
upgrade_failed: EXIT_CODES.UNKNOWN,
|
|
168
61
|
});
|
|
169
|
-
/**
|
|
170
|
-
* Look up the frozen default `retryable`/`next` for a `kind`. Total (never
|
|
171
|
-
* throws) — a `kind` outside `RETRY_POLICY` (e.g. `scraps data`'s own
|
|
172
|
-
* `run_failed`/`in_progress` states, which aren't part of `classifyError`'s
|
|
173
|
-
* taxonomy) falls back to the conservative "not retryable, nothing to
|
|
174
|
-
* suggest" default. Callers with a genuine kind-is-wrong override (an
|
|
175
|
-
* `ApiError` 429, `scraps data`'s `in_progress` refusal) pass their own
|
|
176
|
-
* `retryable`/`next` instead of trusting this lookup — see classifyError and
|
|
177
|
-
* scraps.ts's `reportDataState`.
|
|
178
|
-
*/
|
|
179
62
|
export function retryFieldsFor(kind) {
|
|
180
63
|
const policy = RETRY_POLICY[kind];
|
|
181
64
|
if (!policy)
|
|
182
65
|
return { retryable: false };
|
|
183
66
|
return policy.next ? { retryable: policy.retryable, next: [...policy.next] } : { retryable: policy.retryable };
|
|
184
67
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Central status → exit-code map (#71 findings 13/14/60). Agents driving this
|
|
187
|
-
* CLI unattended need to tell "you're not logged in" (3) from "that id
|
|
188
|
-
* doesn't exist" (4) from "the network/API is unreachable" (5) from "you
|
|
189
|
-
* passed a bad flag" (2) — a uniform exit 1 collapses all of these into one
|
|
190
|
-
* undifferentiable signal.
|
|
191
|
-
*/
|
|
192
68
|
export function classifyError(err) {
|
|
193
69
|
const message = err instanceof Error ? err.message : String(err);
|
|
194
|
-
// #88 item 4 — a LOCAL auth failure (no token, or a locally-decoded expired
|
|
195
|
-
// token) never made an HTTP call, so its envelope must never carry
|
|
196
|
-
// `status:401` — that would claim a server response that never happened.
|
|
197
|
-
// Same exit code / kind as a real server 401 (ApiError below); only the
|
|
198
|
-
// envelope shape differs.
|
|
199
|
-
//
|
|
200
|
-
// #169 review round 2, finding 2 — `err.next` is only ever set (in api.ts,
|
|
201
|
-
// via authNextSteps()) for an apiKey-mode failure, where the frozen
|
|
202
|
-
// `next` default ("trawl login --token <jwt>") is inert until a live
|
|
203
|
-
// TRAWL_API_KEY/TRAWL_TOKEN is unset first. Spread AFTER retryFieldsFor so
|
|
204
|
-
// it overrides that default's `next` key; every other AuthError/ApiError
|
|
205
|
-
// 401 (jwt mode, notLoggedInError — no credential at all) leaves `err.next`
|
|
206
|
-
// undefined and keeps the unmodified frozen default, unchanged from before.
|
|
207
70
|
if (err instanceof AuthError) {
|
|
208
71
|
return {
|
|
209
72
|
exitCode: EXIT_CODES.AUTH,
|
|
@@ -226,10 +89,6 @@ export function classifyError(err) {
|
|
|
226
89
|
if (err.status === 404) {
|
|
227
90
|
return { exitCode: EXIT_CODES.NOT_FOUND, envelope: { message, status: 404, kind: 'not_found', ...retryFieldsFor('not_found') } };
|
|
228
91
|
}
|
|
229
|
-
// #170 — a 429 (rate-limited / quota-exhausted) is the one `api`-kind
|
|
230
|
-
// response worth retrying, even though the frozen map's default for
|
|
231
|
-
// `api` is not retryable. Genuinely kind-is-wrong override, not a new
|
|
232
|
-
// kind — `kind` stays `"api"`.
|
|
233
92
|
const retry = err.status === 429 ? { retryable: true } : retryFieldsFor('api');
|
|
234
93
|
return { exitCode: EXIT_CODES.UNKNOWN, envelope: { message, status: err.status, kind: 'api', ...retry } };
|
|
235
94
|
}
|
|
@@ -244,16 +103,6 @@ export function classifyError(err) {
|
|
|
244
103
|
}
|
|
245
104
|
return { exitCode: EXIT_CODES.UNKNOWN, envelope: { message, kind: 'unknown', ...retryFieldsFor('unknown') } };
|
|
246
105
|
}
|
|
247
|
-
/**
|
|
248
|
-
* Print a classified error to the correct stream and return its exit code.
|
|
249
|
-
* stdout is reserved for payload — under --json the error itself IS the
|
|
250
|
-
* payload (`{"error":{message,status,kind,retryable,next?}}`); otherwise the
|
|
251
|
-
* human-readable line goes to stderr, never stdout. (#71 findings 13/14/60)
|
|
252
|
-
*
|
|
253
|
-
* `quiet` skips the human-readable stderr line (used when the caller already
|
|
254
|
-
* printed a fuller diagnostic, e.g. a raw stack trace under --debug) while
|
|
255
|
-
* still emitting the --json payload when requested.
|
|
256
|
-
*/
|
|
257
106
|
export function reportError(err, opts = {}) {
|
|
258
107
|
const { exitCode, envelope } = classifyError(err);
|
|
259
108
|
if (opts.json) {
|
package/dist/lib/format.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
1
|
export declare function table(rows: Record<string, unknown>[], columns: string[]): void;
|
|
2
2
|
export declare function json(data: unknown): void;
|
|
3
|
-
/**
|
|
4
|
-
* One date format across the whole CLI: `DD/MM/YY HH:mm` (local time).
|
|
5
|
-
* #119 — `list` mixed `DD/MM/YY` (last-run col) with `M/D/YYYY,
|
|
6
|
-
* h:mm:ss AM/PM` (toLocaleString), ambiguous day/month side by side.
|
|
7
|
-
* Returns `—` for a missing/invalid date.
|
|
8
|
-
*/
|
|
9
3
|
export declare function formatDate(value: string | number | Date | null | undefined): string;
|
package/dist/lib/format.js
CHANGED
|
@@ -23,12 +23,6 @@ export function table(rows, columns) {
|
|
|
23
23
|
export function json(data) {
|
|
24
24
|
console.log(JSON.stringify(data, null, 2));
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
27
|
-
* One date format across the whole CLI: `DD/MM/YY HH:mm` (local time).
|
|
28
|
-
* #119 — `list` mixed `DD/MM/YY` (last-run col) with `M/D/YYYY,
|
|
29
|
-
* h:mm:ss AM/PM` (toLocaleString), ambiguous day/month side by side.
|
|
30
|
-
* Returns `—` for a missing/invalid date.
|
|
31
|
-
*/
|
|
32
26
|
export function formatDate(value) {
|
|
33
27
|
if (value === null || value === undefined)
|
|
34
28
|
return '—';
|