@trawlme/cli 3.4.2 → 3.5.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
@@ -6,7 +6,7 @@
6
6
 
7
7
  Command-line client for [Trawl](https://trawl.me) — manage your scraps from the terminal.
8
8
 
9
- > ⚠️ **Early stage.** APIs may change before `1.0`. Pin a version if you depend on this in CI.
9
+ > 📌 **Versioned with semantic-release** breaking CLI-surface changes always ship as a new major. Pin a major (`@trawlme/cli@^3`) if you depend on the CLI in CI.
10
10
 
11
11
  > 🤖 **Driving this CLI from an AI agent?** See [docs/agent-quickstart.md](docs/agent-quickstart.md) for the minimal surface (`create`, `--json`, `TRAWL_TOKEN`, exit codes) — this README is the full human/CI guide.
12
12
 
@@ -48,7 +48,7 @@ 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
51
+ trawl run <id> [--watch] [--json] Run a scrap (blocks until finished)
52
52
  trawl list|ls [--json] [--status <success|failure|never|running|regression>] [--limit <n>] [--page <n>]
53
53
  trawl get <id> [--json] Get scrap details
54
54
  trawl data <id> [--json] [--fresh] [--errors] Get scrap data (last persisted run — read-only, no quota; --fresh to launch one)
@@ -59,6 +59,8 @@ trawl whoami [--json] Show the authenticated user's ide
59
59
  trawl ping [--json] Health/version handshake against the Trawl API
60
60
  ```
61
61
 
62
+ (`trawl create` AI-generates and persists a new scrap from a URL + a goal — distinct from `trawl scraps create`, which is raw manual scrap entry.)
63
+
62
64
  > **No breaking change:** every verb above is also still reachable under its pre-reorg path, `trawl scraps <verb>` (e.g. `trawl scraps list`, `trawl scraps run <id>`) — kept as a hidden alias so scripts written before the surface reorg keep working. `trawl --help` only shows the top-level form above; `trawl scraps --help` only shows the remaining scrap-management commands below.
63
65
 
64
66
  `create`/`whoami`/`ping` are fully non-interactive — all three read auth only from `TRAWL_TOKEN`/the stored login token, never prompt. `trawl create` runs the AI wizard server-side (`POST /api/ai/wizard`): generate scrap code from `--prompt` via LLM, persist the scrap, trigger its FIRST run, and auto-fix on failure (default on — `--no-autofix` disables it, sending `autoFix:false`). On a successful first run (human mode) it prints a small **data sample** (item count + first-item fields + one truncated value) as proof of value — best-effort, silent if the sample can't be fetched — and points `Next step` at `trawl data <id>` (the data), with `trawl get <id>` as the secondary detail view. `--json` skips the sample fetch and prints the raw wizard payload verbatim. `success` is an honest outcome of that first run, not "did the HTTP call succeed" — a failed first run is still a 200 response (the scrap was still created; auto-fix, when enabled, retries in the background), and the CLI exits 1 in that case (both human and `--json` modes) even though `--json` always prints the raw payload verbatim. The call legitimately takes 30–250s+ server-side (AI generation + a real run), same long-run timeout as `run`/`data --fresh`/`trigger --wait` below. `trawl whoami`/`trawl ping` mirror the MCP `trawl_whoami`/`trawl_health_ping` tools as closely as the REST surface allows (`GET /api/users/me` / `GET /api/health`) — `ping`'s `--json` payload is admin-enriched (version/uptime/db) and just `{"status":"ok"}` for anyone else.
@@ -243,6 +245,8 @@ This project uses `legacy-peer-deps` (see `.npmrc`) due to a transitive peer-dep
243
245
 
244
246
  Issues and PRs are welcome — please open an issue first for non-trivial changes so we can align on direction.
245
247
 
248
+ Releases: a merge to `main` publishes to the npm **`next`** dist-tag (candidate channel — try it with `npm i -g @trawlme/cli@next`). The **`latest`** tag is promoted manually after QA, so a merge never ships to default installs directly.
249
+
246
250
  ## License
247
251
 
248
252
  MIT — see [LICENSE](./LICENSE).
@@ -88,7 +88,7 @@ async function printDataSample(historyId) {
88
88
  }
89
89
  }
90
90
  export const create = new Command('create')
91
- .description('Create a persistent, self-healing scrap from a URL + a goal (AI-generated)')
91
+ .description('Create a persistent, self-healing scrap from a URL + a goal (AI-generated) — distinct from `trawl scraps create`, which is raw manual entry')
92
92
  .argument('[url]', 'Target public URL (http/https)')
93
93
  .requiredOption('--prompt <goal>', 'What to extract/scrape, in plain language (required)')
94
94
  .option('--url <url>', 'Target public URL — alias of the positional argument')
@@ -6,7 +6,14 @@ export const ping = new Command('ping')
6
6
  .description('Health/version handshake against the Trawl API')
7
7
  .option('--json', 'Output as JSON')
8
8
  .action(async (opts) => {
9
- const data = await api.get('/api/health');
9
+ // #148 the server route is optionalAuth (public, enriched for an admin
10
+ // JWT — see the PingResponse doc above); `api.publicGet` mirrors that:
11
+ // it attaches a stored/env token when one happens to be available but
12
+ // never REQUIRES one, so `ping` works as a zero-config sanity check even
13
+ // with no `trawl login` ever run. `api.get` (the authenticated path)
14
+ // would throw notLoggedInError() locally before this ever reached the
15
+ // network, defeating the whole point of a pre-login handshake.
16
+ const data = await api.publicGet('/api/health');
10
17
  // #106 review (kimi) — honest exit code: a soft-degraded 200 (status !== 'ok')
11
18
  // must exit non-zero so `trawl ping || handle_degraded` doesn't treat a
12
19
  // degraded API as healthy. Applies in BOTH --json and human modes, alongside
@@ -710,7 +710,7 @@ scraps
710
710
  export function attachRunCommand(parent, attachOpts = {}) {
711
711
  return parent
712
712
  .command('run <id>', attachOpts)
713
- .description('Run a scrap')
713
+ .description('Run a scrap (blocks until finished)')
714
714
  .option('-w, --watch', 'Show progress after launching (polls — see `trawl trigger --watch`, #91)')
715
715
  .option('--json', 'Output the raw launch payload as JSON')
716
716
  .action(async (id, opts) => {
package/dist/index.js CHANGED
@@ -293,7 +293,29 @@ export async function runCli(argv = process.argv) {
293
293
  }
294
294
  });
295
295
  try {
296
- await program.parseAsync(argv);
296
+ // #148 — a bare `trawl` (no args at all) reads as "let me look around",
297
+ // not a failure: it should behave exactly like `trawl --help` (same
298
+ // text, stdout, exit 0). Left to commander's own default, a program with
299
+ // subcommands and no root action handler treats zero args as "probably
300
+ // missing subcommand" and calls `this.help({ error: true })` internally
301
+ // (see node_modules/commander/lib/command.js `_parseCommand`) — which
302
+ // writes the SAME help text to stderr and throws a CommanderError with
303
+ // exitCode 1 (caught below, bucketed into HELP_OR_VERSION_CODES since its
304
+ // code is 'commander.help', so only the exit code carries the mistake
305
+ // through — text already went to the wrong stream by then). Intercepting
306
+ // here, before parseAsync ever runs, avoids fighting that internal
307
+ // decision entirely and can't affect a genuine unknown top-level command
308
+ // (`trawl frobnicate`) — that path only runs when `isBareInvocation` is
309
+ // false, so it still reaches `this.unknownCommand()` unchanged (stderr,
310
+ // exit 2, code 'commander.unknownCommand' — never in
311
+ // HELP_OR_VERSION_CODES).
312
+ if (isBareInvocation(argv)) {
313
+ program.outputHelp();
314
+ process.exitCode = 0;
315
+ }
316
+ else {
317
+ await program.parseAsync(argv);
318
+ }
297
319
  }
298
320
  catch (err) {
299
321
  const { debug } = program.opts();
package/dist/lib/api.d.ts CHANGED
@@ -60,6 +60,7 @@ export interface RequestOptions {
60
60
  }
61
61
  export declare const api: {
62
62
  get: <T>(path: string, opts?: RequestOptions) => Promise<T>;
63
+ publicGet: <T>(path: string, opts?: RequestOptions) => Promise<T>;
63
64
  getText: (path: string, opts?: RequestOptions) => Promise<string>;
64
65
  post: <T>(path: string, body?: unknown, opts?: RequestOptions) => Promise<T>;
65
66
  put: <T>(path: string, body?: unknown, opts?: RequestOptions) => Promise<T>;
package/dist/lib/api.js CHANGED
@@ -314,6 +314,47 @@ async function publicPost(path, body, baseUrlOverride, reqOpts = {}) {
314
314
  throw new Error('Invalid JSON in server response');
315
315
  }
316
316
  }
317
+ /**
318
+ * Unauthenticated GET — for endpoints the server itself treats as public/
319
+ * optional-auth (e.g. `GET /api/health`, home.route.js's `optionalAuth`
320
+ * middleware: enriches the response for an admin JWT, passes through
321
+ * otherwise). Unlike `request()`, this NEVER throws `notLoggedInError()` for
322
+ * a missing token — that guard exists for endpoints that genuinely require
323
+ * auth, and applying it here defeated the whole point of a pre-login
324
+ * sanity-check (#148). The token is still attached as a best-effort Cookie
325
+ * when one happens to be stored/env-set, so an already-authenticated caller
326
+ * still gets the admin-enriched payload — it's just never REQUIRED.
327
+ * `throwIfError(res, true)` mirrors `publicPost`'s "public endpoint" 401
328
+ * wording, even though this route's optionalAuth middleware never actually
329
+ * rejects a request for lacking/invalid credentials.
330
+ */
331
+ async function publicGet(path, reqOpts = {}) {
332
+ const token = getToken();
333
+ const url = `${getApiUrl()}${path}`;
334
+ const timeoutMs = getTimeoutMs(reqOpts.timeoutMs);
335
+ const res = await safeFetch(url, {
336
+ headers: {
337
+ 'User-Agent': USER_AGENT,
338
+ ...(token ? { Cookie: `TOKEN=${token}` } : {}),
339
+ },
340
+ signal: AbortSignal.timeout(timeoutMs),
341
+ }, timeoutMs);
342
+ await throwIfError(res, true);
343
+ const text = await res.text();
344
+ try {
345
+ if (!text)
346
+ return {};
347
+ const parsed = JSON.parse(text);
348
+ // Unwrap API envelope { type, message, data: T }
349
+ if (parsed !== null && typeof parsed === 'object' && 'data' in parsed) {
350
+ return parsed.data;
351
+ }
352
+ return parsed;
353
+ }
354
+ catch {
355
+ throw new Error('Invalid JSON in server response');
356
+ }
357
+ }
317
358
  async function getText(path, reqOpts = {}) {
318
359
  const token = getToken();
319
360
  if (!token)
@@ -332,6 +373,7 @@ async function getText(path, reqOpts = {}) {
332
373
  }
333
374
  export const api = {
334
375
  get: (path, opts) => request(path, {}, opts),
376
+ publicGet: (path, opts) => publicGet(path, opts),
335
377
  getText: (path, opts) => getText(path, opts),
336
378
  post: (path, body, opts) => request(path, {
337
379
  method: 'POST',
@@ -3,29 +3,30 @@
3
3
  * art. Distinct from Clawd's 8-bit lane: Pinch is drawn with full 24-bit
4
4
  * (`\x1b[38;2;r;g;bm` / `\x1b[48;2;r;g;bm`) color blocks, not a fixed palette.
5
5
  *
6
- * The 9×6 cube-grid (#133 mini) is packed two grid rows into one terminal
7
- * row: the
8
- * upper row's color becomes the half-block's foreground, the lower row's
9
- * becomes its background, using the upper-half-block glyph '▀' (or '▄' when
10
- * only the lower half is filled). A 6-row grid therefore renders in 3
11
- * terminal rows, 9 columns wide. '.' cells are transparent — no color
12
- * escape is emitted for that half, so the terminal's own background shows
13
- * through.
6
+ * Each grid is packed two rows into one terminal row: the upper row's color
7
+ * becomes the half-block's foreground, the lower row's becomes its
8
+ * background, using the upper-half-block glyph '▀' (or '▄' when only the
9
+ * lower half is filled). '.' cells are transparent no color escape is
10
+ * emitted for that half, so the terminal's own background shows through.
14
11
  *
15
- * Grids + palette are mirrored from trawl_vue
16
- * `src/modules/trawl/assets/mascot/_src/pinch.model.mjs` (AVCOLORS +
17
- * AVATAR_GRIDS, rev r6f) — no cross-repo import (cli is a standalone npm
18
- * package). Claws are 1-wide 2-tall 'O' blocks at cols 0 / 8, on the SIDES
19
- * (raised for wave/thumbsup/celebrating/shocked), per the r6f shape.
12
+ * The grids are the founder-approved 2D front avatar (`AVATAR_GRIDS`, rev
13
+ * r6f) from trawl_vue `src/modules/trawl/assets/mascot/_src/pinch.model.mjs`,
14
+ * ported VERBATIM (no cross-repo import cli is a standalone npm package).
15
+ * Each is 14 cols × 8 rows (the source's 9th all-'.' pad row is dropped) →
16
+ * renders in 4 terminal rows, 14 columns wide. This is the mascot "de face":
17
+ * antennae on top, 2-wide white eyes + navy pupils, pink cheek blush, a
18
+ * U-smile (corners + a 2-wide dropped center), and the signature 2-WIDE
19
+ * orange claws on the body flanks (cols 0-1 / 12-13).
20
20
  *
21
- * State parity (#135): the CLI now carries all 9 canonical AVATAR_STATES
22
- * from the source model idle · working · celebrating · thumbsup · confused
23
- * · wave · heart · sleepy · shocked — with the SAME names (before #135 the
24
- * CLI shipped 4 states and called `working` "thinking", a name not in the
25
- * source). Each is a 9×6 mini redraw preserving that state's distinguishing
26
- * feature.
21
+ * History: #133 shrank this to a 9×6 mini (1-wide claws, a single-pixel
22
+ * mouth) #141 reverted to this faithful 14×9 founder art after it read as
23
+ * "trop loin du 3D".
27
24
  *
28
- * See comes-io/trawl_cli#94, comes-io/trawl_cli#122, comes-io/trawl_cli#135.
25
+ * State parity (#135): the CLI carries all 9 canonical AVATAR_STATES with the
26
+ * source's own names — idle · working · celebrating · thumbsup · confused ·
27
+ * wave · heart · sleepy · shocked.
28
+ *
29
+ * See comes-io/trawl_cli#94, #122, #135, #141.
29
30
  */
30
31
  export type PinchState = 'idle' | 'working' | 'celebrating' | 'thumbsup' | 'confused' | 'wave' | 'heart' | 'sleepy' | 'shocked';
31
32
  /** All 9 canonical states, in the source model's AVATAR_STATES order. */
@@ -42,9 +43,10 @@ export declare function renderPinch(state: PinchState, frame?: number): string;
42
43
  /**
43
44
  * #131 — the ART lines only (no caption), optionally COMPACT: trailing and
44
45
  * leading fully-transparent grid rows are dropped so Pinch takes fewer
45
- * terminal lines (some grids carry an all-'.' padding row). `frame` is passed
46
- * through to `gridForState` — only `working` reacts to it (the cyan antenna
47
- * blip toggles), so the loop can pulse the tips while the body stays still.
46
+ * terminal lines (confused/sleepy carry an all-'.' leading row). `frame` is
47
+ * passed through to `gridForState` — only `working` reacts to it (the cyan
48
+ * antenna blip toggles), so the loop can pulse the tips while the body stays
49
+ * still.
48
50
  */
49
51
  export declare function renderPinchArt(state: PinchState, opts?: {
50
52
  compact?: boolean;
package/dist/lib/pinch.js CHANGED
@@ -3,29 +3,30 @@
3
3
  * art. Distinct from Clawd's 8-bit lane: Pinch is drawn with full 24-bit
4
4
  * (`\x1b[38;2;r;g;bm` / `\x1b[48;2;r;g;bm`) color blocks, not a fixed palette.
5
5
  *
6
- * The 9×6 cube-grid (#133 mini) is packed two grid rows into one terminal
7
- * row: the
8
- * upper row's color becomes the half-block's foreground, the lower row's
9
- * becomes its background, using the upper-half-block glyph '▀' (or '▄' when
10
- * only the lower half is filled). A 6-row grid therefore renders in 3
11
- * terminal rows, 9 columns wide. '.' cells are transparent — no color
12
- * escape is emitted for that half, so the terminal's own background shows
13
- * through.
6
+ * Each grid is packed two rows into one terminal row: the upper row's color
7
+ * becomes the half-block's foreground, the lower row's becomes its
8
+ * background, using the upper-half-block glyph '▀' (or '▄' when only the
9
+ * lower half is filled). '.' cells are transparent no color escape is
10
+ * emitted for that half, so the terminal's own background shows through.
14
11
  *
15
- * Grids + palette are mirrored from trawl_vue
16
- * `src/modules/trawl/assets/mascot/_src/pinch.model.mjs` (AVCOLORS +
17
- * AVATAR_GRIDS, rev r6f) — no cross-repo import (cli is a standalone npm
18
- * package). Claws are 1-wide 2-tall 'O' blocks at cols 0 / 8, on the SIDES
19
- * (raised for wave/thumbsup/celebrating/shocked), per the r6f shape.
12
+ * The grids are the founder-approved 2D front avatar (`AVATAR_GRIDS`, rev
13
+ * r6f) from trawl_vue `src/modules/trawl/assets/mascot/_src/pinch.model.mjs`,
14
+ * ported VERBATIM (no cross-repo import cli is a standalone npm package).
15
+ * Each is 14 cols × 8 rows (the source's 9th all-'.' pad row is dropped) →
16
+ * renders in 4 terminal rows, 14 columns wide. This is the mascot "de face":
17
+ * antennae on top, 2-wide white eyes + navy pupils, pink cheek blush, a
18
+ * U-smile (corners + a 2-wide dropped center), and the signature 2-WIDE
19
+ * orange claws on the body flanks (cols 0-1 / 12-13).
20
20
  *
21
- * State parity (#135): the CLI now carries all 9 canonical AVATAR_STATES
22
- * from the source model idle · working · celebrating · thumbsup · confused
23
- * · wave · heart · sleepy · shocked — with the SAME names (before #135 the
24
- * CLI shipped 4 states and called `working` "thinking", a name not in the
25
- * source). Each is a 9×6 mini redraw preserving that state's distinguishing
26
- * feature.
21
+ * History: #133 shrank this to a 9×6 mini (1-wide claws, a single-pixel
22
+ * mouth) #141 reverted to this faithful 14×9 founder art after it read as
23
+ * "trop loin du 3D".
27
24
  *
28
- * See comes-io/trawl_cli#94, comes-io/trawl_cli#122, comes-io/trawl_cli#135.
25
+ * State parity (#135): the CLI carries all 9 canonical AVATAR_STATES with the
26
+ * source's own names — idle · working · celebrating · thumbsup · confused ·
27
+ * wave · heart · sleepy · shocked.
28
+ *
29
+ * See comes-io/trawl_cli#94, #122, #135, #141.
29
30
  */
30
31
  /** Grid-char → RGB, ported from AVCOLORS (pinch.model.mjs, rev r6f). 'C'
31
32
  * (cyan) is the `working` thinking-blip — not present in any static grid,
@@ -52,107 +53,125 @@ export const PINCH_STATES = [
52
53
  'shocked',
53
54
  ];
54
55
  /**
55
- * Explicit per-state 9×6 grids (#133 mini shape, #135 full state parity).
56
- * The 14×9 grids were ported from trawl_vue's AVATAR_GRIDS (pinch.model.mjs,
57
- * rev r6f); these mini redraws preserve each state's feature at a smaller
58
- * pixel count. Grid layout (cols 0-8, body/shell cols 1-7):
59
- * row 0 antennae (O at cols 2,6 spread to 1,7 when drooped)
60
- * row 1 head top (+ claw at col 0/8 when RAISED)
61
- * row 2 eyes (+ claw at col 0/8 when RAISED)
62
- * row 3 head mid
63
- * row 4 mouth corners + blush (+ claw at col 0/8 when RESTING)
64
- * row 5 head bottom + mouth center (+ claw at col 0/8 when RESTING)
65
- * Claws are the 1-wide 2-tall O blocks at cols 0/8. Eyes: left = W+K at
66
- * cols 2-3, right = K+W at cols 5-6 (pupils inward). The smile is a U (like
67
- * the source 3D / AVGRID): the mouth CORNERS sit on row 4 and the CENTER
68
- * dips to row 5 — never a flat single-row bar (#137).
56
+ * The founder 2D front avatar grids (`AVATAR_GRIDS`, pinch.model.mjs rev
57
+ * r6f), ported VERBATIM rows 0-7 of each 9-row grid (the trailing all-'.'
58
+ * pad row 8 is dropped; it only exists to square the SVG canvas). 14 cols,
59
+ * 8 rows 4 terminal lines. Layout:
60
+ * rows 0-1 antennae (2 orange, drooped/spread for confused & sleepy)
61
+ * row 2 head top (+ claw at cols 0-1/12-13 when RAISED)
62
+ * rows 3-4 eyes: 2-wide W whites (row 3) + K pupils (row 4)
63
+ * row 5 head mid
64
+ * rows 6-7 mouth + blush + the 2-WIDE claws on the flanks
65
+ * The claws are 2×2 O blocks OUTSIDE the body silhouette (cols 0-1 / 12-13),
66
+ * raised to rows 3-4 for celebrating / shocked (and the right claw for
67
+ * wave / thumbsup). The smile is a U: corners K on row 6, a 2-wide center
68
+ * KK on row 7.
69
69
  */
70
70
  const GRIDS = {
71
- // calm resting face, both claws down, normal smile.
72
71
  idle: [
73
- '..O...O..',
74
- '.BBBBBBB.',
75
- '.BWKBKWB.',
76
- '.BBBBBBB.',
77
- 'OBPKBKPBO',
78
- 'OBBBKBBBO',
72
+ '....O....O....',
73
+ '....O....O....',
74
+ '..BBBBBBBBBB..',
75
+ '..BWWBBBBWWB..',
76
+ '..BWKBBBBKWB..',
77
+ '..BBBBBBBBBB..',
78
+ 'OOBBPKBBKPBBOO',
79
+ 'OOBBBBKKBBBBOO',
79
80
  ],
80
81
  // idle pose + the cyan thinking-blip on the antenna tips (frame-toggled in
81
82
  // `gridForState`) — the only animated static state.
82
83
  working: [
83
- '..O...O..',
84
- '.BBBBBBB.',
85
- '.BWKBKWB.',
86
- '.BBBBBBB.',
87
- 'OBPKBKPBO',
88
- 'OBBBKBBBO',
84
+ '....O....O....',
85
+ '....O....O....',
86
+ '..BBBBBBBBBB..',
87
+ '..BWWBBBBWWB..',
88
+ '..BWKBBBBKWB..',
89
+ '..BBBBBBBBBB..',
90
+ 'OOBBPKBBKPBBOO',
91
+ 'OOBBBBKKBBBBOO',
89
92
  ],
90
- // both claws raised (rows 1-2) + a big open mouth.
93
+ // both claws raised to rows 3-4 + a big open mouth (rows 6-7).
91
94
  celebrating: [
92
- '..O...O..',
93
- 'OBBBBBBBO',
94
- 'OBWKBKWBO',
95
- '.BBBBBBB.',
96
- '.BKBBBKB.',
97
- '.BBKKKBB.',
95
+ '....O....O....',
96
+ '....O....O....',
97
+ '..BBBBBBBBBB..',
98
+ 'OOBWWBBBBWWBOO',
99
+ 'OOBWKBBBBKWBOO',
100
+ '..BBBBBBBBBB..',
101
+ '..BBPKKKKPBB..',
102
+ '..BBBKKKKBBB..',
98
103
  ],
99
- // right claw raised (rows 1-2) + a thumb pixel poking up above it (row 0,
100
- // col 8); left claw resting.
104
+ // right claw raised (rows 3-4) + a thumb pixel poking up (row 2, col 12);
105
+ // left claw resting on the flank (rows 6-7).
101
106
  thumbsup: [
102
- '..O...O.O',
103
- '.BBBBBBBO',
104
- '.BWKBKWBO',
105
- '.BBBBBBB.',
106
- 'OBPKBKPB.',
107
- 'OBBBKBBB.',
107
+ '....O....O....',
108
+ '....O....O....',
109
+ '..BBBBBBBBBBO.',
110
+ '..BWWBBBBWWBOO',
111
+ '..BWKBBBBKWBOO',
112
+ '..BBBBBBBBBB..',
113
+ 'OOBBPKBBKPBB..',
114
+ 'OOBBBBKKBBBB..',
108
115
  ],
109
- // antennae drooped + spread, flat mouth (no K), claws resting. A '?' is
110
- // appended beside the eye line by `renderPinch`.
116
+ // antennae drooped + spread (row 1, cols 3 & 10), flat mouth (blush only, no
117
+ // smile K on row 6), claws resting. A '?' is appended by `renderPinch`.
111
118
  confused: [
112
- '.O.....O.',
113
- '.BBBBBBB.',
114
- '.BWKBKWB.',
115
- '.BBBBBBB.',
116
- 'OBPBBBPBO',
117
- 'OBBBBBBBO',
119
+ '..............',
120
+ '...O......O...',
121
+ '..BBBBBBBBBB..',
122
+ '..BWWBBBBWWB..',
123
+ '..BWKBBBBKWB..',
124
+ '..BBBBBBBBBB..',
125
+ 'OOBBPBBBBPBBOO',
126
+ 'OOBBBBKKBBBBOO',
118
127
  ],
119
- // right claw raised (rows 1-2), left claw resting; base face.
128
+ // right claw raised (rows 3-4), left claw resting; base face.
120
129
  wave: [
121
- '..O...O..',
122
- '.BBBBBBBO',
123
- '.BWKBKWBO',
124
- '.BBBBBBB.',
125
- 'OBPKBKPB.',
126
- 'OBBBKBBB.',
130
+ '....O....O....',
131
+ '....O....O....',
132
+ '..BBBBBBBBBB..',
133
+ '..BWWBBBBWWBOO',
134
+ '..BWKBBBBKWBOO',
135
+ '..BBBBBBBBBB..',
136
+ 'OOBBPKBBKPBB..',
137
+ 'OOBBBBKKBBBB..',
127
138
  ],
128
- // pink heart-eyes (P replaces the W+K pupils), claws resting, normal smile.
139
+ // pink heart-eyes (P fills both eye blocks, rows 3-4), claws resting, normal
140
+ // smile.
129
141
  heart: [
130
- '..O...O..',
131
- '.BBBBBBB.',
132
- '.BPPBPPB.',
133
- '.BBBBBBB.',
134
- 'OBPKBKPBO',
135
- 'OBBBKBBBO',
142
+ '....O....O....',
143
+ '....O....O....',
144
+ '..BBBBBBBBBB..',
145
+ '..BPPBBBBPPB..',
146
+ '..BPPBBBBPPB..',
147
+ '..BBBBBBBBBB..',
148
+ 'OOBBPKBBKPBBOO',
149
+ 'OOBBBBKKBBBBOO',
136
150
  ],
137
- // closed eyes (flat K line, no white), antennae drooped + spread, flat
138
- // mouth, claws resting — like confused but eyes shut and no '?'.
151
+ // closed eyes (flat K line on row 4, no white), antennae drooped + spread,
152
+ // flat mouth, claws resting — like confused but eyes shut and no '?'.
139
153
  sleepy: [
140
- '.O.....O.',
141
- '.BBBBBBB.',
142
- '.BKKBKKB.',
143
- '.BBBBBBB.',
144
- 'OBPBBBPBO',
145
- 'OBBBBBBBO',
154
+ '..............',
155
+ '...O......O...',
156
+ '..BBBBBBBBBB..',
157
+ '..BWWBBBBWWB..',
158
+ '..BKKBBBBKKB..',
159
+ '..BBBBBBBBBB..',
160
+ 'OOBBPBBBBPBBOO',
161
+ 'OOBBBBKKBBBBOO',
146
162
  ],
147
- // both claws raised (rows 1-2) + a small round mouth (single K, row 4)
148
- // like celebrating's raise but a startled dot mouth, not the wide smile.
163
+ // pupils up a row (row 3 carries K, row 4 all white), both claws raised
164
+ // (rows 3-4), a small round mouth (row 6 only) — startled, not the wide
165
+ // celebrating grin.
149
166
  shocked: [
150
- '..O...O..',
151
- 'OBBBBBBBO',
152
- 'OBWKBKWBO',
153
- '.BBBBBBB.',
154
- '.BBBKBBB.',
155
- '.BBBBBBB.',
167
+ '....O....O....',
168
+ '....O....O....',
169
+ '..BBBBBBBBBB..',
170
+ 'OOBWKBBBBKWBOO',
171
+ 'OOBWWBBBBWWBOO',
172
+ '..BBBBBBBBBB..',
173
+ '..BBPBKKBPBB..',
174
+ '..BBBBBBBBBB..',
156
175
  ],
157
176
  };
158
177
  const CAPTIONS = {
@@ -166,8 +185,8 @@ const CAPTIONS = {
166
185
  sleepy: 'Pinch is sleepy…',
167
186
  shocked: 'Pinch is shocked!',
168
187
  };
169
- /** Antenna-tip cells (row 0) for the `working` cyan blip: cols 2 & 6. */
170
- const ANTENNA_TIP_COLS = [2, 6];
188
+ /** Antenna-tip cells (grid row 0) for the `working` cyan blip: cols 4 & 9. */
189
+ const ANTENNA_TIP_COLS = [4, 9];
171
190
  /** Replace the chars at `indices` in `row` with `ch` — never mutates `row`. */
172
191
  function setCells(row, indices, ch) {
173
192
  const chars = row.split('');
@@ -212,7 +231,7 @@ function renderCell(upper, lower) {
212
231
  return `${fgCode(upperColor)}${bgCode(lowerColor)}▀${RESET}`;
213
232
  }
214
233
  /** Pack a grid into terminal lines, 2 grid rows per line (upper=fg, lower=bg).
215
- * The #133 mini grid is 6 rows → 3 lines. */
234
+ * The 14×8 founder grid is 8 rows → 4 lines. */
216
235
  function renderGrid(rows) {
217
236
  const lines = [];
218
237
  for (let i = 0; i < rows.length; i += 2) {
@@ -238,8 +257,8 @@ export function renderPinch(state, frame = 0) {
238
257
  const grid = gridForState(state, frame);
239
258
  const lines = renderGrid(grid);
240
259
  if (state === 'confused') {
241
- // "beside the art" — a bold '?' to the right of the eye row. In the #133
242
- // mini grid the eyes are grid rows 2-3, which pack into terminal line 1.
260
+ // "beside the art" — a bold '?' to the right of the eye row. The eyes are
261
+ // grid rows 2-3 (head-top + eye-whites), which pack into terminal line 1.
243
262
  lines[1] = `${lines[1]} \x1b[1m?${RESET}`;
244
263
  }
245
264
  return [...lines, CAPTIONS[state]].join('\n');
@@ -247,9 +266,10 @@ export function renderPinch(state, frame = 0) {
247
266
  /**
248
267
  * #131 — the ART lines only (no caption), optionally COMPACT: trailing and
249
268
  * leading fully-transparent grid rows are dropped so Pinch takes fewer
250
- * terminal lines (some grids carry an all-'.' padding row). `frame` is passed
251
- * through to `gridForState` — only `working` reacts to it (the cyan antenna
252
- * blip toggles), so the loop can pulse the tips while the body stays still.
269
+ * terminal lines (confused/sleepy carry an all-'.' leading row). `frame` is
270
+ * passed through to `gridForState` — only `working` reacts to it (the cyan
271
+ * antenna blip toggles), so the loop can pulse the tips while the body stays
272
+ * still.
253
273
  */
254
274
  export function renderPinchArt(state, opts = {}) {
255
275
  let grid = gridForState(state, opts.frame ?? 0);
@@ -27,7 +27,7 @@ first-class on every one, and none of them ever blocks on a prompt (see
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
30
+ trawl run <id> [--watch] [--json] Run a scrap (blocks until finished)
31
31
  trawl list|ls [--json] [--status <s>] [--limit <n>] [--page <n>] List all scraps
32
32
  trawl get <id> [--json] Get scrap details
33
33
  trawl data <id> [--json] [--fresh] [--errors] Get scrap data (last persisted run, or --fresh to launch one)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trawlme/cli",
3
- "version": "3.4.2",
3
+ "version": "3.5.1",
4
4
  "description": "Trawl CLI — manage scraps from the terminal",
5
5
  "type": "module",
6
6
  "bin": {