@trawlme/cli 2.2.0 → 2.3.0

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
@@ -54,7 +54,7 @@ trawl ping [--json] Health/version handshake against
54
54
 
55
55
  > **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.
56
56
 
57
- `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`). `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.
57
+ `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.
58
58
 
59
59
  > **`create` is NOT idempotent, and every wizard-created scrap runs on a DAILY cron by default.** A client-side timeout (exit `5`, a `NetworkError`) does not mean the wizard failed server-side — scrap creation + the first run keep going after the CLI gives up waiting, so the scrap may already exist. Run `trawl list` and look for a matching URL/title **before** retrying — a blind retry creates a DUPLICATE scrap and burns AI-generation quota a second time for the same goal. Separately, the scrap the wizard creates is scheduled to re-run every day at 07:00 UTC (`cron: "0 7 * * *"`, hardcoded server-side, unrelated to `--no-autofix`) — each of those recurring runs consumes execute quota like any other run. Review the generated scrap, then change or disable the schedule with `trawl update <id> --cron <expr>` (or `--no-cron` to disable it). Because the call can legitimately run 250s+, also confirm `TRAWL_TIMEOUT` isn't set to something tighter than `create` needs — the env var always wins over `create`'s own 300s default (see [Environment variables](#environment-variables)), so a value set for another purpose (e.g. a tight CI smoke-test budget) silently clamps `create` too; unset it or raise it before running `create`.
60
60
 
@@ -50,6 +50,41 @@ function scheduleLabel(scrap) {
50
50
  }
51
51
  return 'scheduled daily by default';
52
52
  }
53
+ /**
54
+ * #121 — onboarding principle: a successful `create` should SHOW the value,
55
+ * not just an id. Best-effort fetch of the first run's persisted data (the
56
+ * same read-only, no-quota path `trawl data <id>` uses:
57
+ * `GET /api/historys/:historyId` → a JSON string `{ data: [...] }`) and print
58
+ * a small proof-of-value sample (count + first-item keys + one truncated
59
+ * value line — never a full dump; that's what `trawl data --json` is for).
60
+ * ANY failure (network, parse, no data) is swallowed silently — the sample is
61
+ * a bonus, it must never turn a successful create into a failure or noise.
62
+ */
63
+ async function printDataSample(historyId) {
64
+ try {
65
+ const detail = await api.get(`/api/historys/${historyId}`);
66
+ if (typeof detail?.data !== 'string' || !detail.data)
67
+ return;
68
+ const items = JSON.parse(detail.data)?.data;
69
+ if (!Array.isArray(items) || items.length === 0)
70
+ return;
71
+ console.log(chalk.dim(` Sample: `) + `${items.length} item${items.length === 1 ? '' : 's'}`);
72
+ const first = items[0];
73
+ if (first && typeof first === 'object') {
74
+ const keys = Object.keys(first);
75
+ console.log(chalk.dim(` Fields: `) + keys.join(', '));
76
+ const firstKey = keys[0];
77
+ if (firstKey) {
78
+ const raw = String(first[firstKey] ?? '');
79
+ const val = raw.length > 60 ? `${raw.slice(0, 60)}…` : raw;
80
+ console.log(chalk.dim(` ${firstKey.slice(0, 10).padEnd(10)}: `) + val);
81
+ }
82
+ }
83
+ }
84
+ catch {
85
+ // best-effort — a missing sample never fails or noises up a good create
86
+ }
87
+ }
53
88
  export const create = new Command('create')
54
89
  .description('Create a persistent, self-healing scrap from a URL + a goal (AI-generated)')
55
90
  .argument('[url]', 'Target public URL (http/https)')
@@ -137,8 +172,15 @@ export const create = new Command('create')
137
172
  console.log(chalk.dim(` First run: `) + firstRunLabel(data, autoFixEnabled));
138
173
  if (schedule)
139
174
  console.log(chalk.dim(` Schedule: `) + schedule);
140
- if (scrapId)
141
- console.log(chalk.dim(` Next step: `) + `trawl get ${scrapId}`);
175
+ // #121 — on a successful first run, prove the value: show a small data
176
+ // sample (best-effort, silent on failure) before the next-step hint.
177
+ if (data.success && data.historyId)
178
+ await printDataSample(data.historyId);
179
+ // #121 — the natural next command is the DATA, not the metadata: point
180
+ // at `trawl data` primarily, keep `trawl get` as the secondary detail view.
181
+ if (scrapId) {
182
+ console.log(chalk.dim(` Next step: `) + `trawl data ${scrapId}` + chalk.dim(` (details: trawl get ${scrapId})`));
183
+ }
142
184
  // #114-F3 — only claim a background retry is happening when a scrap
143
185
  // actually exists to retry (never fabricate progress that isn't real);
144
186
  // `firstRunLabel` above already covers the !scrap / autofix-disabled
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trawlme/cli",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Trawl CLI — manage scraps from the terminal",
5
5
  "type": "module",
6
6
  "bin": {