@trawlme/cli 3.7.0 → 3.7.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 CHANGED
@@ -48,14 +48,15 @@ All commands accept a global `--debug` flag to show full error stack traces on f
48
48
  ```
49
49
  trawl create <url> --prompt <goal> [--no-autofix] [--json] # url also accepted as --url <url> (#116)
50
50
  Create a persistent, self-healing scrap from a URL + a goal (AI-generated)
51
- trawl run <id> [--watch] [--json] Run a scrap (blocks until finished)
52
51
  trawl list|ls [--json] [--status <success|failure|never|running|regression>] [--limit <n>] [--page <n>]
53
52
  trawl get <id> [--json] Get scrap details
53
+ trawl run <id> [--watch] [--json] Run a scrap (blocks until finished)
54
+ trawl trigger <id> [--watch] [--wait] [--json] Launch a scrap as a background worker (returns immediately)
54
55
  trawl data <id> [--json] [--fresh] [--errors] Get scrap data (last persisted run — read-only, no quota; --fresh to launch one)
55
56
  trawl history <id> [--json] [-n <limit>] List past runs for a scrap (newest first)
56
57
  trawl run-info <hid> [--json] Show details of a single run
57
- trawl trigger <id> [--watch] [--wait] [--json] Launch a scrap as a background worker (returns immediately)
58
58
  trawl whoami [--json] Show the authenticated user's identity
59
+ trawl token [--json] Print the stored session JWT (for MCP Bearer auth)
59
60
  trawl ping [--json] Health/version handshake against the Trawl API
60
61
  ```
61
62
 
@@ -131,10 +132,9 @@ You can also install skills standalone (without the CLI): `npx @trawlme/skills i
131
132
  ```
132
133
  trawl login [--url <url>] [--token <jwt>] [--email <email>] [--password <pass>] [--json]
133
134
  trawl logout [--json]
134
- trawl token [--json] Print the stored session JWT (for MCP Bearer auth)
135
135
  ```
136
136
 
137
- `login --json` prints `{"ok":true,"apiUrl","config",email?}` on success — never the raw token (that's `token`'s job). `token --json` prints `{"token","exp","expiresAt"}` instead of the bare JWT + stderr advisories. See [Non-interactive rule](#non-interactive-rule) below: a missing `--email`/`--password` refuses immediately (usage error, exit 2) instead of prompting when `--json` is set or stdin/stdout isn't a real TTY.
137
+ `login --json` prints `{"ok":true,"apiUrl","config",email?}` on success — never the raw token (that's `trawl token`'s job, in the [Core commands](#core-commands-agent--human) section above — non-interactive/agent-consumable, unlike `login`/`logout`). `token --json` prints `{"token","exp","expiresAt"}` instead of the bare JWT + stderr advisories. See [Non-interactive rule](#non-interactive-rule) below: a missing `--email`/`--password` refuses immediately (usage error, exit 2) instead of prompting when `--json` is set or stdin/stdout isn't a real TTY.
138
138
 
139
139
  ### Telemetry
140
140
 
@@ -340,6 +340,10 @@ export async function pollRunProgress(id, before, opts = {}) {
340
340
  }
341
341
  process.exitCode = 1;
342
342
  }
343
+ // #149 item 3 — same shared-enum shape as VALID_TIERS below (~line 603):
344
+ // keeps `--status`'s allowed values and its error message in one place,
345
+ // mirrored from lastStatus()'s own return type so the two can never drift.
346
+ const VALID_LAST_STATUSES = ['success', 'failure', 'never', 'running', 'regression'];
343
347
  // list — promoted to a top-level verb (#108, see the AttachOptions comment above)
344
348
  export function attachListCommand(parent, attachOpts = {}) {
345
349
  return parent
@@ -358,6 +362,14 @@ export function attachListCommand(parent, attachOpts = {}) {
358
362
  .option('--limit <n>', 'Show only the first N results')
359
363
  .option('--page <n>', 'Fetch a specific page only (50 per page, no auto-pagination)')
360
364
  .action(async (opts, cmd) => {
365
+ // #149 item 3 — validate --status against its enum the same way
366
+ // --tier already validates (usageError + return, checked first, before
367
+ // any other guard/async work — mirrors `create`/`update`'s --tier
368
+ // check below).
369
+ if (opts.status !== undefined && !VALID_LAST_STATUSES.includes(opts.status)) {
370
+ usageError(`Invalid --status "${opts.status}" (allowed: ${VALID_LAST_STATUSES.join(', ')})`, { json: opts.json });
371
+ return;
372
+ }
361
373
  // Guard: --limit and --page are mutually exclusive
362
374
  if (opts.limit !== undefined && opts.page !== undefined) {
363
375
  usageError('--limit and --page are mutually exclusive. Use one or the other.', { json: opts.json });
@@ -1118,7 +1130,7 @@ export function attachTriggerCommand(parent, attachOpts = {}) {
1118
1130
  .command('trigger <id>', attachOpts)
1119
1131
  .description('Launch a scrap as a background worker (returns immediately)')
1120
1132
  .option('-w, --watch', 'Poll for progress after triggering (#91 — the default async run happens in a separate cron pod; activities SSE never reaches it)')
1121
- .option('--wait', 'Run synchronously and wait for the result (legacy behaviour)')
1133
+ .option('--wait', 'Run synchronously and wait for the result (legacy behaviour — prefer `trawl run` instead)')
1122
1134
  .option('--json', 'Output the raw trigger payload as JSON')
1123
1135
  .action(async (id, opts) => {
1124
1136
  validateObjectId(id);
package/dist/index.d.ts CHANGED
@@ -95,6 +95,24 @@ export declare function hasJsonFlag(argv: string[]): boolean;
95
95
  * walks the whole tree and installs it everywhere. (#86 finding 3)
96
96
  */
97
97
  export declare function applyExitOverride(cmd: Command): void;
98
+ /**
99
+ * #149 item 1 — commander's own usage-error line (unknown option, missing
100
+ * required arg, unknown command, …) is written via `Command#error()` ->
101
+ * `outputError()` straight to `process.stderr` BEFORE the exitOverride throw
102
+ * below is ever caught — in commander's own plain, uncolored `"error: …"`
103
+ * format, a visibly different convention from every other error this CLI
104
+ * prints (`reportError`'s `chalk.red('✗ ' + message)`). `configureOutput`'s
105
+ * `outputError` hook is exactly the one commander documents for reformatting
106
+ * the error text itself (`writeErr` also carries unrelated help output, e.g.
107
+ * `showHelpAfterError` — never enabled in this CLI, but out of scope to
108
+ * touch here). Overriding it on every node in the tree (same walk shape as
109
+ * applyExitOverride, for the same reason: addCommand()-attached subtrees
110
+ * don't otherwise inherit a parent's configureOutput) reformats that one line
111
+ * through the same red ✗ convention, stripping the redundant "error: " prefix
112
+ * first (stripCommanderErrorPrefix — the same prefix also leaks verbatim
113
+ * into the --json envelope's `message` field, see runCli's catch below).
114
+ */
115
+ export declare function applyOutputConfiguration(cmd: Command): void;
98
116
  /**
99
117
  * #88 item 6 — best-effort command-name recovery for a commander parse error
100
118
  * that happened BEFORE any action ran, so `currentCommand` (the preAction
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, CommanderError } from 'commander';
3
+ import chalk from 'chalk';
3
4
  import { readFileSync, realpathSync } from 'node:fs';
4
5
  import { fileURLToPath } from 'node:url';
5
6
  import { dirname, join } from 'node:path';
@@ -14,7 +15,7 @@ import { whoami } from './commands/whoami.js';
14
15
  import { ping } from './commands/ping.js';
15
16
  import { autoUpdateInstalledSkills } from './lib/skills.js';
16
17
  import { initPostHog, captureCommand, shutdown, registerAllowedCommands } from './lib/posthog.js';
17
- import { classifyError, reportError } from './lib/errors.js';
18
+ import { classifyError, reportError, stripCommanderErrorPrefix } from './lib/errors.js';
18
19
  import { renderPinch, pinchEnabled } from './lib/pinch.js';
19
20
  import { maybeNotifyUpdate, scheduleUpdateCheck } from './lib/updateNotifier.js';
20
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -91,26 +92,39 @@ export function createProgram() {
91
92
  .description('Trawl CLI — manage scraps from the terminal')
92
93
  .version(pkg.version)
93
94
  .option('--debug', 'Show full error stack traces');
94
- // Core verbs (#108) — promoted/listed first: create, run, list, get, data,
95
- // history, run-info, trigger, whoami, ping. `list`/`get`/`run`/`data`/
96
- // `history`/`run-info`/`trigger` are built via scraps.ts's exported
97
- // attachXCommand() factories the SAME definition also stays wired
98
- // (hidden) under `scraps` there, so every pre-#108 `trawl scraps <verb>`
99
- // invocation keeps resolving (no breaking change).
95
+ // Core verbs (#108) — promoted/listed first, registration order now
96
+ // follows the guided flow a new user (or agent) actually walks: create it
97
+ // → list/get to see it → run/trigger to execute it data to read the
98
+ // result history/run-info for deeper diagnostics whoami/token/ping as
99
+ // standalone identity/credential/health utilities (#149 item 6 the
100
+ // pre-#149 order had `run` wedged between `create` and `list`, and
101
+ // `trigger` stranded after `run-info`, both ahead of ever having listed or
102
+ // inspected anything). `list`/`get`/`run`/`data`/`history`/`run-info`/
103
+ // `trigger` are built via scraps.ts's exported attachXCommand() factories —
104
+ // the SAME definition also stays wired (hidden) under `scraps` there, so
105
+ // every pre-#108 `trawl scraps <verb>` invocation keeps resolving (no
106
+ // breaking change).
100
107
  //
101
108
  // #114 — `create` replaced `fetch` in this slot: `POST /api/ai/wizard`
102
109
  // (AI-generate + persist + first-run + autofix), a distinct command from
103
110
  // the still-untouched `trawl scraps create` (raw-script management verb).
111
+ //
112
+ // #149 item 5 — `token` promoted from Management into Core: it satisfies
113
+ // every criterion the CORE_GROUP heading itself claims (agent + human,
114
+ // `--json` first-class, never interactive) at least as well as `whoami`/
115
+ // `ping` do, and exists specifically to feed an agent's MCP Bearer auth
116
+ // header (`$(trawl token)`) — a Management/human-CI bucket undersells that.
104
117
  program.commandsGroup(CORE_GROUP);
105
118
  program.addCommand(create);
106
- attachRunCommand(program);
107
119
  attachListCommand(program);
108
120
  attachGetCommand(program);
121
+ attachRunCommand(program);
122
+ attachTriggerCommand(program);
109
123
  attachDataCommand(program);
110
124
  attachHistoryCommand(program);
111
125
  attachRunInfoCommand(program);
112
- attachTriggerCommand(program);
113
126
  program.addCommand(whoami);
127
+ program.addCommand(token);
114
128
  program.addCommand(ping);
115
129
  // Management (#108) — human/CI surface, grouped below. `scraps` still
116
130
  // holds every pre-#108 management command (create/update/delete/banner/
@@ -120,7 +134,6 @@ export function createProgram() {
120
134
  program.addCommand(skills);
121
135
  program.addCommand(login);
122
136
  program.addCommand(logout);
123
- program.addCommand(token);
124
137
  program.addCommand(telemetry);
125
138
  program.addCommand(upgrade);
126
139
  return program;
@@ -216,6 +229,33 @@ export function applyExitOverride(cmd) {
216
229
  for (const sub of cmd.commands)
217
230
  applyExitOverride(sub);
218
231
  }
232
+ /**
233
+ * #149 item 1 — commander's own usage-error line (unknown option, missing
234
+ * required arg, unknown command, …) is written via `Command#error()` ->
235
+ * `outputError()` straight to `process.stderr` BEFORE the exitOverride throw
236
+ * below is ever caught — in commander's own plain, uncolored `"error: …"`
237
+ * format, a visibly different convention from every other error this CLI
238
+ * prints (`reportError`'s `chalk.red('✗ ' + message)`). `configureOutput`'s
239
+ * `outputError` hook is exactly the one commander documents for reformatting
240
+ * the error text itself (`writeErr` also carries unrelated help output, e.g.
241
+ * `showHelpAfterError` — never enabled in this CLI, but out of scope to
242
+ * touch here). Overriding it on every node in the tree (same walk shape as
243
+ * applyExitOverride, for the same reason: addCommand()-attached subtrees
244
+ * don't otherwise inherit a parent's configureOutput) reformats that one line
245
+ * through the same red ✗ convention, stripping the redundant "error: " prefix
246
+ * first (stripCommanderErrorPrefix — the same prefix also leaks verbatim
247
+ * into the --json envelope's `message` field, see runCli's catch below).
248
+ */
249
+ export function applyOutputConfiguration(cmd) {
250
+ cmd.configureOutput({
251
+ outputError: (str, write) => {
252
+ const withoutNewline = str.endsWith('\n') ? str.slice(0, -1) : str;
253
+ write(chalk.red('✗ ' + stripCommanderErrorPrefix(withoutNewline)) + '\n');
254
+ },
255
+ });
256
+ for (const sub of cmd.commands)
257
+ applyOutputConfiguration(sub);
258
+ }
219
259
  /** Commander's own codes for a successful --help/--version/`help` query —
220
260
  * these already printed their own output (to stdout) via commander itself;
221
261
  * runCli's catch must treat them as a clean exit, not an error. (#86 finding 3) */
@@ -264,6 +304,9 @@ export async function runCli(argv = process.argv) {
264
304
  // including subcommands added via addCommand() that don't otherwise
265
305
  // inherit it. (#86 finding 3)
266
306
  applyExitOverride(program);
307
+ // Same tree-walk requirement as applyExitOverride, same reason — installs
308
+ // the unified red ✗ error formatting on every node. (#149 item 1)
309
+ applyOutputConfiguration(program);
267
310
  const commandNames = collectCommandNames(program);
268
311
  // #88 item 6 — 'unknown' is a legitimate resolveCommandName() output (the
269
312
  // fallback for "no command resolved at all"), not free-form user input —
@@ -334,16 +377,22 @@ export async function runCli(argv = process.argv) {
334
377
  else {
335
378
  // A usage error (unknown option/command, missing required arg, …) —
336
379
  // commander already wrote its own human-readable line to stderr via
337
- // Command#error(), so this never duplicates it. Force exit code 2
338
- // (usage) regardless of whichever code commander suggests (it
339
- // defaults these to 1), and add the --json machine envelope when
340
- // resolvable parsing failed before any command's own --json flag
341
- // could be read off `currentCommand` (preAction never fired), so
342
- // scan raw argv instead. (#86 finding 3)
380
+ // Command#error() (now reformatted through the same red convention
381
+ // by applyOutputConfiguration, #149 item 1), so this never duplicates
382
+ // it. Force exit code 2 (usage) regardless of whichever code
383
+ // commander suggests (it defaults these to 1), and add the --json
384
+ // machine envelope when resolvable parsing failed before any
385
+ // command's own --json flag could be read off `currentCommand`
386
+ // (preAction never fired), so scan raw argv instead. (#86 finding 3)
343
387
  if (isDebug)
344
388
  console.error(err);
345
389
  if (hasJsonFlag(argv)) {
346
- console.log(JSON.stringify({ error: { message: err.message, kind: 'usage' } }));
390
+ // #149 item 2 err.message still carries commander's own literal
391
+ // "error: " prefix (set on the CommanderError itself, independent
392
+ // of the outputError override above, which only reformats what
393
+ // gets WRITTEN to stderr) — strip it so the envelope's `message`
394
+ // stays clean for a machine parser.
395
+ console.log(JSON.stringify({ error: { message: stripCommanderErrorPrefix(err.message), kind: 'usage' } }));
347
396
  }
348
397
  process.exitCode = 2;
349
398
  // #88 item 6 — currentCommand is still undefined here whenever the
@@ -21,6 +21,18 @@ export declare class UsageError extends Error {
21
21
  export declare class RefusalError extends Error {
22
22
  constructor(message: string);
23
23
  }
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
+ export declare function stripCommanderErrorPrefix(message: string): string;
24
36
  export interface ErrorEnvelope {
25
37
  message: string;
26
38
  status?: number;
@@ -29,6 +29,20 @@ export class RefusalError extends Error {
29
29
  this.name = 'RefusalError';
30
30
  }
31
31
  }
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
+ export function stripCommanderErrorPrefix(message) {
44
+ return message.startsWith('error: ') ? message.slice('error: '.length) : message;
45
+ }
32
46
  /**
33
47
  * Central status → exit-code map (#71 findings 13/14/60). Agents driving this
34
48
  * CLI unattended need to tell "you're not logged in" (3) from "that id
@@ -20,21 +20,22 @@ agent should never need them.)
20
20
 
21
21
  ## Core commands (agent + human)
22
22
 
23
- These ten commands are the CLI's agent+human surface — `--json` is
23
+ These eleven commands are the CLI's agent+human surface — `--json` is
24
24
  first-class on every one, and none of them ever blocks on a prompt (see
25
25
  [Non-interactive contract](#non-interactive-contract) below):
26
26
 
27
27
  ```
28
28
  trawl create <url> --prompt <goal> [--no-autofix] [--json] # url also accepted as --url <url> (#116)
29
29
  Create a persistent, self-healing scrap from a URL + a goal (AI-generated)
30
- trawl run <id> [--watch] [--json] Run a scrap (blocks until finished)
31
30
  trawl list|ls [--json] [--status <s>] [--limit <n>] [--page <n>] List all scraps
32
31
  trawl get <id> [--json] Get scrap details
32
+ trawl run <id> [--watch] [--json] Run a scrap (blocks until finished)
33
+ trawl trigger <id> [--watch] [--wait] [--json] Launch a scrap as a background worker
33
34
  trawl data <id> [--json] [--fresh] [--errors] Get scrap data (last persisted run, or --fresh to launch one)
34
35
  trawl history <id> [--json] [-n <limit>] List past runs for a scrap
35
36
  trawl run-info <hid> [--json] Show details of a single run
36
- trawl trigger <id> [--watch] [--wait] [--json] Launch a scrap as a background worker
37
37
  trawl whoami [--json] Show the authenticated user's identity
38
+ trawl token [--json] Print the stored session JWT (for MCP Bearer auth)
38
39
  trawl ping [--json] Health/version handshake against the Trawl API
39
40
  ```
40
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trawlme/cli",
3
- "version": "3.7.0",
3
+ "version": "3.7.1",
4
4
  "description": "Trawl CLI — manage scraps from the terminal",
5
5
  "type": "module",
6
6
  "bin": {