@trawlme/cli 3.12.0 → 3.12.2

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.
Files changed (74) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/create.d.ts +0 -28
  3. package/dist/commands/create.js +0 -89
  4. package/dist/commands/doctor.d.ts +0 -79
  5. package/dist/commands/doctor.js +1 -187
  6. package/dist/commands/login.js +0 -67
  7. package/dist/commands/ping.d.ts +0 -15
  8. package/dist/commands/ping.js +0 -15
  9. package/dist/commands/scraps.d.ts +0 -120
  10. package/dist/commands/scraps.js +10 -724
  11. package/dist/commands/skills.js +0 -22
  12. package/dist/commands/spec.d.ts +0 -85
  13. package/dist/commands/spec.js +0 -67
  14. package/dist/commands/telemetry.js +0 -4
  15. package/dist/commands/token.js +0 -28
  16. package/dist/commands/upgrade.js +0 -22
  17. package/dist/commands/whoami.d.ts +0 -12
  18. package/dist/commands/whoami.js +0 -6
  19. package/dist/index.d.ts +0 -188
  20. package/dist/index.js +0 -349
  21. package/dist/lib/api.d.ts +0 -78
  22. package/dist/lib/api.js +1 -320
  23. package/dist/lib/cdp-pipe.d.ts +0 -72
  24. package/dist/lib/cdp-pipe.js +1 -81
  25. package/dist/lib/chrome-discovery.d.ts +0 -11
  26. package/dist/lib/chrome-discovery.js +0 -19
  27. package/dist/lib/chrome-launch.d.ts +0 -40
  28. package/dist/lib/chrome-launch.js +0 -69
  29. package/dist/lib/config.d.ts +0 -53
  30. package/dist/lib/config.js +0 -55
  31. package/dist/lib/confirm.d.ts +0 -55
  32. package/dist/lib/confirm.js +0 -47
  33. package/dist/lib/docs.d.ts +0 -123
  34. package/dist/lib/docs.js +0 -169
  35. package/dist/lib/errors.d.ts +0 -134
  36. package/dist/lib/errors.js +0 -151
  37. package/dist/lib/format.d.ts +0 -6
  38. package/dist/lib/format.js +0 -6
  39. package/dist/lib/json.d.ts +0 -35
  40. package/dist/lib/json.js +0 -48
  41. package/dist/lib/jwt.d.ts +0 -7
  42. package/dist/lib/jwt.js +0 -7
  43. package/dist/lib/pinch.d.ts +0 -53
  44. package/dist/lib/pinch.js +6 -112
  45. package/dist/lib/pinchAnimation.d.ts +0 -16
  46. package/dist/lib/pinchAnimation.js +8 -29
  47. package/dist/lib/posthog.d.ts +0 -9
  48. package/dist/lib/posthog.js +0 -23
  49. package/dist/lib/prompt.js +1 -20
  50. package/dist/lib/secure-transport.d.ts +0 -7
  51. package/dist/lib/secure-transport.js +0 -24
  52. package/dist/lib/session-capture-guard.d.ts +0 -15
  53. package/dist/lib/session-capture-guard.js +0 -5
  54. package/dist/lib/session-capture.d.ts +0 -125
  55. package/dist/lib/session-capture.js +0 -281
  56. package/dist/lib/skills.d.ts +0 -175
  57. package/dist/lib/skills.js +1 -216
  58. package/dist/lib/skillsNudge.d.ts +0 -17
  59. package/dist/lib/skillsNudge.js +0 -83
  60. package/dist/lib/spinner.d.ts +0 -39
  61. package/dist/lib/spinner.js +0 -40
  62. package/dist/lib/storage-state.d.ts +0 -112
  63. package/dist/lib/storage-state.js +0 -131
  64. package/dist/lib/tips.d.ts +0 -38
  65. package/dist/lib/tips.js +0 -77
  66. package/dist/lib/updateCheckWorker.js +0 -14
  67. package/dist/lib/updateNotifier.d.ts +0 -17
  68. package/dist/lib/updateNotifier.js +0 -53
  69. package/dist/lib/validate.d.ts +0 -8
  70. package/dist/lib/validate.js +0 -8
  71. package/dist/lib/version.d.ts +0 -12
  72. package/dist/lib/version.js +1 -13
  73. package/docs/agent-quickstart.md +2 -2
  74. package/package.json +2 -2
package/README.md CHANGED
@@ -36,7 +36,7 @@ Four methods:
36
36
  3. **Token flag** — `trawl login --token <jwt>` (CI/CD, direct JWT)
37
37
  4. **API key** — `TRAWL_API_KEY=trawl_xxx trawl list` (scoped, revocable one-by-one — the recommended credential for an agent driving this CLI; see [docs/agent-quickstart.md](docs/agent-quickstart.md))
38
38
 
39
- A `TRAWL_API_KEY` is a `trawl_*`-prefixed credential (create/revoke one in the Trawl dashboard) sent as `Authorization: Bearer` instead of the session `Cookie: TOKEN=` a JWT uses — `trawl` picks the right one automatically based on the credential's own shape, never a flag. `TRAWL_API_KEY` wins over `TRAWL_TOKEN` when both happen to be set. It is scoped server-side — in practice to a set of scraps, since the dashboard's key-create form sets only the scrap allow-list; narrowing which *actions* a key may perform needs an explicit `scopes` array at creation over the API, and a key without one has every action granted. It works on most of the Core tier — `create`/`list`/`get`/`data`/`history`/`run-info`/`run`/`trigger`/`ping`, including the `--watch` **polling flag** on `run`/`trigger` — plus, outside Core, `scraps account status`/`scraps doctor`/`scraps autofix` (all three only ever read routes trawl_node opened to keys). It does **not** work on `whoami`, `scraps update`/`delete`, `scraps account set`/`delete`/`clear-session`/`session set`, `scraps banner`, `scraps snapshot` (the scrap lookup it starts from is dual-auth, but the `html-snapshot` route it downloads from isn't), or the standalone SSE **command** `scraps watch` (do not conflate the two: `--watch` is a flag on `run`/`trigger` and works under a key; `scraps watch` is a separate command and is JWT-only) — those stay JWT-only and fail with a `kind:"auth"` envelope (exit `3`) pointing at `trawl login` under a key. This list mirrors trawl_node's route wiring as of this writing, not a frozen guarantee — for anything not named here, trust the real `--json` envelope over this paragraph. Never send both a key and a JWT on the same request — the CLI only ever attaches one. `list --unhealthy` (and the plain health badge on `list`/`get`) rides the same `GET /api/scraps`/`GET /api/scraps/:id` routes as the rest of the group — no separate auth path — so it works under a key exactly like plain `list`/`get`, and a scrap-scoped key still only ever sees its own allow-listed scraps through the filter.
39
+ A `TRAWL_API_KEY` is a `trawl_*`-prefixed credential (create/revoke one in the Trawl dashboard) sent as `Authorization: Bearer` instead of the session `Cookie: TOKEN=` a JWT uses — `trawl` picks the right one automatically based on the credential's own shape, never a flag. `TRAWL_API_KEY` wins over `TRAWL_TOKEN` when both happen to be set. It is scoped server-side — in practice to a set of scraps, since the dashboard's key-create form sets only the scrap allow-list; narrowing which *actions* a key may perform needs an explicit `scopes` array at creation over the API, and a key without one has every action granted. It works on most of the Core tier — `create`/`list`/`get`/`data`/`history`/`run-info`/`run`/`trigger`/`ping`, including the `--watch` **polling flag** on `run`/`trigger` — plus, outside Core, `scraps account status`/`scraps doctor`/`scraps autofix` (all three only ever read routes the API opened to keys). It does **not** work on `whoami`, `scraps update`/`delete`, `scraps account set`/`delete`/`clear-session`/`session set`/`session capture`, `scraps banner`, `scraps snapshot` (the scrap lookup it starts from is dual-auth, but the `html-snapshot` route it downloads from isn't), or the standalone SSE **command** `scraps watch` (do not conflate the two: `--watch` is a flag on `run`/`trigger` and works under a key; `scraps watch` is a separate command and is JWT-only) — those stay JWT-only and fail with a `kind:"auth"` envelope (exit `3`) pointing at `trawl login` under a key. This list mirrors the server's route wiring as of this writing, not a frozen guarantee — for anything not named here, trust the real `--json` envelope over this paragraph. Never send both a key and a JWT on the same request — the CLI only ever attaches one. `list --unhealthy` (and the plain health badge on `list`/`get`) rides the same `GET /api/scraps`/`GET /api/scraps/:id` routes as the rest of the group — no separate auth path — so it works under a key exactly like plain `list`/`get`, and a scrap-scoped key still only ever sees its own allow-listed scraps through the filter.
40
40
 
41
41
  Custom API URL: `trawl login --url https://self-hosted.example.com`
42
42
 
@@ -76,7 +76,7 @@ trawl spec [--json] Print a versioned, machine-readab
76
76
  - `history` lists past runs (newest first); `run-info <hid>` shows details of a single run from that history.
77
77
  - `data` returns the last persisted run payload (no execute quota); `--fresh` runs the scrap live instead (consumes execute quota); `--errors` shows the last run's error detail (`--json` on a never-run scrap returns `{"status":"no_runs"}`, exit 0, matching `scraps doctor --json`). `[]` on stdout means a genuine zero-item successful run — a scrap that has never run, whose last run failed, or whose payload aged out of retention returns a `--json` error envelope (exit 4/1/4 respectively) instead. Two more honest states: a run still **in flight** (`status: null` server-side) returns a `kind:"in_progress"` error envelope (exit 1, "retry shortly" — never suggests `--fresh`, which would just 429 against the run already holding the lock); a run whose item count **regressed** vs baseline (`statusDetail: "regression"`) still returns the real, non-empty items on stdout (exit 0) plus a stderr warning pointing at `scraps doctor <id>` — the data itself is genuine even though the run is flagged.
78
78
  - `get` (and anything reading through it, like `data`'s default path) embeds only the newest 100 history rows on the returned scrap object — `run-info` and `scraps doctor` fetch a single run directly and are unaffected by that cap. `list`/`get` show a distinct amber `▼` "regression" badge, never the red `✗` a genuine failure gets (matches `scraps doctor`'s own badge).
79
- - `list`/`get` also show a **health** badge next to the status badge. By default it reads the scrap's own `lastCronOutcome` field (always present, no extra cost): `⚠ cron paused` when the last scheduled tick was skipped for being unhealthy, `—` otherwise — a breadcrumb of the last tick, not a live read (never set on a no-cron scrap, can lag by one tick). `list --unhealthy` asks the API to filter to scraps whose *current* consecutive-failure streak is 3+ (trawl_node's own threshold) and annotates each with the exact `consecutiveFailedRuns`/`unhealthySince` — shown instead of the badge whenever present, and never re-derived client-side from history. `--json` carries whichever fields the request produced: `lastCronOutcome` always, `consecutiveFailedRuns`/`unhealthySince` only when `--unhealthy` was passed. **Old-server safety:** a server predating trawl_node#1952 silently ignores the unknown `--unhealthy` query param and returns everything, unfiltered — the CLI detects that (no returned item carries `consecutiveFailedRuns`) and prints a stderr warning instead of presenting the full list as "your unhealthy scraps".
79
+ - `list`/`get` also show a **health** badge next to the status badge. By default it reads the scrap's own `lastCronOutcome` field (always present, no extra cost): `⚠ cron paused` when the last scheduled tick was skipped for being unhealthy, `—` otherwise — a breadcrumb of the last tick, not a live read (never set on a no-cron scrap, can lag by one tick). `list --unhealthy` asks the API to filter to scraps whose *current* consecutive-failure streak is 3+ (the server's own threshold) and annotates each with the exact `consecutiveFailedRuns`/`unhealthySince` — shown instead of the badge whenever present, and never re-derived client-side from history. `--json` carries whichever fields the request produced: `lastCronOutcome` always, `consecutiveFailedRuns`/`unhealthySince` only when `--unhealthy` was passed. **Old-server safety:** an older server version that predates this filter silently ignores the unknown `--unhealthy` query param and returns everything, unfiltered — the CLI detects that (no returned item carries `consecutiveFailedRuns`) and prints a stderr warning instead of presenting the full list as "your unhealthy scraps".
80
80
  - **Long-running calls (`create`, `run`, `data --fresh`, `trigger --wait`):** these hit server-side paths that can legitimately take 30–250s+ (AI generation + scrap creation + a first run for `create`; proxy tier escalation + AI-fix retries for the other three) — the CLI arms a 300s timeout for exactly these four call sites instead of the generic 30s default. `TRAWL_TIMEOUT` (see below) still overrides ALL requests, including these — set it if you need a tighter or looser ceiling than 300s, but note a global override that tight also clamps `create`.
81
81
  - **`--watch` is poll-based, not a live stream:** the activities SSE endpoint has no backlog and, for the default async `trigger` (no `--wait`), runs in a separate cron-consumer pod whose events never reach the API pod holding the SSE connection — a naive "await the run, then open SSE" shows nothing. `run --watch` and `trigger --watch` instead poll `GET /api/scraps/:id` (terminal status) and the activities REST list until the run finishes, printing each new activity line as it appears. The watched run's outcome drives the exit code too, in BOTH human and `--json` mode: a genuinely failed terminal run, a poll timeout, or a persistently unreachable API all exit non-zero — a clean successful run is the only exit `0`. A run that never reaches a terminal status within 300s prints an honest timeout notice pointing at `scraps doctor <id>` (human mode) — see the `--json` shape below. `scraps watch <id>` (the standalone command, no trigger) is unchanged — it still opens the live SSE stream directly.
82
82
  - `spec --json` prints the CLI's own command tree — `{specVersion, cliVersion, commands[], exitCodes, errorKinds, kindExitCodes, docsUrl?, llmsUrl?}` — DERIVED at runtime by walking the live commander tree (never a hand-maintained file, which would silently drift from reality). Each entry in `commands[]` carries its full path (e.g. `"scraps account session set"`), description, `hidden` (the legacy `scraps <verb>` aliases above), `leaf` (false for a pure namespace/group node like `scraps`/`skills`/`telemetry` — invoking one directly is a guaranteed-failing tool, not a real command), `aliases`, `arguments`, `options`, and an optional per-command `docs` deep link (e.g. every `scraps account *` command points at the account-sessions guide); `exitCodes`/`errorKinds`/`kindExitCodes` are read from the exact same source `classifyError` uses (see [Exit codes](#exit-codes)) — never a second copy. `kindExitCodes` is the inverse of `exitCodes`: `kind -> exitCode`, since exit code `1` alone is a shared bucket (`api`/`refused`/`unknown`/`in_progress`/`run_failed`/`upgrade_failed`) that `exitCodes`' flat label can't disambiguate. `docsUrl` is resolved server-first (`externalDocs.url` on the configured API base's own OpenAPI document, when declared), else derived from a KNOWN first-party `trawl.me` API host, else **omitted entirely** — never a guessed URL pointed at the wrong docs host for a self-hosted install. `llmsUrl` and every per-command `docs` link are narrower: they only ever come from that same known-host derivation, NEVER from a server-declared `docsUrl` — this CLI's own guide slugs have no reason to exist on a third party's own docs root, so a self-hosted server that declares `externalDocs.url` gets a correct top-level `docsUrl` but no `llmsUrl` and no per-command `docs` links. A `failureKind:"auth"` run's JSON payload (`doctor`/`data --errors`/`run-info`) carries the same `docs` field, by the same known-host-only rule. Without `--json`, `spec` prints one short human line (version + visible command count) pointing at `--json`.
@@ -1,37 +1,9 @@
1
1
  import { Command } from 'commander';
2
- /**
3
- * `POST /api/ai/wizard` response contract (#114, trawl_node —
4
- * ai.wizard.service.js#runWizard, shipped S1, contract LOCKED). The wizard
5
- * chains, entirely server-side: AI code generation -> scrap creation ->
6
- * the FIRST run (ScrapsService.load) -> auto-fix on failure (autoFix
7
- * defaults true unless `--no-autofix` maps to `autoFix:false`).
8
- *
9
- * - `success` is an HONEST outcome of that first run, not "did the HTTP
10
- * call succeed" — a failed first run is still a 200 response (the scrap
11
- * itself was created either way; auto-fix, when enabled, retries in the
12
- * background). Callers must branch on `success`, never assume 2xx means
13
- * "the scrap works".
14
- * - `scrap` is the full created Scrap object (present whenever creation got
15
- * far enough to persist it — a hard failure before that point surfaces as
16
- * a real HTTP error instead, handled by the shared error path).
17
- * - `historyId` is best-effort (a failed server-side lookup leaves it
18
- * `null`, never breaks the response) — it points at the first run just
19
- * executed.
20
- */
21
2
  export interface WizardResponse {
22
3
  success: boolean;
23
4
  scrap?: {
24
5
  _id: string;
25
6
  title: string;
26
- /**
27
- * Cron expression the wizard schedules this scrap on. As of #114/S1
28
- * (trawl_node ai.wizard.service.js#runWizard's `scrapBody`) this is
29
- * hardcoded server-side to a DAILY run — `'0 7 * * *'` / `cronTimezone:
30
- * 'UTC'` — unconditionally, regardless of `--prompt`/`--no-autofix`.
31
- * Present on the scrap object returned here (the wizard controller
32
- * passes the created scrap straight through, no stripping) — change or
33
- * disable it with `trawl scraps update <id> --cron <expr>` / `--no-cron`.
34
- */
35
7
  cron?: string | null;
36
8
  cronTimezone?: string;
37
9
  [key: string]: unknown;
@@ -8,12 +8,6 @@ import { requireUrl, requireString } from '../lib/validate.js';
8
8
  import { UsageError } from '../lib/errors.js';
9
9
  import { renderPinch, pinchEnabled } from '../lib/pinch.js';
10
10
  import { startPinchAnimation } from '../lib/pinchAnimation.js';
11
- /** Best-effort, honest first-run summary — never claims a background retry
12
- * happened when auto-fix was disabled for this call, and never claims a
13
- * scrap was persisted when the response carries none (#114-F3 — a hard
14
- * failure before persistence still comes back as `success:false` with no
15
- * `scrap` at all; claiming "auto-fix retrying in the background" then would
16
- * be fabricated — there is nothing to retry). */
17
11
  function firstRunLabel(data, autoFixEnabled) {
18
12
  if (data.success)
19
13
  return 'succeeded';
@@ -23,10 +17,6 @@ function firstRunLabel(data, autoFixEnabled) {
23
17
  return 'failed (auto-fix retrying in the background)';
24
18
  return 'failed';
25
19
  }
26
- /** Best-effort human description of a daily cron (`M H * * *`) — the only
27
- * shape the wizard's server-side default currently produces. Falls back to
28
- * printing the raw expression for anything else rather than guessing at a
29
- * schedule the CLI can't actually parse. */
30
20
  function describeCron(cron) {
31
21
  const match = /^(\d{1,2})\s+(\d{1,2})\s+\*\s+\*\s+\*$/.exec(cron.trim());
32
22
  if (!match)
@@ -34,16 +24,6 @@ function describeCron(cron) {
34
24
  const [, min, hour] = match;
35
25
  return `daily ${hour.padStart(2, '0')}:${min.padStart(2, '0')}`;
36
26
  }
37
- /**
38
- * #114-F2 — a wizard-created scrap runs on a DAILY cron by default
39
- * server-side; surface that up front rather than leaving it to be
40
- * discovered later as an unexpected recurring quota charge. Reads the real
41
- * `cron`/`cronTimezone` field off the response scrap when present; falls
42
- * back to the known wizard default wording ONLY when the field is missing
43
- * from the response (an older server, or a future rename) — never invents a
44
- * schedule value that might not match what the server actually applied.
45
- * Returns null when there's no scrap to schedule at all.
46
- */
47
27
  function scheduleLabel(scrap) {
48
28
  if (!scrap)
49
29
  return null;
@@ -53,24 +33,11 @@ function scheduleLabel(scrap) {
53
33
  }
54
34
  return 'scheduled daily by default';
55
35
  }
56
- /**
57
- * #121 — onboarding principle: a successful `create` should SHOW the value,
58
- * not just an id. Best-effort fetch of the first run's persisted data (the
59
- * same read-only, no-quota path `trawl data <id>` uses:
60
- * `GET /api/historys/:historyId` → a JSON string `{ data: [...] }`) and print
61
- * a small proof-of-value sample (count + first-item keys + one truncated
62
- * value line — never a full dump; that's what `trawl data --json` is for).
63
- * ANY failure (network, parse, no data) is swallowed silently — the sample is
64
- * a bonus, it must never turn a successful create into a failure or noise.
65
- */
66
36
  async function printDataSample(historyId) {
67
37
  try {
68
38
  const detail = await api.get(`/api/historys/${historyId}`);
69
39
  if (typeof detail?.data !== 'string' || !detail.data)
70
40
  return;
71
- // #159 — same JSON-string-within-JSON shape as scrap.history[0].data;
72
- // tolerate the documented raw-control-char server quirk here too instead
73
- // of silently losing the sample to the outer try/catch below.
74
41
  const items = parseServerJson(detail.data)?.data;
75
42
  if (!Array.isArray(items) || items.length === 0)
76
43
  return;
@@ -88,7 +55,6 @@ async function printDataSample(historyId) {
88
55
  }
89
56
  }
90
57
  catch {
91
- // best-effort — a missing sample never fails or noises up a good create
92
58
  }
93
59
  }
94
60
  export const create = new Command('create')
@@ -99,15 +65,6 @@ export const create = new Command('create')
99
65
  .option('--no-autofix', 'Disable AI auto-fix on first-run failure (default: on)')
100
66
  .option('--json', 'Output the raw API payload')
101
67
  .action(async (rawUrl, opts) => {
102
- // Fast, local usage-errors (exit 2) — never a round-trip to the server
103
- // for something we can already tell is bad. Same fail-fast pattern the
104
- // former `fetch` command used for its URL argument.
105
- //
106
- // #116 — the url is accepted BOTH as the positional argument (agent
107
- // one-liner) and as `--url` (muscle memory from `scraps create` and the
108
- // API body {url, goal}). Exactly one is required; both are fine only
109
- // when identical — two DIFFERENT urls is ambiguous, refuse loudly
110
- // rather than silently picking one.
111
68
  if (rawUrl === undefined && opts.url === undefined) {
112
69
  throw new UsageError("missing required argument 'url' (positional, or --url <url>)");
113
70
  }
@@ -121,58 +78,28 @@ export const create = new Command('create')
121
78
  goal,
122
79
  ...(opts.autofix === false && { autoFix: false }),
123
80
  };
124
- // #91/#106-F1 long-run pattern — the wizard runs AI generation + scrap
125
- // creation + a real FIRST run (+ autofix retries) entirely server-side,
126
- // legitimately 30-250s+. The 30s DEFAULT_TIMEOUT_MS would abort it
127
- // mid-flight and fabricate a NetworkError timeout for a request that was
128
- // always going to succeed.
129
81
  const call = () => api.post('/api/ai/wizard', body, { timeoutMs: LONG_RUN_TIMEOUT_MS });
130
- // #106-F2 pattern carried over from `fetch` — under --json stdout must
131
- // be provably pure: no spinner channel at all. Only the human path gets
132
- // the ora progress indicator; --json calls the API directly.
133
82
  let data;
134
83
  if (opts.json) {
135
84
  data = await call();
136
85
  }
137
86
  else {
138
- // #122/#131 — while the server-side wizard runs (legitimately 30-250s+,
139
- // see LONG_RUN_TIMEOUT_MS above) Pinch ANIMATES in place (claw-wiggle)
140
- // on a color-capable TTY; otherwise the ora spinner (which itself
141
- // no-ops under a non-TTY, so piped human output stays clean). Both
142
- // write to stderr only — stdout purity under `--json` is guaranteed by
143
- // the `opts.json` branch above, never by this code.
144
87
  const anim = pinchEnabled() ? startPinchAnimation(`Creating a scrap from ${url}…`) : null;
145
88
  try {
146
89
  data = anim
147
90
  ? await call()
148
91
  : await spin(call, {
149
- // No verdict symbol (#106-F3) — ora's success only means "the
150
- // HTTP call didn't throw", not "the first run succeeded".
151
92
  text: `Creating a scrap from ${url}…`,
152
93
  successText: 'Request complete',
153
94
  });
154
95
  }
155
96
  catch (err) {
156
97
  anim?.stop();
157
- // #114-F1 — a client-side timeout (NetworkError, "timed out after
158
- // …ms" per api.ts's safeFetch) does NOT mean the wizard failed
159
- // server-side: the scrap creation + first run keep going on the
160
- // server after the CLI gives up waiting, so the scrap may already
161
- // exist (or land moments later). Warn BEFORE rethrowing so a retry
162
- // isn't the first instinct — a blind retry creates a duplicate scrap
163
- // and burns AI-generation quota a second time for the same goal.
164
- // Only a NetworkError whose message identifies it as the timeout
165
- // branch qualifies — a DNS/connection-refused NetworkError never
166
- // reached the server at all, so there's nothing to warn about here.
167
98
  if (err instanceof NetworkError && /timed out/i.test(err.message)) {
168
99
  console.error(chalk.yellow('⚠ The request timed out client-side, but the scrap may STILL have been created server-side — run `trawl list` before retrying (a retry creates a DUPLICATE scrap + burns quota).'));
169
100
  }
170
- // Rethrow unchanged so index.ts's classifyError/exit-code taxonomy
171
- // stays intact (this stays a NetworkError -> exit 5, same as before).
172
101
  throw err;
173
102
  }
174
- // Success — stop + erase the animation so the result prints on a clean
175
- // line (the celebrating/confused frame below is the one-shot outcome).
176
103
  anim?.stop();
177
104
  }
178
105
  if (opts.json) {
@@ -190,19 +117,11 @@ export const create = new Command('create')
190
117
  console.log(chalk.dim(` First run: `) + firstRunLabel(data, autoFixEnabled));
191
118
  if (schedule)
192
119
  console.log(chalk.dim(` Schedule: `) + schedule);
193
- // #121 — on a successful first run, prove the value: show a small data
194
- // sample (best-effort, silent on failure) before the next-step hint.
195
120
  if (data.success && data.historyId)
196
121
  await printDataSample(data.historyId);
197
- // #121 — the natural next command is the DATA, not the metadata: point
198
- // at `trawl data` primarily, keep `trawl get` as the secondary detail view.
199
122
  if (scrapId) {
200
123
  console.log(chalk.dim(` Next step: `) + `trawl data ${scrapId}` + chalk.dim(` (details: trawl get ${scrapId})`));
201
124
  }
202
- // #114-F3 — only claim a background retry is happening when a scrap
203
- // actually exists to retry (never fabricate progress that isn't real);
204
- // `firstRunLabel` above already covers the !scrap / autofix-disabled
205
- // wording, this adds the actionable poll target on top.
206
125
  if (!data.success && data.scrap && autoFixEnabled) {
207
126
  const pollTarget = data.historyId
208
127
  ? `\`trawl data ${scrapId}\` or \`trawl run-info ${data.historyId}\``
@@ -210,18 +129,10 @@ export const create = new Command('create')
210
129
  console.log(chalk.yellow(` Note: `) +
211
130
  `Auto-fix is retrying in the background — do NOT re-run create; poll ${pollTarget}.`);
212
131
  }
213
- // #122 — Pinch reacts to the HONEST first-run outcome (same signal the
214
- // ✓/✗ line above already renders): celebrates a real success, looks
215
- // confused on a genuine failure. Belt-and-suspenders `!opts.json`
216
- // alongside pinchEnabled() — see the 'thinking' print above for why.
217
132
  if (!opts.json && pinchEnabled()) {
218
133
  console.log(data.success ? renderPinch('celebrating') : renderPinch('confused'));
219
134
  }
220
135
  }
221
- // Honest exit code alongside the honest payload — a --json caller gets
222
- // the raw body regardless (never wrapped/altered), but a script checking
223
- // the exit code alone must be able to tell "first run failed" from "ran
224
- // fine" without parsing. Same contract the former `fetch` command used.
225
136
  if (!data.success)
226
137
  process.exitCode = 1;
227
138
  });
@@ -1,9 +1,3 @@
1
- /**
2
- * Run diagnostics shape — mirrors the owner-safe REST shape returned by
3
- * GET /api/historys/:id (Phase 1 projection, 2026-05-27).
4
- * Cost and proxy-nature fields are intentionally absent by design.
5
- * Abstract proxyTier (Tier 0–4) is kept.
6
- */
7
1
  export interface Run {
8
2
  _id: string;
9
3
  status: boolean | null;
@@ -28,11 +22,6 @@ export interface Run {
28
22
  block?: {
29
23
  kind?: string | null;
30
24
  } | null;
31
- /** @deprecated trawl_node#1950 renamed this to `block.kind`. Kept as a read
32
- * fallback (see detectWallVendor) for the window where this CLI is
33
- * published ahead of the trawl_node prod tag — a prod backend served from
34
- * the pre-#1950 tag still returns this flat field, not `block.kind`. Drop
35
- * once prod is confirmed on a tag containing #1950. */
36
25
  blockType?: string | null;
37
26
  proxyTier?: string | null;
38
27
  baselineLength?: number | null;
@@ -40,64 +29,14 @@ export interface Run {
40
29
  createdAt?: string;
41
30
  time?: number | null;
42
31
  triggeredBy?: string | null;
43
- /** trawl_node#1975 — freeform failure classification (`'auth'` = login-wall
44
- * empty run, cookies are the fix). Not a TS union — trawl_node's own set
45
- * is additive/open, so this CLI must stay read-safe against a future
46
- * value it doesn't know about yet, same posture as `block.kind` above. */
47
32
  failureKind?: string | null;
48
33
  }
49
- /**
50
- * Resolve a known anti-bot vendor name from a worker `block.kind`
51
- * string. Returns null when the run succeeded, when there is no `block.kind`
52
- * signal at all, or when `block.kind` names something other than a known
53
- * vendor (e.g. `proxy-domain-gate`, `rate_limited_per_host`) — those stay on
54
- * the genuine-error path since we can't honestly attribute them to a specific
55
- * "no reliable bypass" wall.
56
- *
57
- * `blocked` is deliberately NOT consulted here (#177): it is a mid-run,
58
- * attempt-level signal the worker only stamps on one envelope shape, so it
59
- * reads `false` on the great majority of genuinely walled runs (the early-block
60
- * throw path sets `block.kind` but never `blocked` — 63 of 64 walled runs
61
- * measured on prod). `status === true` wins instead: a run that ultimately
62
- * returned data was not walled, even if an earlier tier's `block.kind` stamp
63
- * survived on the row.
64
- *
65
- * trawl_node#1950 renamed the flat `blockType` field to nested `block.kind`.
66
- * Read `block?.kind` first, falling back to the deprecated flat `blockType` —
67
- * this CLI can be published (and talk to prod) before the trawl_node prod tag
68
- * containing #1950 is cut (see the `Run.blockType` doc comment), so a prod
69
- * response can still be the pre-#1950 flat shape for a while.
70
- */
71
34
  export declare function detectWallVendor(run: Pick<Run, 'status' | 'statusDetail' | 'block' | 'blockType'>): string | null;
72
- /**
73
- * #184 — true for a run currently carrying a live (non-stale) login-wall
74
- * verdict. Extracted out of `formatDoctor`'s own local `authWall` const so
75
- * `commands/scraps.ts`'s `doctor`/`run-info` actions can reuse the EXACT
76
- * same guard to decide whether to fire the skills-install safety-net nudge
77
- * (lib/skillsNudge.ts `maybeSuggestSkillsForAuthWall`) — one rule, not two
78
- * copies that could quietly drift apart.
79
- *
80
- * Same staleness guard as `detectWallVendor` above and for the same reason:
81
- * `failureKind` is a terminal classification trawl_node stamps once, but
82
- * `patchForRegression` can flip `status`/`statusDetail` to success/
83
- * regression LATER without ever clearing it — so a run that ultimately
84
- * succeeded or degraded must never still read as an active auth wall.
85
- *
86
- * Typed structurally loose (not `Pick<Run, ...>`) on purpose: `Run.status`/
87
- * `statusDetail` are required fields, but `scraps.ts`'s own `HistoryRun`
88
- * (the run-info command's shape) declares the same three fields OPTIONAL —
89
- * a `Pick<Run, ...>` parameter type would reject that caller at compile
90
- * time even though every field it actually reads is present at runtime.
91
- */
92
35
  export declare function isAuthWall(run: {
93
36
  failureKind?: string | null;
94
37
  status?: boolean | null;
95
38
  statusDetail?: string | null;
96
39
  }): boolean;
97
- /**
98
- * Autofix activity metadata — from the persisted ai_fix_end activity.
99
- * aiUsage (cost) is stripped server-side; all diagnostics are kept.
100
- */
101
40
  export interface FixActivity {
102
41
  outcome: 'applied' | 'failed' | 'skipped' | 'breaker_tripped' | 'timeout' | null;
103
42
  classification?: string | null;
@@ -127,26 +66,8 @@ interface ScrapHead {
127
66
  }
128
67
  export declare function pickRun(run: Run): Partial<Run>;
129
68
  export declare function pickFix(fix: FixActivity | null): Partial<FixActivity> | null;
130
- /**
131
- * Pure formatter — returns a human-readable diagnosis string for a run.
132
- * Used by `doctor`, `doctor --autofix`, and `data --errors`.
133
- *
134
- * @param scrapTitle - Display name of the scrap
135
- * @param run - Owner-safe history run object
136
- * @param fix - Optional autofix activity (null = no fix attempted)
137
- * @param scrapId - Scrap document id (used in hint lines — autofix/snapshot take scrap id, not run id)
138
- * @returns Multi-line string ready for console.log
139
- */
140
69
  export declare function formatDoctor(scrapTitle: string, run: Run, fix?: FixActivity | null, scrapId?: string): string;
141
- /**
142
- * Pure formatter — returns a human-readable autofix detail block.
143
- * Shows diff, dry-run results, knowledge consulted.
144
- */
145
70
  export declare function formatAutofix(fix: FixActivity): string;
146
- /**
147
- * Fetch the latest run + its ai_fix_end activity for a scrap.
148
- * Returns null if the scrap has no runs yet.
149
- */
150
71
  export declare function fetchRunAndFix(scrapId: string): Promise<{
151
72
  scrap: ScrapHead;
152
73
  run: Run;