deepline 0.1.33 → 0.1.35

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.35";
197
+ var SDK_API_CONTRACT = "2026-05-v2-tool-result-contract";
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-execution-result";
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 `toolExecutionResult.toolOutput.raw` field contains the raw tool output.
778
+ * `toolExecutionResult.toolOutput.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,23 @@ 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`,
6240
6577
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
6241
6578
  logs: `deepline runs logs ${runId} --out run.log --json`
6242
6579
  };
6243
- if (dashboardUrl) {
6244
- commands.open = `Open ${dashboardUrl} to see results.`;
6580
+ if (status.dashboardUrl) {
6581
+ commands.open = `Open ${status.dashboardUrl} to see results.`;
6245
6582
  }
6246
6583
  return commands;
6247
6584
  }
@@ -6264,6 +6601,121 @@ function getTimestampField(value, key) {
6264
6601
  }
6265
6602
  return typeof field === "string" && field.trim() ? field : null;
6266
6603
  }
6604
+ function getObjectField(value, key) {
6605
+ const field = getRecordField(value, key);
6606
+ return field && typeof field === "object" && !Array.isArray(field) ? field : null;
6607
+ }
6608
+ function formatCreditAmount(value) {
6609
+ if (typeof value !== "number" || !Number.isFinite(value)) {
6610
+ return String(value ?? "-");
6611
+ }
6612
+ return Number(value.toFixed(8)).toString();
6613
+ }
6614
+ function extractJsonObjectFromText(text) {
6615
+ const start = text.indexOf("{");
6616
+ if (start < 0) {
6617
+ return null;
6618
+ }
6619
+ const suffix = text.slice(start);
6620
+ try {
6621
+ const parsed = JSON.parse(suffix);
6622
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
6623
+ } catch {
6624
+ let depth = 0;
6625
+ let inString = false;
6626
+ let escaped = false;
6627
+ for (let index = start; index < text.length; index += 1) {
6628
+ const char = text[index];
6629
+ if (inString) {
6630
+ if (escaped) {
6631
+ escaped = false;
6632
+ } else if (char === "\\") {
6633
+ escaped = true;
6634
+ } else if (char === '"') {
6635
+ inString = false;
6636
+ }
6637
+ continue;
6638
+ }
6639
+ if (char === '"') {
6640
+ inString = true;
6641
+ } else if (char === "{") {
6642
+ depth += 1;
6643
+ } else if (char === "}") {
6644
+ depth -= 1;
6645
+ if (depth === 0) {
6646
+ try {
6647
+ const parsed = JSON.parse(text.slice(start, index + 1));
6648
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
6649
+ } catch {
6650
+ return null;
6651
+ }
6652
+ }
6653
+ }
6654
+ }
6655
+ }
6656
+ return null;
6657
+ }
6658
+ function extractBillingFromText(text) {
6659
+ if (!text) {
6660
+ return null;
6661
+ }
6662
+ const parsed = extractJsonObjectFromText(text);
6663
+ return getObjectField(parsed, "billing");
6664
+ }
6665
+ function extractToolIdFromErrorText(text) {
6666
+ if (!text) {
6667
+ return null;
6668
+ }
6669
+ const lowerToolMatch = /\btool\s+([A-Za-z0-9_.:-]+)\s+\d{3}\b/.exec(text);
6670
+ if (lowerToolMatch?.[1]) {
6671
+ return lowerToolMatch[1];
6672
+ }
6673
+ const upperToolMatch = /\bTool\s+([A-Za-z0-9_.:-]+)\s+failed\b/.exec(text);
6674
+ return upperToolMatch?.[1] ?? null;
6675
+ }
6676
+ function isInsufficientCreditsBilling(billing) {
6677
+ return billing?.kind === "insufficient_credits";
6678
+ }
6679
+ function extractBillingForStatus(status, error) {
6680
+ const errorBilling = getObjectField(status, "errorBilling");
6681
+ if (errorBilling) {
6682
+ return errorBilling;
6683
+ }
6684
+ const directErrors = getRecordField(status, "errors");
6685
+ if (Array.isArray(directErrors)) {
6686
+ for (const entry of directErrors) {
6687
+ const billing = getObjectField(entry, "billing");
6688
+ if (billing) {
6689
+ return billing;
6690
+ }
6691
+ }
6692
+ }
6693
+ const progressError = getStringField(status.progress, "error");
6694
+ return extractBillingFromText(error) ?? extractBillingFromText(progressError) ?? getObjectField(status, "billing");
6695
+ }
6696
+ function formatInsufficientCreditsMessage(input) {
6697
+ const operation = getStringField(input.billing, "operation_id") ?? getStringField(input.billing, "operation") ?? extractToolIdFromErrorText(input.error) ?? getStringField(input.billing, "provider") ?? "tool call";
6698
+ const balance = formatCreditAmount(input.billing.balance_credits);
6699
+ const required = formatCreditAmount(input.billing.required_credits);
6700
+ const recommended = formatCreditAmount(
6701
+ input.billing.recommended_add_credits ?? input.billing.needed_credits
6702
+ );
6703
+ const billingUrl = getStringField(input.billing, "billing_url");
6704
+ const workspace = getStringField(input.billing, "workspace_id") ?? getStringField(input.billing, "workspaceId");
6705
+ const workspaceSuffix = workspace ? ` (workspace=${workspace})` : "";
6706
+ const addSuffix = billingUrl && recommended !== "-" ? ` Add >=${recommended} at ${billingUrl}.` : billingUrl ? ` Add credits at ${billingUrl}.` : "";
6707
+ return `Workspace balance ${balance} < required ${required} for ${operation}${workspaceSuffix}.${addSuffix}`;
6708
+ }
6709
+ function formatPlayErrorForDisplay(status, error) {
6710
+ if (!error) {
6711
+ return null;
6712
+ }
6713
+ const billing = extractBillingForStatus(status, error);
6714
+ if (isInsufficientCreditsBilling(billing)) {
6715
+ return formatInsufficientCreditsMessage({ billing, error });
6716
+ }
6717
+ return error;
6718
+ }
6267
6719
  function normalizeRunStatusForEnvelope(status) {
6268
6720
  const run = status.run ?? null;
6269
6721
  return {
@@ -6314,18 +6766,33 @@ function normalizeErrorsForEnvelope(status, error) {
6314
6766
  if (Array.isArray(directErrors)) {
6315
6767
  return directErrors.filter(
6316
6768
  (entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)
6317
- );
6769
+ ).map((entry) => {
6770
+ const message2 = typeof entry.message === "string" && entry.message.trim() ? entry.message : error;
6771
+ const billing2 = getObjectField(entry, "billing");
6772
+ if (!isInsufficientCreditsBilling(billing2) || !message2) {
6773
+ return entry;
6774
+ }
6775
+ return {
6776
+ ...entry,
6777
+ message: formatInsufficientCreditsMessage({ billing: billing2, error: message2 }),
6778
+ billing: stripProviderSpendFromBilling(billing2)
6779
+ };
6780
+ });
6318
6781
  }
6319
6782
  if (!error) {
6320
6783
  return [];
6321
6784
  }
6785
+ const nextCommands = buildRunNextCommands(status);
6786
+ const billing = extractBillingForStatus(status, error);
6787
+ const message = formatPlayErrorForDisplay(status, error) ?? error;
6322
6788
  return [
6323
6789
  {
6324
6790
  code: getStringField(status, "errorCode") ?? "RUN_FAILED",
6325
6791
  phase: getStringField(status, "errorPhase") ?? "runtime",
6326
- message: error,
6792
+ message,
6327
6793
  retryable: typeof getRecordField(status, "retryable") === "boolean" ? getRecordField(status, "retryable") : null,
6328
- nextAction: `deepline runs get ${status.runId} --json`
6794
+ ...billing ? { billing: stripProviderSpendFromBilling(billing) } : {},
6795
+ nextAction: nextCommands.get ?? nextCommands.list
6329
6796
  }
6330
6797
  ];
6331
6798
  }
@@ -6374,6 +6841,7 @@ function compactPlayStatus(status) {
6374
6841
  ) : null;
6375
6842
  const progressError = status.progress?.error;
6376
6843
  const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
6844
+ const displayError = formatPlayErrorForDisplay(status, error);
6377
6845
  return {
6378
6846
  runId: status.runId,
6379
6847
  apiVersion: status.apiVersion ?? 1,
@@ -6386,13 +6854,13 @@ function compactPlayStatus(status) {
6386
6854
  steps: normalizeStepsForEnvelope(status),
6387
6855
  errors: normalizeErrorsForEnvelope(status, error),
6388
6856
  logs: normalizeLogsForEnvelope(status),
6389
- ...error ? { error } : {},
6857
+ ...displayError ? { error: displayError } : {},
6390
6858
  ...warnings.length > 0 ? { warnings } : {},
6391
6859
  ...result !== void 0 ? { result } : {},
6392
6860
  ...status.resultView ? { resultView: status.resultView } : {},
6393
6861
  ...datasetStats ? { dataset_stats: datasetStats } : {},
6394
6862
  ...billing ? { billing } : {},
6395
- next: buildRunNextCommands(status.runId, status.dashboardUrl)
6863
+ next: buildRunNextCommands(status)
6396
6864
  };
6397
6865
  }
6398
6866
  function enrichPlayStatusWithDatasetStats(status) {
@@ -6466,7 +6934,8 @@ function writePlayResult(status, jsonOutput, options) {
6466
6934
  lines.push(...formatDatasetStatsLines(datasetStats));
6467
6935
  const progressError = status.progress?.error;
6468
6936
  if (progressError && typeof progressError === "string") {
6469
- lines.push(` error: ${progressError.slice(0, 200)}`);
6937
+ const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
6938
+ lines.push(` error: ${displayError.slice(0, 200)}`);
6470
6939
  }
6471
6940
  const renderedServerView = renderServerResultView(status.resultView);
6472
6941
  if (result) {
@@ -6490,8 +6959,11 @@ var RUN_EXPORT_PAGE_SIZE = 5e3;
6490
6959
  function shellSingleQuote(value) {
6491
6960
  return `'${value.replace(/'/g, `'\\''`)}'`;
6492
6961
  }
6962
+ function sqlStringLiteral(value) {
6963
+ return `'${value.replace(/'/g, "''")}'`;
6964
+ }
6493
6965
  function runExportRetryCommand(runId, outPath, datasetPath) {
6494
- return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote(resolve8(outPath))}`;
6966
+ return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote(resolve9(outPath))}`;
6495
6967
  }
6496
6968
  function extractRunPlayName(status) {
6497
6969
  const run = status.run;
@@ -6508,6 +6980,26 @@ function extractRunPlayName(status) {
6508
6980
  }
6509
6981
  return null;
6510
6982
  }
6983
+ function normalizeCustomerDbIdentifier(value) {
6984
+ return value.split("/").pop().replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
6985
+ }
6986
+ function buildCustomerDbQueryPlan(input) {
6987
+ const playName = extractRunPlayName(input.status);
6988
+ const tableNamespace = input.rowsInfo.tableNamespace?.trim();
6989
+ if (!playName || !tableNamespace || input.rowsInfo.totalRows <= 0) {
6990
+ return null;
6991
+ }
6992
+ const tableName = `${normalizeCustomerDbIdentifier(playName)}_${normalizeCustomerDbIdentifier(
6993
+ tableNamespace
6994
+ )}`;
6995
+ const sql = `select * from "storage"."${tableName}" where _run_id = ${sqlStringLiteral(input.status.runId)} limit ${input.rowsInfo.totalRows}`;
6996
+ const base = `deepline customer-db query --sql ${shellSingleQuote(sql)} --max-rows ${input.rowsInfo.totalRows}`;
6997
+ return {
6998
+ sql,
6999
+ json: `${base} --json`,
7000
+ csv: `${base} --format csv --out ${shellSingleQuote(resolve9(input.outPath))}`
7001
+ };
7002
+ }
6511
7003
  function exportableSheetRow(row) {
6512
7004
  if (!row || typeof row !== "object" || Array.isArray(row)) {
6513
7005
  return null;
@@ -6679,6 +7171,60 @@ async function exportPlayStatusRows(client, status, outPath, options = {}) {
6679
7171
  }
6680
7172
  return { path: writeCanonicalRowsCsv(rowsInfo, outPath), rowsInfo };
6681
7173
  }
7174
+ function extractActiveRunsFromError(error) {
7175
+ if (!(error instanceof DeeplineError)) {
7176
+ return [];
7177
+ }
7178
+ const response = error.details?.response;
7179
+ if (!response || typeof response !== "object" || Array.isArray(response)) {
7180
+ return [];
7181
+ }
7182
+ const details = response.details;
7183
+ if (!details || typeof details !== "object" || Array.isArray(details)) {
7184
+ return [];
7185
+ }
7186
+ const activeRuns = details.activeRuns;
7187
+ return Array.isArray(activeRuns) ? activeRuns.filter(
7188
+ (run) => Boolean(run) && typeof run === "object" && !Array.isArray(run)
7189
+ ) : [];
7190
+ }
7191
+ function activeRunId(run) {
7192
+ return getStringField(run, "workflowId") ?? getStringField(run, "runId");
7193
+ }
7194
+ function formatActiveRunConflictError(input) {
7195
+ const lines = [
7196
+ `Active run exists for ${input.playName}. Use --force to supersede, or inspect/stop the active run first.`
7197
+ ];
7198
+ for (const run of input.activeRuns.slice(0, 3)) {
7199
+ const runId = activeRunId(run);
7200
+ if (!runId) {
7201
+ continue;
7202
+ }
7203
+ const status = getStringField(run, "status");
7204
+ const startedAt = getStringField(run, "startedAt") ?? getStringField(run, "startTime");
7205
+ lines.push(
7206
+ ` active: ${runId}${status ? ` status=${status}` : ""}${startedAt ? ` startedAt=${startedAt}` : ""}`
7207
+ );
7208
+ lines.push(` get: deepline runs get ${runId} --json`);
7209
+ lines.push(
7210
+ ` stop: deepline runs stop ${runId} --reason "stale lock" --json`
7211
+ );
7212
+ }
7213
+ lines.push(` rerun: add --force to the same deepline plays run command`);
7214
+ return lines.join("\n");
7215
+ }
7216
+ function normalizePlayStartError(error, playName) {
7217
+ const activeRuns = extractActiveRunsFromError(error);
7218
+ if (activeRuns.length === 0) {
7219
+ return error;
7220
+ }
7221
+ return new DeeplineError(
7222
+ formatActiveRunConflictError({ playName, activeRuns }),
7223
+ error instanceof DeeplineError ? error.statusCode : 409,
7224
+ "ACTIVE_RUN_EXISTS",
7225
+ error instanceof DeeplineError ? error.details : void 0
7226
+ );
7227
+ }
6682
7228
  function renderServerResultView(value) {
6683
7229
  if (!value || typeof value !== "object" || Array.isArray(value)) {
6684
7230
  return { lines: [], actions: [] };
@@ -6914,11 +7460,11 @@ function shouldUseLocalOnlyPlayCheck() {
6914
7460
  async function handlePlayCheck(args) {
6915
7461
  const options = parsePlayCheckOptions(args);
6916
7462
  if (!isFileTarget(options.target)) {
6917
- const resolved = resolve8(options.target);
7463
+ const resolved = resolve9(options.target);
6918
7464
  console.error(`File not found: ${resolved}`);
6919
7465
  return 1;
6920
7466
  }
6921
- const absolutePlayPath = resolve8(options.target);
7467
+ const absolutePlayPath = resolve9(options.target);
6922
7468
  const sourceCode = readFileSync5(absolutePlayPath, "utf-8");
6923
7469
  let graph;
6924
7470
  try {
@@ -6982,7 +7528,7 @@ async function handleFileBackedRun(options) {
6982
7528
  }
6983
7529
  const client = new DeeplineClient();
6984
7530
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
6985
- const absolutePlayPath = resolve8(options.target.path);
7531
+ const absolutePlayPath = resolve9(options.target.path);
6986
7532
  progress.phase("compiling play");
6987
7533
  const sourceCode = traceCliSync(
6988
7534
  "cli.play_file_read_source",
@@ -7068,6 +7614,8 @@ async function handleFileBackedRun(options) {
7068
7614
  waitTimeoutMs: options.waitTimeoutMs,
7069
7615
  noOpen: options.noOpen,
7070
7616
  progress
7617
+ }).catch((error) => {
7618
+ throw normalizePlayStartError(error, playName);
7071
7619
  })
7072
7620
  );
7073
7621
  if (finalStatus.status === "completed") {
@@ -7086,7 +7634,9 @@ async function handleFileBackedRun(options) {
7086
7634
  const started = await traceCliSpan(
7087
7635
  "cli.play_start_unwatched",
7088
7636
  { targetKind: "file", playName },
7089
- () => client.startPlayRun(startRequest)
7637
+ () => client.startPlayRun(startRequest).catch((error) => {
7638
+ throw normalizePlayStartError(error, playName);
7639
+ })
7090
7640
  );
7091
7641
  const resolvedDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7092
7642
  openPlayDashboard({
@@ -7203,6 +7753,8 @@ async function handleNamedRun(options) {
7203
7753
  waitTimeoutMs: options.waitTimeoutMs,
7204
7754
  noOpen: options.noOpen,
7205
7755
  progress
7756
+ }).catch((error) => {
7757
+ throw normalizePlayStartError(error, playName);
7206
7758
  })
7207
7759
  );
7208
7760
  if (finalStatus.status === "completed") {
@@ -7221,7 +7773,9 @@ async function handleNamedRun(options) {
7221
7773
  const started = await traceCliSpan(
7222
7774
  "cli.play_start_unwatched",
7223
7775
  { targetKind: "name", playName },
7224
- () => client.startPlayRun(startRequest)
7776
+ () => client.startPlayRun(startRequest).catch((error) => {
7777
+ throw normalizePlayStartError(error, playName);
7778
+ })
7225
7779
  );
7226
7780
  const resolvedDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7227
7781
  openPlayDashboard({
@@ -7247,7 +7801,7 @@ async function handlePlayRun(args) {
7247
7801
  if (isFileTarget(options.target.path)) {
7248
7802
  return handleFileBackedRun(options);
7249
7803
  }
7250
- const resolved = resolve8(options.target.path);
7804
+ const resolved = resolve9(options.target.path);
7251
7805
  console.error(`File not found: ${resolved}`);
7252
7806
  const dir = dirname8(resolved);
7253
7807
  if (existsSync6(dir)) {
@@ -7394,14 +7948,14 @@ async function handleRunLogs(args) {
7394
7948
  continue;
7395
7949
  }
7396
7950
  if (arg === "--out" && args[index + 1]) {
7397
- outPath = resolve8(args[++index]);
7951
+ outPath = resolve9(args[++index]);
7398
7952
  }
7399
7953
  }
7400
7954
  const client = new DeeplineClient();
7401
7955
  const status = await client.runs.get(runId);
7402
7956
  const logs = status.progress?.logs ?? [];
7403
7957
  if (outPath) {
7404
- writeFileSync5(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
7958
+ writeFileSync6(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
7405
7959
  printCommandEnvelope({
7406
7960
  runId: status.runId,
7407
7961
  log_path: outPath,
@@ -7457,7 +8011,7 @@ async function handleRunStop(args) {
7457
8011
  return 0;
7458
8012
  }
7459
8013
  async function handleRunExport(args) {
7460
- const usage = "Usage: deepline runs export <run-id> [--dataset result.rows] --out output.csv [--json]";
8014
+ const usage = "Usage: deepline runs export <run-id> [--dataset result.rows] --out output.csv [--metadata-out export.json] [--json]";
7461
8015
  let runId;
7462
8016
  try {
7463
8017
  runId = parseRunIdPositional(args, usage);
@@ -7467,14 +8021,19 @@ async function handleRunExport(args) {
7467
8021
  }
7468
8022
  let outPath = null;
7469
8023
  let datasetPath = null;
8024
+ let metadataOutPath = null;
7470
8025
  for (let index = 0; index < args.length; index += 1) {
7471
8026
  const arg = args[index];
7472
8027
  if (arg === "--out" && args[index + 1]) {
7473
- outPath = resolve8(args[++index]);
8028
+ outPath = resolve9(args[++index]);
7474
8029
  continue;
7475
8030
  }
7476
8031
  if (arg === "--dataset" && args[index + 1]) {
7477
8032
  datasetPath = args[++index];
8033
+ continue;
8034
+ }
8035
+ if (arg === "--metadata-out" && args[index + 1]) {
8036
+ metadataOutPath = resolve9(args[++index]);
7478
8037
  }
7479
8038
  }
7480
8039
  if (!outPath) {
@@ -7486,15 +8045,59 @@ async function handleRunExport(args) {
7486
8045
  const exportResult = await exportPlayStatusRows(client, status, outPath, {
7487
8046
  datasetPath
7488
8047
  });
7489
- printCommandEnvelope({
8048
+ const source = exportResult?.rowsInfo.source ?? datasetPath ?? null;
8049
+ const queryPlan = exportResult && outPath ? buildCustomerDbQueryPlan({
8050
+ status,
8051
+ rowsInfo: exportResult.rowsInfo,
8052
+ outPath
8053
+ }) : null;
8054
+ const next = {
8055
+ ...buildRunNextCommands(status),
8056
+ export: runExportRetryCommand(status.runId, outPath, datasetPath ?? source),
8057
+ ...queryPlan ? {
8058
+ queryJson: queryPlan.json,
8059
+ queryCsv: queryPlan.csv
8060
+ } : {}
8061
+ };
8062
+ const payload = {
7490
8063
  runId: status.runId,
7491
8064
  ...datasetPath ? { dataset: datasetPath } : {},
7492
8065
  csv_path: exportResult?.path ?? null,
8066
+ source,
7493
8067
  rowCount: exportResult?.rowsInfo.totalRows ?? null,
7494
8068
  columns: exportResult?.rowsInfo.columns ?? [],
8069
+ ...queryPlan ? {
8070
+ query: {
8071
+ sql: queryPlan.sql,
8072
+ json: queryPlan.json,
8073
+ csv: queryPlan.csv
8074
+ }
8075
+ } : {},
8076
+ ...metadataOutPath ? { metadata_path: metadataOutPath } : {},
7495
8077
  local: { csv_path: exportResult?.path ?? null },
7496
- render: { sections: [{ title: "run export", lines: [`Exported ${status.runId} to ${exportResult?.path ?? outPath}`] }] }
7497
- }, { json: argsWantJson(args) });
8078
+ next,
8079
+ render: {
8080
+ sections: [
8081
+ {
8082
+ title: "run export",
8083
+ lines: [
8084
+ `Exported ${status.runId} to ${exportResult?.path ?? outPath}`,
8085
+ ...source ? [`source=${source}`] : [],
8086
+ ...queryPlan ? [`query=${queryPlan.json}`] : []
8087
+ ]
8088
+ }
8089
+ ]
8090
+ }
8091
+ };
8092
+ if (metadataOutPath) {
8093
+ writeFileSync6(
8094
+ metadataOutPath,
8095
+ `${JSON.stringify(payload, null, 2)}
8096
+ `,
8097
+ "utf-8"
8098
+ );
8099
+ }
8100
+ printCommandEnvelope(payload, { json: argsWantJson(args) });
7498
8101
  return 0;
7499
8102
  }
7500
8103
  async function handlePlayGet(args) {
@@ -7511,10 +8114,10 @@ async function handlePlayGet(args) {
7511
8114
  for (let index = 1; index < args.length; index += 1) {
7512
8115
  const arg = args[index];
7513
8116
  if (arg === "--out" && args[index + 1]) {
7514
- outPath = resolve8(args[++index]);
8117
+ outPath = resolve9(args[++index]);
7515
8118
  }
7516
8119
  }
7517
- const playName = isFileTarget(target) ? extractPlayName(readFileSync5(resolve8(target), "utf-8"), resolve8(target)) : parseReferencedPlayTarget(target).playName;
8120
+ const playName = isFileTarget(target) ? extractPlayName(readFileSync5(resolve9(target), "utf-8"), resolve9(target)) : parseReferencedPlayTarget(target).playName;
7518
8121
  const detail = isFileTarget(target) ? await client.getPlay(playName) : await assertCanonicalNamedPlayReference(client, target);
7519
8122
  const resolvedSource = detail.play.workingRevision?.sourceCode ?? detail.play.liveRevision?.sourceCode ?? detail.play.currentRevision?.sourceCode ?? detail.play.sourceCode ?? "";
7520
8123
  const materializedFile = outPath ? materializeRemotePlaySource({
@@ -7806,7 +8409,7 @@ async function handlePlayPublish(args) {
7806
8409
  }
7807
8410
  let graph;
7808
8411
  try {
7809
- graph = await collectBundledPlayGraph(resolve8(playName));
8412
+ graph = await collectBundledPlayGraph(resolve9(playName));
7810
8413
  await compileBundledPlayGraphManifests(client, graph);
7811
8414
  await publishImportedPlayDependencies(client, graph);
7812
8415
  } catch (error) {
@@ -8292,30 +8895,34 @@ Examples:
8292
8895
  Notes:
8293
8896
  Writes a returned dataset handle to the requested local CSV path. Use runs get
8294
8897
  first to inspect dataset paths like result.rows or result.nested.contacts.
8898
+ --metadata-out writes the same export metadata object returned by --json,
8899
+ including source and follow-on customer-db query commands when available.
8295
8900
 
8296
8901
  Examples:
8297
8902
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
8298
8903
  deepline runs export play/my-play/run/20260501t000000-000 --dataset result.rows --out output.csv
8904
+ deepline runs export play/my-play/run/20260501t000000-000 --out output.csv --metadata-out output.meta.json
8299
8905
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv --json
8300
8906
  `
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) => {
8907
+ ).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
8908
  process.exitCode = await handleRunExport([
8303
8909
  runId,
8304
8910
  ...options.dataset ? ["--dataset", options.dataset] : [],
8305
8911
  "--out",
8306
8912
  options.out,
8913
+ ...options.metadataOut ? ["--metadata-out", options.metadataOut] : [],
8307
8914
  ...options.json ? ["--json"] : []
8308
8915
  ]);
8309
8916
  });
8310
8917
  }
8311
8918
 
8312
8919
  // src/cli/commands/tools.ts
8313
- import { chmodSync, mkdtempSync, writeFileSync as writeFileSync7 } from "fs";
8920
+ import { chmodSync, mkdtempSync, writeFileSync as writeFileSync8 } from "fs";
8314
8921
  import { tmpdir as tmpdir3 } from "os";
8315
8922
  import { join as join8 } from "path";
8316
8923
 
8317
8924
  // src/tool-output.ts
8318
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
8925
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
8319
8926
  import { homedir as homedir3 } from "os";
8320
8927
  import { join as join7 } from "path";
8321
8928
  function isPlainObject(value) {
@@ -8340,6 +8947,25 @@ function normalizeRows(value) {
8340
8947
  }
8341
8948
  function candidateRoots(payload) {
8342
8949
  const roots = [{ path: null, value: payload }];
8950
+ if (isPlainObject(payload) && isPlainObject(payload.toolExecutionResult)) {
8951
+ roots.push({ path: "toolExecutionResult", value: payload.toolExecutionResult });
8952
+ const toolOutput = payload.toolExecutionResult.toolOutput;
8953
+ if (isPlainObject(toolOutput)) {
8954
+ roots.push({ path: "toolExecutionResult.toolOutput", value: toolOutput });
8955
+ if (Object.prototype.hasOwnProperty.call(toolOutput, "raw")) {
8956
+ roots.push({
8957
+ path: "toolExecutionResult.toolOutput.raw",
8958
+ value: toolOutput.raw
8959
+ });
8960
+ }
8961
+ }
8962
+ }
8963
+ if (isPlainObject(payload) && isPlainObject(payload.output)) {
8964
+ roots.push({ path: "output", value: payload.output });
8965
+ if (Object.prototype.hasOwnProperty.call(payload.output, "body")) {
8966
+ roots.push({ path: "output.body", value: payload.output.body });
8967
+ }
8968
+ }
8343
8969
  if (isPlainObject(payload) && isPlainObject(payload.result)) {
8344
8970
  roots.push({ path: "result", value: payload.result });
8345
8971
  if (isPlainObject(payload.result.data)) {
@@ -8399,7 +9025,7 @@ function ensureOutputDir() {
8399
9025
  function writeJsonOutputFile(payload, stem) {
8400
9026
  const outputDir = ensureOutputDir();
8401
9027
  const outputPath = join7(outputDir, `${stem}_${Date.now()}.json`);
8402
- writeFileSync6(outputPath, JSON.stringify(payload, null, 2), "utf-8");
9028
+ writeFileSync7(outputPath, JSON.stringify(payload, null, 2), "utf-8");
8403
9029
  return outputPath;
8404
9030
  }
8405
9031
  function writeCsvOutputFile(rows, stem) {
@@ -8427,7 +9053,7 @@ function writeCsvOutputFile(rows, stem) {
8427
9053
  for (const row of rows) {
8428
9054
  lines.push(columns.map((column) => escapeCell(row[column])).join(","));
8429
9055
  }
8430
- writeFileSync6(outputPath, `${lines.join("\n")}
9056
+ writeFileSync7(outputPath, `${lines.join("\n")}
8431
9057
  `, "utf-8");
8432
9058
  const previewRows = rows.slice(0, 5);
8433
9059
  const previewColumns = columns.slice(0, 5);
@@ -8473,8 +9099,7 @@ async function listTools(args) {
8473
9099
  title: `${items.length} tools available:`,
8474
9100
  lines: items.flatMap((item) => {
8475
9101
  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}`];
9102
+ return [`${item.toolId}${cats}`, ` ${item.description}`];
8478
9103
  })
8479
9104
  }
8480
9105
  ]
@@ -8553,7 +9178,7 @@ Common commands:
8553
9178
  deepline tools execute hunter_email_verifier --input '{"email":"a@b.com"}'
8554
9179
 
8555
9180
  Output:
8556
- Use describe for tool contracts. get is accepted as a compatibility alias.
9181
+ Use describe for tool contracts.
8557
9182
  Use execute to run a tool. run is accepted as a compatibility alias.
8558
9183
  `
8559
9184
  );
@@ -8600,8 +9225,8 @@ Examples:
8600
9225
  `
8601
9226
  Notes:
8602
9227
  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.
9228
+ and metadata. describe is the supported discovery verb. get is removed in
9229
+ the V2 SDK CLI; use describe for the same metadata surface.
8605
9230
 
8606
9231
  Examples:
8607
9232
  deepline tools describe hunter_email_verifier
@@ -8614,7 +9239,22 @@ Examples:
8614
9239
  ...options.json ? ["--json"] : []
8615
9240
  ]);
8616
9241
  });
8617
- addToolMetadataCommand(tools.command("describe <toolId>").alias("get"));
9242
+ addToolMetadataCommand(tools.command("describe <toolId>"));
9243
+ tools.command("get <toolId>").description("Deprecated. Use tools describe.").addHelpText(
9244
+ "after",
9245
+ `
9246
+ Examples:
9247
+ deepline tools describe hunter_email_verifier --json
9248
+ `
9249
+ ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (toolId, options) => {
9250
+ const message = `tools get has been removed from the V2 SDK CLI. Use: deepline tools describe ${toolId} --json`;
9251
+ if (options.json || shouldEmitJson()) {
9252
+ printJsonError({ message, code: "TOOLS_GET_REMOVED" });
9253
+ } else {
9254
+ console.error(message);
9255
+ }
9256
+ process.exitCode = 2;
9257
+ });
8618
9258
  tools.command("execute <toolId>").alias("run").description("Execute a tool by id.").addHelpText(
8619
9259
  "after",
8620
9260
  `
@@ -8695,6 +9335,7 @@ function printToolDetails(tool, requestedToolId) {
8695
9335
  const stepContributions = arrayField(tool, "stepContributions", "step_contributions");
8696
9336
  const playExpansion = recordField(tool, "playExpansion", "play_expansion");
8697
9337
  const samples = recordField(tool, "samples");
9338
+ const usageGuidance = recordField(tool, "usageGuidance", "usage_guidance");
8698
9339
  console.log(`Tool: ${toolId}`);
8699
9340
  if (displayName) {
8700
9341
  console.log(" Display name:");
@@ -8758,14 +9399,16 @@ function printToolDetails(tool, requestedToolId) {
8758
9399
  console.log(" Tip: pass --payload with a JSON object.");
8759
9400
  }
8760
9401
  printSamples(samples);
9402
+ printUsageGuidance(usageGuidance);
8761
9403
  if (isPlayTool(tool)) {
8762
9404
  console.log(" Play contract:");
8763
9405
  console.log(" - This is a deepline-native waterfall; the returned rows are extracted by target getters, not by hand-authored payload shape.");
8764
9406
  if (playExpansion && typeof playExpansion.group === "string" && playExpansion.group.trim()) {
8765
9407
  console.log(` - Output alias/runtime group is: ${playExpansion.group.trim()}`);
8766
9408
  }
8767
- const getters = recordField(tool, "resultIdentityGetters", "result_identity_getters");
8768
- const targets = Object.keys(getters).filter(Boolean).sort();
9409
+ const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult");
9410
+ const extractedValues = arrayField(toolExecutionResult, "extractedValues", "extracted_values");
9411
+ const targets = extractedValues.map((entry) => isRecord3(entry) && typeof entry.name === "string" ? entry.name : "").filter(Boolean).sort();
8769
9412
  if (targets.length) {
8770
9413
  console.log(` - Built-in extract targets: ${targets.join(", ")}`);
8771
9414
  }
@@ -8784,7 +9427,53 @@ function printToolDetails(tool, requestedToolId) {
8784
9427
  } else {
8785
9428
  console.log(` deepline tools execute ${toolId} --payload '{...}'`);
8786
9429
  }
8787
- console.log(" deepline tools get <tool_id> --json # full machine-readable output");
9430
+ console.log(" deepline tools describe <tool_id> --json");
9431
+ }
9432
+ function printUsageGuidance(usageGuidance) {
9433
+ if (Object.keys(usageGuidance).length === 0) return;
9434
+ const execute = stringField(usageGuidance, "execute");
9435
+ const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult", "tool_execution_result");
9436
+ const toolOutput = recordField(toolExecutionResult, "toolOutput", "tool_output");
9437
+ const extractedLists = extractionEntries(toolExecutionResult, "extractedLists", "extracted_lists");
9438
+ const extractedValues = extractionEntries(toolExecutionResult, "extractedValues", "extracted_values");
9439
+ console.log(" Usage guidance:");
9440
+ if (execute) console.log(` ${execute}`);
9441
+ const raw = pathField(toolOutput, "raw");
9442
+ const meta = pathField(toolOutput, "meta");
9443
+ if (raw) console.log(` Raw tool output: ${raw}`);
9444
+ if (meta) console.log(` Tool output metadata: ${meta}`);
9445
+ printExtractions("Extracted lists", extractedLists);
9446
+ printExtractions("Extracted values", extractedValues);
9447
+ }
9448
+ function pathField(record, key) {
9449
+ const value = record[key];
9450
+ if (typeof value === "string") return value.trim();
9451
+ if (isRecord3(value)) return stringField(value, "path");
9452
+ return "";
9453
+ }
9454
+ function extractionEntries(record, camelKey, snakeKey) {
9455
+ const value = record[camelKey] ?? record[snakeKey];
9456
+ if (Array.isArray(value)) return value;
9457
+ if (!isRecord3(value)) return [];
9458
+ return Object.entries(value).map(
9459
+ ([name, entry]) => isRecord3(entry) ? { name, ...entry } : { name }
9460
+ );
9461
+ }
9462
+ function printExtractions(label, entries) {
9463
+ if (!entries.length) return;
9464
+ console.log(` ${label}:`);
9465
+ for (const entry of entries) {
9466
+ if (!isRecord3(entry)) continue;
9467
+ const name = stringField(entry, "name");
9468
+ const expression = stringField(entry, "expression");
9469
+ const details = recordField(entry, "details");
9470
+ const rawToolOutputPaths = arrayField(details, "rawToolOutputPaths", "raw_tool_output_paths").map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
9471
+ const candidatePaths = arrayField(details, "candidatePaths", "candidate_paths").map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
9472
+ if (!name || !expression) continue;
9473
+ const paths = candidatePaths.length ? candidatePaths : rawToolOutputPaths;
9474
+ const pathSuffix = paths.length ? ` from ${paths.join(", ")}` : "";
9475
+ console.log(` - ${name}: ${expression}${pathSuffix}`);
9476
+ }
8788
9477
  }
8789
9478
  function printToolCost(input) {
8790
9479
  const { cost, billingSource, deeplineCredits, deeplineUsdPerPricingUnit } = input;
@@ -8852,6 +9541,17 @@ function samplePayload(samples, key) {
8852
9541
  function commandEnvelopeFromRawResponse(rawResponse) {
8853
9542
  return isRecord3(rawResponse) ? { ...rawResponse } : { status: "completed", result: rawResponse };
8854
9543
  }
9544
+ function listExtractorPathsFromUsageGuidance(tool) {
9545
+ const toolExecutionResult = tool.usageGuidance?.toolExecutionResult;
9546
+ const extractedLists = Array.isArray(toolExecutionResult?.extractedLists) ? toolExecutionResult.extractedLists : isRecord3(toolExecutionResult?.extractedLists) ? Object.values(toolExecutionResult.extractedLists) : [];
9547
+ return extractedLists.flatMap((entry) => {
9548
+ const paths = entry.details?.candidatePaths ?? entry.details?.rawToolOutputPaths;
9549
+ if (!Array.isArray(paths)) return [];
9550
+ return paths.map(
9551
+ (path) => path.trim().replace(/^toolExecutionResult\.toolOutput\.raw\.?/, "").replace(/^\./, "")
9552
+ ).filter(Boolean);
9553
+ });
9554
+ }
8855
9555
  function isPlayTool(tool) {
8856
9556
  const provider = typeof tool.provider === "string" ? tool.provider : "";
8857
9557
  return provider === "deepline_native" && Boolean(recordField(tool, "playExpansion", "play_expansion"));
@@ -8925,6 +9625,7 @@ function parseExecuteOptions(args) {
8925
9625
  const params = {};
8926
9626
  let outputFormat = "auto";
8927
9627
  let noPreview = false;
9628
+ let fullOutput = false;
8928
9629
  for (let index = 1; index < args.length; index += 1) {
8929
9630
  const arg = args[index];
8930
9631
  if ((arg === "--param" || arg === "-p") && args[index + 1]) {
@@ -8951,6 +9652,7 @@ function parseExecuteOptions(args) {
8951
9652
  }
8952
9653
  if (arg === "--full-output") {
8953
9654
  outputFormat = "json";
9655
+ fullOutput = true;
8954
9656
  continue;
8955
9657
  }
8956
9658
  if (arg === "--no-preview") {
@@ -8959,7 +9661,7 @@ function parseExecuteOptions(args) {
8959
9661
  }
8960
9662
  throw new Error(`Unknown option: ${arg}`);
8961
9663
  }
8962
- return { toolId, params, outputFormat, noPreview };
9664
+ return { toolId, params, outputFormat, noPreview, fullOutput };
8963
9665
  }
8964
9666
  function safeFileStem(value) {
8965
9667
  return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
@@ -8991,7 +9693,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
8991
9693
  description: ${JSON.stringify(`Seed ${input.toolId} rows for workflow expansion.`)},
8992
9694
  });
8993
9695
 
8994
- const list = Object.values(result.lists)[0];
9696
+ const list = Object.values(result.extractedLists)[0];
8995
9697
  const rows = (list?.get() ?? []).slice(0, 100);
8996
9698
  // ${sampleRows}
8997
9699
  // columns: ${columns}
@@ -9008,7 +9710,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
9008
9710
  };
9009
9711
  });
9010
9712
  `;
9011
- writeFileSync7(scriptPath, script, { encoding: "utf-8", mode: 384 });
9713
+ writeFileSync8(scriptPath, script, { encoding: "utf-8", mode: 384 });
9012
9714
  return {
9013
9715
  path: scriptPath,
9014
9716
  projectDir,
@@ -9019,7 +9721,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
9019
9721
  function buildToolExecuteBaseEnvelope(input) {
9020
9722
  const envelope = commandEnvelopeFromRawResponse(input.rawResponse);
9021
9723
  const summaryEntries = Object.entries(input.summary);
9022
- const output = input.listConversion ? {
9724
+ const outputPreview = input.listConversion ? {
9023
9725
  kind: "list",
9024
9726
  rowCount: input.listConversion.rows.length,
9025
9727
  columns: Object.keys(input.listConversion.rows[0] ?? {}),
@@ -9030,6 +9732,7 @@ function buildToolExecuteBaseEnvelope(input) {
9030
9732
  kind: summaryEntries.length > 0 ? "object" : "raw",
9031
9733
  summary: input.summary
9032
9734
  };
9735
+ const envelopeHasCanonicalOutput = isRecord3(envelope.toolExecutionResult) && isRecord3(envelope.toolExecutionResult.toolOutput) && Object.prototype.hasOwnProperty.call(envelope.toolExecutionResult.toolOutput, "raw");
9033
9736
  const actions = input.listConversion ? [
9034
9737
  {
9035
9738
  label: "next",
@@ -9038,7 +9741,7 @@ function buildToolExecuteBaseEnvelope(input) {
9038
9741
  ] : [];
9039
9742
  return {
9040
9743
  ...envelope,
9041
- output,
9744
+ ...envelopeHasCanonicalOutput ? { output_preview: outputPreview } : { output: outputPreview },
9042
9745
  ...summaryEntries.length > 0 ? { summary: input.summary } : {},
9043
9746
  next: input.listConversion ? {
9044
9747
  expandToPlay: "Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
@@ -9091,8 +9794,12 @@ async function executeTool(args) {
9091
9794
  throw error;
9092
9795
  }
9093
9796
  const rawResponse = await client.executeTool(parsed.toolId, parsed.params);
9797
+ if (parsed.fullOutput) {
9798
+ printJson(rawResponse);
9799
+ return 0;
9800
+ }
9094
9801
  const listConversion = tryConvertToList(rawResponse, {
9095
- listExtractorPaths: metadata.listExtractorPaths ?? []
9802
+ listExtractorPaths: listExtractorPathsFromUsageGuidance(metadata)
9096
9803
  });
9097
9804
  const summary = extractSummaryFields(rawResponse);
9098
9805
  const baseEnvelope = buildToolExecuteBaseEnvelope({
@@ -9224,7 +9931,7 @@ async function executeTool(args) {
9224
9931
  // src/cli/commands/update.ts
9225
9932
  import { spawn } from "child_process";
9226
9933
  import { existsSync as existsSync7 } from "fs";
9227
- import { dirname as dirname9, join as join9, resolve as resolve9 } from "path";
9934
+ import { dirname as dirname9, join as join9, resolve as resolve10 } from "path";
9228
9935
  function posixShellQuote(value) {
9229
9936
  return `'${value.replace(/'/g, `'\\''`)}'`;
9230
9937
  }
@@ -9243,7 +9950,7 @@ function buildSourceUpdateCommand(sourceRoot) {
9243
9950
  return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
9244
9951
  }
9245
9952
  function findRepoBackedSdkRoot(startPath) {
9246
- let current = resolve9(startPath);
9953
+ let current = resolve10(startPath);
9247
9954
  while (true) {
9248
9955
  if (existsSync7(join9(current, "sdk", "package.json")) && existsSync7(join9(current, "sdk", "bin", "deepline-dev.ts"))) {
9249
9956
  return current;
@@ -9254,7 +9961,7 @@ function findRepoBackedSdkRoot(startPath) {
9254
9961
  }
9255
9962
  }
9256
9963
  function resolveUpdatePlan() {
9257
- const entrypoint = process.argv[1] ? resolve9(process.argv[1]) : "";
9964
+ const entrypoint = process.argv[1] ? resolve10(process.argv[1]) : "";
9258
9965
  const sourceRoot = entrypoint ? findRepoBackedSdkRoot(dirname9(entrypoint)) : null;
9259
9966
  if (sourceRoot) {
9260
9967
  return {
@@ -9348,7 +10055,7 @@ Examples:
9348
10055
 
9349
10056
  // src/cli/skills-sync.ts
9350
10057
  import { spawn as spawn2, spawnSync } from "child_process";
9351
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync8 } from "fs";
10058
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync9 } from "fs";
9352
10059
  import { homedir as homedir4 } from "os";
9353
10060
  import { dirname as dirname10, join as join10 } from "path";
9354
10061
  var CHECK_TIMEOUT_MS2 = 3e3;
@@ -9375,9 +10082,45 @@ function readLocalSkillsVersion(baseUrl) {
9375
10082
  function writeLocalSkillsVersion(baseUrl, version) {
9376
10083
  const path = sdkSkillsVersionPath(baseUrl);
9377
10084
  mkdirSync5(dirname10(path), { recursive: true });
9378
- writeFileSync8(path, `${version}
10085
+ writeFileSync9(path, `${version}
9379
10086
  `, "utf-8");
9380
10087
  }
10088
+ function installedSdkSkillHasStalePositionalExecuteExamples() {
10089
+ const home = process.env.HOME?.trim() || homedir4();
10090
+ const roots = [
10091
+ join10(home, ".claude", "skills", SDK_SKILL_NAME),
10092
+ join10(home, ".agents", "skills", SDK_SKILL_NAME)
10093
+ ];
10094
+ const staleMarkers = [
10095
+ "ctx.tools.execute(key",
10096
+ "ctx.tools.execute('",
10097
+ 'ctx.tools.execute("',
10098
+ "rowCtx.tools.execute('",
10099
+ 'rowCtx.tools.execute("'
10100
+ ];
10101
+ const scan = (dir) => {
10102
+ for (const entry of readdirSync2(dir)) {
10103
+ const path = join10(dir, entry);
10104
+ const stat3 = statSync(path);
10105
+ if (stat3.isDirectory()) {
10106
+ if (scan(path)) return true;
10107
+ continue;
10108
+ }
10109
+ if (!entry.endsWith(".md")) continue;
10110
+ const text = readFileSync6(path, "utf-8");
10111
+ if (staleMarkers.some((marker) => text.includes(marker))) return true;
10112
+ }
10113
+ return false;
10114
+ };
10115
+ for (const root of roots) {
10116
+ try {
10117
+ if (existsSync8(root) && scan(root)) return true;
10118
+ } catch {
10119
+ continue;
10120
+ }
10121
+ }
10122
+ return false;
10123
+ }
9381
10124
  async function fetchSkillsUpdate(baseUrl, localVersion) {
9382
10125
  const controller = new AbortController();
9383
10126
  const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
@@ -9413,7 +10156,7 @@ function buildSkillsInstallArgs(baseUrl) {
9413
10156
  "skills",
9414
10157
  "add",
9415
10158
  packageUrl,
9416
- "--agents",
10159
+ "--agent",
9417
10160
  ...SKILL_AGENTS,
9418
10161
  "--global",
9419
10162
  "--yes",
@@ -9429,7 +10172,7 @@ function buildBunxSkillsInstallArgs(baseUrl) {
9429
10172
  "skills",
9430
10173
  "add",
9431
10174
  packageUrl,
9432
- "--agents",
10175
+ "--agent",
9433
10176
  ...SKILL_AGENTS,
9434
10177
  "--global",
9435
10178
  "--yes",
@@ -9469,7 +10212,7 @@ function resolveSkillsInstallCommands(baseUrl) {
9469
10212
  return [npxInstall];
9470
10213
  }
9471
10214
  function runOneSkillsInstall(install) {
9472
- return new Promise((resolve10) => {
10215
+ return new Promise((resolve11) => {
9473
10216
  const child = spawn2(install.command, install.args, {
9474
10217
  stdio: ["ignore", "ignore", "pipe"],
9475
10218
  env: process.env
@@ -9479,7 +10222,7 @@ function runOneSkillsInstall(install) {
9479
10222
  stderr += chunk.toString("utf-8");
9480
10223
  });
9481
10224
  child.on("error", (error) => {
9482
- resolve10({
10225
+ resolve11({
9483
10226
  ok: false,
9484
10227
  detail: `failed to start ${install.command}: ${error.message}`,
9485
10228
  manualCommand: install.manualCommand
@@ -9487,11 +10230,11 @@ function runOneSkillsInstall(install) {
9487
10230
  });
9488
10231
  child.on("close", (code) => {
9489
10232
  if (code === 0) {
9490
- resolve10({ ok: true, detail: "", manualCommand: install.manualCommand });
10233
+ resolve11({ ok: true, detail: "", manualCommand: install.manualCommand });
9491
10234
  return;
9492
10235
  }
9493
10236
  const detail = stderr.trim();
9494
- resolve10({
10237
+ resolve11({
9495
10238
  ok: false,
9496
10239
  detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
9497
10240
  manualCommand: install.manualCommand
@@ -9530,10 +10273,19 @@ async function syncSdkSkillsIfNeeded(baseUrl) {
9530
10273
  attemptedSync = true;
9531
10274
  const localVersion = readLocalSkillsVersion(baseUrl);
9532
10275
  const update = await fetchSkillsUpdate(baseUrl, localVersion);
9533
- if (!update?.needsUpdate || !update.remoteVersion) return;
10276
+ const hasStaleInstalledSkill = installedSdkSkillHasStalePositionalExecuteExamples();
10277
+ if (!update?.needsUpdate && !hasStaleInstalledSkill || !update?.remoteVersion) {
10278
+ return;
10279
+ }
9534
10280
  writeSdkSkillsStatusLine("SDK skills changed; syncing deepline-sdk skill...");
9535
10281
  const installed = await runSkillsInstall(baseUrl);
9536
10282
  if (!installed) return;
10283
+ if (installedSdkSkillHasStalePositionalExecuteExamples()) {
10284
+ process.stderr.write(
10285
+ "SDK skills sync completed, but installed deepline-sdk docs still contain stale positional ctx.tools.execute examples.\n"
10286
+ );
10287
+ return;
10288
+ }
9537
10289
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
9538
10290
  writeSdkSkillsStatusLine("SDK skills are up to date.");
9539
10291
  }