deepline 0.1.28 → 0.1.30

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.28";
270
- var SDK_API_CONTRACT = "2026-05-runs-v2";
269
+ var SDK_VERSION = "0.1.30";
270
+ var SDK_API_CONTRACT = "2026-05-runs-v2-datasets";
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)) {
@@ -6254,23 +6407,17 @@ 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 } : {},
6416
+ ...options?.exportedPath ? { local: { csv_path: options.exportedPath } } : {},
6269
6417
  ...status.resultView ? { resultView: status.resultView } : {},
6270
6418
  ...datasetStats ? { dataset_stats: datasetStats } : {},
6271
- ...rowsInfo ? { previewRows: rowsInfo.rows.slice(0, 5) } : {},
6272
6419
  ...billing ? { billing } : {},
6273
- next: buildRunNextCommands(status.runId, rowsInfo)
6420
+ next: buildRunNextCommands(status.runId)
6274
6421
  };
6275
6422
  }
6276
6423
  function enrichPlayStatusWithDatasetStats(status) {
@@ -6308,12 +6455,20 @@ function formatDatasetStatsLines(datasetStats) {
6308
6455
  }
6309
6456
  function writePlayResult(status, jsonOutput, options) {
6310
6457
  if (jsonOutput) {
6311
- process.stdout.write(
6312
- `${JSON.stringify(
6313
- options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status, options)
6314
- )}
6315
- `
6316
- );
6458
+ const payload2 = options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status, options);
6459
+ printCommandEnvelope({
6460
+ ...payload2,
6461
+ render: {
6462
+ sections: [
6463
+ {
6464
+ title: "run result",
6465
+ lines: [
6466
+ `${status.status ?? "running"} ${status.runId ?? "unknown"}`
6467
+ ]
6468
+ }
6469
+ ]
6470
+ }
6471
+ }, { json: true });
6317
6472
  return;
6318
6473
  }
6319
6474
  const result = status.result;
@@ -6330,22 +6485,8 @@ function writePlayResult(status, jsonOutput, options) {
6330
6485
  rowsInfo.columns,
6331
6486
  extractDatasetExecutionStats(status)
6332
6487
  ) : 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
- }
6488
+ if (options?.exportedPath) {
6489
+ lines.push(` exported CSV: file=${options.exportedPath}`);
6349
6490
  }
6350
6491
  for (const warning of warnings) {
6351
6492
  lines.push(` warning: ${warning}`);
@@ -6358,29 +6499,184 @@ function writePlayResult(status, jsonOutput, options) {
6358
6499
  const renderedServerView = renderServerResultView(status.resultView);
6359
6500
  if (result) {
6360
6501
  lines.push(...formatReturnValue(result));
6502
+ lines.push(...collectDatasetHandleLines(result));
6361
6503
  }
6362
6504
  if (renderedServerView.lines.length > 0) {
6363
6505
  lines.push(...renderedServerView.lines);
6364
6506
  }
6365
6507
  lines.push(...renderedServerView.actions);
6366
- console.log(lines.join("\n"));
6508
+ const payload = options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status, options);
6509
+ printCommandEnvelope({
6510
+ ...payload,
6511
+ render: {
6512
+ sections: [{ title: "run result", lines }]
6513
+ }
6514
+ }, { json: jsonOutput, text: `${lines.join("\n")}
6515
+ ` });
6516
+ }
6517
+ var RUN_EXPORT_PAGE_SIZE = 5e3;
6518
+ var PLAY_RUN_OUT_EXPORT_ATTEMPTS = 8;
6519
+ var PLAY_RUN_OUT_EXPORT_RETRY_DELAY_MS = 1e3;
6520
+ function shellSingleQuote(value) {
6521
+ return `'${value.replace(/'/g, `'\\''`)}'`;
6522
+ }
6523
+ function runExportRetryCommand(runId, outPath, datasetPath) {
6524
+ return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote((0, import_node_path9.resolve)(outPath))}`;
6525
+ }
6526
+ function extractRunPlayName(status) {
6527
+ const run = status.run;
6528
+ const candidates = [
6529
+ status.playName,
6530
+ status.name,
6531
+ getRecordField(run, "playName"),
6532
+ getRecordField(run, "name")
6533
+ ];
6534
+ for (const candidate of candidates) {
6535
+ if (typeof candidate === "string" && candidate.trim()) {
6536
+ return candidate.trim();
6537
+ }
6538
+ }
6539
+ return null;
6367
6540
  }
6368
- function exportPlayStatusRows(status, outPath) {
6541
+ function exportableSheetRow(row) {
6542
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
6543
+ return null;
6544
+ }
6545
+ const record = row;
6546
+ const data = record.data;
6547
+ if (data && typeof data === "object" && !Array.isArray(data)) {
6548
+ return data;
6549
+ }
6550
+ const fallback = { ...record };
6551
+ for (const key of [
6552
+ "key",
6553
+ "status",
6554
+ "cellMeta",
6555
+ "inputIndex",
6556
+ "runId",
6557
+ "error",
6558
+ "stage",
6559
+ "provider",
6560
+ "seq",
6561
+ "createdAt",
6562
+ "updatedAt"
6563
+ ]) {
6564
+ delete fallback[key];
6565
+ }
6566
+ return fallback;
6567
+ }
6568
+ async function fetchBackingDatasetRows(input) {
6569
+ const playName = extractRunPlayName(input.status);
6570
+ const tableNamespace = input.rowsInfo.tableNamespace?.trim();
6571
+ if (!playName || !tableNamespace) {
6572
+ return null;
6573
+ }
6574
+ const sheetRows = [];
6575
+ let offset = 0;
6576
+ let expectedTotal = input.rowsInfo.totalRows;
6577
+ while (true) {
6578
+ const page = await input.client.runs.exportDatasetRows({
6579
+ playName,
6580
+ tableNamespace,
6581
+ runId: input.status.runId,
6582
+ limit: RUN_EXPORT_PAGE_SIZE,
6583
+ offset
6584
+ });
6585
+ sheetRows.push(...page.rows);
6586
+ const summaryTotal = page.summary?.stats?.total;
6587
+ if (typeof summaryTotal === "number" && Number.isFinite(summaryTotal)) {
6588
+ expectedTotal = Math.max(expectedTotal, Math.trunc(summaryTotal));
6589
+ }
6590
+ if (page.rows.length < RUN_EXPORT_PAGE_SIZE || sheetRows.length >= expectedTotal) {
6591
+ break;
6592
+ }
6593
+ offset += page.rows.length;
6594
+ }
6595
+ const rows = sheetRows.map(exportableSheetRow).filter((row) => Boolean(row));
6596
+ if (rows.length < input.rowsInfo.totalRows) {
6597
+ return null;
6598
+ }
6599
+ const columns = input.rowsInfo.columnsExplicit && input.rowsInfo.columns.length ? input.rowsInfo.columns : [...new Set(rows.flatMap((row) => Object.keys(row)))];
6600
+ return {
6601
+ ...input.rowsInfo,
6602
+ rows,
6603
+ columns,
6604
+ totalRows: rows.length,
6605
+ complete: true,
6606
+ source: `${input.rowsInfo.source ?? "result.rows"} -> /api/v2/plays/${playName}/sheet?tableNamespace=${tableNamespace}`
6607
+ };
6608
+ }
6609
+ async function exportPlayStatusRows(client, status, outPath, options = {}) {
6369
6610
  if (!outPath) {
6370
6611
  return null;
6371
6612
  }
6372
- const rowsInfo = extractCanonicalRowsInfo(status);
6613
+ const availableRows = collectSerializedDatasetRowsInfos(status);
6614
+ let rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
6615
+ if (!rowsInfo && options.datasetPath) {
6616
+ const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
6617
+ throw new DeeplineError(
6618
+ `Run ${status.runId} did not return a dataset at ${options.datasetPath}.` + (available.length > 0 ? ` Available datasets: ${available.join(", ")}.` : ""),
6619
+ void 0,
6620
+ "RUN_EXPORT_DATASET_NOT_FOUND",
6621
+ { runId: status.runId, dataset: options.datasetPath, available }
6622
+ );
6623
+ }
6624
+ if (!options.datasetPath && availableRows.length > 1) {
6625
+ const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
6626
+ throw new DeeplineError(
6627
+ `Run ${status.runId} returned multiple datasets. Choose one with --dataset <path>: ${available.join(", ")}.`,
6628
+ void 0,
6629
+ "RUN_EXPORT_DATASET_REQUIRED",
6630
+ { runId: status.runId, available }
6631
+ );
6632
+ }
6373
6633
  if (!rowsInfo) {
6374
6634
  throw new DeeplineError(
6375
6635
  `Run ${status.runId} did not expose a row-shaped final output to export.`
6376
6636
  );
6377
6637
  }
6638
+ const attempts = Math.max(1, Math.trunc(options.attempts ?? 1));
6639
+ const retryDelayMs = Math.max(0, Math.trunc(options.retryDelayMs ?? 0));
6640
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
6641
+ if (rowsInfo.complete) {
6642
+ return { path: writeCanonicalRowsCsv(rowsInfo, outPath), rowsInfo };
6643
+ }
6644
+ const fetchedRowsInfo = await fetchBackingDatasetRows({
6645
+ client,
6646
+ status,
6647
+ rowsInfo
6648
+ });
6649
+ if (fetchedRowsInfo?.complete) {
6650
+ return {
6651
+ path: writeCanonicalRowsCsv(fetchedRowsInfo, outPath),
6652
+ rowsInfo: fetchedRowsInfo
6653
+ };
6654
+ }
6655
+ if (attempt < attempts && retryDelayMs > 0) {
6656
+ await sleep4(retryDelayMs);
6657
+ }
6658
+ }
6378
6659
  if (!rowsInfo.complete) {
6660
+ const retryCommand = runExportRetryCommand(
6661
+ status.runId,
6662
+ outPath,
6663
+ options.datasetPath ?? rowsInfo.source
6664
+ );
6379
6665
  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.`
6666
+ `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}`,
6667
+ void 0,
6668
+ "RUN_EXPORT_NOT_READY",
6669
+ {
6670
+ runId: status.runId,
6671
+ previewRowCount: rowsInfo.rows.length,
6672
+ totalRows: rowsInfo.totalRows,
6673
+ tableNamespace: rowsInfo.tableNamespace ?? null,
6674
+ dataset: options.datasetPath ?? rowsInfo.source,
6675
+ retry_command: retryCommand
6676
+ }
6381
6677
  );
6382
6678
  }
6383
- return writeCanonicalRowsCsv(rowsInfo, outPath);
6679
+ return { path: writeCanonicalRowsCsv(rowsInfo, outPath), rowsInfo };
6384
6680
  }
6385
6681
  function renderServerResultView(value) {
6386
6682
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -6454,8 +6750,10 @@ function writeStartedPlayRun(input) {
6454
6750
  dashboardUrl: input.dashboardUrl
6455
6751
  };
6456
6752
  if (input.jsonOutput) {
6457
- process.stdout.write(`${JSON.stringify(payload)}
6458
- `);
6753
+ printCommandEnvelope({
6754
+ ...payload,
6755
+ render: { sections: [{ title: "play run", lines: [`Started ${input.playName}`, `run id: ${input.runId}`] }] }
6756
+ }, { json: true });
6459
6757
  return;
6460
6758
  }
6461
6759
  const lines = [
@@ -6474,10 +6772,14 @@ function writeStartedPlayRun(input) {
6474
6772
  input.progress.writeLine(output, process.stdout);
6475
6773
  return;
6476
6774
  }
6477
- console.log(output);
6775
+ printCommandEnvelope({
6776
+ ...payload,
6777
+ render: { sections: [{ title: "play run", lines }] }
6778
+ }, { json: false, text: `${output}
6779
+ ` });
6478
6780
  }
6479
6781
  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.";
6782
+ 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 --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.map guidance.";
6481
6783
  let filePath = null;
6482
6784
  let playName = null;
6483
6785
  let input = null;
@@ -6547,6 +6849,11 @@ function parsePlayRunOptions(args) {
6547
6849
  }
6548
6850
  continue;
6549
6851
  }
6852
+ if (arg === "--csv" || arg.startsWith("--csv=")) {
6853
+ throw new Error(
6854
+ `--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`
6855
+ );
6856
+ }
6550
6857
  if (arg.startsWith("--")) {
6551
6858
  const { path, value } = parseInputFieldFlag(arg, args[index + 1]);
6552
6859
  input ??= {};
@@ -6732,11 +7039,6 @@ async function handleFileBackedRun(options) {
6732
7039
  const fileInputBindings = fileInputBindingsFromStaticPipeline(
6733
7040
  compilerManifest.staticPipeline
6734
7041
  );
6735
- applyCsvShortcutInput({
6736
- runtimeInput,
6737
- bindings: fileInputBindings,
6738
- fallbackInputPath: "file"
6739
- });
6740
7042
  const stagedFileInputs = await traceCliSpan(
6741
7043
  "cli.play_stage_inputs",
6742
7044
  {
@@ -6778,10 +7080,13 @@ async function handleFileBackedRun(options) {
6778
7080
  progress
6779
7081
  })
6780
7082
  );
6781
- const exportedPath = traceCliSync(
7083
+ const exportResult = await traceCliSpan(
6782
7084
  "cli.play_export_rows",
6783
7085
  { targetKind: "file", playName },
6784
- () => exportPlayStatusRows(finalStatus, options.outPath)
7086
+ () => exportPlayStatusRows(client, finalStatus, options.outPath, {
7087
+ attempts: PLAY_RUN_OUT_EXPORT_ATTEMPTS,
7088
+ retryDelayMs: PLAY_RUN_OUT_EXPORT_RETRY_DELAY_MS
7089
+ })
6785
7090
  );
6786
7091
  if (finalStatus.status === "completed") {
6787
7092
  progress.complete();
@@ -6791,7 +7096,7 @@ async function handleFileBackedRun(options) {
6791
7096
  traceCliSync(
6792
7097
  "cli.play_write_result",
6793
7098
  { targetKind: "file", playName },
6794
- () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath })
7099
+ () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath: exportResult?.path ?? null })
6795
7100
  );
6796
7101
  return finalStatus.status === "completed" ? 0 : 1;
6797
7102
  }
@@ -6881,10 +7186,6 @@ async function handleNamedRun(options) {
6881
7186
  ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
6882
7187
  ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
6883
7188
  ] : [];
6884
- applyCsvShortcutInput({
6885
- runtimeInput,
6886
- bindings: fileInputBindings
6887
- });
6888
7189
  const stagedFileInputs = await traceCliSpan(
6889
7190
  "cli.play_stage_inputs",
6890
7191
  {
@@ -6923,10 +7224,13 @@ async function handleNamedRun(options) {
6923
7224
  progress
6924
7225
  })
6925
7226
  );
6926
- const exportedPath = traceCliSync(
7227
+ const exportResult = await traceCliSpan(
6927
7228
  "cli.play_export_rows",
6928
7229
  { targetKind: "name", playName },
6929
- () => exportPlayStatusRows(finalStatus, options.outPath)
7230
+ () => exportPlayStatusRows(client, finalStatus, options.outPath, {
7231
+ attempts: PLAY_RUN_OUT_EXPORT_ATTEMPTS,
7232
+ retryDelayMs: PLAY_RUN_OUT_EXPORT_RETRY_DELAY_MS
7233
+ })
6930
7234
  );
6931
7235
  if (finalStatus.status === "completed") {
6932
7236
  progress.complete();
@@ -6936,7 +7240,7 @@ async function handleNamedRun(options) {
6936
7240
  traceCliSync(
6937
7241
  "cli.play_write_result",
6938
7242
  { targetKind: "name", playName },
6939
- () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath })
7243
+ () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath: exportResult?.path ?? null })
6940
7244
  );
6941
7245
  return finalStatus.status === "completed" ? 0 : 1;
6942
7246
  }
@@ -7005,7 +7309,7 @@ function parseRunIdPositional(args, usage) {
7005
7309
  }
7006
7310
  continue;
7007
7311
  }
7008
- if ((arg === "--out" || arg === "--reason") && args[index + 1]) {
7312
+ if ((arg === "--out" || arg === "--reason" || arg === "--dataset") && args[index + 1]) {
7009
7313
  index += 1;
7010
7314
  continue;
7011
7315
  }
@@ -7067,26 +7371,15 @@ async function handleRunsList(args) {
7067
7371
  executionTime: run.executionTime,
7068
7372
  playName: run.memo?.playName ?? playName
7069
7373
  }));
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
- }
7374
+ const lines = runs.length === 0 ? [`No runs found for ${playName}.`] : runs.map((run) => `${run.runId} ${run.status} ${formatTimestamp(run.startedAt)}`);
7375
+ printCommandEnvelope({
7376
+ runs,
7377
+ count: runs.length,
7378
+ next: {
7379
+ get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
7380
+ },
7381
+ render: { sections: [{ title: "runs", lines }] }
7382
+ }, { json: argsWantJson(args) });
7090
7383
  return 0;
7091
7384
  }
7092
7385
  async function handleRunTail(args) {
@@ -7140,41 +7433,30 @@ async function handleRunLogs(args) {
7140
7433
  const logs = status.progress?.logs ?? [];
7141
7434
  if (outPath) {
7142
7435
  (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
- }
7436
+ printCommandEnvelope({
7437
+ runId: status.runId,
7438
+ log_path: outPath,
7439
+ lineCount: logs.length,
7440
+ local: { log_path: outPath },
7441
+ render: { sections: [{ title: "run logs", lines: [`Wrote ${logs.length} log lines to ${outPath}`] }] }
7442
+ }, { json: argsWantJson(args) });
7155
7443
  return 0;
7156
7444
  }
7157
7445
  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
- }
7446
+ printCommandEnvelope({
7447
+ runId: status.runId,
7448
+ totalCount: logs.length,
7449
+ returnedCount: entries.length,
7450
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
7451
+ lastSequence: logs.length === 0 ? null : logs.length,
7452
+ truncated: logs.length > entries.length,
7453
+ hasMore: logs.length > entries.length,
7454
+ entries,
7455
+ next: {
7456
+ export: `deepline runs logs ${status.runId} --out run.log --json`
7457
+ },
7458
+ render: { sections: [{ title: "run logs", lines: entries }] }
7459
+ }, { json: argsWantJson(args), text: `${entries.join("\n")}${entries.length > 0 ? "\n" : ""}` });
7178
7460
  return 0;
7179
7461
  }
7180
7462
  async function handleRunStop(args) {
@@ -7195,19 +7477,18 @@ async function handleRunStop(args) {
7195
7477
  }
7196
7478
  const client = new DeeplineClient();
7197
7479
  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
- }
7480
+ const lines = [
7481
+ `Stopped ${result.runId}`,
7482
+ ...result.hitlCancelledCount > 0 ? [`cancelled HITL waits: ${result.hitlCancelledCount}`] : []
7483
+ ];
7484
+ printCommandEnvelope({
7485
+ ...result,
7486
+ render: { sections: [{ title: "run stop", lines }] }
7487
+ }, { json: argsWantJson(args) });
7207
7488
  return 0;
7208
7489
  }
7209
7490
  async function handleRunExport(args) {
7210
- const usage = "Usage: deepline runs export <run-id> --out output.csv [--json]";
7491
+ const usage = "Usage: deepline runs export <run-id> [--dataset result.rows] --out output.csv [--json]";
7211
7492
  let runId;
7212
7493
  try {
7213
7494
  runId = parseRunIdPositional(args, usage);
@@ -7216,10 +7497,15 @@ async function handleRunExport(args) {
7216
7497
  return 1;
7217
7498
  }
7218
7499
  let outPath = null;
7500
+ let datasetPath = null;
7219
7501
  for (let index = 0; index < args.length; index += 1) {
7220
7502
  const arg = args[index];
7221
7503
  if (arg === "--out" && args[index + 1]) {
7222
7504
  outPath = (0, import_node_path9.resolve)(args[++index]);
7505
+ continue;
7506
+ }
7507
+ if (arg === "--dataset" && args[index + 1]) {
7508
+ datasetPath = args[++index];
7223
7509
  }
7224
7510
  }
7225
7511
  if (!outPath) {
@@ -7228,21 +7514,18 @@ async function handleRunExport(args) {
7228
7514
  }
7229
7515
  const client = new DeeplineClient();
7230
7516
  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
- }
7517
+ const exportResult = await exportPlayStatusRows(client, status, outPath, {
7518
+ datasetPath
7519
+ });
7520
+ printCommandEnvelope({
7521
+ runId: status.runId,
7522
+ ...datasetPath ? { dataset: datasetPath } : {},
7523
+ csv_path: exportResult?.path ?? null,
7524
+ rowCount: exportResult?.rowsInfo.totalRows ?? null,
7525
+ columns: exportResult?.rowsInfo.columns ?? [],
7526
+ local: { csv_path: exportResult?.path ?? null },
7527
+ render: { sections: [{ title: "run export", lines: [`Exported ${status.runId} to ${exportResult?.path ?? outPath}`] }] }
7528
+ }, { json: argsWantJson(args) });
7246
7529
  return 0;
7247
7530
  }
7248
7531
  async function handlePlayGet(args) {
@@ -7690,8 +7973,7 @@ Notes:
7690
7973
  Local files are bundled, preflighted, then run in Deepline cloud.
7691
7974
  Named plays run the live saved revision.
7692
7975
  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.
7976
+ Example: --limit 5 becomes input.limit = 5.
7695
7977
  File args accept local paths; the CLI stages files before submit.
7696
7978
  --watch prints logs, previews, stats, and next commands.
7697
7979
  --wait is accepted as a compatibility alias for --watch.
@@ -7724,8 +8006,8 @@ Idempotent execution:
7724
8006
  Examples:
7725
8007
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
7726
8008
  deepline plays run my.play.ts --input @input.json --wait --json
8009
+ deepline plays run enrich.play.ts --input '{"file":"leads.csv"}' --watch --out leads-enriched.csv
7727
8010
  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
8011
  deepline plays run cto-search.play.ts --limit 5 --watch
7730
8012
  deepline runs get <run-id>
7731
8013
  `
@@ -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];
@@ -7929,7 +8212,7 @@ Examples:
7929
8212
  `
7930
8213
  Concepts:
7931
8214
  A run is one execution instance of a play. It has status, progress, logs,
7932
- preview output, recovery metadata, and optional full row export.
8215
+ returned result, dataset previews, recovery metadata, and optional dataset export.
7933
8216
  tail reads the live stream. logs fetches persisted logs after the fact.
7934
8217
  stop mutates cloud state by requesting cancellation.
7935
8218
 
@@ -7942,7 +8225,7 @@ Examples:
7942
8225
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
7943
8226
  `
7944
8227
  );
7945
- runs.command("get <runId>").description("Get status, progress, outputs, errors, and recovery metadata for a play run.").addHelpText(
8228
+ runs.command("get <runId>").description("Get status, progress, result, errors, and recovery metadata for a play run.").addHelpText(
7946
8229
  "after",
7947
8230
  `
7948
8231
  Notes:
@@ -8036,20 +8319,22 @@ Examples:
8036
8319
  ...options.json ? ["--json"] : []
8037
8320
  ]);
8038
8321
  });
8039
- runs.command("export <runId>").description("Export the completed row output for a play run to CSV.").addHelpText(
8322
+ runs.command("export <runId>").description("Export a returned dataset handle for a play run to CSV.").addHelpText(
8040
8323
  "after",
8041
8324
  `
8042
8325
  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.
8326
+ Writes a returned dataset handle to the requested local CSV path. Use runs get
8327
+ first to inspect dataset paths like result.rows or result.nested.contacts.
8045
8328
 
8046
8329
  Examples:
8047
8330
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
8331
+ deepline runs export play/my-play/run/20260501t000000-000 --dataset result.rows --out output.csv
8048
8332
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv --json
8049
8333
  `
8050
- ).requiredOption("--out <path>", "Output CSV path").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
8334
+ ).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
8335
  process.exitCode = await handleRunExport([
8052
8336
  runId,
8337
+ ...options.dataset ? ["--dataset", options.dataset] : [],
8053
8338
  "--out",
8054
8339
  options.out,
8055
8340
  ...options.json ? ["--json"] : []
@@ -8215,19 +8500,19 @@ function toListedTool(tool) {
8215
8500
  async function listTools(args) {
8216
8501
  const client = new DeeplineClient();
8217
8502
  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
- }
8503
+ const render = {
8504
+ sections: [
8505
+ {
8506
+ title: `${items.length} tools available:`,
8507
+ lines: items.flatMap((item) => {
8508
+ const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8509
+ const listHint = item.listExtractorPaths?.length ? ` listExtractorPaths=${item.listExtractorPaths.join(",")}` : "";
8510
+ return [`${item.toolId}${cats}`, ` ${item.description}${listHint}`];
8511
+ })
8512
+ }
8513
+ ]
8514
+ };
8515
+ printCommandEnvelope({ tools: items, count: items.length, render }, { json: argsWantJson(args) });
8231
8516
  return 0;
8232
8517
  }
8233
8518
  async function searchTools(queryInput, options = {}) {
@@ -8245,21 +8530,27 @@ async function searchTools(queryInput, options = {}) {
8245
8530
  includeSearchDebug: options.includeSearchDebug
8246
8531
  });
8247
8532
  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");
8533
+ const envelope = {
8534
+ ...result,
8535
+ tools: items,
8536
+ count: items.length,
8537
+ render: {
8538
+ sections: [
8539
+ {
8540
+ title: `${items.length} tools found:`,
8541
+ lines: items.flatMap((item) => {
8542
+ const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8543
+ return [
8544
+ `${item.toolId}${cats}`,
8545
+ ` ${item.description}`,
8546
+ ...item.inputSchema ? [" inputSchema: yes"] : []
8547
+ ];
8548
+ })
8549
+ }
8550
+ ]
8261
8551
  }
8262
- }
8552
+ };
8553
+ printCommandEnvelope(envelope, { json: options.json || shouldEmitJson() });
8263
8554
  return 0;
8264
8555
  }
8265
8556
  function playIdentifiers(play) {
@@ -8591,6 +8882,9 @@ function samplePayload(samples, key) {
8591
8882
  if (!isRecord3(entry)) return void 0;
8592
8883
  return Object.prototype.hasOwnProperty.call(entry, "payload") ? entry.payload : entry;
8593
8884
  }
8885
+ function commandEnvelopeFromRawResponse(rawResponse) {
8886
+ return isRecord3(rawResponse) ? { ...rawResponse } : { status: "completed", result: rawResponse };
8887
+ }
8594
8888
  function isPlayTool(tool) {
8595
8889
  const provider = typeof tool.provider === "string" ? tool.provider : "";
8596
8890
  return provider === "deepline_native" && Boolean(recordField(tool, "playExpansion", "play_expansion"));
@@ -8755,6 +9049,53 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
8755
9049
  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
9050
  };
8757
9051
  }
9052
+ function buildToolExecuteBaseEnvelope(input) {
9053
+ const envelope = commandEnvelopeFromRawResponse(input.rawResponse);
9054
+ const summaryEntries = Object.entries(input.summary);
9055
+ const output = input.listConversion ? {
9056
+ kind: "list",
9057
+ rowCount: input.listConversion.rows.length,
9058
+ columns: Object.keys(input.listConversion.rows[0] ?? {}),
9059
+ preview: input.listConversion.rows.slice(0, 5),
9060
+ listStrategy: input.listConversion.strategy,
9061
+ listSourcePath: input.listConversion.sourcePath
9062
+ } : {
9063
+ kind: summaryEntries.length > 0 ? "object" : "raw",
9064
+ summary: input.summary
9065
+ };
9066
+ const actions = input.listConversion ? [
9067
+ {
9068
+ label: "next",
9069
+ command: "move starter script into a project folder and expand it into a Deepline play"
9070
+ }
9071
+ ] : [];
9072
+ return {
9073
+ ...envelope,
9074
+ output,
9075
+ ...summaryEntries.length > 0 ? { summary: input.summary } : {},
9076
+ next: input.listConversion ? {
9077
+ expandToPlay: "Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
9078
+ } : {},
9079
+ render: {
9080
+ sections: input.listConversion ? [
9081
+ {
9082
+ title: "output",
9083
+ lines: [
9084
+ `${input.listConversion.rows.length} row(s) extracted from ${input.listConversion.sourcePath ?? "auto-detected list"}`,
9085
+ `columns: ${JSON.stringify(Object.keys(input.listConversion.rows[0] ?? {}))}`,
9086
+ `preview: ${JSON.stringify(input.listConversion.rows.slice(0, 5))}`
9087
+ ]
9088
+ }
9089
+ ] : [
9090
+ {
9091
+ title: "result",
9092
+ lines: summaryEntries.length > 0 ? summaryEntries.map(([key, value]) => `${key}=${String(value)}`) : [JSON.stringify(input.rawResponse, null, 2)]
9093
+ }
9094
+ ],
9095
+ actions
9096
+ }
9097
+ };
9098
+ }
8758
9099
  async function executeTool(args) {
8759
9100
  let parsed;
8760
9101
  try {
@@ -8787,24 +9128,45 @@ async function executeTool(args) {
8787
9128
  listExtractorPaths: metadata.listExtractorPaths ?? []
8788
9129
  });
8789
9130
  const summary = extractSummaryFields(rawResponse);
9131
+ const baseEnvelope = buildToolExecuteBaseEnvelope({
9132
+ toolId: parsed.toolId,
9133
+ rawResponse,
9134
+ listConversion,
9135
+ summary
9136
+ });
8790
9137
  if (parsed.outputFormat === "json" || parsed.outputFormat === "auto" && shouldEmitJson()) {
8791
- process.stdout.write(`${JSON.stringify(rawResponse, null, 2)}
8792
- `);
9138
+ printCommandEnvelope(baseEnvelope, { json: true });
8793
9139
  return 0;
8794
9140
  }
8795
9141
  if (parsed.outputFormat === "json_file") {
8796
9142
  const jsonPath = writeJsonOutputFile(rawResponse, `payload_${parsed.toolId}`);
8797
- console.log(jsonPath);
9143
+ printCommandEnvelope(
9144
+ {
9145
+ ...baseEnvelope,
9146
+ local: {
9147
+ ...isRecord3(baseEnvelope.local) ? baseEnvelope.local : {},
9148
+ payload_file: jsonPath
9149
+ }
9150
+ },
9151
+ { json: true }
9152
+ );
8798
9153
  return 0;
8799
9154
  }
8800
9155
  if (!listConversion) {
8801
9156
  if (parsed.outputFormat === "csv" || parsed.outputFormat === "csv_file") {
8802
9157
  const jsonPath = writeJsonOutputFile(rawResponse, `payload_${parsed.toolId}`);
8803
- console.log(jsonPath);
9158
+ printCommandEnvelope(
9159
+ {
9160
+ ...baseEnvelope,
9161
+ local: {
9162
+ payload_file: jsonPath
9163
+ }
9164
+ },
9165
+ { json: parsed.outputFormat === "csv_file" || shouldEmitJson() }
9166
+ );
8804
9167
  return 0;
8805
9168
  }
8806
- process.stdout.write(`${JSON.stringify(rawResponse, null, 2)}
8807
- `);
9169
+ printCommandEnvelope(baseEnvelope, { json: false });
8808
9170
  return 0;
8809
9171
  }
8810
9172
  const csv = writeCsvOutputFile(listConversion.rows, `${parsed.toolId}_output`);
@@ -8813,8 +9175,51 @@ async function executeTool(args) {
8813
9175
  payload: parsed.params,
8814
9176
  rows: listConversion.rows
8815
9177
  });
9178
+ const materializedEnvelope = {
9179
+ ...baseEnvelope,
9180
+ local: {
9181
+ extracted_csv: csv.path,
9182
+ extracted_csv_rows: csv.rowCount,
9183
+ extracted_csv_columns: csv.columns,
9184
+ preview: csv.preview,
9185
+ starter_script: seededScript.path,
9186
+ project_dir: seededScript.projectDir,
9187
+ copy_to_project: {
9188
+ macos_linux: seededScript.macCopyCommand,
9189
+ windows_powershell: seededScript.windowsCopyCommand
9190
+ }
9191
+ },
9192
+ render: {
9193
+ sections: [
9194
+ {
9195
+ title: `${csv.path} (${csv.rowCount} rows)`,
9196
+ lines: [
9197
+ ...csv.columns.length > 0 ? [`columns: ${JSON.stringify(csv.columns)}`] : [],
9198
+ ...Object.keys(summary).length > 0 ? [`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(", ")}`] : [],
9199
+ `preview: ${JSON.stringify(csv.preview)}`,
9200
+ `starter script: ${seededScript.path}`
9201
+ ]
9202
+ }
9203
+ ],
9204
+ actions: [
9205
+ {
9206
+ label: "next",
9207
+ 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."
9208
+ },
9209
+ {
9210
+ label: "macOS/Linux",
9211
+ command: seededScript.macCopyCommand
9212
+ },
9213
+ {
9214
+ label: "Windows PowerShell",
9215
+ command: seededScript.windowsCopyCommand
9216
+ }
9217
+ ]
9218
+ }
9219
+ };
8816
9220
  if (parsed.outputFormat === "csv_file") {
8817
- process.stdout.write(`${JSON.stringify({
9221
+ printCommandEnvelope({
9222
+ ...materializedEnvelope,
8818
9223
  extracted_csv: csv.path,
8819
9224
  extracted_csv_rows: csv.rowCount,
8820
9225
  extracted_csv_columns: csv.columns,
@@ -8828,28 +9233,24 @@ async function executeTool(args) {
8828
9233
  windows_powershell: seededScript.windowsCopyCommand
8829
9234
  },
8830
9235
  summary
8831
- })}
8832
- `);
9236
+ }, { json: true });
8833
9237
  return 0;
8834
9238
  }
8835
9239
  if (parsed.noPreview) {
8836
- console.log(csv.path);
9240
+ printCommandEnvelope(
9241
+ {
9242
+ ...materializedEnvelope,
9243
+ local: {
9244
+ ...materializedEnvelope.local ?? {},
9245
+ output_path: csv.path
9246
+ }
9247
+ },
9248
+ { json: shouldEmitJson(), text: `${csv.path}
9249
+ ` }
9250
+ );
8837
9251
  return 0;
8838
9252
  }
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}`);
9253
+ printCommandEnvelope(materializedEnvelope, { json: false });
8853
9254
  return 0;
8854
9255
  }
8855
9256
 
@@ -8923,23 +9324,36 @@ function runCommand(command, args) {
8923
9324
  }
8924
9325
  async function handleUpdate(options) {
8925
9326
  const plan = resolveUpdatePlan();
9327
+ const render = {
9328
+ sections: [
9329
+ {
9330
+ title: "update",
9331
+ lines: plan.kind === "source" ? [
9332
+ "This Deepline CLI is running from SDK source, so it cannot safely update itself like an npm global.",
9333
+ `Update the backing checkout with: ${plan.manualCommand}`
9334
+ ] : [`Updating Deepline SDK/CLI with: ${plan.manualCommand}`]
9335
+ }
9336
+ ]
9337
+ };
8926
9338
  if (options.json) {
8927
- process.stdout.write(`${JSON.stringify(plan)}
8928
- `);
9339
+ printCommandEnvelope({ ...plan, render }, { json: true });
8929
9340
  return 0;
8930
9341
  }
8931
9342
  if (options.printCommand) {
8932
- process.stdout.write(`${plan.manualCommand}
8933
- `);
9343
+ printCommandEnvelope(
9344
+ {
9345
+ ...plan,
9346
+ render: {
9347
+ sections: [{ title: "update command", lines: [plan.manualCommand] }]
9348
+ }
9349
+ },
9350
+ { json: false, text: `${plan.manualCommand}
9351
+ ` }
9352
+ );
8934
9353
  return 0;
8935
9354
  }
8936
9355
  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
- );
9356
+ printCommandEnvelope({ ...plan, render }, { json: false });
8943
9357
  return 0;
8944
9358
  }
8945
9359
  process.stderr.write(`Updating Deepline SDK/CLI with: ${plan.manualCommand}
@@ -9252,7 +9666,7 @@ Exit codes:
9252
9666
  registerDbCommands(program);
9253
9667
  registerFeedbackCommands(program);
9254
9668
  registerUpdateCommand(program);
9255
- program.command("health").description("Check server health.").addHelpText(
9669
+ program.command("health").description("Check server health.").option("--json", "Force JSON output.").addHelpText(
9256
9670
  "after",
9257
9671
  `
9258
9672
  Notes: