deepline 0.1.29 → 0.1.31

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.
@@ -243,8 +243,8 @@ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStar
243
243
  }
244
244
 
245
245
  // src/version.ts
246
- var SDK_VERSION = "0.1.29";
247
- var SDK_API_CONTRACT = "2026-05-runs-v2";
246
+ var SDK_VERSION = "0.1.31";
247
+ var SDK_API_CONTRACT = "2026-05-runs-export-v3";
248
248
 
249
249
  // ../shared_libs/play-runtime/coordinator-headers.ts
250
250
  var COORDINATOR_INTERNAL_TOKEN_HEADER = "x-deepline-internal-token";
@@ -653,6 +653,7 @@ var DeeplineClient = class {
653
653
  list: (options2) => this.listRuns(options2),
654
654
  tail: (runId, options2) => this.tailRun(runId, options2),
655
655
  logs: (runId, options2) => this.getRunLogs(runId, options2),
656
+ exportDatasetRows: (input) => this.getPlaySheetRows(input),
656
657
  stop: (runId, options2) => this.stopRun(runId, options2)
657
658
  };
658
659
  }
@@ -680,7 +681,7 @@ var DeeplineClient = class {
680
681
  const target = play.reference || play.name;
681
682
  if (options?.csvInput) {
682
683
  const inputField = typeof options.csvInput.inputField === "string" && options.csvInput.inputField.trim() ? options.csvInput.inputField.trim() : "csv";
683
- return `deepline plays run ${target} --${inputField} leads.csv --watch`;
684
+ return `deepline plays run ${target} --input '${JSON.stringify({ [inputField]: "leads.csv" })}' --watch`;
684
685
  }
685
686
  return `deepline plays run ${target} --input '{...}' --watch`;
686
687
  }
@@ -1340,6 +1341,19 @@ var DeeplineClient = class {
1340
1341
  entries
1341
1342
  };
1342
1343
  }
1344
+ async getPlaySheetRows(input) {
1345
+ const params = new URLSearchParams({
1346
+ tableNamespace: input.tableNamespace,
1347
+ limit: String(input.limit ?? 5e3),
1348
+ offset: String(input.offset ?? 0)
1349
+ });
1350
+ if (input.runId?.trim()) {
1351
+ params.set("runId", input.runId.trim());
1352
+ }
1353
+ return await this.http.get(
1354
+ `/api/v2/plays/${encodeURIComponent(input.playName)}/sheet?${params.toString()}`
1355
+ );
1356
+ }
1343
1357
  /**
1344
1358
  * Stop a run by id using the public runs resource model.
1345
1359
  *
@@ -1952,6 +1966,38 @@ function clip(value, maxLength) {
1952
1966
  return value.length > maxLength ? `${value.slice(0, maxLength - 1)}\u2026` : value;
1953
1967
  }
1954
1968
 
1969
+ // src/cli/command-envelope.ts
1970
+ function renderSections(render) {
1971
+ const lines = [];
1972
+ for (const section of render?.sections ?? []) {
1973
+ if (!section.title || section.lines.length === 0) continue;
1974
+ lines.push(section.title);
1975
+ lines.push(...section.lines.map((line) => ` ${line}`));
1976
+ }
1977
+ for (const action of render?.actions ?? []) {
1978
+ if (!action.label || !action.command) continue;
1979
+ lines.push(`${action.label}: ${action.command}`);
1980
+ }
1981
+ return lines;
1982
+ }
1983
+ function renderCommandEnvelopeText(envelope) {
1984
+ const lines = renderSections(envelope.render);
1985
+ if (lines.length > 0) {
1986
+ return `${lines.join("\n")}
1987
+ `;
1988
+ }
1989
+ return `${JSON.stringify(envelope, null, 2)}
1990
+ `;
1991
+ }
1992
+ function printCommandEnvelope(envelope, options = {}) {
1993
+ const jsonOutput = typeof options.json === "boolean" ? options.json : shouldEmitJson();
1994
+ if (jsonOutput) {
1995
+ printJson(envelope);
1996
+ return;
1997
+ }
1998
+ process.stdout.write(options.text ?? renderCommandEnvelopeText(envelope));
1999
+ }
2000
+
1955
2001
  // src/cli/commands/auth.ts
1956
2002
  var EXIT_OK = 0;
1957
2003
  var EXIT_AUTH = 1;
@@ -2200,6 +2246,7 @@ async function handleStatus(args) {
2200
2246
  const reveal = args.includes("--reveal");
2201
2247
  const jsonOutput = argsWantJson(args);
2202
2248
  let hostStatusPayload = null;
2249
+ const hostLines = [];
2203
2250
  try {
2204
2251
  const { status: hStatus, data: hData } = await httpJson("GET", `${baseUrl}/api/v2/health`, null);
2205
2252
  if (hStatus === 200) {
@@ -2208,11 +2255,9 @@ async function handleStatus(args) {
2208
2255
  hostStatus: hData.status || "ok",
2209
2256
  hostVersion: hData.version || "(unknown)"
2210
2257
  };
2211
- if (!jsonOutput) {
2212
- console.log(`Host: ${baseUrl}`);
2213
- console.log(`Host status: ${hData.status || "ok"}`);
2214
- console.log(`Host version: ${hData.version || "(unknown)"}`);
2215
- }
2258
+ hostLines.push(`Host: ${baseUrl}`);
2259
+ hostLines.push(`Host status: ${hData.status || "ok"}`);
2260
+ hostLines.push(`Host version: ${hData.version || "(unknown)"}`);
2216
2261
  }
2217
2262
  } catch {
2218
2263
  hostStatusPayload = {
@@ -2220,40 +2265,34 @@ async function handleStatus(args) {
2220
2265
  hostStatus: "unreachable",
2221
2266
  hostVersion: null
2222
2267
  };
2223
- if (!jsonOutput) {
2224
- console.log(`Host: ${baseUrl} (unreachable)`);
2225
- }
2268
+ hostLines.push(`Host: ${baseUrl} (unreachable)`);
2226
2269
  }
2227
2270
  const env = loadCliEnv(baseUrl);
2228
2271
  const apiKey = process.env.DEEPLINE_API_KEY?.trim() || env.DEEPLINE_API_KEY || "";
2229
2272
  if (!apiKey) {
2230
2273
  if (env.DEEPLINE_CLAIM_TOKEN?.trim()) {
2231
- if (jsonOutput) {
2232
- process.stdout.write(`${JSON.stringify({
2233
- ...hostStatusPayload ?? { host: baseUrl },
2234
- status: "pending",
2235
- connected: false,
2236
- next: "deepline auth wait"
2237
- })}
2238
- `);
2239
- return EXIT_OK;
2240
- }
2241
- console.log("Status: pending");
2242
- console.log("Run: deepline auth wait");
2243
- return EXIT_OK;
2244
- }
2245
- if (jsonOutput) {
2246
- process.stdout.write(`${JSON.stringify({
2274
+ printCommandEnvelope({
2247
2275
  ...hostStatusPayload ?? { host: baseUrl },
2248
- status: "not connected",
2276
+ status: "pending",
2249
2277
  connected: false,
2250
- next: "deepline auth register"
2251
- })}
2252
- `);
2278
+ next: "deepline auth wait",
2279
+ render: {
2280
+ sections: [{ title: "auth status", lines: [...hostLines, "Status: pending"] }],
2281
+ actions: [{ label: "Run", command: "deepline auth wait" }]
2282
+ }
2283
+ }, { json: jsonOutput });
2253
2284
  return EXIT_OK;
2254
2285
  }
2255
- console.log("Status: not connected");
2256
- console.log("Run: deepline auth register");
2286
+ printCommandEnvelope({
2287
+ ...hostStatusPayload ?? { host: baseUrl },
2288
+ status: "not connected",
2289
+ connected: false,
2290
+ next: "deepline auth register",
2291
+ render: {
2292
+ sections: [{ title: "auth status", lines: [...hostLines, "Status: not connected"] }],
2293
+ actions: [{ label: "Run", command: "deepline auth register" }]
2294
+ }
2295
+ }, { json: jsonOutput });
2257
2296
  return EXIT_OK;
2258
2297
  }
2259
2298
  const { status, data } = await httpJson("POST", `${baseUrl}/api/v2/auth/cli/status`, apiKey, {
@@ -2261,18 +2300,16 @@ async function handleStatus(args) {
2261
2300
  reveal
2262
2301
  });
2263
2302
  if (status === 401 || status === 403) {
2264
- if (jsonOutput) {
2265
- process.stdout.write(`${JSON.stringify({
2266
- ...hostStatusPayload ?? { host: baseUrl },
2267
- status: "unauthorized",
2268
- connected: false,
2269
- next: "deepline auth register"
2270
- })}
2271
- `);
2272
- return EXIT_AUTH;
2273
- }
2274
- console.log("Status: unauthorized");
2275
- console.log("Run: deepline auth register");
2303
+ printCommandEnvelope({
2304
+ ...hostStatusPayload ?? { host: baseUrl },
2305
+ status: "unauthorized",
2306
+ connected: false,
2307
+ next: "deepline auth register",
2308
+ render: {
2309
+ sections: [{ title: "auth status", lines: [...hostLines, "Status: unauthorized"] }],
2310
+ actions: [{ label: "Run", command: "deepline auth register" }]
2311
+ }
2312
+ }, { json: jsonOutput });
2276
2313
  return EXIT_AUTH;
2277
2314
  }
2278
2315
  if (status >= 400) {
@@ -2294,23 +2331,7 @@ async function handleStatus(args) {
2294
2331
  },
2295
2332
  examples: Array.isArray(data.examples) ? data.examples : []
2296
2333
  };
2297
- if (jsonOutput) {
2298
- process.stdout.write(`${JSON.stringify(payload)}
2299
- `);
2300
- } else {
2301
- console.log(`Status: ${payload.status}`);
2302
- console.log(`Rate limit tier: ${payload.rateLimitTier}`);
2303
- if (payload.workspace.name) console.log(`Workspace: ${payload.workspace.name}`);
2304
- if (payload.workspace.slug) console.log(`Workspace slug: ${payload.workspace.slug}`);
2305
- if (payload.workspace.id != null) console.log(`Org ID: ${payload.workspace.id}`);
2306
- if (payload.user.id != null) console.log(`User ID: ${payload.user.id}`);
2307
- if (payload.examples.length > 0) {
2308
- console.log("Examples:");
2309
- for (const example of payload.examples.slice(0, 3)) {
2310
- console.log(` ${String(example)}`);
2311
- }
2312
- }
2313
- }
2334
+ let savedApiKeyPath = null;
2314
2335
  if (reveal) {
2315
2336
  const apiKeyResp = String(data.api_key || apiKey);
2316
2337
  if (apiKeyResp) {
@@ -2319,9 +2340,31 @@ async function handleStatus(args) {
2319
2340
  DEEPLINE_API_KEY: apiKeyResp,
2320
2341
  DEEPLINE_CLAIM_TOKEN: ""
2321
2342
  }, baseUrl);
2322
- console.log(`Saved API key to ${envFilePath(baseUrl)}`);
2343
+ savedApiKeyPath = envFilePath(baseUrl);
2323
2344
  }
2324
2345
  }
2346
+ printCommandEnvelope({
2347
+ ...payload,
2348
+ ...savedApiKeyPath ? { saved_api_key_path: savedApiKeyPath } : {},
2349
+ render: {
2350
+ sections: [
2351
+ {
2352
+ title: "auth status",
2353
+ lines: [
2354
+ ...hostLines,
2355
+ `Status: ${payload.status}`,
2356
+ `Rate limit tier: ${payload.rateLimitTier}`,
2357
+ ...payload.workspace.name ? [`Workspace: ${payload.workspace.name}`] : [],
2358
+ ...payload.workspace.slug ? [`Workspace slug: ${payload.workspace.slug}`] : [],
2359
+ ...payload.workspace.id != null ? [`Org ID: ${payload.workspace.id}`] : [],
2360
+ ...payload.user.id != null ? [`User ID: ${payload.user.id}`] : [],
2361
+ ...payload.examples.length > 0 ? ["Examples:", ...payload.examples.slice(0, 3).map((example) => ` ${String(example)}`)] : [],
2362
+ ...savedApiKeyPath ? [`Saved API key to ${savedApiKeyPath}`] : []
2363
+ ]
2364
+ }
2365
+ ]
2366
+ }
2367
+ }, { json: jsonOutput });
2325
2368
  return EXIT_OK;
2326
2369
  }
2327
2370
  function registerAuthCommands(program) {
@@ -2404,19 +2447,18 @@ import { stringify as stringify2 } from "csv-stringify/sync";
2404
2447
  function humanize(value) {
2405
2448
  return String(value || "").split("_").filter(Boolean).map((token) => token[0]?.toUpperCase() + token.slice(1)).join(" ") || "Unknown";
2406
2449
  }
2407
- function printRecentUsage(entries) {
2450
+ function recentUsageLines(entries) {
2408
2451
  if (entries.length === 0) {
2409
- process.stdout.write("Recent activity: none yet\n");
2410
- return;
2452
+ return ["Recent activity: none yet"];
2411
2453
  }
2412
- process.stdout.write("Recent activity:\n");
2454
+ const lines = ["Recent activity:"];
2413
2455
  for (const entry of entries) {
2414
2456
  const op = `${humanize(entry.provider)} ${humanize(entry.operation)}`.trim();
2415
2457
  const charge = entry.billing_mode === "no_bill" ? "free" : `${entry.credits ?? 0} cr`;
2416
2458
  const status = entry.status || "completed";
2417
- process.stdout.write(` ${op} | ${charge} | ${status} | ${entry.created_at || "unknown"}
2418
- `);
2459
+ lines.push(`${op} | ${charge} | ${status} | ${entry.created_at || "unknown"}`);
2419
2460
  }
2461
+ return lines;
2420
2462
  }
2421
2463
  function summarizeLedgerRows(summary, rows) {
2422
2464
  const netDelta = rows.reduce((sum, row) => {
@@ -2469,49 +2511,49 @@ function defaultLedgerExportPath() {
2469
2511
  async function handleBalance(options) {
2470
2512
  const { http } = getAuthedHttpClient();
2471
2513
  const payload = await http.get("/api/v2/billing/balance");
2472
- if (shouldEmitJson(options.json)) return printJson(payload);
2473
2514
  const status = String(payload.balance_status || "");
2474
- if (status === "no_billing") {
2475
- process.stdout.write("Balance: 0 credits\n");
2476
- process.stdout.write("Billing: No billing account or payment method set up for this workspace.\n");
2477
- return;
2478
- }
2479
- process.stdout.write(`Balance: ${payload.balance ?? "(unknown)"} credits
2480
- `);
2515
+ const lines = status === "no_billing" ? [
2516
+ "Balance: 0 credits",
2517
+ "Billing: No billing account or payment method set up for this workspace."
2518
+ ] : [`Balance: ${payload.balance ?? "(unknown)"} credits`];
2519
+ printCommandEnvelope({
2520
+ ...payload,
2521
+ render: { sections: [{ title: "billing balance", lines }] }
2522
+ }, { json: options.json });
2523
+ return;
2481
2524
  }
2482
2525
  async function handleUsage(options) {
2483
2526
  const { http } = getAuthedHttpClient();
2484
2527
  const params = new URLSearchParams();
2485
2528
  if (options.limit) params.set("recent_limit", options.limit);
2486
2529
  if (options.offset) params.set("recent_offset", options.offset);
2487
- const suffix = params.size > 0 ? `?${params.toString()}` : "";
2530
+ const suffix = Array.from(params).length > 0 ? `?${params.toString()}` : "";
2488
2531
  const payload = await http.get(`/api/v2/billing/usage${suffix}`);
2489
- if (shouldEmitJson(options.json)) return printJson(payload);
2490
2532
  const usage = payload.usage ?? {};
2491
2533
  const quota = payload.quota ?? {};
2492
2534
  const recent = payload.recent ?? {};
2493
- process.stdout.write(`Balance: ${payload.balance ?? "(unknown)"}
2494
- `);
2495
- process.stdout.write(`Last 30 days spent: ${usage.month_spent_credits ?? "(unknown)"}
2496
- `);
2497
- process.stdout.write(
2498
- `Monthly limit: ${quota.enabled ? quota.monthly_credits_limit ?? "(unknown)" : "off"}
2499
- `
2500
- );
2501
- printRecentUsage(Array.isArray(recent.entries) ? recent.entries : []);
2535
+ const lines = [
2536
+ `Balance: ${payload.balance ?? "(unknown)"}`,
2537
+ `Last 30 days spent: ${usage.month_spent_credits ?? "(unknown)"}`,
2538
+ `Monthly limit: ${quota.enabled ? quota.monthly_credits_limit ?? "(unknown)" : "off"}`,
2539
+ ...recentUsageLines(Array.isArray(recent.entries) ? recent.entries : [])
2540
+ ];
2541
+ printCommandEnvelope({
2542
+ ...payload,
2543
+ render: { sections: [{ title: "billing usage", lines }] }
2544
+ }, { json: options.json });
2502
2545
  }
2503
2546
  async function handleLimit(options) {
2504
2547
  const { http } = getAuthedHttpClient();
2505
2548
  const payload = await http.get("/api/v2/billing/limit");
2506
- if (shouldEmitJson(options.json)) return printJson(payload);
2507
- if (payload.enabled) {
2508
- process.stdout.write(`Monthly limit: ${payload.monthly_credits_limit ?? "(unknown)"}
2509
- `);
2510
- process.stdout.write(`Remaining before cap: ${payload.remaining_credits ?? "(unknown)"}
2511
- `);
2512
- return;
2513
- }
2514
- process.stdout.write("Monthly limit: off\n");
2549
+ const lines = payload.enabled ? [
2550
+ `Monthly limit: ${payload.monthly_credits_limit ?? "(unknown)"}`,
2551
+ `Remaining before cap: ${payload.remaining_credits ?? "(unknown)"}`
2552
+ ] : ["Monthly limit: off"];
2553
+ printCommandEnvelope({
2554
+ ...payload,
2555
+ render: { sections: [{ title: "billing limit", lines }] }
2556
+ }, { json: options.json });
2515
2557
  }
2516
2558
  async function handleSetLimit(credits, options) {
2517
2559
  const { http } = getAuthedHttpClient();
@@ -2519,15 +2561,20 @@ async function handleSetLimit(credits, options) {
2519
2561
  method: "PUT",
2520
2562
  body: { monthly_credits_limit: Number.parseInt(credits, 10) }
2521
2563
  });
2522
- if (shouldEmitJson(options.json)) return printJson(payload);
2523
- process.stdout.write(`Monthly billing limit set to ${credits} credits.
2524
- `);
2564
+ printCommandEnvelope({
2565
+ ...payload,
2566
+ render: {
2567
+ sections: [{ title: "billing limit", lines: [`Monthly billing limit set to ${credits} credits.`] }]
2568
+ }
2569
+ }, { json: options.json });
2525
2570
  }
2526
2571
  async function handleLimitOff(options) {
2527
2572
  const { http } = getAuthedHttpClient();
2528
2573
  const payload = await http.request("/api/v2/billing/limit", { method: "DELETE" });
2529
- if (shouldEmitJson(options.json)) return printJson(payload);
2530
- process.stdout.write("Monthly billing limit is now off.\n");
2574
+ printCommandEnvelope({
2575
+ ...payload,
2576
+ render: { sections: [{ title: "billing limit", lines: ["Monthly billing limit is now off."] }] }
2577
+ }, { json: options.json });
2531
2578
  }
2532
2579
  async function handleHistory(options) {
2533
2580
  const { http } = getAuthedHttpClient();
@@ -2546,13 +2593,20 @@ async function handleHistory(options) {
2546
2593
  };
2547
2594
  });
2548
2595
  const outputPath = await writeCsvRowsFile(`billing-history-${options.time}`, rows);
2549
- if (shouldEmitJson(options.json)) {
2550
- return printJson({ output_path: outputPath, row_count: rows.length, time_window: options.time });
2551
- }
2552
- process.stdout.write(`Billing history written to ${outputPath}
2553
- `);
2554
- process.stdout.write(`${rows.length} row(s) exported.
2555
- `);
2596
+ printCommandEnvelope({
2597
+ output_path: outputPath,
2598
+ row_count: rows.length,
2599
+ time_window: options.time,
2600
+ render: {
2601
+ sections: [
2602
+ {
2603
+ title: "billing history",
2604
+ lines: [`Billing history written to ${outputPath}`, `${rows.length} row(s) exported.`]
2605
+ }
2606
+ ]
2607
+ },
2608
+ local: { output_path: outputPath }
2609
+ }, { json: options.json });
2556
2610
  }
2557
2611
  async function handleLedgerExportAll(options) {
2558
2612
  const { http } = getAuthedHttpClient();
@@ -2584,20 +2638,25 @@ async function handleLedgerExportAll(options) {
2584
2638
  if (nextCursor === cursor) break;
2585
2639
  cursor = nextCursor;
2586
2640
  }
2587
- if (shouldEmitJson(options.json)) {
2588
- return printJson({
2589
- output_path: outputPath,
2590
- row_count: summary.row_count,
2591
- net_delta_credits: summary.net_delta_credits,
2592
- scope: "current_auth_context"
2593
- });
2594
- }
2595
- process.stdout.write(`Billing ledger written to ${outputPath}
2596
- `);
2597
- process.stdout.write(`${summary.row_count} row(s) exported for the current auth context.
2598
- `);
2599
- process.stdout.write(`Net ledger delta: ${summary.net_delta_credits} Deepline Credits
2600
- `);
2641
+ printCommandEnvelope({
2642
+ output_path: outputPath,
2643
+ row_count: summary.row_count,
2644
+ net_delta_credits: summary.net_delta_credits,
2645
+ scope: "current_auth_context",
2646
+ render: {
2647
+ sections: [
2648
+ {
2649
+ title: "billing ledger",
2650
+ lines: [
2651
+ `Billing ledger written to ${outputPath}`,
2652
+ `${summary.row_count} row(s) exported for the current auth context.`,
2653
+ `Net ledger delta: ${summary.net_delta_credits} Deepline Credits`
2654
+ ]
2655
+ }
2656
+ ]
2657
+ },
2658
+ local: { output_path: outputPath }
2659
+ }, { json: options.json });
2601
2660
  }
2602
2661
  async function handleCheckout(options) {
2603
2662
  const { http } = getAuthedHttpClient();
@@ -2606,20 +2665,22 @@ async function handleCheckout(options) {
2606
2665
  ...options.credits ? { credits: Number.parseInt(options.credits, 10) } : {},
2607
2666
  ...options.discountCode ? { discountCode: options.discountCode } : {}
2608
2667
  });
2609
- if (shouldEmitJson(options.json)) return printJson(payload);
2610
2668
  const url = String(payload.url || payload.checkout_url || "");
2611
- if (!options.noOpen && url) openInBrowser(url);
2612
- process.stdout.write(`${url || "Checkout session created."}
2613
- `);
2669
+ if (!options.json && !options.noOpen && url) openInBrowser(url);
2670
+ printCommandEnvelope({
2671
+ ...payload,
2672
+ render: { sections: [{ title: "billing checkout", lines: [url || "Checkout session created."] }] }
2673
+ }, { json: options.json });
2614
2674
  }
2615
2675
  async function handleRedeemCode(code, options) {
2616
2676
  const { http } = getAuthedHttpClient();
2617
2677
  const payload = await http.post("/api/v2/billing/checkout/verify", { code });
2618
- if (shouldEmitJson(options.json)) return printJson(payload);
2619
2678
  const url = String(payload.url || "");
2620
- if (!options.noOpen && url) openInBrowser(url);
2621
- process.stdout.write(`${url || "Code redeemed."}
2622
- `);
2679
+ if (!options.json && !options.noOpen && url) openInBrowser(url);
2680
+ printCommandEnvelope({
2681
+ ...payload,
2682
+ render: { sections: [{ title: "billing code", lines: [url || "Code redeemed."] }] }
2683
+ }, { json: options.json });
2623
2684
  }
2624
2685
  function registerBillingCommands(program) {
2625
2686
  const billing = program.command("billing").description("Inspect balance, usage, limits, and checkout flows.").addHelpText(
@@ -2821,6 +2882,25 @@ function isRecord2(value) {
2821
2882
  function isSerializedDataset(value) {
2822
2883
  return isRecord2(value) && value.kind === "dataset" && typeof value.count === "number" && Array.isArray(value.preview);
2823
2884
  }
2885
+ function pathParts(path) {
2886
+ return path.split(".").map((part) => part.trim()).filter(Boolean);
2887
+ }
2888
+ function valueAtPath(root, path) {
2889
+ let cursor = root;
2890
+ for (const part of pathParts(path)) {
2891
+ if (!isRecord2(cursor)) {
2892
+ return void 0;
2893
+ }
2894
+ cursor = cursor[part];
2895
+ }
2896
+ return cursor;
2897
+ }
2898
+ function totalRowsForDataset(result, datasetPath) {
2899
+ const metadata = isRecord2(result._metadata) ? result._metadata : null;
2900
+ const parentPath = datasetPath.split(".").slice(0, -1).join(".");
2901
+ const parent = parentPath ? valueAtPath({ result }, parentPath) : result;
2902
+ return metadata?.totalRows ?? metadata?.rowCount ?? metadata?.count ?? (isRecord2(parent) ? parent.totalRows ?? parent.rowCount ?? parent.count : void 0) ?? result.totalRows ?? result.rowCount ?? result.count;
2903
+ }
2824
2904
  function rowArray(value) {
2825
2905
  if (!Array.isArray(value)) {
2826
2906
  return null;
@@ -2852,11 +2932,81 @@ function inferColumns(rows) {
2852
2932
  }
2853
2933
  return columns;
2854
2934
  }
2855
- function extractCanonicalRowsInfo(statusOrResult) {
2935
+ function canonicalRowsInfoFromCandidate(input) {
2936
+ const candidate = input;
2937
+ if (isSerializedDataset(candidate.value)) {
2938
+ const rawRows = rowArray(candidate.value.preview) ?? [];
2939
+ const totalRows2 = readNumber(candidate.value.count) ?? rawRows.length;
2940
+ const hasExplicitColumns = Array.isArray(candidate.value.columns) && candidate.value.columns.every((column) => typeof column === "string");
2941
+ const rawColumns = hasExplicitColumns ? candidate.value.columns : inferColumns(rawRows);
2942
+ const { rows: rows2, columns } = sanitizeCsvProjectionInfo({
2943
+ rows: rawRows,
2944
+ columns: rawColumns
2945
+ });
2946
+ return {
2947
+ rows: rows2,
2948
+ totalRows: totalRows2,
2949
+ columns,
2950
+ columnsExplicit: hasExplicitColumns,
2951
+ complete: rows2.length === totalRows2,
2952
+ source: candidate.source,
2953
+ datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
2954
+ tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null
2955
+ };
2956
+ }
2957
+ if (candidate.serializedOnly) {
2958
+ return null;
2959
+ }
2960
+ const rows = rowArray(candidate.value);
2961
+ if (!rows) {
2962
+ return null;
2963
+ }
2964
+ const totalRows = readNumber(candidate.total) ?? rows.length;
2965
+ const sanitized = sanitizeCsvProjectionInfo({
2966
+ rows,
2967
+ columns: inferColumns(rows)
2968
+ });
2969
+ return {
2970
+ rows: sanitized.rows,
2971
+ totalRows,
2972
+ columns: sanitized.columns,
2973
+ complete: rows.length === totalRows,
2974
+ source: candidate.source
2975
+ };
2976
+ }
2977
+ function collectDatasetCandidates(input) {
2978
+ if (input.depth && input.depth > 16) {
2979
+ return;
2980
+ }
2981
+ if (isSerializedDataset(input.value)) {
2982
+ input.output.push({
2983
+ source: input.path,
2984
+ value: input.value,
2985
+ total: input.total
2986
+ });
2987
+ return;
2988
+ }
2989
+ if (!isRecord2(input.value)) {
2990
+ return;
2991
+ }
2992
+ for (const [key, child] of Object.entries(input.value)) {
2993
+ if (key === "preview" || key === "access") {
2994
+ continue;
2995
+ }
2996
+ collectDatasetCandidates({
2997
+ value: child,
2998
+ path: `${input.path}.${key}`,
2999
+ total: totalRowsForDataset(input.value, `${input.path}.${key}`),
3000
+ output: input.output,
3001
+ depth: (input.depth ?? 0) + 1
3002
+ });
3003
+ }
3004
+ }
3005
+ function collectCanonicalRowsInfos(statusOrResult) {
2856
3006
  const root = isRecord2(statusOrResult) ? statusOrResult : null;
2857
3007
  const result = isRecord2(root?.result) ? root.result : root;
2858
3008
  if (!result) {
2859
- return null;
3009
+ return [];
2860
3010
  }
2861
3011
  const metadata = isRecord2(result._metadata) ? result._metadata : null;
2862
3012
  const totalFromMetadata = metadata?.totalRows ?? metadata?.rowCount ?? metadata?.count;
@@ -2876,41 +3026,57 @@ function extractCanonicalRowsInfo(statusOrResult) {
2876
3026
  { source: "result.output.results", value: result.output.results, total: outputTotalFromMetadata ?? result.output.totalRows ?? result.output.rowCount ?? result.output.count }
2877
3027
  );
2878
3028
  }
3029
+ collectDatasetCandidates({
3030
+ value: result,
3031
+ path: "result",
3032
+ total: totalFromMetadata,
3033
+ output: candidates
3034
+ });
3035
+ const seen = /* @__PURE__ */ new Set();
3036
+ const infos = [];
2879
3037
  for (const candidate of candidates) {
2880
- if (isSerializedDataset(candidate.value)) {
2881
- const rawRows = rowArray(candidate.value.preview) ?? [];
2882
- const totalRows2 = readNumber(candidate.value.count) ?? rawRows.length;
2883
- const rawColumns = Array.isArray(candidate.value.columns) && candidate.value.columns.every((column) => typeof column === "string") ? candidate.value.columns : inferColumns(rawRows);
2884
- const { rows: rows2, columns } = sanitizeCsvProjectionInfo({
2885
- rows: rawRows,
2886
- columns: rawColumns
2887
- });
2888
- return {
2889
- rows: rows2,
2890
- totalRows: totalRows2,
2891
- columns,
2892
- complete: rows2.length === totalRows2,
2893
- source: candidate.source
2894
- };
3038
+ if (seen.has(candidate.source)) {
3039
+ continue;
3040
+ }
3041
+ seen.add(candidate.source);
3042
+ const info = canonicalRowsInfoFromCandidate(candidate);
3043
+ if (info) {
3044
+ infos.push(info);
2895
3045
  }
2896
- const rows = rowArray(candidate.value);
2897
- if (!rows) {
3046
+ }
3047
+ return infos;
3048
+ }
3049
+ function collectSerializedDatasetRowsInfos(statusOrResult) {
3050
+ const root = isRecord2(statusOrResult) ? statusOrResult : null;
3051
+ const result = isRecord2(root?.result) ? root.result : root;
3052
+ if (!result) {
3053
+ return [];
3054
+ }
3055
+ const candidates = [];
3056
+ collectDatasetCandidates({
3057
+ value: result,
3058
+ path: "result",
3059
+ output: candidates
3060
+ });
3061
+ const seen = /* @__PURE__ */ new Set();
3062
+ const infos = [];
3063
+ for (const candidate of candidates) {
3064
+ if (seen.has(candidate.source)) {
2898
3065
  continue;
2899
3066
  }
2900
- const totalRows = readNumber(candidate.total) ?? rows.length;
2901
- const sanitized = sanitizeCsvProjectionInfo({
2902
- rows,
2903
- columns: inferColumns(rows)
3067
+ seen.add(candidate.source);
3068
+ const info = canonicalRowsInfoFromCandidate({
3069
+ ...candidate,
3070
+ serializedOnly: true
2904
3071
  });
2905
- return {
2906
- rows: sanitized.rows,
2907
- totalRows,
2908
- columns: sanitized.columns,
2909
- complete: rows.length === totalRows,
2910
- source: candidate.source
2911
- };
3072
+ if (info) {
3073
+ infos.push(info);
3074
+ }
2912
3075
  }
2913
- return null;
3076
+ return infos;
3077
+ }
3078
+ function extractCanonicalRowsInfo(statusOrResult) {
3079
+ return collectCanonicalRowsInfos(statusOrResult)[0] ?? null;
2914
3080
  }
2915
3081
  function percentText(numerator, denominator) {
2916
3082
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
@@ -3237,7 +3403,7 @@ function formatCell(value) {
3237
3403
  const text = typeof value === "object" ? JSON.stringify(value) : String(value);
3238
3404
  return text.length > 80 ? `${text.slice(0, 77)}...` : text;
3239
3405
  }
3240
- function printTable(result) {
3406
+ function tableLines(result) {
3241
3407
  const rows = result.rows.filter(
3242
3408
  (row) => Boolean(row) && typeof row === "object" && !Array.isArray(row)
3243
3409
  );
@@ -3245,17 +3411,17 @@ function printTable(result) {
3245
3411
  const businessColumns = responseColumns.filter((column) => !column.startsWith("_"));
3246
3412
  const columns = businessColumns.length > 0 ? businessColumns : responseColumns;
3247
3413
  const hiddenColumns = responseColumns.filter((column) => !columns.includes(column));
3248
- console.log(
3414
+ const lines = [
3249
3415
  `${result.command} returned ${result.row_count_returned} row(s)` + (result.truncated ? " (truncated)" : "")
3250
- );
3416
+ ];
3251
3417
  if (hiddenColumns.length > 0) {
3252
- console.log(
3418
+ lines.push(
3253
3419
  `Showing ${columns.length}/${responseColumns.length} columns; hidden metadata: ${hiddenColumns.join(", ")}`
3254
3420
  );
3255
- console.log("Use --json or select metadata columns explicitly when you need run ids/errors/stages.");
3421
+ lines.push("Use --json or select metadata columns explicitly when you need run ids/errors/stages.");
3256
3422
  }
3257
3423
  if (rows.length === 0) {
3258
- return;
3424
+ return lines;
3259
3425
  }
3260
3426
  const widths = columns.map(
3261
3427
  (column) => Math.min(
@@ -3266,13 +3432,14 @@ function printTable(result) {
3266
3432
  )
3267
3433
  )
3268
3434
  );
3269
- console.log(columns.map((column, index) => column.padEnd(widths[index])).join(" "));
3270
- console.log(widths.map((width) => "-".repeat(width)).join(" "));
3435
+ lines.push(columns.map((column, index) => column.padEnd(widths[index])).join(" "));
3436
+ lines.push(widths.map((width) => "-".repeat(width)).join(" "));
3271
3437
  for (const row of rows) {
3272
- console.log(
3438
+ lines.push(
3273
3439
  columns.map((column, index) => formatCell(row[column]).padEnd(widths[index])).join(" ")
3274
3440
  );
3275
3441
  }
3442
+ return lines;
3276
3443
  }
3277
3444
  async function handleDbQuery(args) {
3278
3445
  const sqlIndex = args.indexOf("--sql");
@@ -3286,18 +3453,18 @@ async function handleDbQuery(args) {
3286
3453
  const jsonOutput = argsWantJson(args);
3287
3454
  const client = new DeeplineClient();
3288
3455
  const result = await client.queryCustomerDb({ sql, maxRows });
3289
- if (jsonOutput) {
3290
- process.stdout.write(`${JSON.stringify(result)}
3291
- `);
3292
- return 0;
3293
- }
3294
- printTable(result);
3295
- console.error(
3296
- `Tool equivalent: deepline tools execute query_customer_db --payload ${JSON.stringify({
3297
- sql,
3298
- ...maxRows ? { max_rows: maxRows } : {}
3299
- })} --json`
3300
- );
3456
+ const toolCommand = `deepline tools execute query_customer_db --payload ${JSON.stringify({
3457
+ sql,
3458
+ ...maxRows ? { max_rows: maxRows } : {}
3459
+ })} --json`;
3460
+ printCommandEnvelope({
3461
+ ...result,
3462
+ next: { toolEquivalent: toolCommand },
3463
+ render: {
3464
+ sections: [{ title: "customer db query", lines: tableLines(result) }],
3465
+ actions: [{ label: "Tool equivalent", command: toolCommand }]
3466
+ }
3467
+ }, { json: jsonOutput });
3301
3468
  return 0;
3302
3469
  }
3303
3470
  function registerDbCommands(program) {
@@ -3345,11 +3512,12 @@ async function handleFeedback(text, options) {
3345
3512
  ...options.command ? { command: options.command } : {},
3346
3513
  ...options.payload ? { payload: options.payload } : {}
3347
3514
  });
3348
- if (shouldEmitJson(options.json)) {
3349
- printJson(response);
3350
- return;
3351
- }
3352
- process.stdout.write("Feedback submitted. Thank you.\n");
3515
+ printCommandEnvelope({
3516
+ ...response,
3517
+ render: {
3518
+ sections: [{ title: "feedback", lines: ["Feedback submitted. Thank you."] }]
3519
+ }
3520
+ }, { json: options.json });
3353
3521
  }
3354
3522
  function registerFeedbackCommands(program) {
3355
3523
  const feedback = program.command("feedback").description("Submit CLI feedback to Deepline.").addHelpText(
@@ -3381,37 +3549,37 @@ Examples:
3381
3549
  async function fetchOrganizations(http, apiKey) {
3382
3550
  return http.post("/api/v2/auth/cli/organizations", { api_key: apiKey });
3383
3551
  }
3384
- function printOrgList(orgs) {
3385
- for (const [index, org] of orgs.entries()) {
3552
+ function orgListLines(orgs) {
3553
+ return orgs.map((org, index) => {
3386
3554
  const current = org.is_current ? " (current)" : "";
3387
3555
  const role = org.role ? ` [${org.role}]` : "";
3388
- process.stdout.write(` ${index + 1}. ${org.name}${role}${current}
3389
- `);
3390
- }
3556
+ return `${index + 1}. ${org.name}${role}${current}`;
3557
+ });
3391
3558
  }
3392
3559
  async function handleOrgList(options) {
3393
3560
  const config = resolveConfig();
3394
3561
  const http = new HttpClient(config);
3395
3562
  const payload = await fetchOrganizations(http, config.apiKey);
3396
- if (shouldEmitJson(options.json)) {
3397
- printJson(payload);
3398
- return;
3399
- }
3400
- process.stdout.write("Your organizations:\n");
3401
- printOrgList(payload.organizations);
3563
+ printCommandEnvelope({
3564
+ ...payload,
3565
+ render: {
3566
+ sections: [{ title: "Your organizations:", lines: orgListLines(payload.organizations) }]
3567
+ }
3568
+ }, { json: options.json });
3402
3569
  }
3403
3570
  async function handleOrgSwitch(selection, options) {
3404
3571
  const config = resolveConfig();
3405
3572
  const http = new HttpClient(config);
3406
3573
  const payload = await fetchOrganizations(http, config.apiKey);
3407
3574
  if (!selection && !options.orgId) {
3408
- if (shouldEmitJson(options.json)) {
3409
- printJson(payload);
3410
- return;
3411
- }
3412
- process.stdout.write("Your organizations:\n");
3413
- printOrgList(payload.organizations);
3414
- process.stdout.write("\nRun: deepline org switch <number>\n");
3575
+ printCommandEnvelope({
3576
+ ...payload,
3577
+ next: { switch: "deepline org switch <number>" },
3578
+ render: {
3579
+ sections: [{ title: "Your organizations:", lines: orgListLines(payload.organizations) }],
3580
+ actions: [{ label: "Run", command: "deepline org switch <number>" }]
3581
+ }
3582
+ }, { json: options.json });
3415
3583
  return;
3416
3584
  }
3417
3585
  let target = payload.organizations.find((org) => org.org_id === options.orgId);
@@ -3427,12 +3595,12 @@ async function handleOrgSwitch(selection, options) {
3427
3595
  throw new Error("Could not resolve the selected organization.");
3428
3596
  }
3429
3597
  if (target.is_current) {
3430
- if (shouldEmitJson(options.json)) {
3431
- printJson({ ok: true, unchanged: true, organization: target });
3432
- return;
3433
- }
3434
- process.stdout.write(`Already on ${target.name}.
3435
- `);
3598
+ printCommandEnvelope({
3599
+ ok: true,
3600
+ unchanged: true,
3601
+ organization: target,
3602
+ render: { sections: [{ title: "org switch", lines: [`Already on ${target.name}.`] }] }
3603
+ }, { json: options.json });
3436
3604
  return;
3437
3605
  }
3438
3606
  const switched = await http.post(
@@ -3444,14 +3612,24 @@ async function handleOrgSwitch(selection, options) {
3444
3612
  DEEPLINE_ACTIVE_ORG_ID: switched.org_id,
3445
3613
  DEEPLINE_ACTIVE_ORG_NAME: switched.org_name
3446
3614
  });
3447
- if (shouldEmitJson(options.json)) {
3448
- printJson({ ok: true, host_env_path: hostEnvFilePath(config.baseUrl), ...switched });
3449
- return;
3450
- }
3451
- process.stdout.write(`Switched to ${switched.org_name}.
3452
- `);
3453
- process.stdout.write(`Saved host auth in ${hostEnvFilePath(config.baseUrl)}
3454
- `);
3615
+ const { api_key: _apiKey, ...publicSwitched } = switched;
3616
+ printCommandEnvelope({
3617
+ ok: true,
3618
+ host_env_path: hostEnvFilePath(config.baseUrl),
3619
+ ...publicSwitched,
3620
+ api_key_saved: true,
3621
+ render: {
3622
+ sections: [
3623
+ {
3624
+ title: "org switch",
3625
+ lines: [
3626
+ `Switched to ${switched.org_name}.`,
3627
+ `Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
3628
+ ]
3629
+ }
3630
+ ]
3631
+ }
3632
+ }, { json: options.json });
3455
3633
  }
3456
3634
  function registerOrgCommands(program) {
3457
3635
  const org = program.command("org").description("List and switch organizations.").addHelpText(
@@ -5134,6 +5312,9 @@ function traceCliSync(phase, fields, run) {
5134
5312
  throw error;
5135
5313
  }
5136
5314
  }
5315
+ function sleep4(ms) {
5316
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
5317
+ }
5137
5318
  function parseReferencedPlayTarget(target) {
5138
5319
  const trimmed = target.trim();
5139
5320
  const slashIndex = trimmed.indexOf("/");
@@ -5358,15 +5539,6 @@ function fileInputBindingsFromStaticPipeline(staticPipeline) {
5358
5539
  );
5359
5540
  return inputField ? [{ inputPath: inputField }] : [];
5360
5541
  }
5361
- function applyCsvShortcutInput(input) {
5362
- const csvValue = getDottedInputValue(input.runtimeInput, "csv");
5363
- if (csvValue == null || csvValue === "") return;
5364
- const candidate = input.bindings.find((binding) => binding.inputPath !== "csv")?.inputPath ?? input.fallbackInputPath ?? null;
5365
- if (!candidate || candidate === "csv") return;
5366
- const existing = getDottedInputValue(input.runtimeInput, candidate);
5367
- if (existing != null && existing !== "") return;
5368
- setDottedInputValue(input.runtimeInput, candidate, csvValue);
5369
- }
5370
5542
  function isLocalFilePathValue(value) {
5371
5543
  if (typeof value !== "string" || !value.trim()) return false;
5372
5544
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
@@ -6034,25 +6206,38 @@ function formatReturnValue(result) {
6034
6206
  }
6035
6207
  return lines;
6036
6208
  }
6037
- function buildOutputSummary(rowsInfo, runId, exportedPath) {
6038
- if (!rowsInfo) {
6039
- return exportedPath ? { csv_path: exportedPath } : null;
6209
+ function isDatasetHandle(value) {
6210
+ return Boolean(
6211
+ value && typeof value === "object" && !Array.isArray(value) && value.kind === "dataset"
6212
+ );
6213
+ }
6214
+ function collectDatasetHandleLines(value, path = "result") {
6215
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
6216
+ return [];
6040
6217
  }
6041
- const isPartial = !rowsInfo.complete;
6042
- return {
6043
- kind: "rows",
6044
- rowCount: rowsInfo.totalRows,
6045
- previewRowCount: rowsInfo.rows.length,
6046
- ...isPartial ? {
6047
- isPartial: true,
6048
- previewCount: rowsInfo.rows.length,
6049
- totalCount: rowsInfo.totalRows
6050
- } : { isPartial: false },
6051
- complete: rowsInfo.complete,
6052
- columns: rowsInfo.columns,
6053
- source: rowsInfo.source,
6054
- ...exportedPath ? { csv_path: exportedPath } : {}
6055
- };
6218
+ if (isDatasetHandle(value)) {
6219
+ const record = value;
6220
+ const count = typeof record.count === "number" ? record.count : typeof record.rowCount === "number" ? record.rowCount : null;
6221
+ const preview = Array.isArray(record.preview) ? record.preview : [];
6222
+ const lines2 = [
6223
+ ` dataset ${typeof record.path === "string" ? record.path : path}: rows=${count === null ? "-" : formatInteger(count)} preview=${formatInteger(preview.length)}`
6224
+ ];
6225
+ if (typeof record.queryDatasetCommand === "string") {
6226
+ lines2.push(` query dataset: ${record.queryDatasetCommand}`);
6227
+ }
6228
+ if (typeof record.slowExportAsCsvCommand === "string") {
6229
+ lines2.push(` export CSV: ${record.slowExportAsCsvCommand}`);
6230
+ }
6231
+ return lines2;
6232
+ }
6233
+ const lines = [];
6234
+ for (const [key, child] of Object.entries(value)) {
6235
+ if (key === "preview" || key === "access") {
6236
+ continue;
6237
+ }
6238
+ lines.push(...collectDatasetHandleLines(child, `${path}.${key}`));
6239
+ }
6240
+ return lines;
6056
6241
  }
6057
6242
  function buildRunWarnings(status, rowsInfo) {
6058
6243
  if (status.status === "completed" && rowsInfo?.totalRows === 0) {
@@ -6065,19 +6250,12 @@ function buildRunWarnings(status, rowsInfo) {
6065
6250
  }
6066
6251
  return [];
6067
6252
  }
6068
- function buildRunNextCommands(runId, rowsInfo) {
6069
- const commands = {
6253
+ function buildRunNextCommands(runId) {
6254
+ return {
6070
6255
  get: `deepline runs get ${runId} --json`,
6071
6256
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
6072
6257
  logs: `deepline runs logs ${runId} --out run.log --json`
6073
6258
  };
6074
- if (!rowsInfo || rowsInfo.complete) {
6075
- commands.exportCsv = buildRunExportCommand(runId);
6076
- }
6077
- return commands;
6078
- }
6079
- function buildRunExportCommand(runId) {
6080
- return `deepline runs export ${runId} --out output.csv`;
6081
6259
  }
6082
6260
  var RUN_LOG_PREVIEW_LIMIT = 20;
6083
6261
  function getRecordField(value, key) {
@@ -6132,31 +6310,6 @@ function normalizeProgressForEnvelope(status, rowsInfo) {
6132
6310
  wait: status.wait ?? null
6133
6311
  };
6134
6312
  }
6135
- function normalizeOutputsForEnvelope(rowsInfo, runId, exportedPath) {
6136
- if (!rowsInfo) {
6137
- return exportedPath ? [{ name: "output", kind: "file", path: exportedPath }] : [];
6138
- }
6139
- const isPartial = !rowsInfo.complete;
6140
- return [
6141
- {
6142
- name: "rows",
6143
- kind: "dataset",
6144
- rowCount: rowsInfo.totalRows,
6145
- columns: rowsInfo.columns,
6146
- preview: rowsInfo.rows.slice(0, 5),
6147
- previewRowCount: Math.min(rowsInfo.rows.length, 5),
6148
- previewLimit: 5,
6149
- ...isPartial ? {
6150
- isPartial: true,
6151
- previewCount: rowsInfo.rows.length,
6152
- totalCount: rowsInfo.totalRows
6153
- } : { isPartial: false },
6154
- complete: rowsInfo.complete,
6155
- source: rowsInfo.source,
6156
- ...exportedPath ? { csv_path: exportedPath } : {}
6157
- }
6158
- ];
6159
- }
6160
6313
  function normalizeStepsForEnvelope(status) {
6161
6314
  const directSteps = getRecordField(status, "steps");
6162
6315
  if (Array.isArray(directSteps)) {
@@ -6218,7 +6371,7 @@ function stripProviderSpendFromBilling(value) {
6218
6371
  }
6219
6372
  return next;
6220
6373
  }
6221
- function compactPlayStatus(status, options) {
6374
+ function compactPlayStatus(status) {
6222
6375
  const rowsInfo = extractCanonicalRowsInfo(status);
6223
6376
  const result = status && typeof status === "object" ? status.result : null;
6224
6377
  const warnings = buildRunWarnings(status, rowsInfo);
@@ -6241,23 +6394,16 @@ function compactPlayStatus(status, options) {
6241
6394
  status: status.status,
6242
6395
  run: normalizeRunStatusForEnvelope(status),
6243
6396
  progress: normalizeProgressForEnvelope(status, rowsInfo),
6244
- outputs: normalizeOutputsForEnvelope(
6245
- rowsInfo,
6246
- status.runId,
6247
- options?.exportedPath
6248
- ),
6249
6397
  steps: normalizeStepsForEnvelope(status),
6250
6398
  errors: normalizeErrorsForEnvelope(status, error),
6251
6399
  logs: normalizeLogsForEnvelope(status),
6252
6400
  ...error ? { error } : {},
6253
6401
  ...warnings.length > 0 ? { warnings } : {},
6254
- output: buildOutputSummary(rowsInfo, status.runId, options?.exportedPath) ?? result ?? null,
6255
6402
  ...result !== void 0 ? { result } : {},
6256
6403
  ...status.resultView ? { resultView: status.resultView } : {},
6257
6404
  ...datasetStats ? { dataset_stats: datasetStats } : {},
6258
- ...rowsInfo ? { previewRows: rowsInfo.rows.slice(0, 5) } : {},
6259
6405
  ...billing ? { billing } : {},
6260
- next: buildRunNextCommands(status.runId, rowsInfo)
6406
+ next: buildRunNextCommands(status.runId)
6261
6407
  };
6262
6408
  }
6263
6409
  function enrichPlayStatusWithDatasetStats(status) {
@@ -6295,12 +6441,20 @@ function formatDatasetStatsLines(datasetStats) {
6295
6441
  }
6296
6442
  function writePlayResult(status, jsonOutput, options) {
6297
6443
  if (jsonOutput) {
6298
- process.stdout.write(
6299
- `${JSON.stringify(
6300
- options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status, options)
6301
- )}
6302
- `
6303
- );
6444
+ const payload2 = options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status);
6445
+ printCommandEnvelope({
6446
+ ...payload2,
6447
+ render: {
6448
+ sections: [
6449
+ {
6450
+ title: "run result",
6451
+ lines: [
6452
+ `${status.status ?? "running"} ${status.runId ?? "unknown"}`
6453
+ ]
6454
+ }
6455
+ ]
6456
+ }
6457
+ }, { json: true });
6304
6458
  return;
6305
6459
  }
6306
6460
  const result = status.result;
@@ -6317,23 +6471,6 @@ function writePlayResult(status, jsonOutput, options) {
6317
6471
  rowsInfo.columns,
6318
6472
  extractDatasetExecutionStats(status)
6319
6473
  ) : null;
6320
- const outputSummary = buildOutputSummary(
6321
- rowsInfo,
6322
- runId,
6323
- options?.exportedPath
6324
- );
6325
- if (outputSummary) {
6326
- const columns = Array.isArray(outputSummary.columns) ? outputSummary.columns.length : 0;
6327
- const path = typeof outputSummary.csv_path === "string" ? ` file=${outputSummary.csv_path}` : "";
6328
- lines.push(
6329
- ` output: rows=${formatInteger(outputSummary.rowCount)} columns=${formatInteger(columns)}${path}`
6330
- );
6331
- if (outputSummary.isPartial === true) {
6332
- lines.push(
6333
- ` partial output: showing ${formatInteger(outputSummary.previewCount)} preview row(s) of ${formatInteger(outputSummary.totalCount)}`
6334
- );
6335
- }
6336
- }
6337
6474
  for (const warning of warnings) {
6338
6475
  lines.push(` warning: ${warning}`);
6339
6476
  }
@@ -6345,29 +6482,213 @@ function writePlayResult(status, jsonOutput, options) {
6345
6482
  const renderedServerView = renderServerResultView(status.resultView);
6346
6483
  if (result) {
6347
6484
  lines.push(...formatReturnValue(result));
6485
+ lines.push(...collectDatasetHandleLines(result));
6348
6486
  }
6349
6487
  if (renderedServerView.lines.length > 0) {
6350
6488
  lines.push(...renderedServerView.lines);
6351
6489
  }
6352
6490
  lines.push(...renderedServerView.actions);
6353
- console.log(lines.join("\n"));
6491
+ const payload = options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status);
6492
+ printCommandEnvelope({
6493
+ ...payload,
6494
+ render: {
6495
+ sections: [{ title: "run result", lines }]
6496
+ }
6497
+ }, { json: jsonOutput, text: `${lines.join("\n")}
6498
+ ` });
6499
+ }
6500
+ var RUN_EXPORT_PAGE_SIZE = 5e3;
6501
+ function shellSingleQuote(value) {
6502
+ return `'${value.replace(/'/g, `'\\''`)}'`;
6503
+ }
6504
+ function runExportRetryCommand(runId, outPath, datasetPath) {
6505
+ return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote(resolve8(outPath))}`;
6506
+ }
6507
+ function extractRunPlayName(status) {
6508
+ const run = status.run;
6509
+ const candidates = [
6510
+ status.playName,
6511
+ status.name,
6512
+ getRecordField(run, "playName"),
6513
+ getRecordField(run, "name")
6514
+ ];
6515
+ for (const candidate of candidates) {
6516
+ if (typeof candidate === "string" && candidate.trim()) {
6517
+ return candidate.trim();
6518
+ }
6519
+ }
6520
+ return null;
6521
+ }
6522
+ function exportableSheetRow(row) {
6523
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
6524
+ return null;
6525
+ }
6526
+ const record = row;
6527
+ const data = record.data;
6528
+ if (data && typeof data === "object" && !Array.isArray(data)) {
6529
+ return data;
6530
+ }
6531
+ const fallback = { ...record };
6532
+ for (const key of [
6533
+ "key",
6534
+ "status",
6535
+ "cellMeta",
6536
+ "inputIndex",
6537
+ "runId",
6538
+ "error",
6539
+ "stage",
6540
+ "provider",
6541
+ "seq",
6542
+ "createdAt",
6543
+ "updatedAt"
6544
+ ]) {
6545
+ delete fallback[key];
6546
+ }
6547
+ return fallback;
6548
+ }
6549
+ function mergeExportColumns(preferredColumns, rows) {
6550
+ const columns = [];
6551
+ const seen = /* @__PURE__ */ new Set();
6552
+ for (const column of preferredColumns) {
6553
+ if (!column || seen.has(column)) {
6554
+ continue;
6555
+ }
6556
+ seen.add(column);
6557
+ columns.push(column);
6558
+ }
6559
+ for (const row of rows) {
6560
+ for (const column of Object.keys(row)) {
6561
+ if (seen.has(column)) {
6562
+ continue;
6563
+ }
6564
+ seen.add(column);
6565
+ columns.push(column);
6566
+ }
6567
+ }
6568
+ return columns;
6354
6569
  }
6355
- function exportPlayStatusRows(status, outPath) {
6570
+ async function fetchBackingDatasetRows(input) {
6571
+ const playName = extractRunPlayName(input.status);
6572
+ const tableNamespace = input.rowsInfo.tableNamespace?.trim();
6573
+ if (!playName || !tableNamespace) {
6574
+ return null;
6575
+ }
6576
+ const sheetRows = [];
6577
+ let offset = 0;
6578
+ let expectedTotal = input.rowsInfo.totalRows;
6579
+ while (true) {
6580
+ const page = await input.client.runs.exportDatasetRows({
6581
+ playName,
6582
+ tableNamespace,
6583
+ runId: input.status.runId,
6584
+ limit: RUN_EXPORT_PAGE_SIZE,
6585
+ offset
6586
+ });
6587
+ sheetRows.push(...page.rows);
6588
+ const summaryTotal = page.summary?.stats?.total;
6589
+ if (typeof summaryTotal === "number" && Number.isFinite(summaryTotal)) {
6590
+ expectedTotal = Math.max(expectedTotal, Math.trunc(summaryTotal));
6591
+ }
6592
+ if (page.rows.length < RUN_EXPORT_PAGE_SIZE || sheetRows.length >= expectedTotal) {
6593
+ break;
6594
+ }
6595
+ offset += page.rows.length;
6596
+ }
6597
+ const rows = sheetRows.map(exportableSheetRow).filter((row) => Boolean(row));
6598
+ if (rows.length < input.rowsInfo.totalRows) {
6599
+ return null;
6600
+ }
6601
+ const columns = mergeExportColumns(
6602
+ input.rowsInfo.columnsExplicit ? input.rowsInfo.columns : [],
6603
+ rows
6604
+ );
6605
+ return {
6606
+ ...input.rowsInfo,
6607
+ rows,
6608
+ columns,
6609
+ totalRows: rows.length,
6610
+ complete: true,
6611
+ source: `${input.rowsInfo.source ?? "result.rows"} -> /api/v2/plays/${playName}/sheet?tableNamespace=${tableNamespace}`
6612
+ };
6613
+ }
6614
+ async function exportPlayStatusRows(client, status, outPath, options = {}) {
6356
6615
  if (!outPath) {
6357
6616
  return null;
6358
6617
  }
6359
- const rowsInfo = extractCanonicalRowsInfo(status);
6618
+ const availableRows = collectSerializedDatasetRowsInfos(status);
6619
+ let rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
6620
+ if (!rowsInfo && options.datasetPath) {
6621
+ const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
6622
+ throw new DeeplineError(
6623
+ `Run ${status.runId} did not return a dataset at ${options.datasetPath}.` + (available.length > 0 ? ` Available datasets: ${available.join(", ")}.` : ""),
6624
+ void 0,
6625
+ "RUN_EXPORT_DATASET_NOT_FOUND",
6626
+ { runId: status.runId, dataset: options.datasetPath, available }
6627
+ );
6628
+ }
6629
+ if (!options.datasetPath && availableRows.length > 1) {
6630
+ const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
6631
+ throw new DeeplineError(
6632
+ `Run ${status.runId} returned multiple datasets. Choose one with --dataset <path>: ${available.join(", ")}.`,
6633
+ void 0,
6634
+ "RUN_EXPORT_DATASET_REQUIRED",
6635
+ { runId: status.runId, available }
6636
+ );
6637
+ }
6360
6638
  if (!rowsInfo) {
6361
6639
  throw new DeeplineError(
6362
6640
  `Run ${status.runId} did not expose a row-shaped final output to export.`
6363
6641
  );
6364
6642
  }
6643
+ const attempts = Math.max(1, Math.trunc(options.attempts ?? 1));
6644
+ const retryDelayMs = Math.max(0, Math.trunc(options.retryDelayMs ?? 0));
6645
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
6646
+ let fetchedRowsInfo = null;
6647
+ try {
6648
+ fetchedRowsInfo = await fetchBackingDatasetRows({
6649
+ client,
6650
+ status,
6651
+ rowsInfo
6652
+ });
6653
+ } catch (error) {
6654
+ if (!rowsInfo.complete) {
6655
+ throw error;
6656
+ }
6657
+ }
6658
+ if (fetchedRowsInfo?.complete) {
6659
+ return {
6660
+ path: writeCanonicalRowsCsv(fetchedRowsInfo, outPath),
6661
+ rowsInfo: fetchedRowsInfo
6662
+ };
6663
+ }
6664
+ if (rowsInfo.complete) {
6665
+ return { path: writeCanonicalRowsCsv(rowsInfo, outPath), rowsInfo };
6666
+ }
6667
+ if (attempt < attempts && retryDelayMs > 0) {
6668
+ await sleep4(retryDelayMs);
6669
+ }
6670
+ }
6365
6671
  if (!rowsInfo.complete) {
6672
+ const retryCommand = runExportRetryCommand(
6673
+ status.runId,
6674
+ outPath,
6675
+ options.datasetPath ?? rowsInfo.source
6676
+ );
6366
6677
  throw new DeeplineError(
6367
- `Run output only includes ${rowsInfo.rows.length} preview row(s) of ${rowsInfo.totalRows}; full dataset export is not available from this status response yet.`
6678
+ `Run output only includes ${rowsInfo.rows.length} preview row(s) of ${rowsInfo.totalRows}; the backing dataset was not ready to export yet. Retry with: ${retryCommand}`,
6679
+ void 0,
6680
+ "RUN_EXPORT_NOT_READY",
6681
+ {
6682
+ runId: status.runId,
6683
+ previewRowCount: rowsInfo.rows.length,
6684
+ totalRows: rowsInfo.totalRows,
6685
+ tableNamespace: rowsInfo.tableNamespace ?? null,
6686
+ dataset: options.datasetPath ?? rowsInfo.source,
6687
+ retry_command: retryCommand
6688
+ }
6368
6689
  );
6369
6690
  }
6370
- return writeCanonicalRowsCsv(rowsInfo, outPath);
6691
+ return { path: writeCanonicalRowsCsv(rowsInfo, outPath), rowsInfo };
6371
6692
  }
6372
6693
  function renderServerResultView(value) {
6373
6694
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -6441,8 +6762,10 @@ function writeStartedPlayRun(input) {
6441
6762
  dashboardUrl: input.dashboardUrl
6442
6763
  };
6443
6764
  if (input.jsonOutput) {
6444
- process.stdout.write(`${JSON.stringify(payload)}
6445
- `);
6765
+ printCommandEnvelope({
6766
+ ...payload,
6767
+ render: { sections: [{ title: "play run", lines: [`Started ${input.playName}`, `run id: ${input.runId}`] }] }
6768
+ }, { json: true });
6446
6769
  return;
6447
6770
  }
6448
6771
  const lines = [
@@ -6461,10 +6784,14 @@ function writeStartedPlayRun(input) {
6461
6784
  input.progress.writeLine(output, process.stdout);
6462
6785
  return;
6463
6786
  }
6464
- console.log(output);
6787
+ printCommandEnvelope({
6788
+ ...payload,
6789
+ render: { sections: [{ title: "play run", lines }] }
6790
+ }, { json: false, text: `${output}
6791
+ ` });
6465
6792
  }
6466
6793
  function parsePlayRunOptions(args) {
6467
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--<input> value]\n Unknown --<input> value flags, such as --csv leads.csv or --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.map guidance.";
6794
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--watch] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--watch] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--watch] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--watch] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.map guidance.";
6468
6795
  let filePath = null;
6469
6796
  let playName = null;
6470
6797
  let input = null;
@@ -6475,7 +6802,6 @@ function parsePlayRunOptions(args) {
6475
6802
  const emitLogs = !jsonOutput || args.includes("--logs");
6476
6803
  const force = args.includes("--force");
6477
6804
  const noOpen = args.includes("--no-open");
6478
- let outPath = null;
6479
6805
  let waitTimeoutMs = null;
6480
6806
  for (let index = 0; index < args.length; index += 1) {
6481
6807
  const arg = args[index];
@@ -6503,9 +6829,10 @@ function parsePlayRunOptions(args) {
6503
6829
  revisionSelector = "latest";
6504
6830
  continue;
6505
6831
  }
6506
- if (arg === "--out" && args[index + 1]) {
6507
- outPath = resolve8(args[++index]);
6508
- continue;
6832
+ if (arg === "--out" || arg.startsWith("--out=")) {
6833
+ throw new Error(
6834
+ "--out is not a plays run flag. Run the play first, then export rows with: deepline runs export <run-id> --out output.csv"
6835
+ );
6509
6836
  }
6510
6837
  if (arg === "--poll-interval-ms" || arg === "--interval-ms") {
6511
6838
  throw new Error(
@@ -6534,6 +6861,11 @@ function parsePlayRunOptions(args) {
6534
6861
  }
6535
6862
  continue;
6536
6863
  }
6864
+ if (arg === "--csv" || arg.startsWith("--csv=")) {
6865
+ throw new Error(
6866
+ `--csv is not a plays run flag. Pass CSV paths through --input, for example: deepline plays run my.play.ts --input '{"file":"leads.csv"}' --watch`
6867
+ );
6868
+ }
6537
6869
  if (arg.startsWith("--")) {
6538
6870
  const { path, value } = parseInputFieldFlag(arg, args[index + 1]);
6539
6871
  input ??= {};
@@ -6570,11 +6902,6 @@ function parsePlayRunOptions(args) {
6570
6902
  "--live, --latest, and --revision-id only apply to named plays."
6571
6903
  );
6572
6904
  }
6573
- if (outPath && !watch) {
6574
- throw new Error(
6575
- "--out requires --watch so the CLI can export the completed run output."
6576
- );
6577
- }
6578
6905
  return {
6579
6906
  target: filePath ? { kind: "file", path: filePath } : { kind: "name", name: playName },
6580
6907
  input,
@@ -6585,7 +6912,6 @@ function parsePlayRunOptions(args) {
6585
6912
  jsonOutput,
6586
6913
  waitTimeoutMs,
6587
6914
  force,
6588
- outPath,
6589
6915
  noOpen
6590
6916
  };
6591
6917
  }
@@ -6719,11 +7045,6 @@ async function handleFileBackedRun(options) {
6719
7045
  const fileInputBindings = fileInputBindingsFromStaticPipeline(
6720
7046
  compilerManifest.staticPipeline
6721
7047
  );
6722
- applyCsvShortcutInput({
6723
- runtimeInput,
6724
- bindings: fileInputBindings,
6725
- fallbackInputPath: "file"
6726
- });
6727
7048
  const stagedFileInputs = await traceCliSpan(
6728
7049
  "cli.play_stage_inputs",
6729
7050
  {
@@ -6765,11 +7086,6 @@ async function handleFileBackedRun(options) {
6765
7086
  progress
6766
7087
  })
6767
7088
  );
6768
- const exportedPath = traceCliSync(
6769
- "cli.play_export_rows",
6770
- { targetKind: "file", playName },
6771
- () => exportPlayStatusRows(finalStatus, options.outPath)
6772
- );
6773
7089
  if (finalStatus.status === "completed") {
6774
7090
  progress.complete();
6775
7091
  } else {
@@ -6778,7 +7094,7 @@ async function handleFileBackedRun(options) {
6778
7094
  traceCliSync(
6779
7095
  "cli.play_write_result",
6780
7096
  { targetKind: "file", playName },
6781
- () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath })
7097
+ () => writePlayResult(finalStatus, options.jsonOutput)
6782
7098
  );
6783
7099
  return finalStatus.status === "completed" ? 0 : 1;
6784
7100
  }
@@ -6868,10 +7184,6 @@ async function handleNamedRun(options) {
6868
7184
  ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
6869
7185
  ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
6870
7186
  ] : [];
6871
- applyCsvShortcutInput({
6872
- runtimeInput,
6873
- bindings: fileInputBindings
6874
- });
6875
7187
  const stagedFileInputs = await traceCliSpan(
6876
7188
  "cli.play_stage_inputs",
6877
7189
  {
@@ -6910,11 +7222,6 @@ async function handleNamedRun(options) {
6910
7222
  progress
6911
7223
  })
6912
7224
  );
6913
- const exportedPath = traceCliSync(
6914
- "cli.play_export_rows",
6915
- { targetKind: "name", playName },
6916
- () => exportPlayStatusRows(finalStatus, options.outPath)
6917
- );
6918
7225
  if (finalStatus.status === "completed") {
6919
7226
  progress.complete();
6920
7227
  } else {
@@ -6923,7 +7230,7 @@ async function handleNamedRun(options) {
6923
7230
  traceCliSync(
6924
7231
  "cli.play_write_result",
6925
7232
  { targetKind: "name", playName },
6926
- () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath })
7233
+ () => writePlayResult(finalStatus, options.jsonOutput)
6927
7234
  );
6928
7235
  return finalStatus.status === "completed" ? 0 : 1;
6929
7236
  }
@@ -6992,7 +7299,7 @@ function parseRunIdPositional(args, usage) {
6992
7299
  }
6993
7300
  continue;
6994
7301
  }
6995
- if ((arg === "--out" || arg === "--reason") && args[index + 1]) {
7302
+ if ((arg === "--out" || arg === "--reason" || arg === "--dataset") && args[index + 1]) {
6996
7303
  index += 1;
6997
7304
  continue;
6998
7305
  }
@@ -7054,26 +7361,15 @@ async function handleRunsList(args) {
7054
7361
  executionTime: run.executionTime,
7055
7362
  playName: run.memo?.playName ?? playName
7056
7363
  }));
7057
- if (argsWantJson(args)) {
7058
- process.stdout.write(
7059
- `${JSON.stringify({
7060
- runs,
7061
- count: runs.length,
7062
- next: {
7063
- get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
7064
- }
7065
- })}
7066
- `
7067
- );
7068
- } else {
7069
- if (runs.length === 0) {
7070
- console.log(`No runs found for ${playName}.`);
7071
- } else {
7072
- for (const run of runs) {
7073
- console.log(`${run.runId} ${run.status} ${formatTimestamp(run.startedAt)}`);
7074
- }
7075
- }
7076
- }
7364
+ const lines = runs.length === 0 ? [`No runs found for ${playName}.`] : runs.map((run) => `${run.runId} ${run.status} ${formatTimestamp(run.startedAt)}`);
7365
+ printCommandEnvelope({
7366
+ runs,
7367
+ count: runs.length,
7368
+ next: {
7369
+ get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
7370
+ },
7371
+ render: { sections: [{ title: "runs", lines }] }
7372
+ }, { json: argsWantJson(args) });
7077
7373
  return 0;
7078
7374
  }
7079
7375
  async function handleRunTail(args) {
@@ -7127,41 +7423,30 @@ async function handleRunLogs(args) {
7127
7423
  const logs = status.progress?.logs ?? [];
7128
7424
  if (outPath) {
7129
7425
  writeFileSync5(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
7130
- if (argsWantJson(args)) {
7131
- process.stdout.write(
7132
- `${JSON.stringify({
7133
- runId: status.runId,
7134
- log_path: outPath,
7135
- lineCount: logs.length
7136
- })}
7137
- `
7138
- );
7139
- } else {
7140
- console.log(`Wrote ${logs.length} log lines to ${outPath}`);
7141
- }
7426
+ printCommandEnvelope({
7427
+ runId: status.runId,
7428
+ log_path: outPath,
7429
+ lineCount: logs.length,
7430
+ local: { log_path: outPath },
7431
+ render: { sections: [{ title: "run logs", lines: [`Wrote ${logs.length} log lines to ${outPath}`] }] }
7432
+ }, { json: argsWantJson(args) });
7142
7433
  return 0;
7143
7434
  }
7144
7435
  const entries = logs.slice(Math.max(0, logs.length - limit));
7145
- if (argsWantJson(args)) {
7146
- process.stdout.write(
7147
- `${JSON.stringify({
7148
- runId: status.runId,
7149
- totalCount: logs.length,
7150
- returnedCount: entries.length,
7151
- firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
7152
- lastSequence: logs.length === 0 ? null : logs.length,
7153
- truncated: logs.length > entries.length,
7154
- hasMore: logs.length > entries.length,
7155
- entries,
7156
- next: {
7157
- export: `deepline runs logs ${status.runId} --out run.log --json`
7158
- }
7159
- })}
7160
- `
7161
- );
7162
- } else {
7163
- process.stdout.write(`${entries.join("\n")}${entries.length > 0 ? "\n" : ""}`);
7164
- }
7436
+ printCommandEnvelope({
7437
+ runId: status.runId,
7438
+ totalCount: logs.length,
7439
+ returnedCount: entries.length,
7440
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
7441
+ lastSequence: logs.length === 0 ? null : logs.length,
7442
+ truncated: logs.length > entries.length,
7443
+ hasMore: logs.length > entries.length,
7444
+ entries,
7445
+ next: {
7446
+ export: `deepline runs logs ${status.runId} --out run.log --json`
7447
+ },
7448
+ render: { sections: [{ title: "run logs", lines: entries }] }
7449
+ }, { json: argsWantJson(args), text: `${entries.join("\n")}${entries.length > 0 ? "\n" : ""}` });
7165
7450
  return 0;
7166
7451
  }
7167
7452
  async function handleRunStop(args) {
@@ -7182,19 +7467,18 @@ async function handleRunStop(args) {
7182
7467
  }
7183
7468
  const client = new DeeplineClient();
7184
7469
  const result = await client.runs.stop(runId, { reason });
7185
- if (argsWantJson(args)) {
7186
- process.stdout.write(`${JSON.stringify(result)}
7187
- `);
7188
- } else {
7189
- console.log(`Stopped ${result.runId}`);
7190
- if (result.hitlCancelledCount > 0) {
7191
- console.log(` cancelled HITL waits: ${result.hitlCancelledCount}`);
7192
- }
7193
- }
7470
+ const lines = [
7471
+ `Stopped ${result.runId}`,
7472
+ ...result.hitlCancelledCount > 0 ? [`cancelled HITL waits: ${result.hitlCancelledCount}`] : []
7473
+ ];
7474
+ printCommandEnvelope({
7475
+ ...result,
7476
+ render: { sections: [{ title: "run stop", lines }] }
7477
+ }, { json: argsWantJson(args) });
7194
7478
  return 0;
7195
7479
  }
7196
7480
  async function handleRunExport(args) {
7197
- const usage = "Usage: deepline runs export <run-id> --out output.csv [--json]";
7481
+ const usage = "Usage: deepline runs export <run-id> [--dataset result.rows] --out output.csv [--json]";
7198
7482
  let runId;
7199
7483
  try {
7200
7484
  runId = parseRunIdPositional(args, usage);
@@ -7203,10 +7487,15 @@ async function handleRunExport(args) {
7203
7487
  return 1;
7204
7488
  }
7205
7489
  let outPath = null;
7490
+ let datasetPath = null;
7206
7491
  for (let index = 0; index < args.length; index += 1) {
7207
7492
  const arg = args[index];
7208
7493
  if (arg === "--out" && args[index + 1]) {
7209
7494
  outPath = resolve8(args[++index]);
7495
+ continue;
7496
+ }
7497
+ if (arg === "--dataset" && args[index + 1]) {
7498
+ datasetPath = args[++index];
7210
7499
  }
7211
7500
  }
7212
7501
  if (!outPath) {
@@ -7215,21 +7504,18 @@ async function handleRunExport(args) {
7215
7504
  }
7216
7505
  const client = new DeeplineClient();
7217
7506
  const status = await client.getPlayStatus(runId);
7218
- const exportedPath = exportPlayStatusRows(status, outPath);
7219
- if (argsWantJson(args)) {
7220
- const rowsInfo = extractCanonicalRowsInfo(status);
7221
- process.stdout.write(
7222
- `${JSON.stringify({
7223
- runId: status.runId,
7224
- csv_path: exportedPath,
7225
- rowCount: rowsInfo?.totalRows ?? null,
7226
- columns: rowsInfo?.columns ?? []
7227
- })}
7228
- `
7229
- );
7230
- } else {
7231
- console.log(`Exported ${status.runId} to ${exportedPath}`);
7232
- }
7507
+ const exportResult = await exportPlayStatusRows(client, status, outPath, {
7508
+ datasetPath
7509
+ });
7510
+ printCommandEnvelope({
7511
+ runId: status.runId,
7512
+ ...datasetPath ? { dataset: datasetPath } : {},
7513
+ csv_path: exportResult?.path ?? null,
7514
+ rowCount: exportResult?.rowsInfo.totalRows ?? null,
7515
+ columns: exportResult?.rowsInfo.columns ?? [],
7516
+ local: { csv_path: exportResult?.path ?? null },
7517
+ render: { sections: [{ title: "run export", lines: [`Exported ${status.runId} to ${exportResult?.path ?? outPath}`] }] }
7518
+ }, { json: argsWantJson(args) });
7233
7519
  return 0;
7234
7520
  }
7235
7521
  async function handlePlayGet(args) {
@@ -7677,8 +7963,7 @@ Notes:
7677
7963
  Local files are bundled, preflighted, then run in Deepline cloud.
7678
7964
  Named plays run the live saved revision.
7679
7965
  Unknown --foo value and --foo.bar value flags are passed into play input.
7680
- Example: --csv leads.csv becomes input.csv = "leads.csv"; --limit 5 becomes
7681
- input.limit = 5.
7966
+ Example: --limit 5 becomes input.limit = 5.
7682
7967
  File args accept local paths; the CLI stages files before submit.
7683
7968
  --watch prints logs, previews, stats, and next commands.
7684
7969
  --wait is accepted as a compatibility alias for --watch.
@@ -7712,16 +7997,13 @@ Examples:
7712
7997
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
7713
7998
  deepline plays run my.play.ts --input @input.json --wait --json
7714
7999
  deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}' --watch
7715
- deepline plays run enrich.play.ts --csv leads.csv --watch --out leads-enriched.csv
7716
8000
  deepline plays run cto-search.play.ts --limit 5 --watch
8001
+ deepline runs export <run-id> --out output.csv
7717
8002
  deepline runs get <run-id>
7718
8003
  `
7719
8004
  ).option("--file <path>", "Local play file to run").option("--name <name>", "Saved play name to run").option("-i, --input <json>", "Input JSON object or @file path").option("--live", "Run the current live revision explicitly").option("--latest", "Run the newest saved revision, even if it is not live").option(
7720
8005
  "--revision-id <id>",
7721
8006
  "Run a specific saved revision instead of the live revision"
7722
- ).option(
7723
- "--out <path>",
7724
- "Write the completed row output to CSV; requires --watch"
7725
8007
  ).option("--watch", "Stream logs until completion").option("--wait", "Alias for --watch; stream logs until completion").option(
7726
8008
  "--logs",
7727
8009
  "When output is non-interactive, stream play logs to stderr while waiting"
@@ -7730,8 +8012,9 @@ Examples:
7730
8012
  `
7731
8013
  Pass-through input flags:
7732
8014
  Unknown flags are accepted intentionally and become play input fields. Use
7733
- this for play-specific inputs like --csv leads.csv, --limit 5, or
7734
- --filters.title "GTM Engineer".
8015
+ this for play-specific inputs like --limit 5 or --filters.title "GTM Engineer".
8016
+ For CSV file inputs, prefer --input '{"file":"leads.csv"}' so the field name
8017
+ matches the play's ctx.csv(input.file) contract.
7735
8018
  `
7736
8019
  ).action(async (target, options, command) => {
7737
8020
  const passthroughArgs = [...command.args];
@@ -7752,7 +8035,6 @@ Pass-through input flags:
7752
8035
  ...options.live ? ["--live"] : [],
7753
8036
  ...options.latest ? ["--latest"] : [],
7754
8037
  ...options.revisionId ? ["--revision-id", options.revisionId] : [],
7755
- ...options.out ? ["--out", options.out] : [],
7756
8038
  ...options.watch || options.wait ? ["--watch"] : [],
7757
8039
  ...options.logs ? ["--logs"] : [],
7758
8040
  ...options.tailTimeoutMs ? ["--tail-timeout-ms", options.tailTimeoutMs] : [],
@@ -7916,7 +8198,7 @@ Examples:
7916
8198
  `
7917
8199
  Concepts:
7918
8200
  A run is one execution instance of a play. It has status, progress, logs,
7919
- preview output, recovery metadata, and optional full row export.
8201
+ returned result, dataset previews, recovery metadata, and optional dataset export.
7920
8202
  tail reads the live stream. logs fetches persisted logs after the fact.
7921
8203
  stop mutates cloud state by requesting cancellation.
7922
8204
 
@@ -7929,7 +8211,7 @@ Examples:
7929
8211
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
7930
8212
  `
7931
8213
  );
7932
- runs.command("get <runId>").description("Get status, progress, outputs, errors, and recovery metadata for a play run.").addHelpText(
8214
+ runs.command("get <runId>").description("Get status, progress, result, errors, and recovery metadata for a play run.").addHelpText(
7933
8215
  "after",
7934
8216
  `
7935
8217
  Notes:
@@ -8023,20 +8305,22 @@ Examples:
8023
8305
  ...options.json ? ["--json"] : []
8024
8306
  ]);
8025
8307
  });
8026
- runs.command("export <runId>").description("Export the completed row output for a play run to CSV.").addHelpText(
8308
+ runs.command("export <runId>").description("Export a returned dataset handle for a play run to CSV.").addHelpText(
8027
8309
  "after",
8028
8310
  `
8029
8311
  Notes:
8030
- Writes the completed row output to the requested local CSV path. Use runs get
8031
- first when you need to confirm the run is complete or inspect preview output.
8312
+ Writes a returned dataset handle to the requested local CSV path. Use runs get
8313
+ first to inspect dataset paths like result.rows or result.nested.contacts.
8032
8314
 
8033
8315
  Examples:
8034
8316
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
8317
+ deepline runs export play/my-play/run/20260501t000000-000 --dataset result.rows --out output.csv
8035
8318
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv --json
8036
8319
  `
8037
- ).requiredOption("--out <path>", "Output CSV path").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
8320
+ ).requiredOption("--out <path>", "Output CSV path").option("--dataset <path>", "Returned dataset handle path, such as result.rows").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
8038
8321
  process.exitCode = await handleRunExport([
8039
8322
  runId,
8323
+ ...options.dataset ? ["--dataset", options.dataset] : [],
8040
8324
  "--out",
8041
8325
  options.out,
8042
8326
  ...options.json ? ["--json"] : []
@@ -8202,19 +8486,19 @@ function toListedTool(tool) {
8202
8486
  async function listTools(args) {
8203
8487
  const client = new DeeplineClient();
8204
8488
  const items = (await client.listTools()).map(toListedTool);
8205
- if (argsWantJson(args)) {
8206
- process.stdout.write(`${JSON.stringify(items)}
8207
- `);
8208
- return 0;
8209
- }
8210
- console.log(`${items.length} tools available:
8211
- `);
8212
- for (const item of items) {
8213
- const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8214
- const listHint = item.listExtractorPaths?.length ? ` listExtractorPaths=${item.listExtractorPaths.join(",")}` : "";
8215
- console.log(` ${item.toolId}${cats}`);
8216
- console.log(` ${item.description}${listHint}`);
8217
- }
8489
+ const render = {
8490
+ sections: [
8491
+ {
8492
+ title: `${items.length} tools available:`,
8493
+ lines: items.flatMap((item) => {
8494
+ const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8495
+ const listHint = item.listExtractorPaths?.length ? ` listExtractorPaths=${item.listExtractorPaths.join(",")}` : "";
8496
+ return [`${item.toolId}${cats}`, ` ${item.description}${listHint}`];
8497
+ })
8498
+ }
8499
+ ]
8500
+ };
8501
+ printCommandEnvelope({ tools: items, count: items.length, render }, { json: argsWantJson(args) });
8218
8502
  return 0;
8219
8503
  }
8220
8504
  async function searchTools(queryInput, options = {}) {
@@ -8232,21 +8516,27 @@ async function searchTools(queryInput, options = {}) {
8232
8516
  includeSearchDebug: options.includeSearchDebug
8233
8517
  });
8234
8518
  const items = result.tools.map(toListedTool);
8235
- if (options.json || shouldEmitJson()) {
8236
- process.stdout.write(`${JSON.stringify({ ...result, tools: items })}
8237
- `);
8238
- return 0;
8239
- }
8240
- console.log(`${items.length} tools found:
8241
- `);
8242
- for (const item of items) {
8243
- const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8244
- console.log(` ${item.toolId}${cats}`);
8245
- console.log(` ${item.description}`);
8246
- if (item.inputSchema) {
8247
- console.log(" inputSchema: yes");
8519
+ const envelope = {
8520
+ ...result,
8521
+ tools: items,
8522
+ count: items.length,
8523
+ render: {
8524
+ sections: [
8525
+ {
8526
+ title: `${items.length} tools found:`,
8527
+ lines: items.flatMap((item) => {
8528
+ const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8529
+ return [
8530
+ `${item.toolId}${cats}`,
8531
+ ` ${item.description}`,
8532
+ ...item.inputSchema ? [" inputSchema: yes"] : []
8533
+ ];
8534
+ })
8535
+ }
8536
+ ]
8248
8537
  }
8249
- }
8538
+ };
8539
+ printCommandEnvelope(envelope, { json: options.json || shouldEmitJson() });
8250
8540
  return 0;
8251
8541
  }
8252
8542
  function playIdentifiers(play) {
@@ -8578,6 +8868,9 @@ function samplePayload(samples, key) {
8578
8868
  if (!isRecord3(entry)) return void 0;
8579
8869
  return Object.prototype.hasOwnProperty.call(entry, "payload") ? entry.payload : entry;
8580
8870
  }
8871
+ function commandEnvelopeFromRawResponse(rawResponse) {
8872
+ return isRecord3(rawResponse) ? { ...rawResponse } : { status: "completed", result: rawResponse };
8873
+ }
8581
8874
  function isPlayTool(tool) {
8582
8875
  const provider = typeof tool.provider === "string" ? tool.provider : "";
8583
8876
  return provider === "deepline_native" && Boolean(recordField(tool, "playExpansion", "play_expansion"));
@@ -8742,6 +9035,53 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
8742
9035
  windowsCopyCommand: `New-Item -ItemType Directory -Force -Path ${powerShellQuote(projectDir.replace(/\//g, "\\"))} | Out-Null; Copy-Item -LiteralPath ${powerShellQuote(scriptPath)} -Destination ${powerShellQuote(`${projectDir.replace(/\//g, "\\")}\\${fileName}`)}`
8743
9036
  };
8744
9037
  }
9038
+ function buildToolExecuteBaseEnvelope(input) {
9039
+ const envelope = commandEnvelopeFromRawResponse(input.rawResponse);
9040
+ const summaryEntries = Object.entries(input.summary);
9041
+ const output = input.listConversion ? {
9042
+ kind: "list",
9043
+ rowCount: input.listConversion.rows.length,
9044
+ columns: Object.keys(input.listConversion.rows[0] ?? {}),
9045
+ preview: input.listConversion.rows.slice(0, 5),
9046
+ listStrategy: input.listConversion.strategy,
9047
+ listSourcePath: input.listConversion.sourcePath
9048
+ } : {
9049
+ kind: summaryEntries.length > 0 ? "object" : "raw",
9050
+ summary: input.summary
9051
+ };
9052
+ const actions = input.listConversion ? [
9053
+ {
9054
+ label: "next",
9055
+ command: "move starter script into a project folder and expand it into a Deepline play"
9056
+ }
9057
+ ] : [];
9058
+ return {
9059
+ ...envelope,
9060
+ output,
9061
+ ...summaryEntries.length > 0 ? { summary: input.summary } : {},
9062
+ next: input.listConversion ? {
9063
+ expandToPlay: "Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
9064
+ } : {},
9065
+ render: {
9066
+ sections: input.listConversion ? [
9067
+ {
9068
+ title: "output",
9069
+ lines: [
9070
+ `${input.listConversion.rows.length} row(s) extracted from ${input.listConversion.sourcePath ?? "auto-detected list"}`,
9071
+ `columns: ${JSON.stringify(Object.keys(input.listConversion.rows[0] ?? {}))}`,
9072
+ `preview: ${JSON.stringify(input.listConversion.rows.slice(0, 5))}`
9073
+ ]
9074
+ }
9075
+ ] : [
9076
+ {
9077
+ title: "result",
9078
+ lines: summaryEntries.length > 0 ? summaryEntries.map(([key, value]) => `${key}=${String(value)}`) : [JSON.stringify(input.rawResponse, null, 2)]
9079
+ }
9080
+ ],
9081
+ actions
9082
+ }
9083
+ };
9084
+ }
8745
9085
  async function executeTool(args) {
8746
9086
  let parsed;
8747
9087
  try {
@@ -8774,24 +9114,45 @@ async function executeTool(args) {
8774
9114
  listExtractorPaths: metadata.listExtractorPaths ?? []
8775
9115
  });
8776
9116
  const summary = extractSummaryFields(rawResponse);
9117
+ const baseEnvelope = buildToolExecuteBaseEnvelope({
9118
+ toolId: parsed.toolId,
9119
+ rawResponse,
9120
+ listConversion,
9121
+ summary
9122
+ });
8777
9123
  if (parsed.outputFormat === "json" || parsed.outputFormat === "auto" && shouldEmitJson()) {
8778
- process.stdout.write(`${JSON.stringify(rawResponse, null, 2)}
8779
- `);
9124
+ printCommandEnvelope(baseEnvelope, { json: true });
8780
9125
  return 0;
8781
9126
  }
8782
9127
  if (parsed.outputFormat === "json_file") {
8783
9128
  const jsonPath = writeJsonOutputFile(rawResponse, `payload_${parsed.toolId}`);
8784
- console.log(jsonPath);
9129
+ printCommandEnvelope(
9130
+ {
9131
+ ...baseEnvelope,
9132
+ local: {
9133
+ ...isRecord3(baseEnvelope.local) ? baseEnvelope.local : {},
9134
+ payload_file: jsonPath
9135
+ }
9136
+ },
9137
+ { json: true }
9138
+ );
8785
9139
  return 0;
8786
9140
  }
8787
9141
  if (!listConversion) {
8788
9142
  if (parsed.outputFormat === "csv" || parsed.outputFormat === "csv_file") {
8789
9143
  const jsonPath = writeJsonOutputFile(rawResponse, `payload_${parsed.toolId}`);
8790
- console.log(jsonPath);
9144
+ printCommandEnvelope(
9145
+ {
9146
+ ...baseEnvelope,
9147
+ local: {
9148
+ payload_file: jsonPath
9149
+ }
9150
+ },
9151
+ { json: parsed.outputFormat === "csv_file" || shouldEmitJson() }
9152
+ );
8791
9153
  return 0;
8792
9154
  }
8793
- process.stdout.write(`${JSON.stringify(rawResponse, null, 2)}
8794
- `);
9155
+ printCommandEnvelope(baseEnvelope, { json: false });
8795
9156
  return 0;
8796
9157
  }
8797
9158
  const csv = writeCsvOutputFile(listConversion.rows, `${parsed.toolId}_output`);
@@ -8800,8 +9161,51 @@ async function executeTool(args) {
8800
9161
  payload: parsed.params,
8801
9162
  rows: listConversion.rows
8802
9163
  });
9164
+ const materializedEnvelope = {
9165
+ ...baseEnvelope,
9166
+ local: {
9167
+ extracted_csv: csv.path,
9168
+ extracted_csv_rows: csv.rowCount,
9169
+ extracted_csv_columns: csv.columns,
9170
+ preview: csv.preview,
9171
+ starter_script: seededScript.path,
9172
+ project_dir: seededScript.projectDir,
9173
+ copy_to_project: {
9174
+ macos_linux: seededScript.macCopyCommand,
9175
+ windows_powershell: seededScript.windowsCopyCommand
9176
+ }
9177
+ },
9178
+ render: {
9179
+ sections: [
9180
+ {
9181
+ title: `${csv.path} (${csv.rowCount} rows)`,
9182
+ lines: [
9183
+ ...csv.columns.length > 0 ? [`columns: ${JSON.stringify(csv.columns)}`] : [],
9184
+ ...Object.keys(summary).length > 0 ? [`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(", ")}`] : [],
9185
+ `preview: ${JSON.stringify(csv.preview)}`,
9186
+ `starter script: ${seededScript.path}`
9187
+ ]
9188
+ }
9189
+ ],
9190
+ actions: [
9191
+ {
9192
+ label: "next",
9193
+ command: "Move the script into a project folder and expand it into a Deepline play. Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
9194
+ },
9195
+ {
9196
+ label: "macOS/Linux",
9197
+ command: seededScript.macCopyCommand
9198
+ },
9199
+ {
9200
+ label: "Windows PowerShell",
9201
+ command: seededScript.windowsCopyCommand
9202
+ }
9203
+ ]
9204
+ }
9205
+ };
8803
9206
  if (parsed.outputFormat === "csv_file") {
8804
- process.stdout.write(`${JSON.stringify({
9207
+ printCommandEnvelope({
9208
+ ...materializedEnvelope,
8805
9209
  extracted_csv: csv.path,
8806
9210
  extracted_csv_rows: csv.rowCount,
8807
9211
  extracted_csv_columns: csv.columns,
@@ -8815,28 +9219,24 @@ async function executeTool(args) {
8815
9219
  windows_powershell: seededScript.windowsCopyCommand
8816
9220
  },
8817
9221
  summary
8818
- })}
8819
- `);
9222
+ }, { json: true });
8820
9223
  return 0;
8821
9224
  }
8822
9225
  if (parsed.noPreview) {
8823
- console.log(csv.path);
9226
+ printCommandEnvelope(
9227
+ {
9228
+ ...materializedEnvelope,
9229
+ local: {
9230
+ ...materializedEnvelope.local ?? {},
9231
+ output_path: csv.path
9232
+ }
9233
+ },
9234
+ { json: shouldEmitJson(), text: `${csv.path}
9235
+ ` }
9236
+ );
8824
9237
  return 0;
8825
9238
  }
8826
- console.log(`${csv.path} (${csv.rowCount} rows)`);
8827
- if (csv.columns.length > 0) {
8828
- console.log(`columns: ${JSON.stringify(csv.columns)}`);
8829
- }
8830
- if (Object.keys(summary).length > 0) {
8831
- console.log(`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(", ")}`);
8832
- }
8833
- console.log(`preview: ${JSON.stringify(csv.preview)}`);
8834
- console.log(`starter script: ${seededScript.path}`);
8835
- console.log(
8836
- "next: Move the script into a project folder and expand it into a Deepline play. Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
8837
- );
8838
- console.log(`macOS/Linux: ${seededScript.macCopyCommand}`);
8839
- console.log(`Windows PowerShell: ${seededScript.windowsCopyCommand}`);
9239
+ printCommandEnvelope(materializedEnvelope, { json: false });
8840
9240
  return 0;
8841
9241
  }
8842
9242
 
@@ -8910,23 +9310,36 @@ function runCommand(command, args) {
8910
9310
  }
8911
9311
  async function handleUpdate(options) {
8912
9312
  const plan = resolveUpdatePlan();
9313
+ const render = {
9314
+ sections: [
9315
+ {
9316
+ title: "update",
9317
+ lines: plan.kind === "source" ? [
9318
+ "This Deepline CLI is running from SDK source, so it cannot safely update itself like an npm global.",
9319
+ `Update the backing checkout with: ${plan.manualCommand}`
9320
+ ] : [`Updating Deepline SDK/CLI with: ${plan.manualCommand}`]
9321
+ }
9322
+ ]
9323
+ };
8913
9324
  if (options.json) {
8914
- process.stdout.write(`${JSON.stringify(plan)}
8915
- `);
9325
+ printCommandEnvelope({ ...plan, render }, { json: true });
8916
9326
  return 0;
8917
9327
  }
8918
9328
  if (options.printCommand) {
8919
- process.stdout.write(`${plan.manualCommand}
8920
- `);
9329
+ printCommandEnvelope(
9330
+ {
9331
+ ...plan,
9332
+ render: {
9333
+ sections: [{ title: "update command", lines: [plan.manualCommand] }]
9334
+ }
9335
+ },
9336
+ { json: false, text: `${plan.manualCommand}
9337
+ ` }
9338
+ );
8921
9339
  return 0;
8922
9340
  }
8923
9341
  if (plan.kind === "source") {
8924
- process.stdout.write(
8925
- `This Deepline CLI is running from SDK source, so it cannot safely update itself like an npm global.
8926
- Update the backing checkout with:
8927
- ${plan.manualCommand}
8928
- `
8929
- );
9342
+ printCommandEnvelope({ ...plan, render }, { json: false });
8930
9343
  return 0;
8931
9344
  }
8932
9345
  process.stderr.write(`Updating Deepline SDK/CLI with: ${plan.manualCommand}
@@ -9239,7 +9652,7 @@ Exit codes:
9239
9652
  registerDbCommands(program);
9240
9653
  registerFeedbackCommands(program);
9241
9654
  registerUpdateCommand(program);
9242
- program.command("health").description("Check server health.").addHelpText(
9655
+ program.command("health").description("Check server health.").option("--json", "Force JSON output.").addHelpText(
9243
9656
  "after",
9244
9657
  `
9245
9658
  Notes: