deepline 0.1.33 → 0.1.36

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.
@@ -193,8 +193,8 @@ function resolveConfig(options) {
193
193
  }
194
194
 
195
195
  // src/version.ts
196
- var SDK_VERSION = "0.1.33";
197
- var SDK_API_CONTRACT = "2026-05-host-env-generic-play-input-flags";
196
+ var SDK_VERSION = "0.1.36";
197
+ var SDK_API_CONTRACT = "2026-05-v2-tool-response";
198
198
 
199
199
  // ../shared_libs/play-runtime/coordinator-headers.ts
200
200
  var COORDINATOR_INTERNAL_TOKEN_HEADER = "x-deepline-internal-token";
@@ -276,7 +276,7 @@ var HttpClient = class {
276
276
  const response = await fetch(candidateUrl, {
277
277
  method,
278
278
  headers,
279
- body: options?.formData !== void 0 ? options.formData : options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
279
+ body: options?.formData !== void 0 ? typeof options.formData === "function" ? options.formData() : options.formData : options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
280
280
  signal: controller.signal
281
281
  });
282
282
  clearTimeout(timeoutId);
@@ -359,10 +359,13 @@ var HttpClient = class {
359
359
  throw new AuthError();
360
360
  }
361
361
  if (!response.ok) {
362
+ const body = await response.text();
363
+ const parsed = parseResponseBody(body);
362
364
  throw new DeeplineError(
363
- `HTTP ${response.status}`,
365
+ apiErrorMessage(parsed, response.status),
364
366
  response.status,
365
- "API_ERROR"
367
+ "API_ERROR",
368
+ { response: parsed }
366
369
  );
367
370
  }
368
371
  if (!response.body) {
@@ -412,6 +415,26 @@ var HttpClient = class {
412
415
  return this.request(path, { method: "DELETE" });
413
416
  }
414
417
  };
418
+ function parseResponseBody(body) {
419
+ try {
420
+ return JSON.parse(body);
421
+ } catch {
422
+ return body;
423
+ }
424
+ }
425
+ function apiErrorMessage(parsed, status) {
426
+ const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
427
+ if (typeof errorValue === "string") {
428
+ return errorValue;
429
+ }
430
+ if (errorValue && typeof errorValue === "object" && "message" in errorValue && typeof errorValue.message === "string") {
431
+ return errorValue.message;
432
+ }
433
+ if (typeof parsed === "object" && parsed && "message" in parsed && typeof parsed.message === "string") {
434
+ return parsed.message;
435
+ }
436
+ return `HTTP ${status}`;
437
+ }
415
438
  function parseRetryAfter(response) {
416
439
  const header = response.headers.get("retry-after");
417
440
  if (header) {
@@ -475,15 +498,17 @@ function decodeSseFrame(frame) {
475
498
  return parsed;
476
499
  }
477
500
  function sleep(ms) {
478
- return new Promise((resolve10) => setTimeout(resolve10, ms));
501
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
479
502
  }
480
503
 
481
504
  // src/client.ts
482
505
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
483
506
  var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
507
+ var EXECUTE_RESPONSE_CONTRACT_HEADER = "x-deepline-execute-response-contract";
508
+ var V2_EXECUTE_RESPONSE_CONTRACT = "v2-tool-response";
484
509
  var COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1e3];
485
510
  function sleep2(ms) {
486
- return new Promise((resolve10) => setTimeout(resolve10, ms));
511
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
487
512
  }
488
513
  function isTransientCompileManifestError(error) {
489
514
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
@@ -749,13 +774,16 @@ var DeeplineClient = class {
749
774
  /**
750
775
  * Execute a tool and return the standard execution envelope.
751
776
  *
752
- * The `result.data` field contains the provider payload. `result.meta`
753
- * contains provider/upstream metadata such as HTTP status or paging details.
777
+ * The `toolResponse.raw` field contains the raw tool response.
778
+ * `toolResponse.meta` contains tool/provider metadata.
754
779
  * Top-level fields such as `status`, `job_id`, and `billing` describe the
755
- * Deepline execution.
780
+ * Deepline execution envelope.
756
781
  */
757
782
  async executeTool(toolId, input, options) {
758
- const headers = options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : void 0;
783
+ const headers = {
784
+ [EXECUTE_RESPONSE_CONTRACT_HEADER]: V2_EXECUTE_RESPONSE_CONTRACT,
785
+ ...options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : {}
786
+ };
759
787
  return this.http.post(
760
788
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
761
789
  { payload: input },
@@ -1053,34 +1081,37 @@ var DeeplineClient = class {
1053
1081
  * ```
1054
1082
  */
1055
1083
  async stagePlayFiles(files) {
1056
- const formData = new FormData();
1057
- formData.set(
1058
- "metadata",
1059
- JSON.stringify({
1060
- files: files.map((file, index) => ({
1061
- index,
1062
- logicalPath: file.logicalPath,
1063
- contentHash: file.contentHash,
1064
- contentType: file.contentType,
1065
- bytes: file.bytes
1066
- }))
1067
- })
1068
- );
1069
- for (const [index, file] of files.entries()) {
1070
- const bytes = decodeBase64Bytes(file.contentBase64);
1071
- const body = bytes.buffer.slice(
1072
- bytes.byteOffset,
1073
- bytes.byteOffset + bytes.byteLength
1074
- );
1084
+ const buildFormData = () => {
1085
+ const formData = new FormData();
1075
1086
  formData.set(
1076
- `file:${index}`,
1077
- new Blob([body], { type: file.contentType }),
1078
- file.logicalPath
1087
+ "metadata",
1088
+ JSON.stringify({
1089
+ files: files.map((file, index) => ({
1090
+ index,
1091
+ logicalPath: file.logicalPath,
1092
+ contentHash: file.contentHash,
1093
+ contentType: file.contentType,
1094
+ bytes: file.bytes
1095
+ }))
1096
+ })
1079
1097
  );
1080
- }
1098
+ for (const [index, file] of files.entries()) {
1099
+ const bytes = decodeBase64Bytes(file.contentBase64);
1100
+ const body = bytes.buffer.slice(
1101
+ bytes.byteOffset,
1102
+ bytes.byteOffset + bytes.byteLength
1103
+ );
1104
+ formData.set(
1105
+ `file:${index}`,
1106
+ new Blob([body], { type: file.contentType }),
1107
+ file.logicalPath
1108
+ );
1109
+ }
1110
+ return formData;
1111
+ };
1081
1112
  const response = await this.http.postFormData(
1082
1113
  "/api/v2/plays/files/stage",
1083
- formData
1114
+ buildFormData
1084
1115
  );
1085
1116
  return response.files;
1086
1117
  }
@@ -1874,6 +1905,103 @@ function csvStringFromRows(rows, columns) {
1874
1905
  ...columns?.length ? { columns } : {}
1875
1906
  });
1876
1907
  }
1908
+ function parseMaybeJsonObject(value) {
1909
+ if (typeof value !== "string") {
1910
+ return value;
1911
+ }
1912
+ const trimmed = value.trim();
1913
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
1914
+ return value;
1915
+ }
1916
+ try {
1917
+ return JSON.parse(trimmed);
1918
+ } catch {
1919
+ return value;
1920
+ }
1921
+ }
1922
+ function flattenObjectColumns(row) {
1923
+ const flattened = {};
1924
+ for (const [key, rawValue] of Object.entries(row)) {
1925
+ const value = parseMaybeJsonObject(rawValue);
1926
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1927
+ for (const [nestedKey, nestedValue] of Object.entries(
1928
+ value
1929
+ )) {
1930
+ flattened[`${key}.${nestedKey}`] = nestedValue && typeof nestedValue === "object" ? JSON.stringify(nestedValue) : nestedValue;
1931
+ }
1932
+ continue;
1933
+ }
1934
+ flattened[key] = Array.isArray(value) ? JSON.stringify(value) : value;
1935
+ }
1936
+ return flattened;
1937
+ }
1938
+ function recordRows(value) {
1939
+ return value.filter(
1940
+ (row) => Boolean(row) && typeof row === "object" && !Array.isArray(row)
1941
+ );
1942
+ }
1943
+ function dataExportRows(rows) {
1944
+ return rows.map((row) => flattenObjectColumns(row));
1945
+ }
1946
+ function dataExportColumns(rows, preferredColumns = []) {
1947
+ const discoveredColumns = [
1948
+ ...new Set(rows.flatMap((row) => Object.keys(row)))
1949
+ ];
1950
+ if (rows.length === 0) {
1951
+ return [...new Set(preferredColumns.filter(Boolean))];
1952
+ }
1953
+ const discovered = new Set(discoveredColumns);
1954
+ const columns = [];
1955
+ const seen = /* @__PURE__ */ new Set();
1956
+ for (const column of preferredColumns) {
1957
+ if (!column) {
1958
+ continue;
1959
+ }
1960
+ const expandedColumns = discovered.has(column) ? [column] : discoveredColumns.filter(
1961
+ (discoveredColumn) => discoveredColumn.startsWith(`${column}.`)
1962
+ );
1963
+ for (const expandedColumn of expandedColumns) {
1964
+ if (seen.has(expandedColumn)) {
1965
+ continue;
1966
+ }
1967
+ seen.add(expandedColumn);
1968
+ columns.push(expandedColumn);
1969
+ }
1970
+ }
1971
+ for (const column of discoveredColumns) {
1972
+ if (seen.has(column)) {
1973
+ continue;
1974
+ }
1975
+ seen.add(column);
1976
+ columns.push(column);
1977
+ }
1978
+ return columns;
1979
+ }
1980
+ function dataExportCsvString(rows, preferredColumns = []) {
1981
+ const flattenedRows = dataExportRows(rows);
1982
+ return csvStringFromRows(
1983
+ flattenedRows,
1984
+ dataExportColumns(flattenedRows, preferredColumns)
1985
+ );
1986
+ }
1987
+ function markdownCell(value) {
1988
+ if (value === null || value === void 0) {
1989
+ return "";
1990
+ }
1991
+ const text = typeof value === "object" ? JSON.stringify(value) : String(value);
1992
+ return text.replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
1993
+ }
1994
+ function markdownTableFromRows(rows, preferredColumns = []) {
1995
+ const flattenedRows = dataExportRows(rows);
1996
+ const columns = dataExportColumns(flattenedRows, preferredColumns);
1997
+ const header = `| ${columns.map(markdownCell).join(" | ")} |`;
1998
+ const separator = `| ${columns.map(() => "---").join(" | ")} |`;
1999
+ const body = flattenedRows.map(
2000
+ (row) => `| ${columns.map((column) => markdownCell(row[column])).join(" | ")} |`
2001
+ );
2002
+ return `${[header, separator, ...body].join("\n")}
2003
+ `;
2004
+ }
1877
2005
  function printJson(value) {
1878
2006
  process.stdout.write(`${JSON.stringify(value, null, 2)}
1879
2007
  `);
@@ -2050,7 +2178,7 @@ function buildCandidateUrls2(url) {
2050
2178
  }
2051
2179
  }
2052
2180
  function sleep3(ms) {
2053
- return new Promise((resolve10) => setTimeout(resolve10, ms));
2181
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
2054
2182
  }
2055
2183
  function printDeeplineLogo() {
2056
2184
  if (process.stdout.isTTY && (process.stdout.columns ?? 80) >= 70) {
@@ -3250,10 +3378,12 @@ function writeCanonicalRowsCsv(rowsInfo, outPath) {
3250
3378
  rows: rowsInfo.rows,
3251
3379
  columns: rowsInfo.columns
3252
3380
  });
3381
+ const rows = dataExportRows(sanitized.rows);
3382
+ const columns = dataExportColumns(rows, sanitized.columns);
3253
3383
  const resolved = resolve4(outPath);
3254
3384
  writeFileSync4(
3255
3385
  resolved,
3256
- csvStringFromRows(sanitized.rows, sanitized.columns),
3386
+ csvStringFromRows(rows, columns),
3257
3387
  "utf-8"
3258
3388
  );
3259
3389
  return resolved;
@@ -3374,6 +3504,14 @@ Examples:
3374
3504
  }
3375
3505
 
3376
3506
  // src/cli/commands/db.ts
3507
+ import { writeFileSync as writeFileSync5 } from "fs";
3508
+ import { resolve as resolve5 } from "path";
3509
+ var CUSTOMER_DB_QUERY_FORMATS = /* @__PURE__ */ new Set([
3510
+ "table",
3511
+ "json",
3512
+ "csv",
3513
+ "markdown"
3514
+ ]);
3377
3515
  function parsePositiveInteger(value, flagName) {
3378
3516
  const parsed = Number.parseInt(value, 10);
3379
3517
  if (!Number.isFinite(parsed) || parsed <= 0) {
@@ -3387,10 +3525,8 @@ function formatCell(value) {
3387
3525
  return text.length > 80 ? `${text.slice(0, 77)}...` : text;
3388
3526
  }
3389
3527
  function tableLines(result) {
3390
- const rows = result.rows.filter(
3391
- (row) => Boolean(row) && typeof row === "object" && !Array.isArray(row)
3392
- );
3393
- const responseColumns = result.columns.length > 0 ? result.columns.map((column) => column.name) : [...new Set(rows.flatMap((row) => Object.keys(row)))];
3528
+ const rows = dataExportRows(customerDbRows(result));
3529
+ const responseColumns = dataExportColumns(rows, customerDbColumnNames(result));
3394
3530
  const businessColumns = responseColumns.filter((column) => !column.startsWith("_"));
3395
3531
  const columns = businessColumns.length > 0 ? businessColumns : responseColumns;
3396
3532
  const hiddenColumns = responseColumns.filter((column) => !columns.includes(column));
@@ -3424,22 +3560,146 @@ function tableLines(result) {
3424
3560
  }
3425
3561
  return lines;
3426
3562
  }
3563
+ function customerDbRows(result) {
3564
+ return recordRows(result.rows);
3565
+ }
3566
+ function customerDbColumnNames(result) {
3567
+ return result.columns.map((column) => column.name).filter(Boolean);
3568
+ }
3569
+ function writeCustomerDbCsv(result, outPath) {
3570
+ const resolved = resolve5(outPath);
3571
+ writeFileSync5(
3572
+ resolved,
3573
+ dataExportCsvString(customerDbRows(result), customerDbColumnNames(result)),
3574
+ "utf-8"
3575
+ );
3576
+ return resolved;
3577
+ }
3578
+ function dbQueryExportEnvelope(input) {
3579
+ const destination = input.outPath ?? "stdout";
3580
+ return {
3581
+ command: input.result.command,
3582
+ format: input.format,
3583
+ row_count: input.result.row_count,
3584
+ row_count_returned: input.result.row_count_returned,
3585
+ truncated: input.result.truncated,
3586
+ ...input.outPath ? { file: input.outPath, local: { file: input.outPath } } : {},
3587
+ next: { toolEquivalent: input.toolCommand },
3588
+ render: {
3589
+ sections: [
3590
+ {
3591
+ title: "customer db export",
3592
+ lines: [
3593
+ `Rendered ${input.result.row_count_returned} row(s) as ${input.format} to ${destination}`
3594
+ ]
3595
+ }
3596
+ ],
3597
+ actions: [{ label: "Tool equivalent", command: input.toolCommand }]
3598
+ }
3599
+ };
3600
+ }
3427
3601
  async function handleDbQuery(args) {
3428
3602
  const sqlIndex = args.indexOf("--sql");
3429
3603
  const sql = sqlIndex >= 0 ? args[sqlIndex + 1]?.trim() : "";
3430
3604
  if (!sql) {
3431
- console.error('Usage: deepline db query --sql "select * from table limit 20" [--max-rows N] [--json]');
3605
+ console.error(
3606
+ 'Usage: deepline db query --sql "select * from table limit 20" [--max-rows N] [--json]'
3607
+ );
3432
3608
  return 1;
3433
3609
  }
3434
3610
  const maxRowsIndex = args.indexOf("--max-rows");
3435
3611
  const maxRows = maxRowsIndex >= 0 && args[maxRowsIndex + 1] ? parsePositiveInteger(args[maxRowsIndex + 1], "--max-rows") : void 0;
3612
+ const formatIndex = args.indexOf("--format");
3613
+ const format = formatIndex >= 0 ? args[formatIndex + 1]?.trim().toLowerCase() : "";
3614
+ if (format && !CUSTOMER_DB_QUERY_FORMATS.has(format)) {
3615
+ console.error(
3616
+ 'Usage: deepline db query --sql "select * from table limit 20" [--format table|json|csv|markdown] [--out path]'
3617
+ );
3618
+ return 1;
3619
+ }
3620
+ const outIndex = args.indexOf("--out");
3621
+ const outPath = outIndex >= 0 ? args[outIndex + 1]?.trim() : "";
3622
+ if (outIndex >= 0 && !outPath) {
3623
+ console.error("--out requires a path.");
3624
+ return 1;
3625
+ }
3436
3626
  const jsonOutput = argsWantJson(args);
3627
+ const explicitJsonOutput = args.includes("--json");
3437
3628
  const client = new DeeplineClient();
3438
3629
  const result = await client.queryCustomerDb({ sql, maxRows });
3439
3630
  const toolCommand = `deepline tools execute query_customer_db --payload ${JSON.stringify({
3440
3631
  sql,
3441
3632
  ...maxRows ? { max_rows: maxRows } : {}
3442
3633
  })} --json`;
3634
+ if (format === "csv") {
3635
+ if (outPath) {
3636
+ const exportedPath = writeCustomerDbCsv(result, outPath);
3637
+ printCommandEnvelope(
3638
+ dbQueryExportEnvelope({
3639
+ result,
3640
+ format,
3641
+ outPath: exportedPath,
3642
+ toolCommand
3643
+ }),
3644
+ {
3645
+ json: jsonOutput,
3646
+ text: `Exported ${result.row_count_returned} row(s) to ${exportedPath}
3647
+ `
3648
+ }
3649
+ );
3650
+ return 0;
3651
+ }
3652
+ printCommandEnvelope(
3653
+ dbQueryExportEnvelope({
3654
+ result,
3655
+ format,
3656
+ outPath: null,
3657
+ toolCommand
3658
+ }),
3659
+ {
3660
+ json: explicitJsonOutput,
3661
+ text: dataExportCsvString(customerDbRows(result), customerDbColumnNames(result))
3662
+ }
3663
+ );
3664
+ return 0;
3665
+ }
3666
+ if (format === "markdown") {
3667
+ const content = markdownTableFromRows(
3668
+ customerDbRows(result),
3669
+ customerDbColumnNames(result)
3670
+ );
3671
+ if (outPath) {
3672
+ const exportedPath = resolve5(outPath);
3673
+ writeFileSync5(exportedPath, content, "utf-8");
3674
+ printCommandEnvelope(
3675
+ dbQueryExportEnvelope({
3676
+ result,
3677
+ format,
3678
+ outPath: exportedPath,
3679
+ toolCommand
3680
+ }),
3681
+ {
3682
+ json: jsonOutput,
3683
+ text: `Exported ${result.row_count_returned} row(s) to ${exportedPath}
3684
+ `
3685
+ }
3686
+ );
3687
+ return 0;
3688
+ }
3689
+ printCommandEnvelope(
3690
+ dbQueryExportEnvelope({
3691
+ result,
3692
+ format,
3693
+ outPath: null,
3694
+ toolCommand
3695
+ }),
3696
+ {
3697
+ json: explicitJsonOutput,
3698
+ text: content
3699
+ }
3700
+ );
3701
+ return 0;
3702
+ }
3443
3703
  printCommandEnvelope({
3444
3704
  ...result,
3445
3705
  next: { toolEquivalent: toolCommand },
@@ -3447,7 +3707,7 @@ async function handleDbQuery(args) {
3447
3707
  sections: [{ title: "customer db query", lines: tableLines(result) }],
3448
3708
  actions: [{ label: "Tool equivalent", command: toolCommand }]
3449
3709
  }
3450
- }, { json: jsonOutput });
3710
+ }, { json: jsonOutput || format === "json" });
3451
3711
  return 0;
3452
3712
  }
3453
3713
  function registerDbCommands(program) {
@@ -3457,11 +3717,14 @@ function registerDbCommands(program) {
3457
3717
  Notes:
3458
3718
  Runs SQL against the active workspace customer database through Deepline APIs.
3459
3719
  Results are bounded by the server and --max-rows. Use --json for stable output.
3720
+ Use --format csv or --format markdown for agent-readable exports and display tables.
3460
3721
 
3461
3722
  Examples:
3462
3723
  deepline db query --sql "select * from companies limit 20"
3463
3724
  deepline db query --sql "select domain, name from companies limit 20" --json
3464
3725
  deepline db query --sql "select * from contacts" --max-rows 100 --json
3726
+ deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
3727
+ deepline db query --sql "select domain, name from companies limit 20" --format markdown
3465
3728
  `
3466
3729
  );
3467
3730
  db.command("query").alias("psql").description("Run SQL against the tenant customer database.").addHelpText(
@@ -3470,17 +3733,23 @@ Examples:
3470
3733
  Notes:
3471
3734
  Requires --sql. Output is a compact table in a terminal and raw JSON with
3472
3735
  --json or when stdout is piped. The active auth workspace determines scope.
3736
+ --format csv and --format markdown are explicit data/display formats and can
3737
+ be written directly with --out.
3473
3738
 
3474
3739
  Examples:
3475
3740
  deepline db query --sql "select * from companies limit 20"
3476
3741
  deepline db query --sql "select domain, name from companies limit 20" --json
3477
3742
  deepline db psql --sql "select count(*) from contacts" --json
3743
+ deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
3744
+ deepline db query --sql "select domain, name from companies limit 20" --format markdown
3478
3745
  `
3479
- ).requiredOption("--sql <sql>", "SQL statement").option("--max-rows <n>", "Maximum returned rows").option("--json", "Emit raw JSON response. Also automatic when stdout is piped").action(async (options) => {
3746
+ ).requiredOption("--sql <sql>", "SQL statement").option("--max-rows <n>", "Maximum returned rows").option("--format <format>", "Output format: table, json, csv, or markdown").option("--out <path>", "Write csv or markdown output to a file").option("--json", "Emit raw JSON response. Also automatic when stdout is piped").action(async (options) => {
3480
3747
  process.exitCode = await handleDbQuery([
3481
3748
  "--sql",
3482
3749
  options.sql,
3483
3750
  ...options.maxRows ? ["--max-rows", options.maxRows] : [],
3751
+ ...options.format ? ["--format", options.format] : [],
3752
+ ...options.out ? ["--out", options.out] : [],
3484
3753
  ...options.json ? ["--json"] : []
3485
3754
  ]);
3486
3755
  });
@@ -3661,13 +3930,13 @@ import {
3661
3930
  readFileSync as readFileSync5,
3662
3931
  readdirSync,
3663
3932
  realpathSync,
3664
- writeFileSync as writeFileSync5
3933
+ writeFileSync as writeFileSync6
3665
3934
  } from "fs";
3666
- import { basename as basename3, dirname as dirname8, join as join6, resolve as resolve8 } from "path";
3935
+ import { basename as basename3, dirname as dirname8, join as join6, resolve as resolve9 } from "path";
3667
3936
 
3668
3937
  // src/plays/bundle-play-file.ts
3669
3938
  import { tmpdir as tmpdir2 } from "os";
3670
- import { dirname as dirname7, join as join5, resolve as resolve7 } from "path";
3939
+ import { dirname as dirname7, join as join5, resolve as resolve8 } from "path";
3671
3940
  import { fileURLToPath } from "url";
3672
3941
  import { existsSync as existsSync5 } from "fs";
3673
3942
 
@@ -3676,7 +3945,7 @@ import { createHash } from "crypto";
3676
3945
  import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
3677
3946
  import { mkdir as mkdir3, readFile, realpath, stat, writeFile as writeFile3 } from "fs/promises";
3678
3947
  import { tmpdir } from "os";
3679
- import { basename, dirname as dirname5, extname, isAbsolute, join as join3, resolve as resolve5 } from "path";
3948
+ import { basename, dirname as dirname5, extname, isAbsolute, join as join3, resolve as resolve6 } from "path";
3680
3949
  import { builtinModules } from "module";
3681
3950
  import { build } from "esbuild";
3682
3951
 
@@ -3768,7 +4037,7 @@ async function normalizeLocalPath(filePath) {
3768
4037
  try {
3769
4038
  return await realpath(filePath);
3770
4039
  } catch {
3771
- return resolve5(filePath);
4040
+ return resolve6(filePath);
3772
4041
  }
3773
4042
  }
3774
4043
  function createPlayWorkspace(entryFile) {
@@ -3937,7 +4206,7 @@ function readPackageVersionFromPackageJson(packageJsonPath, packageName) {
3937
4206
  return null;
3938
4207
  }
3939
4208
  function findPackageJsonPathFrom(startDir, packageName) {
3940
- let current = resolve5(startDir);
4209
+ let current = resolve6(startDir);
3941
4210
  while (true) {
3942
4211
  const packageJsonPath = join3(
3943
4212
  current,
@@ -3964,7 +4233,7 @@ function findPackageJsonPath(packageName, fromFile, adapter) {
3964
4233
  ];
3965
4234
  const seen = /* @__PURE__ */ new Set();
3966
4235
  for (const startDir of startDirs) {
3967
- const normalized = resolve5(startDir);
4236
+ const normalized = resolve6(startDir);
3968
4237
  if (seen.has(normalized)) continue;
3969
4238
  seen.add(normalized);
3970
4239
  const packageJsonPath = findPackageJsonPathFrom(normalized, packageName);
@@ -4201,7 +4470,7 @@ async function resolveLocalImport(fromFile, specifier) {
4201
4470
  if (specifier.startsWith("file:")) {
4202
4471
  return normalizeLocalPath(new URL(specifier).pathname);
4203
4472
  }
4204
- const base = isAbsolute(specifier) ? resolve5(specifier) : resolve5(dirname5(fromFile), specifier);
4473
+ const base = isAbsolute(specifier) ? resolve6(specifier) : resolve6(dirname5(fromFile), specifier);
4205
4474
  const candidates = [base];
4206
4475
  const explicitExtension = extname(base).toLowerCase();
4207
4476
  if (!explicitExtension) {
@@ -4399,7 +4668,7 @@ function normalizeSourceMapForRuntime(sourceMapText) {
4399
4668
  if (sourcePath.startsWith("data:") || sourcePath.startsWith("node:") || sourcePath.startsWith("/") || /^[a-zA-Z]+:\/\//.test(sourcePath)) {
4400
4669
  return sourcePath;
4401
4670
  }
4402
- return resolve5(process.cwd(), sourcePath);
4671
+ return resolve6(process.cwd(), sourcePath);
4403
4672
  });
4404
4673
  parsed.sourceRoot = void 0;
4405
4674
  return JSON.stringify(parsed);
@@ -4721,7 +4990,7 @@ function resolveExecutionProfile(override) {
4721
4990
  // src/plays/local-file-discovery.ts
4722
4991
  import { createHash as createHash2 } from "crypto";
4723
4992
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
4724
- import { basename as basename2, dirname as dirname6, extname as extname2, isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve6 } from "path";
4993
+ import { basename as basename2, dirname as dirname6, extname as extname2, isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve7 } from "path";
4725
4994
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
4726
4995
  function sha2562(buffer) {
4727
4996
  return createHash2("sha256").update(buffer).digest("hex");
@@ -4922,7 +5191,7 @@ function isPathInsideDirectory2(filePath, directory) {
4922
5191
  return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute2(relativePath);
4923
5192
  }
4924
5193
  async function resolveLocalImport2(fromFile, specifier) {
4925
- const base = isAbsolute2(specifier) ? resolve6(specifier) : resolve6(dirname6(fromFile), specifier);
5194
+ const base = isAbsolute2(specifier) ? resolve7(specifier) : resolve7(dirname6(fromFile), specifier);
4926
5195
  const candidates = [base];
4927
5196
  const explicitExtension = extname2(base).toLowerCase();
4928
5197
  if (!explicitExtension) {
@@ -4940,13 +5209,13 @@ async function resolveLocalImport2(fromFile, specifier) {
4940
5209
  throw new Error(`Could not resolve local import "${specifier}" from ${fromFile}`);
4941
5210
  }
4942
5211
  async function discoverPackagedLocalFiles(entryFile) {
4943
- const absoluteEntryFile = resolve6(entryFile);
5212
+ const absoluteEntryFile = resolve7(entryFile);
4944
5213
  const packagingRoot = dirname6(absoluteEntryFile);
4945
5214
  const files = /* @__PURE__ */ new Map();
4946
5215
  const unresolved = [];
4947
5216
  const visitedFiles = /* @__PURE__ */ new Set();
4948
5217
  const visitSourceFile = async (filePath) => {
4949
- const absolutePath = resolve6(filePath);
5218
+ const absolutePath = resolve7(filePath);
4950
5219
  if (visitedFiles.has(absolutePath)) {
4951
5220
  return;
4952
5221
  }
@@ -4978,7 +5247,7 @@ async function discoverPackagedLocalFiles(entryFile) {
4978
5247
  message: "Could not resolve this ctx.csv(...) path at submit time. Use a string literal, a top-level const string, or pass a runtime input like input.file."
4979
5248
  });
4980
5249
  } else {
4981
- const absoluteCsvPath = resolve6(dirname6(absolutePath), resolvedPath);
5250
+ const absoluteCsvPath = resolve7(dirname6(absolutePath), resolvedPath);
4982
5251
  if (isAbsolute2(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
4983
5252
  unresolved.push({
4984
5253
  sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
@@ -5018,23 +5287,23 @@ async function discoverPackagedLocalFiles(entryFile) {
5018
5287
  // src/plays/bundle-play-file.ts
5019
5288
  var PLAY_BUNDLE_CACHE_VERSION2 = 30;
5020
5289
  var MODULE_DIR = dirname7(fileURLToPath(import.meta.url));
5021
- var SDK_PACKAGE_ROOT = resolve7(MODULE_DIR, "..", "..");
5022
- var SOURCE_REPO_ROOT = resolve7(SDK_PACKAGE_ROOT, "..");
5290
+ var SDK_PACKAGE_ROOT = resolve8(MODULE_DIR, "..", "..");
5291
+ var SOURCE_REPO_ROOT = resolve8(SDK_PACKAGE_ROOT, "..");
5023
5292
  var HAS_SOURCE_BUNDLING_SOURCES = existsSync5(
5024
- resolve7(SOURCE_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5293
+ resolve8(SOURCE_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5025
5294
  );
5026
- var PACKAGED_REPO_ROOT = resolve7(SDK_PACKAGE_ROOT, "dist", "repo");
5295
+ var PACKAGED_REPO_ROOT = resolve8(SDK_PACKAGE_ROOT, "dist", "repo");
5027
5296
  var HAS_PACKAGED_BUNDLING_SOURCES = existsSync5(
5028
- resolve7(PACKAGED_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5297
+ resolve8(PACKAGED_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5029
5298
  );
5030
- var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED_BUNDLING_SOURCES ? PACKAGED_REPO_ROOT : resolve7(SDK_PACKAGE_ROOT, "..");
5031
- var SDK_SOURCE_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? resolve7(SOURCE_REPO_ROOT, "sdk", "src") : HAS_PACKAGED_BUNDLING_SOURCES ? resolve7(PACKAGED_REPO_ROOT, "sdk", "src") : resolve7(SDK_PACKAGE_ROOT, "src");
5032
- var SDK_PACKAGE_JSON = resolve7(SDK_PACKAGE_ROOT, "package.json");
5033
- var SDK_ENTRY_FILE = resolve7(SDK_SOURCE_ROOT, "index.ts");
5034
- var SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : resolve7(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
5035
- var SDK_WORKERS_ENTRY_FILE = resolve7(SDK_SOURCE_ROOT, "worker-play-entry.ts");
5036
- var WORKERS_HARNESS_ENTRY_FILE = resolve7(PROJECT_ROOT, "apps", "play-runner-workers", "src", "entry.ts");
5037
- var WORKERS_HARNESS_FILES_DIR = resolve7(PROJECT_ROOT, "apps", "play-runner-workers", "src");
5299
+ var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED_BUNDLING_SOURCES ? PACKAGED_REPO_ROOT : resolve8(SDK_PACKAGE_ROOT, "..");
5300
+ var SDK_SOURCE_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? resolve8(SOURCE_REPO_ROOT, "sdk", "src") : HAS_PACKAGED_BUNDLING_SOURCES ? resolve8(PACKAGED_REPO_ROOT, "sdk", "src") : resolve8(SDK_PACKAGE_ROOT, "src");
5301
+ var SDK_PACKAGE_JSON = resolve8(SDK_PACKAGE_ROOT, "package.json");
5302
+ var SDK_ENTRY_FILE = resolve8(SDK_SOURCE_ROOT, "index.ts");
5303
+ var SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : resolve8(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
5304
+ var SDK_WORKERS_ENTRY_FILE = resolve8(SDK_SOURCE_ROOT, "worker-play-entry.ts");
5305
+ var WORKERS_HARNESS_ENTRY_FILE = resolve8(PROJECT_ROOT, "apps", "play-runner-workers", "src", "entry.ts");
5306
+ var WORKERS_HARNESS_FILES_DIR = resolve8(PROJECT_ROOT, "apps", "play-runner-workers", "src");
5038
5307
  var hasWarnedAboutNonDevelopmentBundling = false;
5039
5308
  function warnAboutNonDevelopmentBundling(filePath) {
5040
5309
  if (hasWarnedAboutNonDevelopmentBundling) {
@@ -5058,7 +5327,7 @@ function defaultPlayBundleTarget() {
5058
5327
  function createSdkPlayBundlingAdapter() {
5059
5328
  return {
5060
5329
  projectRoot: PROJECT_ROOT,
5061
- nodeModulesDir: resolve7(PROJECT_ROOT, "node_modules"),
5330
+ nodeModulesDir: resolve8(PROJECT_ROOT, "node_modules"),
5062
5331
  cacheDir: join5(tmpdir2(), `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION2}`),
5063
5332
  sdkSourceRoot: SDK_SOURCE_ROOT,
5064
5333
  sdkPackageJson: SDK_PACKAGE_JSON,
@@ -5296,7 +5565,7 @@ function traceCliSync(phase, fields, run) {
5296
5565
  }
5297
5566
  }
5298
5567
  function sleep4(ms) {
5299
- return new Promise((resolve10) => setTimeout(resolve10, ms));
5568
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
5300
5569
  }
5301
5570
  function parseReferencedPlayTarget(target) {
5302
5571
  const trimmed = target.trim();
@@ -5342,7 +5611,7 @@ function formatPlayListReference(play) {
5342
5611
  function defaultMaterializedPlayPath(reference) {
5343
5612
  const playName = parseReferencedPlayTarget(reference).unqualifiedPlayName;
5344
5613
  const safeName = playName.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
5345
- return resolve8(`${safeName || "play"}.play.ts`);
5614
+ return resolve9(`${safeName || "play"}.play.ts`);
5346
5615
  }
5347
5616
  function materializeRemotePlaySource(input) {
5348
5617
  if (isFileTarget(input.target)) {
@@ -5357,10 +5626,10 @@ function materializeRemotePlaySource(input) {
5357
5626
  if (existingSource === input.sourceCode) {
5358
5627
  return { path: outputPath, status: "unchanged", created: false };
5359
5628
  }
5360
- writeFileSync5(outputPath, input.sourceCode, "utf-8");
5629
+ writeFileSync6(outputPath, input.sourceCode, "utf-8");
5361
5630
  return { path: outputPath, status: "updated", created: false };
5362
5631
  }
5363
- writeFileSync5(outputPath, input.sourceCode, "utf-8");
5632
+ writeFileSync6(outputPath, input.sourceCode, "utf-8");
5364
5633
  return { path: outputPath, status: "created", created: true };
5365
5634
  }
5366
5635
  function formatLoadedPlayMessage(materializedFile) {
@@ -5405,7 +5674,7 @@ function extractPlayName(code, filePath) {
5405
5674
  throw buildMissingDefinePlayError(filePath);
5406
5675
  }
5407
5676
  function isFileTarget(target) {
5408
- return existsSync6(resolve8(target));
5677
+ return existsSync6(resolve9(target));
5409
5678
  }
5410
5679
  function looksLikeFilePath(target) {
5411
5680
  if (target.trim().toLowerCase().startsWith("prebuilt/")) {
@@ -5424,7 +5693,7 @@ function parsePositiveInteger2(value, flagName) {
5424
5693
  return parsed;
5425
5694
  }
5426
5695
  function parseJsonInput(raw) {
5427
- const source = raw.startsWith("@") ? readFileSync5(resolve8(raw.slice(1)), "utf-8") : raw;
5696
+ const source = raw.startsWith("@") ? readFileSync5(resolve9(raw.slice(1)), "utf-8") : raw;
5428
5697
  const parsed = JSON.parse(source);
5429
5698
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
5430
5699
  throw new Error("--input must be a JSON object.");
@@ -5525,7 +5794,7 @@ function fileInputBindingsFromStaticPipeline(staticPipeline) {
5525
5794
  function isLocalFilePathValue(value) {
5526
5795
  if (typeof value !== "string" || !value.trim()) return false;
5527
5796
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
5528
- return existsSync6(resolve8(value));
5797
+ return existsSync6(resolve9(value));
5529
5798
  }
5530
5799
  function inputContainsLocalFilePath(value) {
5531
5800
  if (isLocalFilePathValue(value)) {
@@ -5551,7 +5820,7 @@ async function stageFileInputArgs(input) {
5551
5820
  const localFiles = uniqueBindings.flatMap((binding) => {
5552
5821
  const value = getDottedInputValue(input.runtimeInput, binding.inputPath);
5553
5822
  if (!isLocalFilePathValue(value)) return [];
5554
- const absolutePath = resolve8(value);
5823
+ const absolutePath = resolve9(value);
5555
5824
  return [{ binding, absolutePath, logicalPath: basename3(absolutePath) }];
5556
5825
  });
5557
5826
  if (localFiles.length === 0) {
@@ -5587,9 +5856,9 @@ function stageFile(logicalPath, absolutePath) {
5587
5856
  }
5588
5857
  function normalizePlayPath(filePath) {
5589
5858
  try {
5590
- return realpathSync.native(resolve8(filePath));
5859
+ return realpathSync.native(resolve9(filePath));
5591
5860
  } catch {
5592
- return resolve8(filePath);
5861
+ return resolve9(filePath);
5593
5862
  }
5594
5863
  }
5595
5864
  function formatBundlingErrors(filePath, errors) {
@@ -5742,11 +6011,42 @@ function isTransientPlayStreamError(error) {
5742
6011
  text
5743
6012
  );
5744
6013
  }
6014
+ function playStatusErrorText(status) {
6015
+ const chunks = [];
6016
+ const progressError = status.progress?.error;
6017
+ if (typeof progressError === "string" && progressError.trim()) {
6018
+ chunks.push(progressError.trim());
6019
+ }
6020
+ const errorValue = status.error;
6021
+ if (typeof errorValue === "string" && errorValue.trim()) {
6022
+ chunks.push(errorValue.trim());
6023
+ }
6024
+ const errors = status.errors;
6025
+ if (Array.isArray(errors)) {
6026
+ for (const error of errors) {
6027
+ if (typeof error === "string" && error.trim()) {
6028
+ chunks.push(error.trim());
6029
+ } else if (error && typeof error === "object") {
6030
+ const message = error.message;
6031
+ if (typeof message === "string" && message.trim()) {
6032
+ chunks.push(message.trim());
6033
+ }
6034
+ }
6035
+ }
6036
+ }
6037
+ return chunks.join("; ") || status.status;
6038
+ }
6039
+ function isRetryablePendingStartFailure(status) {
6040
+ if (status.status !== "failed") return false;
6041
+ if (status.runId && status.runId !== "pending") return false;
6042
+ return isTransientPlayStreamError(new Error(playStatusErrorText(status)));
6043
+ }
5745
6044
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
5746
6045
  "completed",
5747
6046
  "failed",
5748
6047
  "cancelled"
5749
6048
  ]);
6049
+ var PLAY_START_TRANSIENT_RETRY_DELAYS_MS = [500, 1500];
5750
6050
  function getEventPayload(event) {
5751
6051
  return event.payload && typeof event.payload === "object" ? event.payload : {};
5752
6052
  }
@@ -5908,6 +6208,34 @@ async function waitForPlayCompletionByStream(input) {
5908
6208
  );
5909
6209
  }
5910
6210
  async function startAndWaitForPlayCompletionByStream(input) {
6211
+ for (let attempt = 0; attempt <= PLAY_START_TRANSIENT_RETRY_DELAYS_MS.length; attempt += 1) {
6212
+ const status = await startAndWaitForPlayCompletionByStreamOnce(input);
6213
+ const retryDelayMs = PLAY_START_TRANSIENT_RETRY_DELAYS_MS[attempt];
6214
+ if (retryDelayMs === void 0 || !isRetryablePendingStartFailure(status)) {
6215
+ return status;
6216
+ }
6217
+ if (!input.jsonOutput) {
6218
+ input.progress.writeLine(
6219
+ `[play watch] start failed before run id with a transient error; retrying (${playStatusErrorText(status)})`
6220
+ );
6221
+ }
6222
+ recordCliTrace({
6223
+ phase: "cli.play_start_stream_retry",
6224
+ ms: 0,
6225
+ ok: true,
6226
+ playName: input.playName,
6227
+ attempt: attempt + 1,
6228
+ reason: playStatusErrorText(status)
6229
+ });
6230
+ await sleep4(retryDelayMs);
6231
+ }
6232
+ throw new DeeplineError(
6233
+ `Play ${input.playName} did not start after retrying transient start failures.`,
6234
+ void 0,
6235
+ "PLAY_START_RETRY_EXHAUSTED"
6236
+ );
6237
+ }
6238
+ async function startAndWaitForPlayCompletionByStreamOnce(input) {
5911
6239
  const startedAt = Date.now();
5912
6240
  const dashboardUrl = buildPlayDashboardUrl(
5913
6241
  input.client.baseUrl,
@@ -6234,14 +6562,24 @@ function buildRunWarnings(status, rowsInfo) {
6234
6562
  }
6235
6563
  return [];
6236
6564
  }
6237
- function buildRunNextCommands(runId, dashboardUrl) {
6565
+ function buildRunNextCommands(status) {
6566
+ const runId = status.runId?.trim();
6567
+ if (!runId) {
6568
+ const playName = extractRunPlayName(status);
6569
+ return playName ? {
6570
+ list: `deepline runs list --play ${playName} --json`
6571
+ } : {
6572
+ list: "deepline runs list --json"
6573
+ };
6574
+ }
6238
6575
  const commands = {
6239
6576
  get: `deepline runs get ${runId} --json`,
6577
+ full: `deepline runs get ${runId} --full --json`,
6240
6578
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
6241
6579
  logs: `deepline runs logs ${runId} --out run.log --json`
6242
6580
  };
6243
- if (dashboardUrl) {
6244
- commands.open = `Open ${dashboardUrl} to see results.`;
6581
+ if (status.dashboardUrl) {
6582
+ commands.open = `Open ${status.dashboardUrl} to see results.`;
6245
6583
  }
6246
6584
  return commands;
6247
6585
  }
@@ -6264,6 +6602,121 @@ function getTimestampField(value, key) {
6264
6602
  }
6265
6603
  return typeof field === "string" && field.trim() ? field : null;
6266
6604
  }
6605
+ function getObjectField(value, key) {
6606
+ const field = getRecordField(value, key);
6607
+ return field && typeof field === "object" && !Array.isArray(field) ? field : null;
6608
+ }
6609
+ function formatCreditAmount(value) {
6610
+ if (typeof value !== "number" || !Number.isFinite(value)) {
6611
+ return String(value ?? "-");
6612
+ }
6613
+ return Number(value.toFixed(8)).toString();
6614
+ }
6615
+ function extractJsonObjectFromText(text) {
6616
+ const start = text.indexOf("{");
6617
+ if (start < 0) {
6618
+ return null;
6619
+ }
6620
+ const suffix = text.slice(start);
6621
+ try {
6622
+ const parsed = JSON.parse(suffix);
6623
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
6624
+ } catch {
6625
+ let depth = 0;
6626
+ let inString = false;
6627
+ let escaped = false;
6628
+ for (let index = start; index < text.length; index += 1) {
6629
+ const char = text[index];
6630
+ if (inString) {
6631
+ if (escaped) {
6632
+ escaped = false;
6633
+ } else if (char === "\\") {
6634
+ escaped = true;
6635
+ } else if (char === '"') {
6636
+ inString = false;
6637
+ }
6638
+ continue;
6639
+ }
6640
+ if (char === '"') {
6641
+ inString = true;
6642
+ } else if (char === "{") {
6643
+ depth += 1;
6644
+ } else if (char === "}") {
6645
+ depth -= 1;
6646
+ if (depth === 0) {
6647
+ try {
6648
+ const parsed = JSON.parse(text.slice(start, index + 1));
6649
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
6650
+ } catch {
6651
+ return null;
6652
+ }
6653
+ }
6654
+ }
6655
+ }
6656
+ }
6657
+ return null;
6658
+ }
6659
+ function extractBillingFromText(text) {
6660
+ if (!text) {
6661
+ return null;
6662
+ }
6663
+ const parsed = extractJsonObjectFromText(text);
6664
+ return getObjectField(parsed, "billing");
6665
+ }
6666
+ function extractToolIdFromErrorText(text) {
6667
+ if (!text) {
6668
+ return null;
6669
+ }
6670
+ const lowerToolMatch = /\btool\s+([A-Za-z0-9_.:-]+)\s+\d{3}\b/.exec(text);
6671
+ if (lowerToolMatch?.[1]) {
6672
+ return lowerToolMatch[1];
6673
+ }
6674
+ const upperToolMatch = /\bTool\s+([A-Za-z0-9_.:-]+)\s+failed\b/.exec(text);
6675
+ return upperToolMatch?.[1] ?? null;
6676
+ }
6677
+ function isInsufficientCreditsBilling(billing) {
6678
+ return billing?.kind === "insufficient_credits";
6679
+ }
6680
+ function extractBillingForStatus(status, error) {
6681
+ const errorBilling = getObjectField(status, "errorBilling");
6682
+ if (errorBilling) {
6683
+ return errorBilling;
6684
+ }
6685
+ const directErrors = getRecordField(status, "errors");
6686
+ if (Array.isArray(directErrors)) {
6687
+ for (const entry of directErrors) {
6688
+ const billing = getObjectField(entry, "billing");
6689
+ if (billing) {
6690
+ return billing;
6691
+ }
6692
+ }
6693
+ }
6694
+ const progressError = getStringField(status.progress, "error");
6695
+ return extractBillingFromText(error) ?? extractBillingFromText(progressError) ?? getObjectField(status, "billing");
6696
+ }
6697
+ function formatInsufficientCreditsMessage(input) {
6698
+ const operation = getStringField(input.billing, "operation_id") ?? getStringField(input.billing, "operation") ?? extractToolIdFromErrorText(input.error) ?? getStringField(input.billing, "provider") ?? "tool call";
6699
+ const balance = formatCreditAmount(input.billing.balance_credits);
6700
+ const required = formatCreditAmount(input.billing.required_credits);
6701
+ const recommended = formatCreditAmount(
6702
+ input.billing.recommended_add_credits ?? input.billing.needed_credits
6703
+ );
6704
+ const billingUrl = getStringField(input.billing, "billing_url");
6705
+ const workspace = getStringField(input.billing, "workspace_id") ?? getStringField(input.billing, "workspaceId");
6706
+ const workspaceSuffix = workspace ? ` (workspace=${workspace})` : "";
6707
+ const addSuffix = billingUrl && recommended !== "-" ? ` Add >=${recommended} at ${billingUrl}.` : billingUrl ? ` Add credits at ${billingUrl}.` : "";
6708
+ return `Workspace balance ${balance} < required ${required} for ${operation}${workspaceSuffix}.${addSuffix}`;
6709
+ }
6710
+ function formatPlayErrorForDisplay(status, error) {
6711
+ if (!error) {
6712
+ return null;
6713
+ }
6714
+ const billing = extractBillingForStatus(status, error);
6715
+ if (isInsufficientCreditsBilling(billing)) {
6716
+ return formatInsufficientCreditsMessage({ billing, error });
6717
+ }
6718
+ return error;
6719
+ }
6267
6720
  function normalizeRunStatusForEnvelope(status) {
6268
6721
  const run = status.run ?? null;
6269
6722
  return {
@@ -6314,18 +6767,33 @@ function normalizeErrorsForEnvelope(status, error) {
6314
6767
  if (Array.isArray(directErrors)) {
6315
6768
  return directErrors.filter(
6316
6769
  (entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)
6317
- );
6770
+ ).map((entry) => {
6771
+ const message2 = typeof entry.message === "string" && entry.message.trim() ? entry.message : error;
6772
+ const billing2 = getObjectField(entry, "billing");
6773
+ if (!isInsufficientCreditsBilling(billing2) || !message2) {
6774
+ return entry;
6775
+ }
6776
+ return {
6777
+ ...entry,
6778
+ message: formatInsufficientCreditsMessage({ billing: billing2, error: message2 }),
6779
+ billing: stripProviderSpendFromBilling(billing2)
6780
+ };
6781
+ });
6318
6782
  }
6319
6783
  if (!error) {
6320
6784
  return [];
6321
6785
  }
6786
+ const nextCommands = buildRunNextCommands(status);
6787
+ const billing = extractBillingForStatus(status, error);
6788
+ const message = formatPlayErrorForDisplay(status, error) ?? error;
6322
6789
  return [
6323
6790
  {
6324
6791
  code: getStringField(status, "errorCode") ?? "RUN_FAILED",
6325
6792
  phase: getStringField(status, "errorPhase") ?? "runtime",
6326
- message: error,
6793
+ message,
6327
6794
  retryable: typeof getRecordField(status, "retryable") === "boolean" ? getRecordField(status, "retryable") : null,
6328
- nextAction: `deepline runs get ${status.runId} --json`
6795
+ ...billing ? { billing: stripProviderSpendFromBilling(billing) } : {},
6796
+ nextAction: nextCommands.get ?? nextCommands.list
6329
6797
  }
6330
6798
  ];
6331
6799
  }
@@ -6374,6 +6842,7 @@ function compactPlayStatus(status) {
6374
6842
  ) : null;
6375
6843
  const progressError = status.progress?.error;
6376
6844
  const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
6845
+ const displayError = formatPlayErrorForDisplay(status, error);
6377
6846
  return {
6378
6847
  runId: status.runId,
6379
6848
  apiVersion: status.apiVersion ?? 1,
@@ -6386,13 +6855,13 @@ function compactPlayStatus(status) {
6386
6855
  steps: normalizeStepsForEnvelope(status),
6387
6856
  errors: normalizeErrorsForEnvelope(status, error),
6388
6857
  logs: normalizeLogsForEnvelope(status),
6389
- ...error ? { error } : {},
6858
+ ...displayError ? { error: displayError } : {},
6390
6859
  ...warnings.length > 0 ? { warnings } : {},
6391
6860
  ...result !== void 0 ? { result } : {},
6392
6861
  ...status.resultView ? { resultView: status.resultView } : {},
6393
6862
  ...datasetStats ? { dataset_stats: datasetStats } : {},
6394
6863
  ...billing ? { billing } : {},
6395
- next: buildRunNextCommands(status.runId, status.dashboardUrl)
6864
+ next: buildRunNextCommands(status)
6396
6865
  };
6397
6866
  }
6398
6867
  function enrichPlayStatusWithDatasetStats(status) {
@@ -6466,7 +6935,8 @@ function writePlayResult(status, jsonOutput, options) {
6466
6935
  lines.push(...formatDatasetStatsLines(datasetStats));
6467
6936
  const progressError = status.progress?.error;
6468
6937
  if (progressError && typeof progressError === "string") {
6469
- lines.push(` error: ${progressError.slice(0, 200)}`);
6938
+ const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
6939
+ lines.push(` error: ${displayError.slice(0, 200)}`);
6470
6940
  }
6471
6941
  const renderedServerView = renderServerResultView(status.resultView);
6472
6942
  if (result) {
@@ -6490,8 +6960,11 @@ var RUN_EXPORT_PAGE_SIZE = 5e3;
6490
6960
  function shellSingleQuote(value) {
6491
6961
  return `'${value.replace(/'/g, `'\\''`)}'`;
6492
6962
  }
6963
+ function sqlStringLiteral(value) {
6964
+ return `'${value.replace(/'/g, "''")}'`;
6965
+ }
6493
6966
  function runExportRetryCommand(runId, outPath, datasetPath) {
6494
- return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote(resolve8(outPath))}`;
6967
+ return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote(resolve9(outPath))}`;
6495
6968
  }
6496
6969
  function extractRunPlayName(status) {
6497
6970
  const run = status.run;
@@ -6508,6 +6981,26 @@ function extractRunPlayName(status) {
6508
6981
  }
6509
6982
  return null;
6510
6983
  }
6984
+ function normalizeCustomerDbIdentifier(value) {
6985
+ return value.split("/").pop().replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
6986
+ }
6987
+ function buildCustomerDbQueryPlan(input) {
6988
+ const playName = extractRunPlayName(input.status);
6989
+ const tableNamespace = input.rowsInfo.tableNamespace?.trim();
6990
+ if (!playName || !tableNamespace || input.rowsInfo.totalRows <= 0) {
6991
+ return null;
6992
+ }
6993
+ const tableName = `${normalizeCustomerDbIdentifier(playName)}_${normalizeCustomerDbIdentifier(
6994
+ tableNamespace
6995
+ )}`;
6996
+ const sql = `select * from "storage"."${tableName}" where _run_id = ${sqlStringLiteral(input.status.runId)} limit ${input.rowsInfo.totalRows}`;
6997
+ const base = `deepline customer-db query --sql ${shellSingleQuote(sql)} --max-rows ${input.rowsInfo.totalRows}`;
6998
+ return {
6999
+ sql,
7000
+ json: `${base} --json`,
7001
+ csv: `${base} --format csv --out ${shellSingleQuote(resolve9(input.outPath))}`
7002
+ };
7003
+ }
6511
7004
  function exportableSheetRow(row) {
6512
7005
  if (!row || typeof row !== "object" || Array.isArray(row)) {
6513
7006
  return null;
@@ -6679,6 +7172,60 @@ async function exportPlayStatusRows(client, status, outPath, options = {}) {
6679
7172
  }
6680
7173
  return { path: writeCanonicalRowsCsv(rowsInfo, outPath), rowsInfo };
6681
7174
  }
7175
+ function extractActiveRunsFromError(error) {
7176
+ if (!(error instanceof DeeplineError)) {
7177
+ return [];
7178
+ }
7179
+ const response = error.details?.response;
7180
+ if (!response || typeof response !== "object" || Array.isArray(response)) {
7181
+ return [];
7182
+ }
7183
+ const details = response.details;
7184
+ if (!details || typeof details !== "object" || Array.isArray(details)) {
7185
+ return [];
7186
+ }
7187
+ const activeRuns = details.activeRuns;
7188
+ return Array.isArray(activeRuns) ? activeRuns.filter(
7189
+ (run) => Boolean(run) && typeof run === "object" && !Array.isArray(run)
7190
+ ) : [];
7191
+ }
7192
+ function activeRunId(run) {
7193
+ return getStringField(run, "workflowId") ?? getStringField(run, "runId");
7194
+ }
7195
+ function formatActiveRunConflictError(input) {
7196
+ const lines = [
7197
+ `Active run exists for ${input.playName}. Use --force to supersede, or inspect/stop the active run first.`
7198
+ ];
7199
+ for (const run of input.activeRuns.slice(0, 3)) {
7200
+ const runId = activeRunId(run);
7201
+ if (!runId) {
7202
+ continue;
7203
+ }
7204
+ const status = getStringField(run, "status");
7205
+ const startedAt = getStringField(run, "startedAt") ?? getStringField(run, "startTime");
7206
+ lines.push(
7207
+ ` active: ${runId}${status ? ` status=${status}` : ""}${startedAt ? ` startedAt=${startedAt}` : ""}`
7208
+ );
7209
+ lines.push(` get: deepline runs get ${runId} --json`);
7210
+ lines.push(
7211
+ ` stop: deepline runs stop ${runId} --reason "stale lock" --json`
7212
+ );
7213
+ }
7214
+ lines.push(` rerun: add --force to the same deepline plays run command`);
7215
+ return lines.join("\n");
7216
+ }
7217
+ function normalizePlayStartError(error, playName) {
7218
+ const activeRuns = extractActiveRunsFromError(error);
7219
+ if (activeRuns.length === 0) {
7220
+ return error;
7221
+ }
7222
+ return new DeeplineError(
7223
+ formatActiveRunConflictError({ playName, activeRuns }),
7224
+ error instanceof DeeplineError ? error.statusCode : 409,
7225
+ "ACTIVE_RUN_EXISTS",
7226
+ error instanceof DeeplineError ? error.details : void 0
7227
+ );
7228
+ }
6682
7229
  function renderServerResultView(value) {
6683
7230
  if (!value || typeof value !== "object" || Array.isArray(value)) {
6684
7231
  return { lines: [], actions: [] };
@@ -6722,6 +7269,10 @@ function renderServerResultView(value) {
6722
7269
  );
6723
7270
  }
6724
7271
  const tables = Array.isArray(view.tables) ? view.tables : [];
7272
+ const topLevelOutputTable = view.topLevelOutputTable && typeof view.topLevelOutputTable === "object" && !Array.isArray(view.topLevelOutputTable) ? view.topLevelOutputTable : null;
7273
+ if (typeof topLevelOutputTable?.queryCommand === "string") {
7274
+ lines.push(` top-level outputs: ${topLevelOutputTable.queryCommand}`);
7275
+ }
6725
7276
  if (tables.length > 0) {
6726
7277
  lines.push(" tables:");
6727
7278
  for (const table of tables.slice(0, 6)) {
@@ -6738,6 +7289,21 @@ function renderServerResultView(value) {
6738
7289
  lines.push(
6739
7290
  ` ${String(table.tableNamespace ?? "table")}${lineLabel}: ${rowLabel}${details.join(" ")}`
6740
7291
  );
7292
+ if (typeof table.queryDatasetCommand === "string") {
7293
+ lines.push(` inspect rows: ${table.queryDatasetCommand}`);
7294
+ }
7295
+ if (table.debugHelp && typeof table.debugHelp === "object" && !Array.isArray(table.debugHelp)) {
7296
+ const debugHelp = table.debugHelp;
7297
+ const rawResultFields = Array.isArray(debugHelp.rawResultFields) ? debugHelp.rawResultFields.filter(
7298
+ (field) => typeof field === "string"
7299
+ ) : [];
7300
+ if (rawResultFields.length > 0) {
7301
+ lines.push(` tool-result columns: ${rawResultFields.join(", ")}`);
7302
+ }
7303
+ }
7304
+ if (typeof table.slowExportAsCsvCommand === "string") {
7305
+ lines.push(` export rows: ${table.slowExportAsCsvCommand}`);
7306
+ }
6741
7307
  }
6742
7308
  }
6743
7309
  return { lines: lines.length > 1 ? lines : [], actions: [] };
@@ -6763,7 +7329,8 @@ function writeStartedPlayRun(input) {
6763
7329
  ` get status: deepline runs get ${input.runId} --json`,
6764
7330
  ` logs: deepline runs logs ${input.runId} --json`,
6765
7331
  ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
6766
- ` result JSON: deepline runs get ${input.runId} --json`
7332
+ ` result JSON: deepline runs get ${input.runId} --json`,
7333
+ ` full debug JSON: deepline runs get ${input.runId} --full --json`
6767
7334
  ];
6768
7335
  if (input.dashboardUrl) {
6769
7336
  lines.push(` play page: ${input.dashboardUrl}`);
@@ -6914,11 +7481,11 @@ function shouldUseLocalOnlyPlayCheck() {
6914
7481
  async function handlePlayCheck(args) {
6915
7482
  const options = parsePlayCheckOptions(args);
6916
7483
  if (!isFileTarget(options.target)) {
6917
- const resolved = resolve8(options.target);
7484
+ const resolved = resolve9(options.target);
6918
7485
  console.error(`File not found: ${resolved}`);
6919
7486
  return 1;
6920
7487
  }
6921
- const absolutePlayPath = resolve8(options.target);
7488
+ const absolutePlayPath = resolve9(options.target);
6922
7489
  const sourceCode = readFileSync5(absolutePlayPath, "utf-8");
6923
7490
  let graph;
6924
7491
  try {
@@ -6982,7 +7549,7 @@ async function handleFileBackedRun(options) {
6982
7549
  }
6983
7550
  const client = new DeeplineClient();
6984
7551
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
6985
- const absolutePlayPath = resolve8(options.target.path);
7552
+ const absolutePlayPath = resolve9(options.target.path);
6986
7553
  progress.phase("compiling play");
6987
7554
  const sourceCode = traceCliSync(
6988
7555
  "cli.play_file_read_source",
@@ -7068,6 +7635,8 @@ async function handleFileBackedRun(options) {
7068
7635
  waitTimeoutMs: options.waitTimeoutMs,
7069
7636
  noOpen: options.noOpen,
7070
7637
  progress
7638
+ }).catch((error) => {
7639
+ throw normalizePlayStartError(error, playName);
7071
7640
  })
7072
7641
  );
7073
7642
  if (finalStatus.status === "completed") {
@@ -7086,7 +7655,9 @@ async function handleFileBackedRun(options) {
7086
7655
  const started = await traceCliSpan(
7087
7656
  "cli.play_start_unwatched",
7088
7657
  { targetKind: "file", playName },
7089
- () => client.startPlayRun(startRequest)
7658
+ () => client.startPlayRun(startRequest).catch((error) => {
7659
+ throw normalizePlayStartError(error, playName);
7660
+ })
7090
7661
  );
7091
7662
  const resolvedDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7092
7663
  openPlayDashboard({
@@ -7203,6 +7774,8 @@ async function handleNamedRun(options) {
7203
7774
  waitTimeoutMs: options.waitTimeoutMs,
7204
7775
  noOpen: options.noOpen,
7205
7776
  progress
7777
+ }).catch((error) => {
7778
+ throw normalizePlayStartError(error, playName);
7206
7779
  })
7207
7780
  );
7208
7781
  if (finalStatus.status === "completed") {
@@ -7221,7 +7794,9 @@ async function handleNamedRun(options) {
7221
7794
  const started = await traceCliSpan(
7222
7795
  "cli.play_start_unwatched",
7223
7796
  { targetKind: "name", playName },
7224
- () => client.startPlayRun(startRequest)
7797
+ () => client.startPlayRun(startRequest).catch((error) => {
7798
+ throw normalizePlayStartError(error, playName);
7799
+ })
7225
7800
  );
7226
7801
  const resolvedDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7227
7802
  openPlayDashboard({
@@ -7247,7 +7822,7 @@ async function handlePlayRun(args) {
7247
7822
  if (isFileTarget(options.target.path)) {
7248
7823
  return handleFileBackedRun(options);
7249
7824
  }
7250
- const resolved = resolve8(options.target.path);
7825
+ const resolved = resolve9(options.target.path);
7251
7826
  console.error(`File not found: ${resolved}`);
7252
7827
  const dir = dirname8(resolved);
7253
7828
  if (existsSync6(dir)) {
@@ -7394,14 +7969,14 @@ async function handleRunLogs(args) {
7394
7969
  continue;
7395
7970
  }
7396
7971
  if (arg === "--out" && args[index + 1]) {
7397
- outPath = resolve8(args[++index]);
7972
+ outPath = resolve9(args[++index]);
7398
7973
  }
7399
7974
  }
7400
7975
  const client = new DeeplineClient();
7401
7976
  const status = await client.runs.get(runId);
7402
7977
  const logs = status.progress?.logs ?? [];
7403
7978
  if (outPath) {
7404
- writeFileSync5(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
7979
+ writeFileSync6(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
7405
7980
  printCommandEnvelope({
7406
7981
  runId: status.runId,
7407
7982
  log_path: outPath,
@@ -7457,7 +8032,7 @@ async function handleRunStop(args) {
7457
8032
  return 0;
7458
8033
  }
7459
8034
  async function handleRunExport(args) {
7460
- const usage = "Usage: deepline runs export <run-id> [--dataset result.rows] --out output.csv [--json]";
8035
+ const usage = "Usage: deepline runs export <run-id> [--dataset result.rows] --out output.csv [--metadata-out export.json] [--json]";
7461
8036
  let runId;
7462
8037
  try {
7463
8038
  runId = parseRunIdPositional(args, usage);
@@ -7467,14 +8042,19 @@ async function handleRunExport(args) {
7467
8042
  }
7468
8043
  let outPath = null;
7469
8044
  let datasetPath = null;
8045
+ let metadataOutPath = null;
7470
8046
  for (let index = 0; index < args.length; index += 1) {
7471
8047
  const arg = args[index];
7472
8048
  if (arg === "--out" && args[index + 1]) {
7473
- outPath = resolve8(args[++index]);
8049
+ outPath = resolve9(args[++index]);
7474
8050
  continue;
7475
8051
  }
7476
8052
  if (arg === "--dataset" && args[index + 1]) {
7477
8053
  datasetPath = args[++index];
8054
+ continue;
8055
+ }
8056
+ if (arg === "--metadata-out" && args[index + 1]) {
8057
+ metadataOutPath = resolve9(args[++index]);
7478
8058
  }
7479
8059
  }
7480
8060
  if (!outPath) {
@@ -7486,15 +8066,59 @@ async function handleRunExport(args) {
7486
8066
  const exportResult = await exportPlayStatusRows(client, status, outPath, {
7487
8067
  datasetPath
7488
8068
  });
7489
- printCommandEnvelope({
8069
+ const source = exportResult?.rowsInfo.source ?? datasetPath ?? null;
8070
+ const queryPlan = exportResult && outPath ? buildCustomerDbQueryPlan({
8071
+ status,
8072
+ rowsInfo: exportResult.rowsInfo,
8073
+ outPath
8074
+ }) : null;
8075
+ const next = {
8076
+ ...buildRunNextCommands(status),
8077
+ export: runExportRetryCommand(status.runId, outPath, datasetPath ?? source),
8078
+ ...queryPlan ? {
8079
+ queryJson: queryPlan.json,
8080
+ queryCsv: queryPlan.csv
8081
+ } : {}
8082
+ };
8083
+ const payload = {
7490
8084
  runId: status.runId,
7491
8085
  ...datasetPath ? { dataset: datasetPath } : {},
7492
8086
  csv_path: exportResult?.path ?? null,
8087
+ source,
7493
8088
  rowCount: exportResult?.rowsInfo.totalRows ?? null,
7494
8089
  columns: exportResult?.rowsInfo.columns ?? [],
8090
+ ...queryPlan ? {
8091
+ query: {
8092
+ sql: queryPlan.sql,
8093
+ json: queryPlan.json,
8094
+ csv: queryPlan.csv
8095
+ }
8096
+ } : {},
8097
+ ...metadataOutPath ? { metadata_path: metadataOutPath } : {},
7495
8098
  local: { csv_path: exportResult?.path ?? null },
7496
- render: { sections: [{ title: "run export", lines: [`Exported ${status.runId} to ${exportResult?.path ?? outPath}`] }] }
7497
- }, { json: argsWantJson(args) });
8099
+ next,
8100
+ render: {
8101
+ sections: [
8102
+ {
8103
+ title: "run export",
8104
+ lines: [
8105
+ `Exported ${status.runId} to ${exportResult?.path ?? outPath}`,
8106
+ ...source ? [`source=${source}`] : [],
8107
+ ...queryPlan ? [`query=${queryPlan.json}`] : []
8108
+ ]
8109
+ }
8110
+ ]
8111
+ }
8112
+ };
8113
+ if (metadataOutPath) {
8114
+ writeFileSync6(
8115
+ metadataOutPath,
8116
+ `${JSON.stringify(payload, null, 2)}
8117
+ `,
8118
+ "utf-8"
8119
+ );
8120
+ }
8121
+ printCommandEnvelope(payload, { json: argsWantJson(args) });
7498
8122
  return 0;
7499
8123
  }
7500
8124
  async function handlePlayGet(args) {
@@ -7511,10 +8135,10 @@ async function handlePlayGet(args) {
7511
8135
  for (let index = 1; index < args.length; index += 1) {
7512
8136
  const arg = args[index];
7513
8137
  if (arg === "--out" && args[index + 1]) {
7514
- outPath = resolve8(args[++index]);
8138
+ outPath = resolve9(args[++index]);
7515
8139
  }
7516
8140
  }
7517
- const playName = isFileTarget(target) ? extractPlayName(readFileSync5(resolve8(target), "utf-8"), resolve8(target)) : parseReferencedPlayTarget(target).playName;
8141
+ const playName = isFileTarget(target) ? extractPlayName(readFileSync5(resolve9(target), "utf-8"), resolve9(target)) : parseReferencedPlayTarget(target).playName;
7518
8142
  const detail = isFileTarget(target) ? await client.getPlay(playName) : await assertCanonicalNamedPlayReference(client, target);
7519
8143
  const resolvedSource = detail.play.workingRevision?.sourceCode ?? detail.play.liveRevision?.sourceCode ?? detail.play.currentRevision?.sourceCode ?? detail.play.sourceCode ?? "";
7520
8144
  const materializedFile = outPath ? materializeRemotePlaySource({
@@ -7806,7 +8430,7 @@ async function handlePlayPublish(args) {
7806
8430
  }
7807
8431
  let graph;
7808
8432
  try {
7809
- graph = await collectBundledPlayGraph(resolve8(playName));
8433
+ graph = await collectBundledPlayGraph(resolve9(playName));
7810
8434
  await compileBundledPlayGraphManifests(client, graph);
7811
8435
  await publishImportedPlayDependencies(client, graph);
7812
8436
  } catch (error) {
@@ -8292,30 +8916,34 @@ Examples:
8292
8916
  Notes:
8293
8917
  Writes a returned dataset handle to the requested local CSV path. Use runs get
8294
8918
  first to inspect dataset paths like result.rows or result.nested.contacts.
8919
+ --metadata-out writes the same export metadata object returned by --json,
8920
+ including source and follow-on customer-db query commands when available.
8295
8921
 
8296
8922
  Examples:
8297
8923
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
8298
8924
  deepline runs export play/my-play/run/20260501t000000-000 --dataset result.rows --out output.csv
8925
+ deepline runs export play/my-play/run/20260501t000000-000 --out output.csv --metadata-out output.meta.json
8299
8926
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv --json
8300
8927
  `
8301
- ).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) => {
8928
+ ).requiredOption("--out <path>", "Output CSV path").option("--dataset <path>", "Returned dataset handle path, such as result.rows").option("--metadata-out <path>", "Write export metadata JSON to a file").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
8302
8929
  process.exitCode = await handleRunExport([
8303
8930
  runId,
8304
8931
  ...options.dataset ? ["--dataset", options.dataset] : [],
8305
8932
  "--out",
8306
8933
  options.out,
8934
+ ...options.metadataOut ? ["--metadata-out", options.metadataOut] : [],
8307
8935
  ...options.json ? ["--json"] : []
8308
8936
  ]);
8309
8937
  });
8310
8938
  }
8311
8939
 
8312
8940
  // src/cli/commands/tools.ts
8313
- import { chmodSync, mkdtempSync, writeFileSync as writeFileSync7 } from "fs";
8941
+ import { chmodSync, mkdtempSync, writeFileSync as writeFileSync8 } from "fs";
8314
8942
  import { tmpdir as tmpdir3 } from "os";
8315
8943
  import { join as join8 } from "path";
8316
8944
 
8317
8945
  // src/tool-output.ts
8318
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
8946
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
8319
8947
  import { homedir as homedir3 } from "os";
8320
8948
  import { join as join7 } from "path";
8321
8949
  function isPlainObject(value) {
@@ -8340,6 +8968,21 @@ function normalizeRows(value) {
8340
8968
  }
8341
8969
  function candidateRoots(payload) {
8342
8970
  const roots = [{ path: null, value: payload }];
8971
+ if (isPlainObject(payload) && isPlainObject(payload.toolResponse)) {
8972
+ roots.push({ path: "toolResponse", value: payload.toolResponse });
8973
+ if (Object.prototype.hasOwnProperty.call(payload.toolResponse, "raw")) {
8974
+ roots.push({
8975
+ path: "toolResponse.raw",
8976
+ value: payload.toolResponse.raw
8977
+ });
8978
+ }
8979
+ }
8980
+ if (isPlainObject(payload) && isPlainObject(payload.output)) {
8981
+ roots.push({ path: "output", value: payload.output });
8982
+ if (Object.prototype.hasOwnProperty.call(payload.output, "body")) {
8983
+ roots.push({ path: "output.body", value: payload.output.body });
8984
+ }
8985
+ }
8343
8986
  if (isPlainObject(payload) && isPlainObject(payload.result)) {
8344
8987
  roots.push({ path: "result", value: payload.result });
8345
8988
  if (isPlainObject(payload.result.data)) {
@@ -8399,7 +9042,7 @@ function ensureOutputDir() {
8399
9042
  function writeJsonOutputFile(payload, stem) {
8400
9043
  const outputDir = ensureOutputDir();
8401
9044
  const outputPath = join7(outputDir, `${stem}_${Date.now()}.json`);
8402
- writeFileSync6(outputPath, JSON.stringify(payload, null, 2), "utf-8");
9045
+ writeFileSync7(outputPath, JSON.stringify(payload, null, 2), "utf-8");
8403
9046
  return outputPath;
8404
9047
  }
8405
9048
  function writeCsvOutputFile(rows, stem) {
@@ -8427,7 +9070,7 @@ function writeCsvOutputFile(rows, stem) {
8427
9070
  for (const row of rows) {
8428
9071
  lines.push(columns.map((column) => escapeCell(row[column])).join(","));
8429
9072
  }
8430
- writeFileSync6(outputPath, `${lines.join("\n")}
9073
+ writeFileSync7(outputPath, `${lines.join("\n")}
8431
9074
  `, "utf-8");
8432
9075
  const previewRows = rows.slice(0, 5);
8433
9076
  const previewColumns = columns.slice(0, 5);
@@ -8473,8 +9116,7 @@ async function listTools(args) {
8473
9116
  title: `${items.length} tools available:`,
8474
9117
  lines: items.flatMap((item) => {
8475
9118
  const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8476
- const listHint = item.listExtractorPaths?.length ? ` listExtractorPaths=${item.listExtractorPaths.join(",")}` : "";
8477
- return [`${item.toolId}${cats}`, ` ${item.description}${listHint}`];
9119
+ return [`${item.toolId}${cats}`, ` ${item.description}`];
8478
9120
  })
8479
9121
  }
8480
9122
  ]
@@ -8553,7 +9195,7 @@ Common commands:
8553
9195
  deepline tools execute hunter_email_verifier --input '{"email":"a@b.com"}'
8554
9196
 
8555
9197
  Output:
8556
- Use describe for tool contracts. get is accepted as a compatibility alias.
9198
+ Use describe for tool contracts.
8557
9199
  Use execute to run a tool. run is accepted as a compatibility alias.
8558
9200
  `
8559
9201
  );
@@ -8600,8 +9242,8 @@ Examples:
8600
9242
  `
8601
9243
  Notes:
8602
9244
  Shows the tool contract, input schema, output schema, Deepline cost, aliases,
8603
- and metadata. describe is the preferred discovery verb. get is kept as a
8604
- compatibility alias for the same metadata surface.
9245
+ and metadata. describe is the supported discovery verb. get is removed in
9246
+ the V2 SDK CLI; use describe for the same metadata surface.
8605
9247
 
8606
9248
  Examples:
8607
9249
  deepline tools describe hunter_email_verifier
@@ -8614,7 +9256,22 @@ Examples:
8614
9256
  ...options.json ? ["--json"] : []
8615
9257
  ]);
8616
9258
  });
8617
- addToolMetadataCommand(tools.command("describe <toolId>").alias("get"));
9259
+ addToolMetadataCommand(tools.command("describe <toolId>"));
9260
+ tools.command("get <toolId>").description("Deprecated. Use tools describe.").addHelpText(
9261
+ "after",
9262
+ `
9263
+ Examples:
9264
+ deepline tools describe hunter_email_verifier --json
9265
+ `
9266
+ ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (toolId, options) => {
9267
+ const message = `tools get has been removed from the V2 SDK CLI. Use: deepline tools describe ${toolId} --json`;
9268
+ if (options.json || shouldEmitJson()) {
9269
+ printJsonError({ message, code: "TOOLS_GET_REMOVED" });
9270
+ } else {
9271
+ console.error(message);
9272
+ }
9273
+ process.exitCode = 2;
9274
+ });
8618
9275
  tools.command("execute <toolId>").alias("run").description("Execute a tool by id.").addHelpText(
8619
9276
  "after",
8620
9277
  `
@@ -8623,7 +9280,7 @@ Notes:
8623
9280
  waterfalls, row maps, checkpoints, and retries.
8624
9281
  execute is the canonical execution verb. run is a compatibility alias.
8625
9282
  Calling a provider-backed tool can spend Deepline credits. Use --json for the
8626
- stable result payload and --full-output when debugging response metadata.
9283
+ stable result payload plus output preview and debugging helpers.
8627
9284
 
8628
9285
  Examples:
8629
9286
  deepline tools execute hunter_email_verifier --input '{"email":"a@b.com"}'
@@ -8636,7 +9293,7 @@ Examples:
8636
9293
  }, []).option("--json [payload]", "Emit JSON output. Use `--input` or `--payload` for passing JSON params.").option("--input <payload>", "Merge a JSON object into the tool params").option("--payload <payload>", "Merge a JSON object into the tool params").option(
8637
9294
  "--output-format <format>",
8638
9295
  "Output format: auto, csv, csv_file, json, or json_file"
8639
- ).option("--full-output", "Emit the full JSON payload").option("--no-preview", "Only print the extracted output path when applicable").action(async (toolId, options) => {
9296
+ ).option("--no-preview", "Only print the extracted output path when applicable").action(async (toolId, options) => {
8640
9297
  const args = [
8641
9298
  toolId,
8642
9299
  ...options.param.flatMap((value) => ["--param", value]),
@@ -8645,7 +9302,6 @@ Examples:
8645
9302
  ...options.input ? ["--input", options.input] : [],
8646
9303
  ...options.payload ? ["--payload", options.payload] : [],
8647
9304
  ...options.outputFormat ? ["--output-format", options.outputFormat] : [],
8648
- ...options.fullOutput ? ["--full-output"] : [],
8649
9305
  ...options.preview === false ? ["--no-preview"] : []
8650
9306
  ];
8651
9307
  process.exitCode = await executeTool(args);
@@ -8670,13 +9326,29 @@ async function getTool(args) {
8670
9326
  throw error;
8671
9327
  }
8672
9328
  if (argsWantJson(args)) {
8673
- process.stdout.write(`${JSON.stringify(tool)}
9329
+ process.stdout.write(`${JSON.stringify(toolMetadataJsonForDescribe(tool, toolId))}
8674
9330
  `);
8675
9331
  return 0;
8676
9332
  }
8677
9333
  printToolDetails(tool, toolId);
8678
9334
  return 0;
8679
9335
  }
9336
+ function toolMetadataJsonForDescribe(tool, requestedToolId) {
9337
+ const toolId = String(tool.toolId || requestedToolId);
9338
+ return {
9339
+ ...tool,
9340
+ toolId,
9341
+ provider: tool.provider,
9342
+ displayName: tool.displayName,
9343
+ runtimeOutputHelp: {
9344
+ contract: "tools describe shows the declared schema and semantic getters; it is not an observed provider response.",
9345
+ observeActualShape: `deepline tools execute ${toolId} --input '{...}' --json`,
9346
+ observedOutput: `deepline tools execute ${toolId} --input '{...}' --json`,
9347
+ forPlayGetterBugs: "Run the play, then inspect the emitted table commands from runs get. Use deepline db query against the run tables before editing getters.",
9348
+ executeOutputFields: "tools execute JSON may include output_preview for this direct probe only; play debugging uses run tables."
9349
+ }
9350
+ };
9351
+ }
8680
9352
  function printToolDetails(tool, requestedToolId) {
8681
9353
  const toolId = String(tool.toolId || requestedToolId);
8682
9354
  const operation = typeof tool.operation === "string" ? tool.operation : "";
@@ -8695,7 +9367,12 @@ function printToolDetails(tool, requestedToolId) {
8695
9367
  const stepContributions = arrayField(tool, "stepContributions", "step_contributions");
8696
9368
  const playExpansion = recordField(tool, "playExpansion", "play_expansion");
8697
9369
  const samples = recordField(tool, "samples");
9370
+ const usageGuidance = recordField(tool, "usageGuidance", "usage_guidance");
8698
9371
  console.log(`Tool: ${toolId}`);
9372
+ console.log(" Runtime output help:");
9373
+ console.log(" describe shows declared schema/getters, not an observed provider response");
9374
+ console.log(` observe actual shape: deepline tools execute ${toolId} --input '{...}' --json`);
9375
+ console.log(" for play getter bugs: run the play, then use the db query commands printed by runs get");
8699
9376
  if (displayName) {
8700
9377
  console.log(" Display name:");
8701
9378
  console.log(` ${displayName}`);
@@ -8758,14 +9435,16 @@ function printToolDetails(tool, requestedToolId) {
8758
9435
  console.log(" Tip: pass --payload with a JSON object.");
8759
9436
  }
8760
9437
  printSamples(samples);
9438
+ printUsageGuidance(usageGuidance);
8761
9439
  if (isPlayTool(tool)) {
8762
9440
  console.log(" Play contract:");
8763
9441
  console.log(" - This is a deepline-native waterfall; the returned rows are extracted by target getters, not by hand-authored payload shape.");
8764
9442
  if (playExpansion && typeof playExpansion.group === "string" && playExpansion.group.trim()) {
8765
9443
  console.log(` - Output alias/runtime group is: ${playExpansion.group.trim()}`);
8766
9444
  }
8767
- const getters = recordField(tool, "resultIdentityGetters", "result_identity_getters");
8768
- const targets = Object.keys(getters).filter(Boolean).sort();
9445
+ const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult");
9446
+ const extractedValues = arrayField(toolExecutionResult, "extractedValues", "extracted_values");
9447
+ const targets = extractedValues.map((entry) => isRecord3(entry) && typeof entry.name === "string" ? entry.name : "").filter(Boolean).sort();
8769
9448
  if (targets.length) {
8770
9449
  console.log(` - Built-in extract targets: ${targets.join(", ")}`);
8771
9450
  }
@@ -8784,7 +9463,39 @@ function printToolDetails(tool, requestedToolId) {
8784
9463
  } else {
8785
9464
  console.log(` deepline tools execute ${toolId} --payload '{...}'`);
8786
9465
  }
8787
- console.log(" deepline tools get <tool_id> --json # full machine-readable output");
9466
+ console.log(" deepline tools describe <tool_id> --json");
9467
+ }
9468
+ function printUsageGuidance(usageGuidance) {
9469
+ if (Object.keys(usageGuidance).length === 0) return;
9470
+ const execute = stringField(usageGuidance, "execute");
9471
+ const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult", "tool_execution_result");
9472
+ const toolResponse = recordField(toolExecutionResult, "toolResponse", "tool_response");
9473
+ const extractedLists = arrayField(toolExecutionResult, "extractedLists", "extracted_lists");
9474
+ const extractedValues = arrayField(toolExecutionResult, "extractedValues", "extracted_values");
9475
+ console.log(" Usage guidance:");
9476
+ if (execute) console.log(` ${execute}`);
9477
+ const raw = stringField(toolResponse, "raw");
9478
+ const meta = stringField(toolResponse, "meta");
9479
+ if (raw) console.log(` Raw tool response: ${raw}`);
9480
+ if (meta) console.log(` Tool response metadata: ${meta}`);
9481
+ printExtractions("Extracted lists", extractedLists);
9482
+ printExtractions("Extracted values", extractedValues);
9483
+ }
9484
+ function printExtractions(label, entries) {
9485
+ if (!entries.length) return;
9486
+ console.log(` ${label}:`);
9487
+ for (const entry of entries) {
9488
+ if (!isRecord3(entry)) continue;
9489
+ const name = stringField(entry, "name");
9490
+ const expression = stringField(entry, "expression");
9491
+ const details = recordField(entry, "details");
9492
+ const rawToolOutputPaths = arrayField(details, "rawToolOutputPaths", "raw_tool_output_paths").map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
9493
+ const candidatePaths = arrayField(details, "candidatePaths", "candidate_paths").map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
9494
+ if (!name || !expression) continue;
9495
+ const paths = candidatePaths.length ? candidatePaths : rawToolOutputPaths;
9496
+ const pathSuffix = paths.length ? ` from ${paths.join(", ")}` : "";
9497
+ console.log(` - ${name}: ${expression}${pathSuffix}`);
9498
+ }
8788
9499
  }
8789
9500
  function printToolCost(input) {
8790
9501
  const { cost, billingSource, deeplineCredits, deeplineUsdPerPricingUnit } = input;
@@ -8852,6 +9563,17 @@ function samplePayload(samples, key) {
8852
9563
  function commandEnvelopeFromRawResponse(rawResponse) {
8853
9564
  return isRecord3(rawResponse) ? { ...rawResponse } : { status: "completed", result: rawResponse };
8854
9565
  }
9566
+ function listExtractorPathsFromUsageGuidance(tool) {
9567
+ const toolExecutionResult = tool.usageGuidance?.toolExecutionResult;
9568
+ const extractedLists = Array.isArray(toolExecutionResult?.extractedLists) ? toolExecutionResult.extractedLists : isRecord3(toolExecutionResult?.extractedLists) ? Object.values(toolExecutionResult.extractedLists) : [];
9569
+ return extractedLists.flatMap((entry) => {
9570
+ const paths = entry.details?.candidatePaths ?? entry.details?.rawToolOutputPaths;
9571
+ if (!Array.isArray(paths)) return [];
9572
+ return paths.map(
9573
+ (path) => path.trim().replace(/^toolExecutionResult\.toolResponse\.raw\.?/, "").replace(/^toolExecutionResult\.toolOutput\.raw\.?/, "").replace(/^\./, "")
9574
+ ).filter(Boolean);
9575
+ });
9576
+ }
8855
9577
  function isPlayTool(tool) {
8856
9578
  const provider = typeof tool.provider === "string" ? tool.provider : "";
8857
9579
  return provider === "deepline_native" && Boolean(recordField(tool, "playExpansion", "play_expansion"));
@@ -8920,7 +9642,7 @@ function normalizeOutputFormat(raw) {
8920
9642
  function parseExecuteOptions(args) {
8921
9643
  const toolId = args[0];
8922
9644
  if (!toolId) {
8923
- throw new Error(`Usage: deepline tools execute <toolId> [--param key=value ...] [--input '{"k":"v"}'] [--output-format auto|csv|csv_file|json|json_file] [--full-output] [--no-preview]`);
9645
+ throw new Error(`Usage: deepline tools execute <toolId> [--param key=value ...] [--input '{"k":"v"}'] [--output-format auto|csv|csv_file|json|json_file] [--no-preview]`);
8924
9646
  }
8925
9647
  const params = {};
8926
9648
  let outputFormat = "auto";
@@ -8949,10 +9671,6 @@ function parseExecuteOptions(args) {
8949
9671
  outputFormat = normalizeOutputFormat(args[++index]);
8950
9672
  continue;
8951
9673
  }
8952
- if (arg === "--full-output") {
8953
- outputFormat = "json";
8954
- continue;
8955
- }
8956
9674
  if (arg === "--no-preview") {
8957
9675
  noPreview = true;
8958
9676
  continue;
@@ -8991,7 +9709,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
8991
9709
  description: ${JSON.stringify(`Seed ${input.toolId} rows for workflow expansion.`)},
8992
9710
  });
8993
9711
 
8994
- const list = Object.values(result.lists)[0];
9712
+ const list = Object.values(result.extractedLists)[0];
8995
9713
  const rows = (list?.get() ?? []).slice(0, 100);
8996
9714
  // ${sampleRows}
8997
9715
  // columns: ${columns}
@@ -9008,7 +9726,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
9008
9726
  };
9009
9727
  });
9010
9728
  `;
9011
- writeFileSync7(scriptPath, script, { encoding: "utf-8", mode: 384 });
9729
+ writeFileSync8(scriptPath, script, { encoding: "utf-8", mode: 384 });
9012
9730
  return {
9013
9731
  path: scriptPath,
9014
9732
  projectDir,
@@ -9019,7 +9737,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
9019
9737
  function buildToolExecuteBaseEnvelope(input) {
9020
9738
  const envelope = commandEnvelopeFromRawResponse(input.rawResponse);
9021
9739
  const summaryEntries = Object.entries(input.summary);
9022
- const output = input.listConversion ? {
9740
+ const outputPreview = input.listConversion ? {
9023
9741
  kind: "list",
9024
9742
  rowCount: input.listConversion.rows.length,
9025
9743
  columns: Object.keys(input.listConversion.rows[0] ?? {}),
@@ -9030,6 +9748,8 @@ function buildToolExecuteBaseEnvelope(input) {
9030
9748
  kind: summaryEntries.length > 0 ? "object" : "raw",
9031
9749
  summary: input.summary
9032
9750
  };
9751
+ const envelopeHasCanonicalOutput = isRecord3(envelope.toolResponse) && Object.prototype.hasOwnProperty.call(envelope.toolResponse, "raw");
9752
+ const inspectCommand = `deepline tools execute ${input.toolId} --input ${shellQuote(JSON.stringify(input.params))} --json`;
9033
9753
  const actions = input.listConversion ? [
9034
9754
  {
9035
9755
  label: "next",
@@ -9038,17 +9758,23 @@ function buildToolExecuteBaseEnvelope(input) {
9038
9758
  ] : [];
9039
9759
  return {
9040
9760
  ...envelope,
9041
- output,
9761
+ ...envelopeHasCanonicalOutput ? { output_preview: outputPreview } : { output: outputPreview },
9042
9762
  ...summaryEntries.length > 0 ? { summary: input.summary } : {},
9043
- next: input.listConversion ? {
9044
- expandToPlay: "Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
9045
- } : {},
9763
+ next: {
9764
+ inspect: inspectCommand,
9765
+ playDebugging: "When fixing a play getter, inspect the actual play run table with runs get / inspect rows; do not copy CLI preview paths blindly.",
9766
+ ...input.listConversion ? {
9767
+ expandToPlay: "Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again.",
9768
+ listSourcePath: input.listConversion.sourcePath ?? "auto-detected list in the CLI response preview"
9769
+ } : {}
9770
+ },
9046
9771
  render: {
9047
9772
  sections: input.listConversion ? [
9048
9773
  {
9049
9774
  title: "output",
9050
9775
  lines: [
9051
9776
  `${input.listConversion.rows.length} row(s) extracted from ${input.listConversion.sourcePath ?? "auto-detected list"}`,
9777
+ "paths above are observed from this execute response; use run table rows to debug play getters",
9052
9778
  `columns: ${JSON.stringify(Object.keys(input.listConversion.rows[0] ?? {}))}`,
9053
9779
  `preview: ${JSON.stringify(input.listConversion.rows.slice(0, 5))}`
9054
9780
  ]
@@ -9092,11 +9818,12 @@ async function executeTool(args) {
9092
9818
  }
9093
9819
  const rawResponse = await client.executeTool(parsed.toolId, parsed.params);
9094
9820
  const listConversion = tryConvertToList(rawResponse, {
9095
- listExtractorPaths: metadata.listExtractorPaths ?? []
9821
+ listExtractorPaths: listExtractorPathsFromUsageGuidance(metadata)
9096
9822
  });
9097
9823
  const summary = extractSummaryFields(rawResponse);
9098
9824
  const baseEnvelope = buildToolExecuteBaseEnvelope({
9099
9825
  toolId: parsed.toolId,
9826
+ params: parsed.params,
9100
9827
  rawResponse,
9101
9828
  listConversion,
9102
9829
  summary
@@ -9224,7 +9951,7 @@ async function executeTool(args) {
9224
9951
  // src/cli/commands/update.ts
9225
9952
  import { spawn } from "child_process";
9226
9953
  import { existsSync as existsSync7 } from "fs";
9227
- import { dirname as dirname9, join as join9, resolve as resolve9 } from "path";
9954
+ import { dirname as dirname9, join as join9, resolve as resolve10 } from "path";
9228
9955
  function posixShellQuote(value) {
9229
9956
  return `'${value.replace(/'/g, `'\\''`)}'`;
9230
9957
  }
@@ -9243,7 +9970,7 @@ function buildSourceUpdateCommand(sourceRoot) {
9243
9970
  return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
9244
9971
  }
9245
9972
  function findRepoBackedSdkRoot(startPath) {
9246
- let current = resolve9(startPath);
9973
+ let current = resolve10(startPath);
9247
9974
  while (true) {
9248
9975
  if (existsSync7(join9(current, "sdk", "package.json")) && existsSync7(join9(current, "sdk", "bin", "deepline-dev.ts"))) {
9249
9976
  return current;
@@ -9254,7 +9981,7 @@ function findRepoBackedSdkRoot(startPath) {
9254
9981
  }
9255
9982
  }
9256
9983
  function resolveUpdatePlan() {
9257
- const entrypoint = process.argv[1] ? resolve9(process.argv[1]) : "";
9984
+ const entrypoint = process.argv[1] ? resolve10(process.argv[1]) : "";
9258
9985
  const sourceRoot = entrypoint ? findRepoBackedSdkRoot(dirname9(entrypoint)) : null;
9259
9986
  if (sourceRoot) {
9260
9987
  return {
@@ -9348,7 +10075,7 @@ Examples:
9348
10075
 
9349
10076
  // src/cli/skills-sync.ts
9350
10077
  import { spawn as spawn2, spawnSync } from "child_process";
9351
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync8 } from "fs";
10078
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync9 } from "fs";
9352
10079
  import { homedir as homedir4 } from "os";
9353
10080
  import { dirname as dirname10, join as join10 } from "path";
9354
10081
  var CHECK_TIMEOUT_MS2 = 3e3;
@@ -9375,9 +10102,45 @@ function readLocalSkillsVersion(baseUrl) {
9375
10102
  function writeLocalSkillsVersion(baseUrl, version) {
9376
10103
  const path = sdkSkillsVersionPath(baseUrl);
9377
10104
  mkdirSync5(dirname10(path), { recursive: true });
9378
- writeFileSync8(path, `${version}
10105
+ writeFileSync9(path, `${version}
9379
10106
  `, "utf-8");
9380
10107
  }
10108
+ function installedSdkSkillHasStalePositionalExecuteExamples() {
10109
+ const home = process.env.HOME?.trim() || homedir4();
10110
+ const roots = [
10111
+ join10(home, ".claude", "skills", SDK_SKILL_NAME),
10112
+ join10(home, ".agents", "skills", SDK_SKILL_NAME)
10113
+ ];
10114
+ const staleMarkers = [
10115
+ "ctx.tools.execute(key",
10116
+ "ctx.tools.execute('",
10117
+ 'ctx.tools.execute("',
10118
+ "rowCtx.tools.execute('",
10119
+ 'rowCtx.tools.execute("'
10120
+ ];
10121
+ const scan = (dir) => {
10122
+ for (const entry of readdirSync2(dir)) {
10123
+ const path = join10(dir, entry);
10124
+ const stat3 = statSync(path);
10125
+ if (stat3.isDirectory()) {
10126
+ if (scan(path)) return true;
10127
+ continue;
10128
+ }
10129
+ if (!entry.endsWith(".md")) continue;
10130
+ const text = readFileSync6(path, "utf-8");
10131
+ if (staleMarkers.some((marker) => text.includes(marker))) return true;
10132
+ }
10133
+ return false;
10134
+ };
10135
+ for (const root of roots) {
10136
+ try {
10137
+ if (existsSync8(root) && scan(root)) return true;
10138
+ } catch {
10139
+ continue;
10140
+ }
10141
+ }
10142
+ return false;
10143
+ }
9381
10144
  async function fetchSkillsUpdate(baseUrl, localVersion) {
9382
10145
  const controller = new AbortController();
9383
10146
  const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
@@ -9413,7 +10176,7 @@ function buildSkillsInstallArgs(baseUrl) {
9413
10176
  "skills",
9414
10177
  "add",
9415
10178
  packageUrl,
9416
- "--agents",
10179
+ "--agent",
9417
10180
  ...SKILL_AGENTS,
9418
10181
  "--global",
9419
10182
  "--yes",
@@ -9429,7 +10192,7 @@ function buildBunxSkillsInstallArgs(baseUrl) {
9429
10192
  "skills",
9430
10193
  "add",
9431
10194
  packageUrl,
9432
- "--agents",
10195
+ "--agent",
9433
10196
  ...SKILL_AGENTS,
9434
10197
  "--global",
9435
10198
  "--yes",
@@ -9469,7 +10232,7 @@ function resolveSkillsInstallCommands(baseUrl) {
9469
10232
  return [npxInstall];
9470
10233
  }
9471
10234
  function runOneSkillsInstall(install) {
9472
- return new Promise((resolve10) => {
10235
+ return new Promise((resolve11) => {
9473
10236
  const child = spawn2(install.command, install.args, {
9474
10237
  stdio: ["ignore", "ignore", "pipe"],
9475
10238
  env: process.env
@@ -9479,7 +10242,7 @@ function runOneSkillsInstall(install) {
9479
10242
  stderr += chunk.toString("utf-8");
9480
10243
  });
9481
10244
  child.on("error", (error) => {
9482
- resolve10({
10245
+ resolve11({
9483
10246
  ok: false,
9484
10247
  detail: `failed to start ${install.command}: ${error.message}`,
9485
10248
  manualCommand: install.manualCommand
@@ -9487,11 +10250,11 @@ function runOneSkillsInstall(install) {
9487
10250
  });
9488
10251
  child.on("close", (code) => {
9489
10252
  if (code === 0) {
9490
- resolve10({ ok: true, detail: "", manualCommand: install.manualCommand });
10253
+ resolve11({ ok: true, detail: "", manualCommand: install.manualCommand });
9491
10254
  return;
9492
10255
  }
9493
10256
  const detail = stderr.trim();
9494
- resolve10({
10257
+ resolve11({
9495
10258
  ok: false,
9496
10259
  detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
9497
10260
  manualCommand: install.manualCommand
@@ -9530,10 +10293,19 @@ async function syncSdkSkillsIfNeeded(baseUrl) {
9530
10293
  attemptedSync = true;
9531
10294
  const localVersion = readLocalSkillsVersion(baseUrl);
9532
10295
  const update = await fetchSkillsUpdate(baseUrl, localVersion);
9533
- if (!update?.needsUpdate || !update.remoteVersion) return;
10296
+ const hasStaleInstalledSkill = installedSdkSkillHasStalePositionalExecuteExamples();
10297
+ if (!update?.needsUpdate && !hasStaleInstalledSkill || !update?.remoteVersion) {
10298
+ return;
10299
+ }
9534
10300
  writeSdkSkillsStatusLine("SDK skills changed; syncing deepline-sdk skill...");
9535
10301
  const installed = await runSkillsInstall(baseUrl);
9536
10302
  if (!installed) return;
10303
+ if (installedSdkSkillHasStalePositionalExecuteExamples()) {
10304
+ process.stderr.write(
10305
+ "SDK skills sync completed, but installed deepline-sdk docs still contain stale positional ctx.tools.execute examples.\n"
10306
+ );
10307
+ return;
10308
+ }
9537
10309
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
9538
10310
  writeSdkSkillsStatusLine("SDK skills are up to date.");
9539
10311
  }