@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 +4 -4
- package/dist/commands/scraps.js +13 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.js +66 -17
- package/dist/lib/errors.d.ts +12 -0
- package/dist/lib/errors.js +14 -0
- package/docs/agent-quickstart.md +4 -3
- package/package.json +1 -1
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
|
|
package/dist/commands/scraps.js
CHANGED
|
@@ -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
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
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()
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
//
|
|
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
|
-
|
|
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
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/errors.js
CHANGED
|
@@ -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
|
package/docs/agent-quickstart.md
CHANGED
|
@@ -20,21 +20,22 @@ agent should never need them.)
|
|
|
20
20
|
|
|
21
21
|
## Core commands (agent + human)
|
|
22
22
|
|
|
23
|
-
These
|
|
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
|
|