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