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.
package/dist/cli/index.js CHANGED
@@ -266,8 +266,8 @@ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStar
266
266
  }
267
267
 
268
268
  // src/version.ts
269
- var SDK_VERSION = "0.1.29";
270
- var SDK_API_CONTRACT = "2026-05-runs-v2";
269
+ var SDK_VERSION = "0.1.31";
270
+ var SDK_API_CONTRACT = "2026-05-runs-export-v3";
271
271
 
272
272
  // ../shared_libs/play-runtime/coordinator-headers.ts
273
273
  var COORDINATOR_INTERNAL_TOKEN_HEADER = "x-deepline-internal-token";
@@ -676,6 +676,7 @@ var DeeplineClient = class {
676
676
  list: (options2) => this.listRuns(options2),
677
677
  tail: (runId, options2) => this.tailRun(runId, options2),
678
678
  logs: (runId, options2) => this.getRunLogs(runId, options2),
679
+ exportDatasetRows: (input) => this.getPlaySheetRows(input),
679
680
  stop: (runId, options2) => this.stopRun(runId, options2)
680
681
  };
681
682
  }
@@ -703,7 +704,7 @@ var DeeplineClient = class {
703
704
  const target = play.reference || play.name;
704
705
  if (options?.csvInput) {
705
706
  const inputField = typeof options.csvInput.inputField === "string" && options.csvInput.inputField.trim() ? options.csvInput.inputField.trim() : "csv";
706
- return `deepline plays run ${target} --${inputField} leads.csv --watch`;
707
+ return `deepline plays run ${target} --input '${JSON.stringify({ [inputField]: "leads.csv" })}' --watch`;
707
708
  }
708
709
  return `deepline plays run ${target} --input '{...}' --watch`;
709
710
  }
@@ -1363,6 +1364,19 @@ var DeeplineClient = class {
1363
1364
  entries
1364
1365
  };
1365
1366
  }
1367
+ async getPlaySheetRows(input) {
1368
+ const params = new URLSearchParams({
1369
+ tableNamespace: input.tableNamespace,
1370
+ limit: String(input.limit ?? 5e3),
1371
+ offset: String(input.offset ?? 0)
1372
+ });
1373
+ if (input.runId?.trim()) {
1374
+ params.set("runId", input.runId.trim());
1375
+ }
1376
+ return await this.http.get(
1377
+ `/api/v2/plays/${encodeURIComponent(input.playName)}/sheet?${params.toString()}`
1378
+ );
1379
+ }
1366
1380
  /**
1367
1381
  * Stop a run by id using the public runs resource model.
1368
1382
  *
@@ -1970,6 +1984,38 @@ function clip(value, maxLength) {
1970
1984
  return value.length > maxLength ? `${value.slice(0, maxLength - 1)}\u2026` : value;
1971
1985
  }
1972
1986
 
1987
+ // src/cli/command-envelope.ts
1988
+ function renderSections(render) {
1989
+ const lines = [];
1990
+ for (const section of render?.sections ?? []) {
1991
+ if (!section.title || section.lines.length === 0) continue;
1992
+ lines.push(section.title);
1993
+ lines.push(...section.lines.map((line) => ` ${line}`));
1994
+ }
1995
+ for (const action of render?.actions ?? []) {
1996
+ if (!action.label || !action.command) continue;
1997
+ lines.push(`${action.label}: ${action.command}`);
1998
+ }
1999
+ return lines;
2000
+ }
2001
+ function renderCommandEnvelopeText(envelope) {
2002
+ const lines = renderSections(envelope.render);
2003
+ if (lines.length > 0) {
2004
+ return `${lines.join("\n")}
2005
+ `;
2006
+ }
2007
+ return `${JSON.stringify(envelope, null, 2)}
2008
+ `;
2009
+ }
2010
+ function printCommandEnvelope(envelope, options = {}) {
2011
+ const jsonOutput = typeof options.json === "boolean" ? options.json : shouldEmitJson();
2012
+ if (jsonOutput) {
2013
+ printJson(envelope);
2014
+ return;
2015
+ }
2016
+ process.stdout.write(options.text ?? renderCommandEnvelopeText(envelope));
2017
+ }
2018
+
1973
2019
  // src/cli/commands/auth.ts
1974
2020
  var EXIT_OK = 0;
1975
2021
  var EXIT_AUTH = 1;
@@ -2218,6 +2264,7 @@ async function handleStatus(args) {
2218
2264
  const reveal = args.includes("--reveal");
2219
2265
  const jsonOutput = argsWantJson(args);
2220
2266
  let hostStatusPayload = null;
2267
+ const hostLines = [];
2221
2268
  try {
2222
2269
  const { status: hStatus, data: hData } = await httpJson("GET", `${baseUrl}/api/v2/health`, null);
2223
2270
  if (hStatus === 200) {
@@ -2226,11 +2273,9 @@ async function handleStatus(args) {
2226
2273
  hostStatus: hData.status || "ok",
2227
2274
  hostVersion: hData.version || "(unknown)"
2228
2275
  };
2229
- if (!jsonOutput) {
2230
- console.log(`Host: ${baseUrl}`);
2231
- console.log(`Host status: ${hData.status || "ok"}`);
2232
- console.log(`Host version: ${hData.version || "(unknown)"}`);
2233
- }
2276
+ hostLines.push(`Host: ${baseUrl}`);
2277
+ hostLines.push(`Host status: ${hData.status || "ok"}`);
2278
+ hostLines.push(`Host version: ${hData.version || "(unknown)"}`);
2234
2279
  }
2235
2280
  } catch {
2236
2281
  hostStatusPayload = {
@@ -2238,40 +2283,34 @@ async function handleStatus(args) {
2238
2283
  hostStatus: "unreachable",
2239
2284
  hostVersion: null
2240
2285
  };
2241
- if (!jsonOutput) {
2242
- console.log(`Host: ${baseUrl} (unreachable)`);
2243
- }
2286
+ hostLines.push(`Host: ${baseUrl} (unreachable)`);
2244
2287
  }
2245
2288
  const env = loadCliEnv(baseUrl);
2246
2289
  const apiKey = process.env.DEEPLINE_API_KEY?.trim() || env.DEEPLINE_API_KEY || "";
2247
2290
  if (!apiKey) {
2248
2291
  if (env.DEEPLINE_CLAIM_TOKEN?.trim()) {
2249
- if (jsonOutput) {
2250
- process.stdout.write(`${JSON.stringify({
2251
- ...hostStatusPayload ?? { host: baseUrl },
2252
- status: "pending",
2253
- connected: false,
2254
- next: "deepline auth wait"
2255
- })}
2256
- `);
2257
- return EXIT_OK;
2258
- }
2259
- console.log("Status: pending");
2260
- console.log("Run: deepline auth wait");
2261
- return EXIT_OK;
2262
- }
2263
- if (jsonOutput) {
2264
- process.stdout.write(`${JSON.stringify({
2292
+ printCommandEnvelope({
2265
2293
  ...hostStatusPayload ?? { host: baseUrl },
2266
- status: "not connected",
2294
+ status: "pending",
2267
2295
  connected: false,
2268
- next: "deepline auth register"
2269
- })}
2270
- `);
2296
+ next: "deepline auth wait",
2297
+ render: {
2298
+ sections: [{ title: "auth status", lines: [...hostLines, "Status: pending"] }],
2299
+ actions: [{ label: "Run", command: "deepline auth wait" }]
2300
+ }
2301
+ }, { json: jsonOutput });
2271
2302
  return EXIT_OK;
2272
2303
  }
2273
- console.log("Status: not connected");
2274
- console.log("Run: deepline auth register");
2304
+ printCommandEnvelope({
2305
+ ...hostStatusPayload ?? { host: baseUrl },
2306
+ status: "not connected",
2307
+ connected: false,
2308
+ next: "deepline auth register",
2309
+ render: {
2310
+ sections: [{ title: "auth status", lines: [...hostLines, "Status: not connected"] }],
2311
+ actions: [{ label: "Run", command: "deepline auth register" }]
2312
+ }
2313
+ }, { json: jsonOutput });
2275
2314
  return EXIT_OK;
2276
2315
  }
2277
2316
  const { status, data } = await httpJson("POST", `${baseUrl}/api/v2/auth/cli/status`, apiKey, {
@@ -2279,18 +2318,16 @@ async function handleStatus(args) {
2279
2318
  reveal
2280
2319
  });
2281
2320
  if (status === 401 || status === 403) {
2282
- if (jsonOutput) {
2283
- process.stdout.write(`${JSON.stringify({
2284
- ...hostStatusPayload ?? { host: baseUrl },
2285
- status: "unauthorized",
2286
- connected: false,
2287
- next: "deepline auth register"
2288
- })}
2289
- `);
2290
- return EXIT_AUTH;
2291
- }
2292
- console.log("Status: unauthorized");
2293
- console.log("Run: deepline auth register");
2321
+ printCommandEnvelope({
2322
+ ...hostStatusPayload ?? { host: baseUrl },
2323
+ status: "unauthorized",
2324
+ connected: false,
2325
+ next: "deepline auth register",
2326
+ render: {
2327
+ sections: [{ title: "auth status", lines: [...hostLines, "Status: unauthorized"] }],
2328
+ actions: [{ label: "Run", command: "deepline auth register" }]
2329
+ }
2330
+ }, { json: jsonOutput });
2294
2331
  return EXIT_AUTH;
2295
2332
  }
2296
2333
  if (status >= 400) {
@@ -2312,23 +2349,7 @@ async function handleStatus(args) {
2312
2349
  },
2313
2350
  examples: Array.isArray(data.examples) ? data.examples : []
2314
2351
  };
2315
- if (jsonOutput) {
2316
- process.stdout.write(`${JSON.stringify(payload)}
2317
- `);
2318
- } else {
2319
- console.log(`Status: ${payload.status}`);
2320
- console.log(`Rate limit tier: ${payload.rateLimitTier}`);
2321
- if (payload.workspace.name) console.log(`Workspace: ${payload.workspace.name}`);
2322
- if (payload.workspace.slug) console.log(`Workspace slug: ${payload.workspace.slug}`);
2323
- if (payload.workspace.id != null) console.log(`Org ID: ${payload.workspace.id}`);
2324
- if (payload.user.id != null) console.log(`User ID: ${payload.user.id}`);
2325
- if (payload.examples.length > 0) {
2326
- console.log("Examples:");
2327
- for (const example of payload.examples.slice(0, 3)) {
2328
- console.log(` ${String(example)}`);
2329
- }
2330
- }
2331
- }
2352
+ let savedApiKeyPath = null;
2332
2353
  if (reveal) {
2333
2354
  const apiKeyResp = String(data.api_key || apiKey);
2334
2355
  if (apiKeyResp) {
@@ -2337,9 +2358,31 @@ async function handleStatus(args) {
2337
2358
  DEEPLINE_API_KEY: apiKeyResp,
2338
2359
  DEEPLINE_CLAIM_TOKEN: ""
2339
2360
  }, baseUrl);
2340
- console.log(`Saved API key to ${envFilePath(baseUrl)}`);
2361
+ savedApiKeyPath = envFilePath(baseUrl);
2341
2362
  }
2342
2363
  }
2364
+ printCommandEnvelope({
2365
+ ...payload,
2366
+ ...savedApiKeyPath ? { saved_api_key_path: savedApiKeyPath } : {},
2367
+ render: {
2368
+ sections: [
2369
+ {
2370
+ title: "auth status",
2371
+ lines: [
2372
+ ...hostLines,
2373
+ `Status: ${payload.status}`,
2374
+ `Rate limit tier: ${payload.rateLimitTier}`,
2375
+ ...payload.workspace.name ? [`Workspace: ${payload.workspace.name}`] : [],
2376
+ ...payload.workspace.slug ? [`Workspace slug: ${payload.workspace.slug}`] : [],
2377
+ ...payload.workspace.id != null ? [`Org ID: ${payload.workspace.id}`] : [],
2378
+ ...payload.user.id != null ? [`User ID: ${payload.user.id}`] : [],
2379
+ ...payload.examples.length > 0 ? ["Examples:", ...payload.examples.slice(0, 3).map((example) => ` ${String(example)}`)] : [],
2380
+ ...savedApiKeyPath ? [`Saved API key to ${savedApiKeyPath}`] : []
2381
+ ]
2382
+ }
2383
+ ]
2384
+ }
2385
+ }, { json: jsonOutput });
2343
2386
  return EXIT_OK;
2344
2387
  }
2345
2388
  function registerAuthCommands(program) {
@@ -2422,19 +2465,18 @@ var import_sync3 = require("csv-stringify/sync");
2422
2465
  function humanize(value) {
2423
2466
  return String(value || "").split("_").filter(Boolean).map((token) => token[0]?.toUpperCase() + token.slice(1)).join(" ") || "Unknown";
2424
2467
  }
2425
- function printRecentUsage(entries) {
2468
+ function recentUsageLines(entries) {
2426
2469
  if (entries.length === 0) {
2427
- process.stdout.write("Recent activity: none yet\n");
2428
- return;
2470
+ return ["Recent activity: none yet"];
2429
2471
  }
2430
- process.stdout.write("Recent activity:\n");
2472
+ const lines = ["Recent activity:"];
2431
2473
  for (const entry of entries) {
2432
2474
  const op = `${humanize(entry.provider)} ${humanize(entry.operation)}`.trim();
2433
2475
  const charge = entry.billing_mode === "no_bill" ? "free" : `${entry.credits ?? 0} cr`;
2434
2476
  const status = entry.status || "completed";
2435
- process.stdout.write(` ${op} | ${charge} | ${status} | ${entry.created_at || "unknown"}
2436
- `);
2477
+ lines.push(`${op} | ${charge} | ${status} | ${entry.created_at || "unknown"}`);
2437
2478
  }
2479
+ return lines;
2438
2480
  }
2439
2481
  function summarizeLedgerRows(summary, rows) {
2440
2482
  const netDelta = rows.reduce((sum, row) => {
@@ -2487,49 +2529,49 @@ function defaultLedgerExportPath() {
2487
2529
  async function handleBalance(options) {
2488
2530
  const { http } = getAuthedHttpClient();
2489
2531
  const payload = await http.get("/api/v2/billing/balance");
2490
- if (shouldEmitJson(options.json)) return printJson(payload);
2491
2532
  const status = String(payload.balance_status || "");
2492
- if (status === "no_billing") {
2493
- process.stdout.write("Balance: 0 credits\n");
2494
- process.stdout.write("Billing: No billing account or payment method set up for this workspace.\n");
2495
- return;
2496
- }
2497
- process.stdout.write(`Balance: ${payload.balance ?? "(unknown)"} credits
2498
- `);
2533
+ const lines = status === "no_billing" ? [
2534
+ "Balance: 0 credits",
2535
+ "Billing: No billing account or payment method set up for this workspace."
2536
+ ] : [`Balance: ${payload.balance ?? "(unknown)"} credits`];
2537
+ printCommandEnvelope({
2538
+ ...payload,
2539
+ render: { sections: [{ title: "billing balance", lines }] }
2540
+ }, { json: options.json });
2541
+ return;
2499
2542
  }
2500
2543
  async function handleUsage(options) {
2501
2544
  const { http } = getAuthedHttpClient();
2502
2545
  const params = new URLSearchParams();
2503
2546
  if (options.limit) params.set("recent_limit", options.limit);
2504
2547
  if (options.offset) params.set("recent_offset", options.offset);
2505
- const suffix = params.size > 0 ? `?${params.toString()}` : "";
2548
+ const suffix = Array.from(params).length > 0 ? `?${params.toString()}` : "";
2506
2549
  const payload = await http.get(`/api/v2/billing/usage${suffix}`);
2507
- if (shouldEmitJson(options.json)) return printJson(payload);
2508
2550
  const usage = payload.usage ?? {};
2509
2551
  const quota = payload.quota ?? {};
2510
2552
  const recent = payload.recent ?? {};
2511
- process.stdout.write(`Balance: ${payload.balance ?? "(unknown)"}
2512
- `);
2513
- process.stdout.write(`Last 30 days spent: ${usage.month_spent_credits ?? "(unknown)"}
2514
- `);
2515
- process.stdout.write(
2516
- `Monthly limit: ${quota.enabled ? quota.monthly_credits_limit ?? "(unknown)" : "off"}
2517
- `
2518
- );
2519
- printRecentUsage(Array.isArray(recent.entries) ? recent.entries : []);
2553
+ const lines = [
2554
+ `Balance: ${payload.balance ?? "(unknown)"}`,
2555
+ `Last 30 days spent: ${usage.month_spent_credits ?? "(unknown)"}`,
2556
+ `Monthly limit: ${quota.enabled ? quota.monthly_credits_limit ?? "(unknown)" : "off"}`,
2557
+ ...recentUsageLines(Array.isArray(recent.entries) ? recent.entries : [])
2558
+ ];
2559
+ printCommandEnvelope({
2560
+ ...payload,
2561
+ render: { sections: [{ title: "billing usage", lines }] }
2562
+ }, { json: options.json });
2520
2563
  }
2521
2564
  async function handleLimit(options) {
2522
2565
  const { http } = getAuthedHttpClient();
2523
2566
  const payload = await http.get("/api/v2/billing/limit");
2524
- if (shouldEmitJson(options.json)) return printJson(payload);
2525
- if (payload.enabled) {
2526
- process.stdout.write(`Monthly limit: ${payload.monthly_credits_limit ?? "(unknown)"}
2527
- `);
2528
- process.stdout.write(`Remaining before cap: ${payload.remaining_credits ?? "(unknown)"}
2529
- `);
2530
- return;
2531
- }
2532
- process.stdout.write("Monthly limit: off\n");
2567
+ const lines = payload.enabled ? [
2568
+ `Monthly limit: ${payload.monthly_credits_limit ?? "(unknown)"}`,
2569
+ `Remaining before cap: ${payload.remaining_credits ?? "(unknown)"}`
2570
+ ] : ["Monthly limit: off"];
2571
+ printCommandEnvelope({
2572
+ ...payload,
2573
+ render: { sections: [{ title: "billing limit", lines }] }
2574
+ }, { json: options.json });
2533
2575
  }
2534
2576
  async function handleSetLimit(credits, options) {
2535
2577
  const { http } = getAuthedHttpClient();
@@ -2537,15 +2579,20 @@ async function handleSetLimit(credits, options) {
2537
2579
  method: "PUT",
2538
2580
  body: { monthly_credits_limit: Number.parseInt(credits, 10) }
2539
2581
  });
2540
- if (shouldEmitJson(options.json)) return printJson(payload);
2541
- process.stdout.write(`Monthly billing limit set to ${credits} credits.
2542
- `);
2582
+ printCommandEnvelope({
2583
+ ...payload,
2584
+ render: {
2585
+ sections: [{ title: "billing limit", lines: [`Monthly billing limit set to ${credits} credits.`] }]
2586
+ }
2587
+ }, { json: options.json });
2543
2588
  }
2544
2589
  async function handleLimitOff(options) {
2545
2590
  const { http } = getAuthedHttpClient();
2546
2591
  const payload = await http.request("/api/v2/billing/limit", { method: "DELETE" });
2547
- if (shouldEmitJson(options.json)) return printJson(payload);
2548
- process.stdout.write("Monthly billing limit is now off.\n");
2592
+ printCommandEnvelope({
2593
+ ...payload,
2594
+ render: { sections: [{ title: "billing limit", lines: ["Monthly billing limit is now off."] }] }
2595
+ }, { json: options.json });
2549
2596
  }
2550
2597
  async function handleHistory(options) {
2551
2598
  const { http } = getAuthedHttpClient();
@@ -2564,13 +2611,20 @@ async function handleHistory(options) {
2564
2611
  };
2565
2612
  });
2566
2613
  const outputPath = await writeCsvRowsFile(`billing-history-${options.time}`, rows);
2567
- if (shouldEmitJson(options.json)) {
2568
- return printJson({ output_path: outputPath, row_count: rows.length, time_window: options.time });
2569
- }
2570
- process.stdout.write(`Billing history written to ${outputPath}
2571
- `);
2572
- process.stdout.write(`${rows.length} row(s) exported.
2573
- `);
2614
+ printCommandEnvelope({
2615
+ output_path: outputPath,
2616
+ row_count: rows.length,
2617
+ time_window: options.time,
2618
+ render: {
2619
+ sections: [
2620
+ {
2621
+ title: "billing history",
2622
+ lines: [`Billing history written to ${outputPath}`, `${rows.length} row(s) exported.`]
2623
+ }
2624
+ ]
2625
+ },
2626
+ local: { output_path: outputPath }
2627
+ }, { json: options.json });
2574
2628
  }
2575
2629
  async function handleLedgerExportAll(options) {
2576
2630
  const { http } = getAuthedHttpClient();
@@ -2602,20 +2656,25 @@ async function handleLedgerExportAll(options) {
2602
2656
  if (nextCursor === cursor) break;
2603
2657
  cursor = nextCursor;
2604
2658
  }
2605
- if (shouldEmitJson(options.json)) {
2606
- return printJson({
2607
- output_path: outputPath,
2608
- row_count: summary.row_count,
2609
- net_delta_credits: summary.net_delta_credits,
2610
- scope: "current_auth_context"
2611
- });
2612
- }
2613
- process.stdout.write(`Billing ledger written to ${outputPath}
2614
- `);
2615
- process.stdout.write(`${summary.row_count} row(s) exported for the current auth context.
2616
- `);
2617
- process.stdout.write(`Net ledger delta: ${summary.net_delta_credits} Deepline Credits
2618
- `);
2659
+ printCommandEnvelope({
2660
+ output_path: outputPath,
2661
+ row_count: summary.row_count,
2662
+ net_delta_credits: summary.net_delta_credits,
2663
+ scope: "current_auth_context",
2664
+ render: {
2665
+ sections: [
2666
+ {
2667
+ title: "billing ledger",
2668
+ lines: [
2669
+ `Billing ledger written to ${outputPath}`,
2670
+ `${summary.row_count} row(s) exported for the current auth context.`,
2671
+ `Net ledger delta: ${summary.net_delta_credits} Deepline Credits`
2672
+ ]
2673
+ }
2674
+ ]
2675
+ },
2676
+ local: { output_path: outputPath }
2677
+ }, { json: options.json });
2619
2678
  }
2620
2679
  async function handleCheckout(options) {
2621
2680
  const { http } = getAuthedHttpClient();
@@ -2624,20 +2683,22 @@ async function handleCheckout(options) {
2624
2683
  ...options.credits ? { credits: Number.parseInt(options.credits, 10) } : {},
2625
2684
  ...options.discountCode ? { discountCode: options.discountCode } : {}
2626
2685
  });
2627
- if (shouldEmitJson(options.json)) return printJson(payload);
2628
2686
  const url = String(payload.url || payload.checkout_url || "");
2629
- if (!options.noOpen && url) openInBrowser(url);
2630
- process.stdout.write(`${url || "Checkout session created."}
2631
- `);
2687
+ if (!options.json && !options.noOpen && url) openInBrowser(url);
2688
+ printCommandEnvelope({
2689
+ ...payload,
2690
+ render: { sections: [{ title: "billing checkout", lines: [url || "Checkout session created."] }] }
2691
+ }, { json: options.json });
2632
2692
  }
2633
2693
  async function handleRedeemCode(code, options) {
2634
2694
  const { http } = getAuthedHttpClient();
2635
2695
  const payload = await http.post("/api/v2/billing/checkout/verify", { code });
2636
- if (shouldEmitJson(options.json)) return printJson(payload);
2637
2696
  const url = String(payload.url || "");
2638
- if (!options.noOpen && url) openInBrowser(url);
2639
- process.stdout.write(`${url || "Code redeemed."}
2640
- `);
2697
+ if (!options.json && !options.noOpen && url) openInBrowser(url);
2698
+ printCommandEnvelope({
2699
+ ...payload,
2700
+ render: { sections: [{ title: "billing code", lines: [url || "Code redeemed."] }] }
2701
+ }, { json: options.json });
2641
2702
  }
2642
2703
  function registerBillingCommands(program) {
2643
2704
  const billing = program.command("billing").description("Inspect balance, usage, limits, and checkout flows.").addHelpText(
@@ -2839,6 +2900,25 @@ function isRecord2(value) {
2839
2900
  function isSerializedDataset(value) {
2840
2901
  return isRecord2(value) && value.kind === "dataset" && typeof value.count === "number" && Array.isArray(value.preview);
2841
2902
  }
2903
+ function pathParts(path) {
2904
+ return path.split(".").map((part) => part.trim()).filter(Boolean);
2905
+ }
2906
+ function valueAtPath(root, path) {
2907
+ let cursor = root;
2908
+ for (const part of pathParts(path)) {
2909
+ if (!isRecord2(cursor)) {
2910
+ return void 0;
2911
+ }
2912
+ cursor = cursor[part];
2913
+ }
2914
+ return cursor;
2915
+ }
2916
+ function totalRowsForDataset(result, datasetPath) {
2917
+ const metadata = isRecord2(result._metadata) ? result._metadata : null;
2918
+ const parentPath = datasetPath.split(".").slice(0, -1).join(".");
2919
+ const parent = parentPath ? valueAtPath({ result }, parentPath) : result;
2920
+ return metadata?.totalRows ?? metadata?.rowCount ?? metadata?.count ?? (isRecord2(parent) ? parent.totalRows ?? parent.rowCount ?? parent.count : void 0) ?? result.totalRows ?? result.rowCount ?? result.count;
2921
+ }
2842
2922
  function rowArray(value) {
2843
2923
  if (!Array.isArray(value)) {
2844
2924
  return null;
@@ -2870,11 +2950,81 @@ function inferColumns(rows) {
2870
2950
  }
2871
2951
  return columns;
2872
2952
  }
2873
- function extractCanonicalRowsInfo(statusOrResult) {
2953
+ function canonicalRowsInfoFromCandidate(input) {
2954
+ const candidate = input;
2955
+ if (isSerializedDataset(candidate.value)) {
2956
+ const rawRows = rowArray(candidate.value.preview) ?? [];
2957
+ const totalRows2 = readNumber(candidate.value.count) ?? rawRows.length;
2958
+ const hasExplicitColumns = Array.isArray(candidate.value.columns) && candidate.value.columns.every((column) => typeof column === "string");
2959
+ const rawColumns = hasExplicitColumns ? candidate.value.columns : inferColumns(rawRows);
2960
+ const { rows: rows2, columns } = sanitizeCsvProjectionInfo({
2961
+ rows: rawRows,
2962
+ columns: rawColumns
2963
+ });
2964
+ return {
2965
+ rows: rows2,
2966
+ totalRows: totalRows2,
2967
+ columns,
2968
+ columnsExplicit: hasExplicitColumns,
2969
+ complete: rows2.length === totalRows2,
2970
+ source: candidate.source,
2971
+ datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
2972
+ tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null
2973
+ };
2974
+ }
2975
+ if (candidate.serializedOnly) {
2976
+ return null;
2977
+ }
2978
+ const rows = rowArray(candidate.value);
2979
+ if (!rows) {
2980
+ return null;
2981
+ }
2982
+ const totalRows = readNumber(candidate.total) ?? rows.length;
2983
+ const sanitized = sanitizeCsvProjectionInfo({
2984
+ rows,
2985
+ columns: inferColumns(rows)
2986
+ });
2987
+ return {
2988
+ rows: sanitized.rows,
2989
+ totalRows,
2990
+ columns: sanitized.columns,
2991
+ complete: rows.length === totalRows,
2992
+ source: candidate.source
2993
+ };
2994
+ }
2995
+ function collectDatasetCandidates(input) {
2996
+ if (input.depth && input.depth > 16) {
2997
+ return;
2998
+ }
2999
+ if (isSerializedDataset(input.value)) {
3000
+ input.output.push({
3001
+ source: input.path,
3002
+ value: input.value,
3003
+ total: input.total
3004
+ });
3005
+ return;
3006
+ }
3007
+ if (!isRecord2(input.value)) {
3008
+ return;
3009
+ }
3010
+ for (const [key, child] of Object.entries(input.value)) {
3011
+ if (key === "preview" || key === "access") {
3012
+ continue;
3013
+ }
3014
+ collectDatasetCandidates({
3015
+ value: child,
3016
+ path: `${input.path}.${key}`,
3017
+ total: totalRowsForDataset(input.value, `${input.path}.${key}`),
3018
+ output: input.output,
3019
+ depth: (input.depth ?? 0) + 1
3020
+ });
3021
+ }
3022
+ }
3023
+ function collectCanonicalRowsInfos(statusOrResult) {
2874
3024
  const root = isRecord2(statusOrResult) ? statusOrResult : null;
2875
3025
  const result = isRecord2(root?.result) ? root.result : root;
2876
3026
  if (!result) {
2877
- return null;
3027
+ return [];
2878
3028
  }
2879
3029
  const metadata = isRecord2(result._metadata) ? result._metadata : null;
2880
3030
  const totalFromMetadata = metadata?.totalRows ?? metadata?.rowCount ?? metadata?.count;
@@ -2894,41 +3044,57 @@ function extractCanonicalRowsInfo(statusOrResult) {
2894
3044
  { source: "result.output.results", value: result.output.results, total: outputTotalFromMetadata ?? result.output.totalRows ?? result.output.rowCount ?? result.output.count }
2895
3045
  );
2896
3046
  }
3047
+ collectDatasetCandidates({
3048
+ value: result,
3049
+ path: "result",
3050
+ total: totalFromMetadata,
3051
+ output: candidates
3052
+ });
3053
+ const seen = /* @__PURE__ */ new Set();
3054
+ const infos = [];
2897
3055
  for (const candidate of candidates) {
2898
- if (isSerializedDataset(candidate.value)) {
2899
- const rawRows = rowArray(candidate.value.preview) ?? [];
2900
- const totalRows2 = readNumber(candidate.value.count) ?? rawRows.length;
2901
- const rawColumns = Array.isArray(candidate.value.columns) && candidate.value.columns.every((column) => typeof column === "string") ? candidate.value.columns : inferColumns(rawRows);
2902
- const { rows: rows2, columns } = sanitizeCsvProjectionInfo({
2903
- rows: rawRows,
2904
- columns: rawColumns
2905
- });
2906
- return {
2907
- rows: rows2,
2908
- totalRows: totalRows2,
2909
- columns,
2910
- complete: rows2.length === totalRows2,
2911
- source: candidate.source
2912
- };
3056
+ if (seen.has(candidate.source)) {
3057
+ continue;
3058
+ }
3059
+ seen.add(candidate.source);
3060
+ const info = canonicalRowsInfoFromCandidate(candidate);
3061
+ if (info) {
3062
+ infos.push(info);
2913
3063
  }
2914
- const rows = rowArray(candidate.value);
2915
- if (!rows) {
3064
+ }
3065
+ return infos;
3066
+ }
3067
+ function collectSerializedDatasetRowsInfos(statusOrResult) {
3068
+ const root = isRecord2(statusOrResult) ? statusOrResult : null;
3069
+ const result = isRecord2(root?.result) ? root.result : root;
3070
+ if (!result) {
3071
+ return [];
3072
+ }
3073
+ const candidates = [];
3074
+ collectDatasetCandidates({
3075
+ value: result,
3076
+ path: "result",
3077
+ output: candidates
3078
+ });
3079
+ const seen = /* @__PURE__ */ new Set();
3080
+ const infos = [];
3081
+ for (const candidate of candidates) {
3082
+ if (seen.has(candidate.source)) {
2916
3083
  continue;
2917
3084
  }
2918
- const totalRows = readNumber(candidate.total) ?? rows.length;
2919
- const sanitized = sanitizeCsvProjectionInfo({
2920
- rows,
2921
- columns: inferColumns(rows)
3085
+ seen.add(candidate.source);
3086
+ const info = canonicalRowsInfoFromCandidate({
3087
+ ...candidate,
3088
+ serializedOnly: true
2922
3089
  });
2923
- return {
2924
- rows: sanitized.rows,
2925
- totalRows,
2926
- columns: sanitized.columns,
2927
- complete: rows.length === totalRows,
2928
- source: candidate.source
2929
- };
3090
+ if (info) {
3091
+ infos.push(info);
3092
+ }
2930
3093
  }
2931
- return null;
3094
+ return infos;
3095
+ }
3096
+ function extractCanonicalRowsInfo(statusOrResult) {
3097
+ return collectCanonicalRowsInfos(statusOrResult)[0] ?? null;
2932
3098
  }
2933
3099
  function percentText(numerator, denominator) {
2934
3100
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
@@ -3255,7 +3421,7 @@ function formatCell(value) {
3255
3421
  const text = typeof value === "object" ? JSON.stringify(value) : String(value);
3256
3422
  return text.length > 80 ? `${text.slice(0, 77)}...` : text;
3257
3423
  }
3258
- function printTable(result) {
3424
+ function tableLines(result) {
3259
3425
  const rows = result.rows.filter(
3260
3426
  (row) => Boolean(row) && typeof row === "object" && !Array.isArray(row)
3261
3427
  );
@@ -3263,17 +3429,17 @@ function printTable(result) {
3263
3429
  const businessColumns = responseColumns.filter((column) => !column.startsWith("_"));
3264
3430
  const columns = businessColumns.length > 0 ? businessColumns : responseColumns;
3265
3431
  const hiddenColumns = responseColumns.filter((column) => !columns.includes(column));
3266
- console.log(
3432
+ const lines = [
3267
3433
  `${result.command} returned ${result.row_count_returned} row(s)` + (result.truncated ? " (truncated)" : "")
3268
- );
3434
+ ];
3269
3435
  if (hiddenColumns.length > 0) {
3270
- console.log(
3436
+ lines.push(
3271
3437
  `Showing ${columns.length}/${responseColumns.length} columns; hidden metadata: ${hiddenColumns.join(", ")}`
3272
3438
  );
3273
- console.log("Use --json or select metadata columns explicitly when you need run ids/errors/stages.");
3439
+ lines.push("Use --json or select metadata columns explicitly when you need run ids/errors/stages.");
3274
3440
  }
3275
3441
  if (rows.length === 0) {
3276
- return;
3442
+ return lines;
3277
3443
  }
3278
3444
  const widths = columns.map(
3279
3445
  (column) => Math.min(
@@ -3284,13 +3450,14 @@ function printTable(result) {
3284
3450
  )
3285
3451
  )
3286
3452
  );
3287
- console.log(columns.map((column, index) => column.padEnd(widths[index])).join(" "));
3288
- console.log(widths.map((width) => "-".repeat(width)).join(" "));
3453
+ lines.push(columns.map((column, index) => column.padEnd(widths[index])).join(" "));
3454
+ lines.push(widths.map((width) => "-".repeat(width)).join(" "));
3289
3455
  for (const row of rows) {
3290
- console.log(
3456
+ lines.push(
3291
3457
  columns.map((column, index) => formatCell(row[column]).padEnd(widths[index])).join(" ")
3292
3458
  );
3293
3459
  }
3460
+ return lines;
3294
3461
  }
3295
3462
  async function handleDbQuery(args) {
3296
3463
  const sqlIndex = args.indexOf("--sql");
@@ -3304,18 +3471,18 @@ async function handleDbQuery(args) {
3304
3471
  const jsonOutput = argsWantJson(args);
3305
3472
  const client = new DeeplineClient();
3306
3473
  const result = await client.queryCustomerDb({ sql, maxRows });
3307
- if (jsonOutput) {
3308
- process.stdout.write(`${JSON.stringify(result)}
3309
- `);
3310
- return 0;
3311
- }
3312
- printTable(result);
3313
- console.error(
3314
- `Tool equivalent: deepline tools execute query_customer_db --payload ${JSON.stringify({
3315
- sql,
3316
- ...maxRows ? { max_rows: maxRows } : {}
3317
- })} --json`
3318
- );
3474
+ const toolCommand = `deepline tools execute query_customer_db --payload ${JSON.stringify({
3475
+ sql,
3476
+ ...maxRows ? { max_rows: maxRows } : {}
3477
+ })} --json`;
3478
+ printCommandEnvelope({
3479
+ ...result,
3480
+ next: { toolEquivalent: toolCommand },
3481
+ render: {
3482
+ sections: [{ title: "customer db query", lines: tableLines(result) }],
3483
+ actions: [{ label: "Tool equivalent", command: toolCommand }]
3484
+ }
3485
+ }, { json: jsonOutput });
3319
3486
  return 0;
3320
3487
  }
3321
3488
  function registerDbCommands(program) {
@@ -3363,11 +3530,12 @@ async function handleFeedback(text, options) {
3363
3530
  ...options.command ? { command: options.command } : {},
3364
3531
  ...options.payload ? { payload: options.payload } : {}
3365
3532
  });
3366
- if (shouldEmitJson(options.json)) {
3367
- printJson(response);
3368
- return;
3369
- }
3370
- process.stdout.write("Feedback submitted. Thank you.\n");
3533
+ printCommandEnvelope({
3534
+ ...response,
3535
+ render: {
3536
+ sections: [{ title: "feedback", lines: ["Feedback submitted. Thank you."] }]
3537
+ }
3538
+ }, { json: options.json });
3371
3539
  }
3372
3540
  function registerFeedbackCommands(program) {
3373
3541
  const feedback = program.command("feedback").description("Submit CLI feedback to Deepline.").addHelpText(
@@ -3399,37 +3567,37 @@ Examples:
3399
3567
  async function fetchOrganizations(http, apiKey) {
3400
3568
  return http.post("/api/v2/auth/cli/organizations", { api_key: apiKey });
3401
3569
  }
3402
- function printOrgList(orgs) {
3403
- for (const [index, org] of orgs.entries()) {
3570
+ function orgListLines(orgs) {
3571
+ return orgs.map((org, index) => {
3404
3572
  const current = org.is_current ? " (current)" : "";
3405
3573
  const role = org.role ? ` [${org.role}]` : "";
3406
- process.stdout.write(` ${index + 1}. ${org.name}${role}${current}
3407
- `);
3408
- }
3574
+ return `${index + 1}. ${org.name}${role}${current}`;
3575
+ });
3409
3576
  }
3410
3577
  async function handleOrgList(options) {
3411
3578
  const config = resolveConfig();
3412
3579
  const http = new HttpClient(config);
3413
3580
  const payload = await fetchOrganizations(http, config.apiKey);
3414
- if (shouldEmitJson(options.json)) {
3415
- printJson(payload);
3416
- return;
3417
- }
3418
- process.stdout.write("Your organizations:\n");
3419
- printOrgList(payload.organizations);
3581
+ printCommandEnvelope({
3582
+ ...payload,
3583
+ render: {
3584
+ sections: [{ title: "Your organizations:", lines: orgListLines(payload.organizations) }]
3585
+ }
3586
+ }, { json: options.json });
3420
3587
  }
3421
3588
  async function handleOrgSwitch(selection, options) {
3422
3589
  const config = resolveConfig();
3423
3590
  const http = new HttpClient(config);
3424
3591
  const payload = await fetchOrganizations(http, config.apiKey);
3425
3592
  if (!selection && !options.orgId) {
3426
- if (shouldEmitJson(options.json)) {
3427
- printJson(payload);
3428
- return;
3429
- }
3430
- process.stdout.write("Your organizations:\n");
3431
- printOrgList(payload.organizations);
3432
- process.stdout.write("\nRun: deepline org switch <number>\n");
3593
+ printCommandEnvelope({
3594
+ ...payload,
3595
+ next: { switch: "deepline org switch <number>" },
3596
+ render: {
3597
+ sections: [{ title: "Your organizations:", lines: orgListLines(payload.organizations) }],
3598
+ actions: [{ label: "Run", command: "deepline org switch <number>" }]
3599
+ }
3600
+ }, { json: options.json });
3433
3601
  return;
3434
3602
  }
3435
3603
  let target = payload.organizations.find((org) => org.org_id === options.orgId);
@@ -3445,12 +3613,12 @@ async function handleOrgSwitch(selection, options) {
3445
3613
  throw new Error("Could not resolve the selected organization.");
3446
3614
  }
3447
3615
  if (target.is_current) {
3448
- if (shouldEmitJson(options.json)) {
3449
- printJson({ ok: true, unchanged: true, organization: target });
3450
- return;
3451
- }
3452
- process.stdout.write(`Already on ${target.name}.
3453
- `);
3616
+ printCommandEnvelope({
3617
+ ok: true,
3618
+ unchanged: true,
3619
+ organization: target,
3620
+ render: { sections: [{ title: "org switch", lines: [`Already on ${target.name}.`] }] }
3621
+ }, { json: options.json });
3454
3622
  return;
3455
3623
  }
3456
3624
  const switched = await http.post(
@@ -3462,14 +3630,24 @@ async function handleOrgSwitch(selection, options) {
3462
3630
  DEEPLINE_ACTIVE_ORG_ID: switched.org_id,
3463
3631
  DEEPLINE_ACTIVE_ORG_NAME: switched.org_name
3464
3632
  });
3465
- if (shouldEmitJson(options.json)) {
3466
- printJson({ ok: true, host_env_path: hostEnvFilePath(config.baseUrl), ...switched });
3467
- return;
3468
- }
3469
- process.stdout.write(`Switched to ${switched.org_name}.
3470
- `);
3471
- process.stdout.write(`Saved host auth in ${hostEnvFilePath(config.baseUrl)}
3472
- `);
3633
+ const { api_key: _apiKey, ...publicSwitched } = switched;
3634
+ printCommandEnvelope({
3635
+ ok: true,
3636
+ host_env_path: hostEnvFilePath(config.baseUrl),
3637
+ ...publicSwitched,
3638
+ api_key_saved: true,
3639
+ render: {
3640
+ sections: [
3641
+ {
3642
+ title: "org switch",
3643
+ lines: [
3644
+ `Switched to ${switched.org_name}.`,
3645
+ `Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
3646
+ ]
3647
+ }
3648
+ ]
3649
+ }
3650
+ }, { json: options.json });
3473
3651
  }
3474
3652
  function registerOrgCommands(program) {
3475
3653
  const org = program.command("org").description("List and switch organizations.").addHelpText(
@@ -5147,6 +5325,9 @@ function traceCliSync(phase, fields, run) {
5147
5325
  throw error;
5148
5326
  }
5149
5327
  }
5328
+ function sleep4(ms) {
5329
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
5330
+ }
5150
5331
  function parseReferencedPlayTarget(target) {
5151
5332
  const trimmed = target.trim();
5152
5333
  const slashIndex = trimmed.indexOf("/");
@@ -5371,15 +5552,6 @@ function fileInputBindingsFromStaticPipeline(staticPipeline) {
5371
5552
  );
5372
5553
  return inputField ? [{ inputPath: inputField }] : [];
5373
5554
  }
5374
- function applyCsvShortcutInput(input) {
5375
- const csvValue = getDottedInputValue(input.runtimeInput, "csv");
5376
- if (csvValue == null || csvValue === "") return;
5377
- const candidate = input.bindings.find((binding) => binding.inputPath !== "csv")?.inputPath ?? input.fallbackInputPath ?? null;
5378
- if (!candidate || candidate === "csv") return;
5379
- const existing = getDottedInputValue(input.runtimeInput, candidate);
5380
- if (existing != null && existing !== "") return;
5381
- setDottedInputValue(input.runtimeInput, candidate, csvValue);
5382
- }
5383
5555
  function isLocalFilePathValue(value) {
5384
5556
  if (typeof value !== "string" || !value.trim()) return false;
5385
5557
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
@@ -6047,25 +6219,38 @@ function formatReturnValue(result) {
6047
6219
  }
6048
6220
  return lines;
6049
6221
  }
6050
- function buildOutputSummary(rowsInfo, runId, exportedPath) {
6051
- if (!rowsInfo) {
6052
- return exportedPath ? { csv_path: exportedPath } : null;
6222
+ function isDatasetHandle(value) {
6223
+ return Boolean(
6224
+ value && typeof value === "object" && !Array.isArray(value) && value.kind === "dataset"
6225
+ );
6226
+ }
6227
+ function collectDatasetHandleLines(value, path = "result") {
6228
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
6229
+ return [];
6053
6230
  }
6054
- const isPartial = !rowsInfo.complete;
6055
- return {
6056
- kind: "rows",
6057
- rowCount: rowsInfo.totalRows,
6058
- previewRowCount: rowsInfo.rows.length,
6059
- ...isPartial ? {
6060
- isPartial: true,
6061
- previewCount: rowsInfo.rows.length,
6062
- totalCount: rowsInfo.totalRows
6063
- } : { isPartial: false },
6064
- complete: rowsInfo.complete,
6065
- columns: rowsInfo.columns,
6066
- source: rowsInfo.source,
6067
- ...exportedPath ? { csv_path: exportedPath } : {}
6068
- };
6231
+ if (isDatasetHandle(value)) {
6232
+ const record = value;
6233
+ const count = typeof record.count === "number" ? record.count : typeof record.rowCount === "number" ? record.rowCount : null;
6234
+ const preview = Array.isArray(record.preview) ? record.preview : [];
6235
+ const lines2 = [
6236
+ ` dataset ${typeof record.path === "string" ? record.path : path}: rows=${count === null ? "-" : formatInteger(count)} preview=${formatInteger(preview.length)}`
6237
+ ];
6238
+ if (typeof record.queryDatasetCommand === "string") {
6239
+ lines2.push(` query dataset: ${record.queryDatasetCommand}`);
6240
+ }
6241
+ if (typeof record.slowExportAsCsvCommand === "string") {
6242
+ lines2.push(` export CSV: ${record.slowExportAsCsvCommand}`);
6243
+ }
6244
+ return lines2;
6245
+ }
6246
+ const lines = [];
6247
+ for (const [key, child] of Object.entries(value)) {
6248
+ if (key === "preview" || key === "access") {
6249
+ continue;
6250
+ }
6251
+ lines.push(...collectDatasetHandleLines(child, `${path}.${key}`));
6252
+ }
6253
+ return lines;
6069
6254
  }
6070
6255
  function buildRunWarnings(status, rowsInfo) {
6071
6256
  if (status.status === "completed" && rowsInfo?.totalRows === 0) {
@@ -6078,19 +6263,12 @@ function buildRunWarnings(status, rowsInfo) {
6078
6263
  }
6079
6264
  return [];
6080
6265
  }
6081
- function buildRunNextCommands(runId, rowsInfo) {
6082
- const commands = {
6266
+ function buildRunNextCommands(runId) {
6267
+ return {
6083
6268
  get: `deepline runs get ${runId} --json`,
6084
6269
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
6085
6270
  logs: `deepline runs logs ${runId} --out run.log --json`
6086
6271
  };
6087
- if (!rowsInfo || rowsInfo.complete) {
6088
- commands.exportCsv = buildRunExportCommand(runId);
6089
- }
6090
- return commands;
6091
- }
6092
- function buildRunExportCommand(runId) {
6093
- return `deepline runs export ${runId} --out output.csv`;
6094
6272
  }
6095
6273
  var RUN_LOG_PREVIEW_LIMIT = 20;
6096
6274
  function getRecordField(value, key) {
@@ -6145,31 +6323,6 @@ function normalizeProgressForEnvelope(status, rowsInfo) {
6145
6323
  wait: status.wait ?? null
6146
6324
  };
6147
6325
  }
6148
- function normalizeOutputsForEnvelope(rowsInfo, runId, exportedPath) {
6149
- if (!rowsInfo) {
6150
- return exportedPath ? [{ name: "output", kind: "file", path: exportedPath }] : [];
6151
- }
6152
- const isPartial = !rowsInfo.complete;
6153
- return [
6154
- {
6155
- name: "rows",
6156
- kind: "dataset",
6157
- rowCount: rowsInfo.totalRows,
6158
- columns: rowsInfo.columns,
6159
- preview: rowsInfo.rows.slice(0, 5),
6160
- previewRowCount: Math.min(rowsInfo.rows.length, 5),
6161
- previewLimit: 5,
6162
- ...isPartial ? {
6163
- isPartial: true,
6164
- previewCount: rowsInfo.rows.length,
6165
- totalCount: rowsInfo.totalRows
6166
- } : { isPartial: false },
6167
- complete: rowsInfo.complete,
6168
- source: rowsInfo.source,
6169
- ...exportedPath ? { csv_path: exportedPath } : {}
6170
- }
6171
- ];
6172
- }
6173
6326
  function normalizeStepsForEnvelope(status) {
6174
6327
  const directSteps = getRecordField(status, "steps");
6175
6328
  if (Array.isArray(directSteps)) {
@@ -6231,7 +6384,7 @@ function stripProviderSpendFromBilling(value) {
6231
6384
  }
6232
6385
  return next;
6233
6386
  }
6234
- function compactPlayStatus(status, options) {
6387
+ function compactPlayStatus(status) {
6235
6388
  const rowsInfo = extractCanonicalRowsInfo(status);
6236
6389
  const result = status && typeof status === "object" ? status.result : null;
6237
6390
  const warnings = buildRunWarnings(status, rowsInfo);
@@ -6254,23 +6407,16 @@ function compactPlayStatus(status, options) {
6254
6407
  status: status.status,
6255
6408
  run: normalizeRunStatusForEnvelope(status),
6256
6409
  progress: normalizeProgressForEnvelope(status, rowsInfo),
6257
- outputs: normalizeOutputsForEnvelope(
6258
- rowsInfo,
6259
- status.runId,
6260
- options?.exportedPath
6261
- ),
6262
6410
  steps: normalizeStepsForEnvelope(status),
6263
6411
  errors: normalizeErrorsForEnvelope(status, error),
6264
6412
  logs: normalizeLogsForEnvelope(status),
6265
6413
  ...error ? { error } : {},
6266
6414
  ...warnings.length > 0 ? { warnings } : {},
6267
- output: buildOutputSummary(rowsInfo, status.runId, options?.exportedPath) ?? result ?? null,
6268
6415
  ...result !== void 0 ? { result } : {},
6269
6416
  ...status.resultView ? { resultView: status.resultView } : {},
6270
6417
  ...datasetStats ? { dataset_stats: datasetStats } : {},
6271
- ...rowsInfo ? { previewRows: rowsInfo.rows.slice(0, 5) } : {},
6272
6418
  ...billing ? { billing } : {},
6273
- next: buildRunNextCommands(status.runId, rowsInfo)
6419
+ next: buildRunNextCommands(status.runId)
6274
6420
  };
6275
6421
  }
6276
6422
  function enrichPlayStatusWithDatasetStats(status) {
@@ -6308,12 +6454,20 @@ function formatDatasetStatsLines(datasetStats) {
6308
6454
  }
6309
6455
  function writePlayResult(status, jsonOutput, options) {
6310
6456
  if (jsonOutput) {
6311
- process.stdout.write(
6312
- `${JSON.stringify(
6313
- options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status, options)
6314
- )}
6315
- `
6316
- );
6457
+ const payload2 = options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status);
6458
+ printCommandEnvelope({
6459
+ ...payload2,
6460
+ render: {
6461
+ sections: [
6462
+ {
6463
+ title: "run result",
6464
+ lines: [
6465
+ `${status.status ?? "running"} ${status.runId ?? "unknown"}`
6466
+ ]
6467
+ }
6468
+ ]
6469
+ }
6470
+ }, { json: true });
6317
6471
  return;
6318
6472
  }
6319
6473
  const result = status.result;
@@ -6330,23 +6484,6 @@ function writePlayResult(status, jsonOutput, options) {
6330
6484
  rowsInfo.columns,
6331
6485
  extractDatasetExecutionStats(status)
6332
6486
  ) : null;
6333
- const outputSummary = buildOutputSummary(
6334
- rowsInfo,
6335
- runId,
6336
- options?.exportedPath
6337
- );
6338
- if (outputSummary) {
6339
- const columns = Array.isArray(outputSummary.columns) ? outputSummary.columns.length : 0;
6340
- const path = typeof outputSummary.csv_path === "string" ? ` file=${outputSummary.csv_path}` : "";
6341
- lines.push(
6342
- ` output: rows=${formatInteger(outputSummary.rowCount)} columns=${formatInteger(columns)}${path}`
6343
- );
6344
- if (outputSummary.isPartial === true) {
6345
- lines.push(
6346
- ` partial output: showing ${formatInteger(outputSummary.previewCount)} preview row(s) of ${formatInteger(outputSummary.totalCount)}`
6347
- );
6348
- }
6349
- }
6350
6487
  for (const warning of warnings) {
6351
6488
  lines.push(` warning: ${warning}`);
6352
6489
  }
@@ -6358,29 +6495,213 @@ function writePlayResult(status, jsonOutput, options) {
6358
6495
  const renderedServerView = renderServerResultView(status.resultView);
6359
6496
  if (result) {
6360
6497
  lines.push(...formatReturnValue(result));
6498
+ lines.push(...collectDatasetHandleLines(result));
6361
6499
  }
6362
6500
  if (renderedServerView.lines.length > 0) {
6363
6501
  lines.push(...renderedServerView.lines);
6364
6502
  }
6365
6503
  lines.push(...renderedServerView.actions);
6366
- console.log(lines.join("\n"));
6504
+ const payload = options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status);
6505
+ printCommandEnvelope({
6506
+ ...payload,
6507
+ render: {
6508
+ sections: [{ title: "run result", lines }]
6509
+ }
6510
+ }, { json: jsonOutput, text: `${lines.join("\n")}
6511
+ ` });
6512
+ }
6513
+ var RUN_EXPORT_PAGE_SIZE = 5e3;
6514
+ function shellSingleQuote(value) {
6515
+ return `'${value.replace(/'/g, `'\\''`)}'`;
6516
+ }
6517
+ function runExportRetryCommand(runId, outPath, datasetPath) {
6518
+ return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote((0, import_node_path9.resolve)(outPath))}`;
6519
+ }
6520
+ function extractRunPlayName(status) {
6521
+ const run = status.run;
6522
+ const candidates = [
6523
+ status.playName,
6524
+ status.name,
6525
+ getRecordField(run, "playName"),
6526
+ getRecordField(run, "name")
6527
+ ];
6528
+ for (const candidate of candidates) {
6529
+ if (typeof candidate === "string" && candidate.trim()) {
6530
+ return candidate.trim();
6531
+ }
6532
+ }
6533
+ return null;
6534
+ }
6535
+ function exportableSheetRow(row) {
6536
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
6537
+ return null;
6538
+ }
6539
+ const record = row;
6540
+ const data = record.data;
6541
+ if (data && typeof data === "object" && !Array.isArray(data)) {
6542
+ return data;
6543
+ }
6544
+ const fallback = { ...record };
6545
+ for (const key of [
6546
+ "key",
6547
+ "status",
6548
+ "cellMeta",
6549
+ "inputIndex",
6550
+ "runId",
6551
+ "error",
6552
+ "stage",
6553
+ "provider",
6554
+ "seq",
6555
+ "createdAt",
6556
+ "updatedAt"
6557
+ ]) {
6558
+ delete fallback[key];
6559
+ }
6560
+ return fallback;
6561
+ }
6562
+ function mergeExportColumns(preferredColumns, rows) {
6563
+ const columns = [];
6564
+ const seen = /* @__PURE__ */ new Set();
6565
+ for (const column of preferredColumns) {
6566
+ if (!column || seen.has(column)) {
6567
+ continue;
6568
+ }
6569
+ seen.add(column);
6570
+ columns.push(column);
6571
+ }
6572
+ for (const row of rows) {
6573
+ for (const column of Object.keys(row)) {
6574
+ if (seen.has(column)) {
6575
+ continue;
6576
+ }
6577
+ seen.add(column);
6578
+ columns.push(column);
6579
+ }
6580
+ }
6581
+ return columns;
6367
6582
  }
6368
- function exportPlayStatusRows(status, outPath) {
6583
+ async function fetchBackingDatasetRows(input) {
6584
+ const playName = extractRunPlayName(input.status);
6585
+ const tableNamespace = input.rowsInfo.tableNamespace?.trim();
6586
+ if (!playName || !tableNamespace) {
6587
+ return null;
6588
+ }
6589
+ const sheetRows = [];
6590
+ let offset = 0;
6591
+ let expectedTotal = input.rowsInfo.totalRows;
6592
+ while (true) {
6593
+ const page = await input.client.runs.exportDatasetRows({
6594
+ playName,
6595
+ tableNamespace,
6596
+ runId: input.status.runId,
6597
+ limit: RUN_EXPORT_PAGE_SIZE,
6598
+ offset
6599
+ });
6600
+ sheetRows.push(...page.rows);
6601
+ const summaryTotal = page.summary?.stats?.total;
6602
+ if (typeof summaryTotal === "number" && Number.isFinite(summaryTotal)) {
6603
+ expectedTotal = Math.max(expectedTotal, Math.trunc(summaryTotal));
6604
+ }
6605
+ if (page.rows.length < RUN_EXPORT_PAGE_SIZE || sheetRows.length >= expectedTotal) {
6606
+ break;
6607
+ }
6608
+ offset += page.rows.length;
6609
+ }
6610
+ const rows = sheetRows.map(exportableSheetRow).filter((row) => Boolean(row));
6611
+ if (rows.length < input.rowsInfo.totalRows) {
6612
+ return null;
6613
+ }
6614
+ const columns = mergeExportColumns(
6615
+ input.rowsInfo.columnsExplicit ? input.rowsInfo.columns : [],
6616
+ rows
6617
+ );
6618
+ return {
6619
+ ...input.rowsInfo,
6620
+ rows,
6621
+ columns,
6622
+ totalRows: rows.length,
6623
+ complete: true,
6624
+ source: `${input.rowsInfo.source ?? "result.rows"} -> /api/v2/plays/${playName}/sheet?tableNamespace=${tableNamespace}`
6625
+ };
6626
+ }
6627
+ async function exportPlayStatusRows(client, status, outPath, options = {}) {
6369
6628
  if (!outPath) {
6370
6629
  return null;
6371
6630
  }
6372
- const rowsInfo = extractCanonicalRowsInfo(status);
6631
+ const availableRows = collectSerializedDatasetRowsInfos(status);
6632
+ let rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
6633
+ if (!rowsInfo && options.datasetPath) {
6634
+ const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
6635
+ throw new DeeplineError(
6636
+ `Run ${status.runId} did not return a dataset at ${options.datasetPath}.` + (available.length > 0 ? ` Available datasets: ${available.join(", ")}.` : ""),
6637
+ void 0,
6638
+ "RUN_EXPORT_DATASET_NOT_FOUND",
6639
+ { runId: status.runId, dataset: options.datasetPath, available }
6640
+ );
6641
+ }
6642
+ if (!options.datasetPath && availableRows.length > 1) {
6643
+ const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
6644
+ throw new DeeplineError(
6645
+ `Run ${status.runId} returned multiple datasets. Choose one with --dataset <path>: ${available.join(", ")}.`,
6646
+ void 0,
6647
+ "RUN_EXPORT_DATASET_REQUIRED",
6648
+ { runId: status.runId, available }
6649
+ );
6650
+ }
6373
6651
  if (!rowsInfo) {
6374
6652
  throw new DeeplineError(
6375
6653
  `Run ${status.runId} did not expose a row-shaped final output to export.`
6376
6654
  );
6377
6655
  }
6656
+ const attempts = Math.max(1, Math.trunc(options.attempts ?? 1));
6657
+ const retryDelayMs = Math.max(0, Math.trunc(options.retryDelayMs ?? 0));
6658
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
6659
+ let fetchedRowsInfo = null;
6660
+ try {
6661
+ fetchedRowsInfo = await fetchBackingDatasetRows({
6662
+ client,
6663
+ status,
6664
+ rowsInfo
6665
+ });
6666
+ } catch (error) {
6667
+ if (!rowsInfo.complete) {
6668
+ throw error;
6669
+ }
6670
+ }
6671
+ if (fetchedRowsInfo?.complete) {
6672
+ return {
6673
+ path: writeCanonicalRowsCsv(fetchedRowsInfo, outPath),
6674
+ rowsInfo: fetchedRowsInfo
6675
+ };
6676
+ }
6677
+ if (rowsInfo.complete) {
6678
+ return { path: writeCanonicalRowsCsv(rowsInfo, outPath), rowsInfo };
6679
+ }
6680
+ if (attempt < attempts && retryDelayMs > 0) {
6681
+ await sleep4(retryDelayMs);
6682
+ }
6683
+ }
6378
6684
  if (!rowsInfo.complete) {
6685
+ const retryCommand = runExportRetryCommand(
6686
+ status.runId,
6687
+ outPath,
6688
+ options.datasetPath ?? rowsInfo.source
6689
+ );
6379
6690
  throw new DeeplineError(
6380
- `Run output only includes ${rowsInfo.rows.length} preview row(s) of ${rowsInfo.totalRows}; full dataset export is not available from this status response yet.`
6691
+ `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}`,
6692
+ void 0,
6693
+ "RUN_EXPORT_NOT_READY",
6694
+ {
6695
+ runId: status.runId,
6696
+ previewRowCount: rowsInfo.rows.length,
6697
+ totalRows: rowsInfo.totalRows,
6698
+ tableNamespace: rowsInfo.tableNamespace ?? null,
6699
+ dataset: options.datasetPath ?? rowsInfo.source,
6700
+ retry_command: retryCommand
6701
+ }
6381
6702
  );
6382
6703
  }
6383
- return writeCanonicalRowsCsv(rowsInfo, outPath);
6704
+ return { path: writeCanonicalRowsCsv(rowsInfo, outPath), rowsInfo };
6384
6705
  }
6385
6706
  function renderServerResultView(value) {
6386
6707
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -6454,8 +6775,10 @@ function writeStartedPlayRun(input) {
6454
6775
  dashboardUrl: input.dashboardUrl
6455
6776
  };
6456
6777
  if (input.jsonOutput) {
6457
- process.stdout.write(`${JSON.stringify(payload)}
6458
- `);
6778
+ printCommandEnvelope({
6779
+ ...payload,
6780
+ render: { sections: [{ title: "play run", lines: [`Started ${input.playName}`, `run id: ${input.runId}`] }] }
6781
+ }, { json: true });
6459
6782
  return;
6460
6783
  }
6461
6784
  const lines = [
@@ -6474,10 +6797,14 @@ function writeStartedPlayRun(input) {
6474
6797
  input.progress.writeLine(output, process.stdout);
6475
6798
  return;
6476
6799
  }
6477
- console.log(output);
6800
+ printCommandEnvelope({
6801
+ ...payload,
6802
+ render: { sections: [{ title: "play run", lines }] }
6803
+ }, { json: false, text: `${output}
6804
+ ` });
6478
6805
  }
6479
6806
  function parsePlayRunOptions(args) {
6480
- 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.";
6807
+ 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.";
6481
6808
  let filePath = null;
6482
6809
  let playName = null;
6483
6810
  let input = null;
@@ -6488,7 +6815,6 @@ function parsePlayRunOptions(args) {
6488
6815
  const emitLogs = !jsonOutput || args.includes("--logs");
6489
6816
  const force = args.includes("--force");
6490
6817
  const noOpen = args.includes("--no-open");
6491
- let outPath = null;
6492
6818
  let waitTimeoutMs = null;
6493
6819
  for (let index = 0; index < args.length; index += 1) {
6494
6820
  const arg = args[index];
@@ -6516,9 +6842,10 @@ function parsePlayRunOptions(args) {
6516
6842
  revisionSelector = "latest";
6517
6843
  continue;
6518
6844
  }
6519
- if (arg === "--out" && args[index + 1]) {
6520
- outPath = (0, import_node_path9.resolve)(args[++index]);
6521
- continue;
6845
+ if (arg === "--out" || arg.startsWith("--out=")) {
6846
+ throw new Error(
6847
+ "--out is not a plays run flag. Run the play first, then export rows with: deepline runs export <run-id> --out output.csv"
6848
+ );
6522
6849
  }
6523
6850
  if (arg === "--poll-interval-ms" || arg === "--interval-ms") {
6524
6851
  throw new Error(
@@ -6547,6 +6874,11 @@ function parsePlayRunOptions(args) {
6547
6874
  }
6548
6875
  continue;
6549
6876
  }
6877
+ if (arg === "--csv" || arg.startsWith("--csv=")) {
6878
+ throw new Error(
6879
+ `--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`
6880
+ );
6881
+ }
6550
6882
  if (arg.startsWith("--")) {
6551
6883
  const { path, value } = parseInputFieldFlag(arg, args[index + 1]);
6552
6884
  input ??= {};
@@ -6583,11 +6915,6 @@ function parsePlayRunOptions(args) {
6583
6915
  "--live, --latest, and --revision-id only apply to named plays."
6584
6916
  );
6585
6917
  }
6586
- if (outPath && !watch) {
6587
- throw new Error(
6588
- "--out requires --watch so the CLI can export the completed run output."
6589
- );
6590
- }
6591
6918
  return {
6592
6919
  target: filePath ? { kind: "file", path: filePath } : { kind: "name", name: playName },
6593
6920
  input,
@@ -6598,7 +6925,6 @@ function parsePlayRunOptions(args) {
6598
6925
  jsonOutput,
6599
6926
  waitTimeoutMs,
6600
6927
  force,
6601
- outPath,
6602
6928
  noOpen
6603
6929
  };
6604
6930
  }
@@ -6732,11 +7058,6 @@ async function handleFileBackedRun(options) {
6732
7058
  const fileInputBindings = fileInputBindingsFromStaticPipeline(
6733
7059
  compilerManifest.staticPipeline
6734
7060
  );
6735
- applyCsvShortcutInput({
6736
- runtimeInput,
6737
- bindings: fileInputBindings,
6738
- fallbackInputPath: "file"
6739
- });
6740
7061
  const stagedFileInputs = await traceCliSpan(
6741
7062
  "cli.play_stage_inputs",
6742
7063
  {
@@ -6778,11 +7099,6 @@ async function handleFileBackedRun(options) {
6778
7099
  progress
6779
7100
  })
6780
7101
  );
6781
- const exportedPath = traceCliSync(
6782
- "cli.play_export_rows",
6783
- { targetKind: "file", playName },
6784
- () => exportPlayStatusRows(finalStatus, options.outPath)
6785
- );
6786
7102
  if (finalStatus.status === "completed") {
6787
7103
  progress.complete();
6788
7104
  } else {
@@ -6791,7 +7107,7 @@ async function handleFileBackedRun(options) {
6791
7107
  traceCliSync(
6792
7108
  "cli.play_write_result",
6793
7109
  { targetKind: "file", playName },
6794
- () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath })
7110
+ () => writePlayResult(finalStatus, options.jsonOutput)
6795
7111
  );
6796
7112
  return finalStatus.status === "completed" ? 0 : 1;
6797
7113
  }
@@ -6881,10 +7197,6 @@ async function handleNamedRun(options) {
6881
7197
  ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
6882
7198
  ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
6883
7199
  ] : [];
6884
- applyCsvShortcutInput({
6885
- runtimeInput,
6886
- bindings: fileInputBindings
6887
- });
6888
7200
  const stagedFileInputs = await traceCliSpan(
6889
7201
  "cli.play_stage_inputs",
6890
7202
  {
@@ -6923,11 +7235,6 @@ async function handleNamedRun(options) {
6923
7235
  progress
6924
7236
  })
6925
7237
  );
6926
- const exportedPath = traceCliSync(
6927
- "cli.play_export_rows",
6928
- { targetKind: "name", playName },
6929
- () => exportPlayStatusRows(finalStatus, options.outPath)
6930
- );
6931
7238
  if (finalStatus.status === "completed") {
6932
7239
  progress.complete();
6933
7240
  } else {
@@ -6936,7 +7243,7 @@ async function handleNamedRun(options) {
6936
7243
  traceCliSync(
6937
7244
  "cli.play_write_result",
6938
7245
  { targetKind: "name", playName },
6939
- () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath })
7246
+ () => writePlayResult(finalStatus, options.jsonOutput)
6940
7247
  );
6941
7248
  return finalStatus.status === "completed" ? 0 : 1;
6942
7249
  }
@@ -7005,7 +7312,7 @@ function parseRunIdPositional(args, usage) {
7005
7312
  }
7006
7313
  continue;
7007
7314
  }
7008
- if ((arg === "--out" || arg === "--reason") && args[index + 1]) {
7315
+ if ((arg === "--out" || arg === "--reason" || arg === "--dataset") && args[index + 1]) {
7009
7316
  index += 1;
7010
7317
  continue;
7011
7318
  }
@@ -7067,26 +7374,15 @@ async function handleRunsList(args) {
7067
7374
  executionTime: run.executionTime,
7068
7375
  playName: run.memo?.playName ?? playName
7069
7376
  }));
7070
- if (argsWantJson(args)) {
7071
- process.stdout.write(
7072
- `${JSON.stringify({
7073
- runs,
7074
- count: runs.length,
7075
- next: {
7076
- get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
7077
- }
7078
- })}
7079
- `
7080
- );
7081
- } else {
7082
- if (runs.length === 0) {
7083
- console.log(`No runs found for ${playName}.`);
7084
- } else {
7085
- for (const run of runs) {
7086
- console.log(`${run.runId} ${run.status} ${formatTimestamp(run.startedAt)}`);
7087
- }
7088
- }
7089
- }
7377
+ const lines = runs.length === 0 ? [`No runs found for ${playName}.`] : runs.map((run) => `${run.runId} ${run.status} ${formatTimestamp(run.startedAt)}`);
7378
+ printCommandEnvelope({
7379
+ runs,
7380
+ count: runs.length,
7381
+ next: {
7382
+ get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
7383
+ },
7384
+ render: { sections: [{ title: "runs", lines }] }
7385
+ }, { json: argsWantJson(args) });
7090
7386
  return 0;
7091
7387
  }
7092
7388
  async function handleRunTail(args) {
@@ -7140,41 +7436,30 @@ async function handleRunLogs(args) {
7140
7436
  const logs = status.progress?.logs ?? [];
7141
7437
  if (outPath) {
7142
7438
  (0, import_node_fs7.writeFileSync)(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
7143
- if (argsWantJson(args)) {
7144
- process.stdout.write(
7145
- `${JSON.stringify({
7146
- runId: status.runId,
7147
- log_path: outPath,
7148
- lineCount: logs.length
7149
- })}
7150
- `
7151
- );
7152
- } else {
7153
- console.log(`Wrote ${logs.length} log lines to ${outPath}`);
7154
- }
7439
+ printCommandEnvelope({
7440
+ runId: status.runId,
7441
+ log_path: outPath,
7442
+ lineCount: logs.length,
7443
+ local: { log_path: outPath },
7444
+ render: { sections: [{ title: "run logs", lines: [`Wrote ${logs.length} log lines to ${outPath}`] }] }
7445
+ }, { json: argsWantJson(args) });
7155
7446
  return 0;
7156
7447
  }
7157
7448
  const entries = logs.slice(Math.max(0, logs.length - limit));
7158
- if (argsWantJson(args)) {
7159
- process.stdout.write(
7160
- `${JSON.stringify({
7161
- runId: status.runId,
7162
- totalCount: logs.length,
7163
- returnedCount: entries.length,
7164
- firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
7165
- lastSequence: logs.length === 0 ? null : logs.length,
7166
- truncated: logs.length > entries.length,
7167
- hasMore: logs.length > entries.length,
7168
- entries,
7169
- next: {
7170
- export: `deepline runs logs ${status.runId} --out run.log --json`
7171
- }
7172
- })}
7173
- `
7174
- );
7175
- } else {
7176
- process.stdout.write(`${entries.join("\n")}${entries.length > 0 ? "\n" : ""}`);
7177
- }
7449
+ printCommandEnvelope({
7450
+ runId: status.runId,
7451
+ totalCount: logs.length,
7452
+ returnedCount: entries.length,
7453
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
7454
+ lastSequence: logs.length === 0 ? null : logs.length,
7455
+ truncated: logs.length > entries.length,
7456
+ hasMore: logs.length > entries.length,
7457
+ entries,
7458
+ next: {
7459
+ export: `deepline runs logs ${status.runId} --out run.log --json`
7460
+ },
7461
+ render: { sections: [{ title: "run logs", lines: entries }] }
7462
+ }, { json: argsWantJson(args), text: `${entries.join("\n")}${entries.length > 0 ? "\n" : ""}` });
7178
7463
  return 0;
7179
7464
  }
7180
7465
  async function handleRunStop(args) {
@@ -7195,19 +7480,18 @@ async function handleRunStop(args) {
7195
7480
  }
7196
7481
  const client = new DeeplineClient();
7197
7482
  const result = await client.runs.stop(runId, { reason });
7198
- if (argsWantJson(args)) {
7199
- process.stdout.write(`${JSON.stringify(result)}
7200
- `);
7201
- } else {
7202
- console.log(`Stopped ${result.runId}`);
7203
- if (result.hitlCancelledCount > 0) {
7204
- console.log(` cancelled HITL waits: ${result.hitlCancelledCount}`);
7205
- }
7206
- }
7483
+ const lines = [
7484
+ `Stopped ${result.runId}`,
7485
+ ...result.hitlCancelledCount > 0 ? [`cancelled HITL waits: ${result.hitlCancelledCount}`] : []
7486
+ ];
7487
+ printCommandEnvelope({
7488
+ ...result,
7489
+ render: { sections: [{ title: "run stop", lines }] }
7490
+ }, { json: argsWantJson(args) });
7207
7491
  return 0;
7208
7492
  }
7209
7493
  async function handleRunExport(args) {
7210
- const usage = "Usage: deepline runs export <run-id> --out output.csv [--json]";
7494
+ const usage = "Usage: deepline runs export <run-id> [--dataset result.rows] --out output.csv [--json]";
7211
7495
  let runId;
7212
7496
  try {
7213
7497
  runId = parseRunIdPositional(args, usage);
@@ -7216,10 +7500,15 @@ async function handleRunExport(args) {
7216
7500
  return 1;
7217
7501
  }
7218
7502
  let outPath = null;
7503
+ let datasetPath = null;
7219
7504
  for (let index = 0; index < args.length; index += 1) {
7220
7505
  const arg = args[index];
7221
7506
  if (arg === "--out" && args[index + 1]) {
7222
7507
  outPath = (0, import_node_path9.resolve)(args[++index]);
7508
+ continue;
7509
+ }
7510
+ if (arg === "--dataset" && args[index + 1]) {
7511
+ datasetPath = args[++index];
7223
7512
  }
7224
7513
  }
7225
7514
  if (!outPath) {
@@ -7228,21 +7517,18 @@ async function handleRunExport(args) {
7228
7517
  }
7229
7518
  const client = new DeeplineClient();
7230
7519
  const status = await client.getPlayStatus(runId);
7231
- const exportedPath = exportPlayStatusRows(status, outPath);
7232
- if (argsWantJson(args)) {
7233
- const rowsInfo = extractCanonicalRowsInfo(status);
7234
- process.stdout.write(
7235
- `${JSON.stringify({
7236
- runId: status.runId,
7237
- csv_path: exportedPath,
7238
- rowCount: rowsInfo?.totalRows ?? null,
7239
- columns: rowsInfo?.columns ?? []
7240
- })}
7241
- `
7242
- );
7243
- } else {
7244
- console.log(`Exported ${status.runId} to ${exportedPath}`);
7245
- }
7520
+ const exportResult = await exportPlayStatusRows(client, status, outPath, {
7521
+ datasetPath
7522
+ });
7523
+ printCommandEnvelope({
7524
+ runId: status.runId,
7525
+ ...datasetPath ? { dataset: datasetPath } : {},
7526
+ csv_path: exportResult?.path ?? null,
7527
+ rowCount: exportResult?.rowsInfo.totalRows ?? null,
7528
+ columns: exportResult?.rowsInfo.columns ?? [],
7529
+ local: { csv_path: exportResult?.path ?? null },
7530
+ render: { sections: [{ title: "run export", lines: [`Exported ${status.runId} to ${exportResult?.path ?? outPath}`] }] }
7531
+ }, { json: argsWantJson(args) });
7246
7532
  return 0;
7247
7533
  }
7248
7534
  async function handlePlayGet(args) {
@@ -7690,8 +7976,7 @@ Notes:
7690
7976
  Local files are bundled, preflighted, then run in Deepline cloud.
7691
7977
  Named plays run the live saved revision.
7692
7978
  Unknown --foo value and --foo.bar value flags are passed into play input.
7693
- Example: --csv leads.csv becomes input.csv = "leads.csv"; --limit 5 becomes
7694
- input.limit = 5.
7979
+ Example: --limit 5 becomes input.limit = 5.
7695
7980
  File args accept local paths; the CLI stages files before submit.
7696
7981
  --watch prints logs, previews, stats, and next commands.
7697
7982
  --wait is accepted as a compatibility alias for --watch.
@@ -7725,16 +8010,13 @@ Examples:
7725
8010
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
7726
8011
  deepline plays run my.play.ts --input @input.json --wait --json
7727
8012
  deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}' --watch
7728
- deepline plays run enrich.play.ts --csv leads.csv --watch --out leads-enriched.csv
7729
8013
  deepline plays run cto-search.play.ts --limit 5 --watch
8014
+ deepline runs export <run-id> --out output.csv
7730
8015
  deepline runs get <run-id>
7731
8016
  `
7732
8017
  ).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(
7733
8018
  "--revision-id <id>",
7734
8019
  "Run a specific saved revision instead of the live revision"
7735
- ).option(
7736
- "--out <path>",
7737
- "Write the completed row output to CSV; requires --watch"
7738
8020
  ).option("--watch", "Stream logs until completion").option("--wait", "Alias for --watch; stream logs until completion").option(
7739
8021
  "--logs",
7740
8022
  "When output is non-interactive, stream play logs to stderr while waiting"
@@ -7743,8 +8025,9 @@ Examples:
7743
8025
  `
7744
8026
  Pass-through input flags:
7745
8027
  Unknown flags are accepted intentionally and become play input fields. Use
7746
- this for play-specific inputs like --csv leads.csv, --limit 5, or
7747
- --filters.title "GTM Engineer".
8028
+ this for play-specific inputs like --limit 5 or --filters.title "GTM Engineer".
8029
+ For CSV file inputs, prefer --input '{"file":"leads.csv"}' so the field name
8030
+ matches the play's ctx.csv(input.file) contract.
7748
8031
  `
7749
8032
  ).action(async (target, options, command) => {
7750
8033
  const passthroughArgs = [...command.args];
@@ -7765,7 +8048,6 @@ Pass-through input flags:
7765
8048
  ...options.live ? ["--live"] : [],
7766
8049
  ...options.latest ? ["--latest"] : [],
7767
8050
  ...options.revisionId ? ["--revision-id", options.revisionId] : [],
7768
- ...options.out ? ["--out", options.out] : [],
7769
8051
  ...options.watch || options.wait ? ["--watch"] : [],
7770
8052
  ...options.logs ? ["--logs"] : [],
7771
8053
  ...options.tailTimeoutMs ? ["--tail-timeout-ms", options.tailTimeoutMs] : [],
@@ -7929,7 +8211,7 @@ Examples:
7929
8211
  `
7930
8212
  Concepts:
7931
8213
  A run is one execution instance of a play. It has status, progress, logs,
7932
- preview output, recovery metadata, and optional full row export.
8214
+ returned result, dataset previews, recovery metadata, and optional dataset export.
7933
8215
  tail reads the live stream. logs fetches persisted logs after the fact.
7934
8216
  stop mutates cloud state by requesting cancellation.
7935
8217
 
@@ -7942,7 +8224,7 @@ Examples:
7942
8224
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
7943
8225
  `
7944
8226
  );
7945
- runs.command("get <runId>").description("Get status, progress, outputs, errors, and recovery metadata for a play run.").addHelpText(
8227
+ runs.command("get <runId>").description("Get status, progress, result, errors, and recovery metadata for a play run.").addHelpText(
7946
8228
  "after",
7947
8229
  `
7948
8230
  Notes:
@@ -8036,20 +8318,22 @@ Examples:
8036
8318
  ...options.json ? ["--json"] : []
8037
8319
  ]);
8038
8320
  });
8039
- runs.command("export <runId>").description("Export the completed row output for a play run to CSV.").addHelpText(
8321
+ runs.command("export <runId>").description("Export a returned dataset handle for a play run to CSV.").addHelpText(
8040
8322
  "after",
8041
8323
  `
8042
8324
  Notes:
8043
- Writes the completed row output to the requested local CSV path. Use runs get
8044
- first when you need to confirm the run is complete or inspect preview output.
8325
+ Writes a returned dataset handle to the requested local CSV path. Use runs get
8326
+ first to inspect dataset paths like result.rows or result.nested.contacts.
8045
8327
 
8046
8328
  Examples:
8047
8329
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
8330
+ deepline runs export play/my-play/run/20260501t000000-000 --dataset result.rows --out output.csv
8048
8331
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv --json
8049
8332
  `
8050
- ).requiredOption("--out <path>", "Output CSV path").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
8333
+ ).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) => {
8051
8334
  process.exitCode = await handleRunExport([
8052
8335
  runId,
8336
+ ...options.dataset ? ["--dataset", options.dataset] : [],
8053
8337
  "--out",
8054
8338
  options.out,
8055
8339
  ...options.json ? ["--json"] : []
@@ -8215,19 +8499,19 @@ function toListedTool(tool) {
8215
8499
  async function listTools(args) {
8216
8500
  const client = new DeeplineClient();
8217
8501
  const items = (await client.listTools()).map(toListedTool);
8218
- if (argsWantJson(args)) {
8219
- process.stdout.write(`${JSON.stringify(items)}
8220
- `);
8221
- return 0;
8222
- }
8223
- console.log(`${items.length} tools available:
8224
- `);
8225
- for (const item of items) {
8226
- const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8227
- const listHint = item.listExtractorPaths?.length ? ` listExtractorPaths=${item.listExtractorPaths.join(",")}` : "";
8228
- console.log(` ${item.toolId}${cats}`);
8229
- console.log(` ${item.description}${listHint}`);
8230
- }
8502
+ const render = {
8503
+ sections: [
8504
+ {
8505
+ title: `${items.length} tools available:`,
8506
+ lines: items.flatMap((item) => {
8507
+ const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8508
+ const listHint = item.listExtractorPaths?.length ? ` listExtractorPaths=${item.listExtractorPaths.join(",")}` : "";
8509
+ return [`${item.toolId}${cats}`, ` ${item.description}${listHint}`];
8510
+ })
8511
+ }
8512
+ ]
8513
+ };
8514
+ printCommandEnvelope({ tools: items, count: items.length, render }, { json: argsWantJson(args) });
8231
8515
  return 0;
8232
8516
  }
8233
8517
  async function searchTools(queryInput, options = {}) {
@@ -8245,21 +8529,27 @@ async function searchTools(queryInput, options = {}) {
8245
8529
  includeSearchDebug: options.includeSearchDebug
8246
8530
  });
8247
8531
  const items = result.tools.map(toListedTool);
8248
- if (options.json || shouldEmitJson()) {
8249
- process.stdout.write(`${JSON.stringify({ ...result, tools: items })}
8250
- `);
8251
- return 0;
8252
- }
8253
- console.log(`${items.length} tools found:
8254
- `);
8255
- for (const item of items) {
8256
- const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8257
- console.log(` ${item.toolId}${cats}`);
8258
- console.log(` ${item.description}`);
8259
- if (item.inputSchema) {
8260
- console.log(" inputSchema: yes");
8532
+ const envelope = {
8533
+ ...result,
8534
+ tools: items,
8535
+ count: items.length,
8536
+ render: {
8537
+ sections: [
8538
+ {
8539
+ title: `${items.length} tools found:`,
8540
+ lines: items.flatMap((item) => {
8541
+ const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8542
+ return [
8543
+ `${item.toolId}${cats}`,
8544
+ ` ${item.description}`,
8545
+ ...item.inputSchema ? [" inputSchema: yes"] : []
8546
+ ];
8547
+ })
8548
+ }
8549
+ ]
8261
8550
  }
8262
- }
8551
+ };
8552
+ printCommandEnvelope(envelope, { json: options.json || shouldEmitJson() });
8263
8553
  return 0;
8264
8554
  }
8265
8555
  function playIdentifiers(play) {
@@ -8591,6 +8881,9 @@ function samplePayload(samples, key) {
8591
8881
  if (!isRecord3(entry)) return void 0;
8592
8882
  return Object.prototype.hasOwnProperty.call(entry, "payload") ? entry.payload : entry;
8593
8883
  }
8884
+ function commandEnvelopeFromRawResponse(rawResponse) {
8885
+ return isRecord3(rawResponse) ? { ...rawResponse } : { status: "completed", result: rawResponse };
8886
+ }
8594
8887
  function isPlayTool(tool) {
8595
8888
  const provider = typeof tool.provider === "string" ? tool.provider : "";
8596
8889
  return provider === "deepline_native" && Boolean(recordField(tool, "playExpansion", "play_expansion"));
@@ -8755,6 +9048,53 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
8755
9048
  windowsCopyCommand: `New-Item -ItemType Directory -Force -Path ${powerShellQuote(projectDir.replace(/\//g, "\\"))} | Out-Null; Copy-Item -LiteralPath ${powerShellQuote(scriptPath)} -Destination ${powerShellQuote(`${projectDir.replace(/\//g, "\\")}\\${fileName}`)}`
8756
9049
  };
8757
9050
  }
9051
+ function buildToolExecuteBaseEnvelope(input) {
9052
+ const envelope = commandEnvelopeFromRawResponse(input.rawResponse);
9053
+ const summaryEntries = Object.entries(input.summary);
9054
+ const output = input.listConversion ? {
9055
+ kind: "list",
9056
+ rowCount: input.listConversion.rows.length,
9057
+ columns: Object.keys(input.listConversion.rows[0] ?? {}),
9058
+ preview: input.listConversion.rows.slice(0, 5),
9059
+ listStrategy: input.listConversion.strategy,
9060
+ listSourcePath: input.listConversion.sourcePath
9061
+ } : {
9062
+ kind: summaryEntries.length > 0 ? "object" : "raw",
9063
+ summary: input.summary
9064
+ };
9065
+ const actions = input.listConversion ? [
9066
+ {
9067
+ label: "next",
9068
+ command: "move starter script into a project folder and expand it into a Deepline play"
9069
+ }
9070
+ ] : [];
9071
+ return {
9072
+ ...envelope,
9073
+ output,
9074
+ ...summaryEntries.length > 0 ? { summary: input.summary } : {},
9075
+ next: input.listConversion ? {
9076
+ expandToPlay: "Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
9077
+ } : {},
9078
+ render: {
9079
+ sections: input.listConversion ? [
9080
+ {
9081
+ title: "output",
9082
+ lines: [
9083
+ `${input.listConversion.rows.length} row(s) extracted from ${input.listConversion.sourcePath ?? "auto-detected list"}`,
9084
+ `columns: ${JSON.stringify(Object.keys(input.listConversion.rows[0] ?? {}))}`,
9085
+ `preview: ${JSON.stringify(input.listConversion.rows.slice(0, 5))}`
9086
+ ]
9087
+ }
9088
+ ] : [
9089
+ {
9090
+ title: "result",
9091
+ lines: summaryEntries.length > 0 ? summaryEntries.map(([key, value]) => `${key}=${String(value)}`) : [JSON.stringify(input.rawResponse, null, 2)]
9092
+ }
9093
+ ],
9094
+ actions
9095
+ }
9096
+ };
9097
+ }
8758
9098
  async function executeTool(args) {
8759
9099
  let parsed;
8760
9100
  try {
@@ -8787,24 +9127,45 @@ async function executeTool(args) {
8787
9127
  listExtractorPaths: metadata.listExtractorPaths ?? []
8788
9128
  });
8789
9129
  const summary = extractSummaryFields(rawResponse);
9130
+ const baseEnvelope = buildToolExecuteBaseEnvelope({
9131
+ toolId: parsed.toolId,
9132
+ rawResponse,
9133
+ listConversion,
9134
+ summary
9135
+ });
8790
9136
  if (parsed.outputFormat === "json" || parsed.outputFormat === "auto" && shouldEmitJson()) {
8791
- process.stdout.write(`${JSON.stringify(rawResponse, null, 2)}
8792
- `);
9137
+ printCommandEnvelope(baseEnvelope, { json: true });
8793
9138
  return 0;
8794
9139
  }
8795
9140
  if (parsed.outputFormat === "json_file") {
8796
9141
  const jsonPath = writeJsonOutputFile(rawResponse, `payload_${parsed.toolId}`);
8797
- console.log(jsonPath);
9142
+ printCommandEnvelope(
9143
+ {
9144
+ ...baseEnvelope,
9145
+ local: {
9146
+ ...isRecord3(baseEnvelope.local) ? baseEnvelope.local : {},
9147
+ payload_file: jsonPath
9148
+ }
9149
+ },
9150
+ { json: true }
9151
+ );
8798
9152
  return 0;
8799
9153
  }
8800
9154
  if (!listConversion) {
8801
9155
  if (parsed.outputFormat === "csv" || parsed.outputFormat === "csv_file") {
8802
9156
  const jsonPath = writeJsonOutputFile(rawResponse, `payload_${parsed.toolId}`);
8803
- console.log(jsonPath);
9157
+ printCommandEnvelope(
9158
+ {
9159
+ ...baseEnvelope,
9160
+ local: {
9161
+ payload_file: jsonPath
9162
+ }
9163
+ },
9164
+ { json: parsed.outputFormat === "csv_file" || shouldEmitJson() }
9165
+ );
8804
9166
  return 0;
8805
9167
  }
8806
- process.stdout.write(`${JSON.stringify(rawResponse, null, 2)}
8807
- `);
9168
+ printCommandEnvelope(baseEnvelope, { json: false });
8808
9169
  return 0;
8809
9170
  }
8810
9171
  const csv = writeCsvOutputFile(listConversion.rows, `${parsed.toolId}_output`);
@@ -8813,8 +9174,51 @@ async function executeTool(args) {
8813
9174
  payload: parsed.params,
8814
9175
  rows: listConversion.rows
8815
9176
  });
9177
+ const materializedEnvelope = {
9178
+ ...baseEnvelope,
9179
+ local: {
9180
+ extracted_csv: csv.path,
9181
+ extracted_csv_rows: csv.rowCount,
9182
+ extracted_csv_columns: csv.columns,
9183
+ preview: csv.preview,
9184
+ starter_script: seededScript.path,
9185
+ project_dir: seededScript.projectDir,
9186
+ copy_to_project: {
9187
+ macos_linux: seededScript.macCopyCommand,
9188
+ windows_powershell: seededScript.windowsCopyCommand
9189
+ }
9190
+ },
9191
+ render: {
9192
+ sections: [
9193
+ {
9194
+ title: `${csv.path} (${csv.rowCount} rows)`,
9195
+ lines: [
9196
+ ...csv.columns.length > 0 ? [`columns: ${JSON.stringify(csv.columns)}`] : [],
9197
+ ...Object.keys(summary).length > 0 ? [`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(", ")}`] : [],
9198
+ `preview: ${JSON.stringify(csv.preview)}`,
9199
+ `starter script: ${seededScript.path}`
9200
+ ]
9201
+ }
9202
+ ],
9203
+ actions: [
9204
+ {
9205
+ label: "next",
9206
+ 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."
9207
+ },
9208
+ {
9209
+ label: "macOS/Linux",
9210
+ command: seededScript.macCopyCommand
9211
+ },
9212
+ {
9213
+ label: "Windows PowerShell",
9214
+ command: seededScript.windowsCopyCommand
9215
+ }
9216
+ ]
9217
+ }
9218
+ };
8816
9219
  if (parsed.outputFormat === "csv_file") {
8817
- process.stdout.write(`${JSON.stringify({
9220
+ printCommandEnvelope({
9221
+ ...materializedEnvelope,
8818
9222
  extracted_csv: csv.path,
8819
9223
  extracted_csv_rows: csv.rowCount,
8820
9224
  extracted_csv_columns: csv.columns,
@@ -8828,28 +9232,24 @@ async function executeTool(args) {
8828
9232
  windows_powershell: seededScript.windowsCopyCommand
8829
9233
  },
8830
9234
  summary
8831
- })}
8832
- `);
9235
+ }, { json: true });
8833
9236
  return 0;
8834
9237
  }
8835
9238
  if (parsed.noPreview) {
8836
- console.log(csv.path);
9239
+ printCommandEnvelope(
9240
+ {
9241
+ ...materializedEnvelope,
9242
+ local: {
9243
+ ...materializedEnvelope.local ?? {},
9244
+ output_path: csv.path
9245
+ }
9246
+ },
9247
+ { json: shouldEmitJson(), text: `${csv.path}
9248
+ ` }
9249
+ );
8837
9250
  return 0;
8838
9251
  }
8839
- console.log(`${csv.path} (${csv.rowCount} rows)`);
8840
- if (csv.columns.length > 0) {
8841
- console.log(`columns: ${JSON.stringify(csv.columns)}`);
8842
- }
8843
- if (Object.keys(summary).length > 0) {
8844
- console.log(`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(", ")}`);
8845
- }
8846
- console.log(`preview: ${JSON.stringify(csv.preview)}`);
8847
- console.log(`starter script: ${seededScript.path}`);
8848
- console.log(
8849
- "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."
8850
- );
8851
- console.log(`macOS/Linux: ${seededScript.macCopyCommand}`);
8852
- console.log(`Windows PowerShell: ${seededScript.windowsCopyCommand}`);
9252
+ printCommandEnvelope(materializedEnvelope, { json: false });
8853
9253
  return 0;
8854
9254
  }
8855
9255
 
@@ -8923,23 +9323,36 @@ function runCommand(command, args) {
8923
9323
  }
8924
9324
  async function handleUpdate(options) {
8925
9325
  const plan = resolveUpdatePlan();
9326
+ const render = {
9327
+ sections: [
9328
+ {
9329
+ title: "update",
9330
+ lines: plan.kind === "source" ? [
9331
+ "This Deepline CLI is running from SDK source, so it cannot safely update itself like an npm global.",
9332
+ `Update the backing checkout with: ${plan.manualCommand}`
9333
+ ] : [`Updating Deepline SDK/CLI with: ${plan.manualCommand}`]
9334
+ }
9335
+ ]
9336
+ };
8926
9337
  if (options.json) {
8927
- process.stdout.write(`${JSON.stringify(plan)}
8928
- `);
9338
+ printCommandEnvelope({ ...plan, render }, { json: true });
8929
9339
  return 0;
8930
9340
  }
8931
9341
  if (options.printCommand) {
8932
- process.stdout.write(`${plan.manualCommand}
8933
- `);
9342
+ printCommandEnvelope(
9343
+ {
9344
+ ...plan,
9345
+ render: {
9346
+ sections: [{ title: "update command", lines: [plan.manualCommand] }]
9347
+ }
9348
+ },
9349
+ { json: false, text: `${plan.manualCommand}
9350
+ ` }
9351
+ );
8934
9352
  return 0;
8935
9353
  }
8936
9354
  if (plan.kind === "source") {
8937
- process.stdout.write(
8938
- `This Deepline CLI is running from SDK source, so it cannot safely update itself like an npm global.
8939
- Update the backing checkout with:
8940
- ${plan.manualCommand}
8941
- `
8942
- );
9355
+ printCommandEnvelope({ ...plan, render }, { json: false });
8943
9356
  return 0;
8944
9357
  }
8945
9358
  process.stderr.write(`Updating Deepline SDK/CLI with: ${plan.manualCommand}
@@ -9252,7 +9665,7 @@ Exit codes:
9252
9665
  registerDbCommands(program);
9253
9666
  registerFeedbackCommands(program);
9254
9667
  registerUpdateCommand(program);
9255
- program.command("health").description("Check server health.").addHelpText(
9668
+ program.command("health").description("Check server health.").option("--json", "Force JSON output.").addHelpText(
9256
9669
  "after",
9257
9670
  `
9258
9671
  Notes: