@trawlme/cli 3.7.4 → 3.7.5

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.
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import { spin } from '../lib/spinner.js';
3
+ import { spin, confirmNonTTY } from '../lib/spinner.js';
4
4
  import { api, LONG_RUN_TIMEOUT_MS } from '../lib/api.js';
5
5
  import { table, json, formatDate } from '../lib/format.js';
6
6
  import { parseServerJson } from '../lib/json.js';
@@ -786,6 +786,24 @@ export function attachRunCommand(parent, attachOpts = {}) {
786
786
  : await spin(call, { text: 'Launching scrap…', successText: 'Scrap launched' });
787
787
  if (opts.json)
788
788
  json(data);
789
+ else if (!opts.watch) {
790
+ /* #166 — see confirmNonTTY. Two deliberate choices here:
791
+ *
792
+ * "run complete", not "launched": `run` is the SYNCHRONOUS verb —
793
+ * GET /api/scraps/load/:id executes the scrap server-side (30-250s)
794
+ * and returns the finished result, so by the time this prints the run
795
+ * is over. The spinner's successText says "Scrap launched", but that
796
+ * is stderr chrome for a human watching it happen; THIS line is the
797
+ * machine surface, where a script reading "launched" would poll for a
798
+ * completion that already happened. `trigger` already draws the same
799
+ * distinction ("Worker triggered" async vs "Worker run complete" under
800
+ * --wait) — this matches that vocabulary.
801
+ *
802
+ * `--watch` is excluded because pollRunProgress below does its own
803
+ * reporting; without that guard a watched run would print this line
804
+ * and then narrate the same run. */
805
+ confirmNonTTY(`Scrap ${id} run complete`);
806
+ }
789
807
  if (opts.watch) {
790
808
  // #107 — under --json, pollRunProgress suppresses its own intermediate
791
809
  // console.log calls and instead emits exactly ONE final NDJSON outcome
@@ -1118,6 +1136,7 @@ scraps
1118
1136
  return;
1119
1137
  }
1120
1138
  await spin(call, { text: 'Deleting…', successText: 'Scrap deleted' });
1139
+ confirmNonTTY(`Scrap ${id} deleted`); // #166
1121
1140
  });
1122
1141
  // banner
1123
1142
  scraps
@@ -1163,6 +1182,8 @@ scraps
1163
1182
  text: 'Uploading banner…',
1164
1183
  successText: `Banner uploaded for scrap ${chalk.bold(id)}`,
1165
1184
  });
1185
+ // #166 — plain, unstyled: chalk.bold would emit escape codes into a pipe.
1186
+ confirmNonTTY(`Banner uploaded for scrap ${id}`);
1166
1187
  });
1167
1188
  // watch (stream activities)
1168
1189
  scraps
@@ -1209,24 +1230,13 @@ export function attachTriggerCommand(parent, attachOpts = {}) {
1209
1230
  if (opts.json) {
1210
1231
  json(data);
1211
1232
  }
1212
- else if (!process.stdout.isTTY) {
1213
- // #160 spin() (lib/spinner.ts, #119) intentionally emits NOTHING
1214
- // when stderr isn't a TTY, so a piped/redirected `trigger` wrote zero
1215
- // bytes to both stdout AND stderr while exiting 0: a caller driving
1216
- // this from a script has no way to tell success from a no-op. Gated
1217
- // on STDOUT specifically that's the stream a script actually reads
1218
- // (`$(trawl trigger …)`, `> out.txt`, `| jq`), independent of
1219
- // whatever spin() decides about stderr — so an interactive TTY
1220
- // session, where the ora spinner already confirmed this on stderr,
1221
- // never sees a duplicate line here. Keeps the exact "Worker
1222
- // triggered"/"Worker run complete" substrings the trawl-internal QA
1223
- // runbook asserts on, plus the scrap id so a script has something to
1224
- // parse. Known residual: stdout attached to a real terminal while
1225
- // stderr is separately redirected (rare) still prints nothing on
1226
- // either stream, same as pre-#160 — not the reported/common shape
1227
- // (full redirection or stdout-only capture), left alone to keep this
1228
- // fix narrowly scoped to the stream it actually touches.
1229
- console.log(opts.wait ? `Worker run complete for scrap ${id}` : `Worker triggered for scrap ${id}`);
1233
+ else {
1234
+ /* #160, folded onto the shared helper by #166 the rationale and the
1235
+ * known residual now live once, on `confirmNonTTY` itself, instead of
1236
+ * in the one call site that happened to be fixed first. Wording is
1237
+ * unchanged on purpose: the trawl-internal QA runbook asserts on the
1238
+ * exact "Worker triggered" / "Worker run complete" substrings. */
1239
+ confirmNonTTY(opts.wait ? `Worker run complete for scrap ${id}` : `Worker triggered for scrap ${id}`);
1230
1240
  }
1231
1241
  // #107 — see the matching comment on `run`'s --watch call above (review
1232
1242
  // F1): honest final NDJSON line + exit code under --json, human mode
@@ -1335,6 +1345,7 @@ account
1335
1345
  text: 'Deleting credentials…',
1336
1346
  successText: 'Account credentials deleted',
1337
1347
  });
1348
+ confirmNonTTY(`Account credentials deleted for scrap ${id}`); // #166
1338
1349
  });
1339
1350
  // account clear-session
1340
1351
  account
@@ -1353,6 +1364,7 @@ account
1353
1364
  text: 'Clearing session…',
1354
1365
  successText: 'Session cleared',
1355
1366
  });
1367
+ confirmNonTTY(`Session cleared for scrap ${id}`); // #166
1356
1368
  });
1357
1369
  // account session subcommand group
1358
1370
  const accountSession = account
@@ -15,4 +15,35 @@ type SpinOptions<T> = string | {
15
15
  * to stderr, stdout stays clean either way).
16
16
  */
17
17
  export declare function spin<T>(action: Action<T>, options?: SpinOptions<T>): Promise<T>;
18
+ /**
19
+ * The counterpart to `spin()`'s silence — print a plain-text confirmation on
20
+ * stdout when, and only when, stdout is not a TTY.
21
+ *
22
+ * #160 then #166. `spin()` above deliberately emits nothing when stderr is not
23
+ * a TTY, so for every command whose only non-`--json` feedback was its
24
+ * `successText`, a piped or redirected invocation wrote **zero bytes to both
25
+ * streams while exiting 0**. Our ICP is agent builders, and agents pipe stdout:
26
+ * a verb that writes nothing on success is unusable from a script, because the
27
+ * caller cannot tell success from a no-op. Two of the affected commands were
28
+ * deletes, where that ambiguity is at its worst.
29
+ *
30
+ * Lives here, next to the silence it compensates for, because #160 fixed
31
+ * `trigger` by inlining this rule and #166 then found five siblings carrying
32
+ * the identical defect — five more inlined copies is how the next one gets
33
+ * missed. Callers pass the message; the gate lives in one place.
34
+ *
35
+ * Gated on **stdout**, not stderr: stdout is the stream a script actually reads
36
+ * (`$(trawl …)`, `> out.txt`, `| jq`), independent of whatever `spin()` decides
37
+ * about stderr. So an interactive session, where the ora spinner already
38
+ * confirmed on stderr, never gets a duplicate line here.
39
+ *
40
+ * Never call this on a `--json` path: under `--json`, stdout must stay exactly
41
+ * one parseable document.
42
+ *
43
+ * Known residual, unchanged from #160: stdout attached to a real terminal while
44
+ * stderr is separately redirected still prints nothing on either stream. That is
45
+ * not the reported or common shape (full redirection, or stdout-only capture),
46
+ * and widening the gate would put a duplicate line in front of interactive users.
47
+ */
48
+ export declare function confirmNonTTY(message: string): void;
18
49
  export {};
@@ -15,3 +15,37 @@ export function spin(action, options) {
15
15
  // oraPromise's own overloads accept (action, string) and (action, options).
16
16
  return oraPromise(action, options);
17
17
  }
18
+ /**
19
+ * The counterpart to `spin()`'s silence — print a plain-text confirmation on
20
+ * stdout when, and only when, stdout is not a TTY.
21
+ *
22
+ * #160 then #166. `spin()` above deliberately emits nothing when stderr is not
23
+ * a TTY, so for every command whose only non-`--json` feedback was its
24
+ * `successText`, a piped or redirected invocation wrote **zero bytes to both
25
+ * streams while exiting 0**. Our ICP is agent builders, and agents pipe stdout:
26
+ * a verb that writes nothing on success is unusable from a script, because the
27
+ * caller cannot tell success from a no-op. Two of the affected commands were
28
+ * deletes, where that ambiguity is at its worst.
29
+ *
30
+ * Lives here, next to the silence it compensates for, because #160 fixed
31
+ * `trigger` by inlining this rule and #166 then found five siblings carrying
32
+ * the identical defect — five more inlined copies is how the next one gets
33
+ * missed. Callers pass the message; the gate lives in one place.
34
+ *
35
+ * Gated on **stdout**, not stderr: stdout is the stream a script actually reads
36
+ * (`$(trawl …)`, `> out.txt`, `| jq`), independent of whatever `spin()` decides
37
+ * about stderr. So an interactive session, where the ora spinner already
38
+ * confirmed on stderr, never gets a duplicate line here.
39
+ *
40
+ * Never call this on a `--json` path: under `--json`, stdout must stay exactly
41
+ * one parseable document.
42
+ *
43
+ * Known residual, unchanged from #160: stdout attached to a real terminal while
44
+ * stderr is separately redirected still prints nothing on either stream. That is
45
+ * not the reported or common shape (full redirection, or stdout-only capture),
46
+ * and widening the gate would put a duplicate line in front of interactive users.
47
+ */
48
+ export function confirmNonTTY(message) {
49
+ if (!process.stdout.isTTY)
50
+ console.log(message);
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trawlme/cli",
3
- "version": "3.7.4",
3
+ "version": "3.7.5",
4
4
  "description": "Trawl CLI — manage scraps from the terminal",
5
5
  "type": "module",
6
6
  "bin": {