deepline 0.1.32 → 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.
@@ -4,7 +4,7 @@
4
4
  import { Command as Command2 } from "commander";
5
5
 
6
6
  // src/config.ts
7
- import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
8
  import { homedir } from "os";
9
9
  import { dirname, join, resolve } from "path";
10
10
 
@@ -44,19 +44,12 @@ var ConfigError = class extends DeeplineError {
44
44
  };
45
45
 
46
46
  // src/config.ts
47
+ var HOST_URL_ENV = "DEEPLINE_HOST_URL";
48
+ var API_KEY_ENV = "DEEPLINE_API_KEY";
47
49
  var PROD_URL = "https://code.deepline.com";
48
50
  var DEFAULT_TIMEOUT = 6e4;
49
51
  var DEFAULT_MAX_RETRIES = 3;
50
- var ACTIVE_DEEPLINE_ENV_FILE = ".env.deepline";
51
- function isProdBaseUrl(baseUrl) {
52
- return baseUrl.trim().replace(/\/$/, "") === PROD_URL;
53
- }
54
- function profileNameForBaseUrl(baseUrl) {
55
- return isProdBaseUrl(baseUrl) ? "prod" : "dev";
56
- }
57
- function projectEnvStartDir() {
58
- return process.env.DEEPLINE_PROJECT_ENV_DIR?.trim() || process.cwd();
59
- }
52
+ var PROJECT_DEEPLINE_ENV_FILE = ".env.deepline";
60
53
  function baseUrlSlug(baseUrl) {
61
54
  let url;
62
55
  try {
@@ -65,7 +58,7 @@ function baseUrlSlug(baseUrl) {
65
58
  return "unknown";
66
59
  }
67
60
  const host = url.hostname || "unknown";
68
- const port = url.port ? parseInt(url.port, 10) : null;
61
+ const port = url.port ? Number.parseInt(url.port, 10) : null;
69
62
  let slug = host.replace(/[^a-zA-Z0-9]/g, "-");
70
63
  if (port && port !== 80 && port !== 443) {
71
64
  slug = `${slug}-${port}`;
@@ -92,79 +85,52 @@ function parseEnvFile(filePath) {
92
85
  }
93
86
  return env;
94
87
  }
95
- function findNearestEnvFile(names, startDir = process.cwd()) {
88
+ function findNearestEnvFile(name, startDir = process.cwd()) {
96
89
  let current = resolve(startDir);
97
90
  while (true) {
98
- for (const name of names) {
99
- const filePath = join(current, name);
100
- if (existsSync(filePath)) return filePath;
101
- }
91
+ const filePath = join(current, name);
92
+ if (existsSync(filePath)) return filePath;
102
93
  const parent = dirname(current);
103
94
  if (parent === current) return null;
104
95
  current = parent;
105
96
  }
106
97
  }
107
- function findNearestEnv(names, startDir = process.cwd()) {
108
- const filePath = findNearestEnvFile(names, startDir);
98
+ function loadProjectDeeplineEnv(startDir = process.cwd()) {
99
+ const filePath = findNearestEnvFile(PROJECT_DEEPLINE_ENV_FILE, startDir);
109
100
  return filePath ? parseEnvFile(filePath) : {};
110
101
  }
111
- function findNearestWorktreeEnv(startDir = process.cwd()) {
112
- return findNearestEnv([".env.worktree"], startDir);
113
- }
114
- function resolveProfileEnvFileNames() {
115
- const explicitProfile = process.env.DEEPLINE_ENV_PROFILE?.trim() || process.env.DEEPLINE_PROFILE?.trim() || "";
116
- const names = [];
117
- if (explicitProfile) names.push(`.env.deepline.${explicitProfile}`);
118
- const nodeEnv = process.env.NODE_ENV?.trim();
119
- if (nodeEnv === "production") names.push(".env.deepline.prod");
120
- else if (nodeEnv === "staging") names.push(".env.deepline.staging");
121
- names.push(ACTIVE_DEEPLINE_ENV_FILE);
122
- return names;
123
- }
124
- function resolveProjectAppEnvFileNames() {
125
- const nodeEnv = process.env.NODE_ENV?.trim();
126
- const names = [];
127
- if (nodeEnv === "production") names.push(".env.prod");
128
- if (nodeEnv === "staging") names.push(".env.staging");
129
- names.push(".env.local", ".env");
130
- return names;
131
- }
132
- function resolveBaseUrlFromEnvValues(env) {
133
- return env.DEEPLINE_ORIGIN_URL?.trim() || env.DEEPLINE_API_BASE_URL?.trim() || "";
134
- }
135
- function loadProjectDeeplineEnv() {
136
- return findNearestEnv(resolveProfileEnvFileNames(), projectEnvStartDir());
137
- }
138
- function loadProjectAppEnv() {
139
- return findNearestEnv(resolveProjectAppEnvFileNames(), projectEnvStartDir());
140
- }
141
- function normalizeWorktreeBaseUrl(baseUrl, worktreeEnv = findNearestWorktreeEnv()) {
142
- const trimmed = baseUrl.trim().replace(/\/$/, "");
143
- if (!trimmed) return trimmed;
102
+ function normalizeBaseUrl(baseUrl) {
103
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
104
+ if (!trimmed) return "";
144
105
  try {
145
106
  const parsed = new URL(trimmed);
146
- if (parsed.hostname.endsWith(".localhost") && parsed.port === "1355") {
147
- const port = worktreeEnv.WORKTREE_APP_PORT || worktreeEnv.PORT;
148
- if (port) return `${parsed.protocol}//localhost:${port}`;
107
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
108
+ return "";
149
109
  }
110
+ return parsed.toString().replace(/\/+$/, "");
150
111
  } catch {
112
+ return "";
151
113
  }
152
- return trimmed;
153
114
  }
154
- function resolveWorktreeBaseUrl() {
155
- const worktreeEnv = findNearestWorktreeEnv();
156
- const declared = worktreeEnv.DEEPLINE_API_BASE_URL || worktreeEnv.WORKTREE_PUBLIC_APP_URL || worktreeEnv.APP_URL || "";
157
- if (declared) return normalizeWorktreeBaseUrl(declared, worktreeEnv);
158
- const port = worktreeEnv.WORKTREE_APP_PORT || worktreeEnv.PORT || "";
159
- return port ? `http://localhost:${port}` : "";
115
+ function firstNonEmpty(...values) {
116
+ for (const value of values) {
117
+ const trimmed = value?.trim();
118
+ if (trimmed) return trimmed;
119
+ }
120
+ return "";
160
121
  }
161
- function sdkCliEnvFilePath(baseUrl) {
122
+ function sdkCliConfigDir(baseUrl) {
162
123
  const home = process.env.HOME?.trim() || homedir();
163
- return join(home, ".local", "deepline", baseUrlSlug(baseUrl || PROD_URL), ".env");
124
+ return join(home, ".local", "deepline", baseUrlSlug(baseUrl || PROD_URL));
125
+ }
126
+ function sdkCliEnvFilePath(baseUrl) {
127
+ return join(sdkCliConfigDir(baseUrl), ".env");
164
128
  }
165
129
  function loadCliEnv(baseUrl = PROD_URL) {
166
- const envPath = sdkCliEnvFilePath(baseUrl);
167
- return parseEnvFile(envPath);
130
+ return parseEnvFile(sdkCliEnvFilePath(baseUrl));
131
+ }
132
+ function hostConfigDirPath(baseUrl) {
133
+ return sdkCliConfigDir(baseUrl);
168
134
  }
169
135
  function hostEnvFilePath(baseUrl) {
170
136
  return sdkCliEnvFilePath(baseUrl);
@@ -175,9 +141,10 @@ function saveHostEnvValues(baseUrl, values) {
175
141
  if (!existsSync(dir)) {
176
142
  mkdirSync(dir, { recursive: true });
177
143
  }
178
- const existing = existsSync(filePath) ? parseEnvFile(filePath) : {};
144
+ const existing = parseEnvFile(filePath);
179
145
  const merged = { ...existing, ...values };
180
- const lines = Object.entries(merged).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${value}`);
146
+ const allowedKeys = /* @__PURE__ */ new Set([HOST_URL_ENV, API_KEY_ENV]);
147
+ const lines = Object.entries(merged).filter(([key, value]) => allowedKeys.has(key) && value !== "").map(([key, value]) => `${key}=${value}`);
181
148
  writeFileSync(filePath, `${lines.join("\n")}
182
149
  `, "utf-8");
183
150
  }
@@ -185,31 +152,36 @@ function loadGlobalCliEnv() {
185
152
  return loadCliEnv(PROD_URL);
186
153
  }
187
154
  function autoDetectBaseUrl() {
188
- const envOrigin = process.env.DEEPLINE_ORIGIN_URL?.trim();
189
- if (envOrigin) return normalizeWorktreeBaseUrl(envOrigin);
190
- const envBase = process.env.DEEPLINE_API_BASE_URL?.trim();
191
- if (envBase) return normalizeWorktreeBaseUrl(envBase);
192
- const projectDeeplineBaseUrl = resolveBaseUrlFromEnvValues(loadProjectDeeplineEnv());
193
- if (projectDeeplineBaseUrl) return normalizeWorktreeBaseUrl(projectDeeplineBaseUrl);
194
- const projectAppBaseUrl = resolveBaseUrlFromEnvValues(loadProjectAppEnv());
195
- if (projectAppBaseUrl) return normalizeWorktreeBaseUrl(projectAppBaseUrl);
196
- const worktreeBaseUrl = resolveWorktreeBaseUrl();
197
- if (worktreeBaseUrl) return worktreeBaseUrl;
155
+ const projectEnv = loadProjectDeeplineEnv();
198
156
  const globalEnv = loadGlobalCliEnv();
199
- const globalOrigin = globalEnv.DEEPLINE_ORIGIN_URL?.trim();
200
- if (globalOrigin) return normalizeWorktreeBaseUrl(globalOrigin);
201
- return PROD_URL;
157
+ return normalizeBaseUrl(process.env[HOST_URL_ENV] ?? "") || normalizeBaseUrl(projectEnv[HOST_URL_ENV] ?? "") || normalizeBaseUrl(globalEnv[HOST_URL_ENV] ?? "") || PROD_URL;
158
+ }
159
+ function resolveApiKeyForBaseUrl(baseUrl, explicitApiKey) {
160
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
161
+ const projectEnv = loadProjectDeeplineEnv();
162
+ const cliEnv = loadCliEnv(normalizedBaseUrl || baseUrl);
163
+ const projectBaseUrl = normalizeBaseUrl(projectEnv[HOST_URL_ENV] ?? "");
164
+ const projectKeyApplies = projectBaseUrl === normalizedBaseUrl;
165
+ return firstNonEmpty(
166
+ explicitApiKey,
167
+ process.env[API_KEY_ENV],
168
+ projectKeyApplies ? projectEnv[API_KEY_ENV] : "",
169
+ cliEnv[API_KEY_ENV]
170
+ );
202
171
  }
203
172
  function resolveConfig(options) {
204
- const requestedBaseUrl = options?.baseUrl?.trim() || autoDetectBaseUrl();
205
- const baseUrl = normalizeWorktreeBaseUrl(requestedBaseUrl);
206
- const cliEnv = loadCliEnv(baseUrl);
207
- const projectDeeplineEnv = loadProjectDeeplineEnv();
208
- const projectAppEnv = loadProjectAppEnv();
209
- const apiKey = options?.apiKey?.trim() || process.env.DEEPLINE_API_KEY?.trim() || projectDeeplineEnv.DEEPLINE_API_KEY || projectAppEnv.DEEPLINE_API_KEY || cliEnv.DEEPLINE_API_KEY || "";
173
+ const baseUrl = normalizeBaseUrl(
174
+ options?.baseUrl?.trim() || autoDetectBaseUrl()
175
+ );
176
+ if (!baseUrl) {
177
+ throw new ConfigError(
178
+ `Invalid ${HOST_URL_ENV}. Expected an http(s) URL such as https://code.deepline.com.`
179
+ );
180
+ }
181
+ const apiKey = resolveApiKeyForBaseUrl(baseUrl, options?.apiKey);
210
182
  if (!apiKey) {
211
183
  throw new ConfigError(
212
- `No API key found. Set DEEPLINE_API_KEY env var, pass apiKey option, or run: deepline auth register`
184
+ `No API key found. Set ${API_KEY_ENV}, add it to .env.deepline, or run: deepline auth register`
213
185
  );
214
186
  }
215
187
  return {
@@ -219,32 +191,10 @@ function resolveConfig(options) {
219
191
  maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES
220
192
  };
221
193
  }
222
- function mergeEnvFile(filePath, values) {
223
- const existing = existsSync(filePath) ? parseEnvFile(filePath) : {};
224
- const merged = { ...existing, ...values };
225
- const dir = dirname(filePath);
226
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
227
- const lines = Object.entries(merged).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${value}`);
228
- writeFileSync(filePath, `${lines.join("\n")}
229
- `, "utf-8");
230
- }
231
- function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStartDir()) {
232
- const root = resolve(startDir);
233
- const profile = profileNameForBaseUrl(baseUrl);
234
- const files = [
235
- join(root, ACTIVE_DEEPLINE_ENV_FILE),
236
- join(root, `.env.deepline.${profile}`)
237
- ];
238
- if (profile === "dev") files.push(join(root, ".env"));
239
- for (const filePath of files) {
240
- mergeEnvFile(filePath, values);
241
- }
242
- return files;
243
- }
244
194
 
245
195
  // src/version.ts
246
- var SDK_VERSION = "0.1.32";
247
- var SDK_API_CONTRACT = "2026-05-generic-play-input-flags";
196
+ var SDK_VERSION = "0.1.35";
197
+ var SDK_API_CONTRACT = "2026-05-v2-tool-result-contract";
248
198
 
249
199
  // ../shared_libs/play-runtime/coordinator-headers.ts
250
200
  var COORDINATOR_INTERNAL_TOKEN_HEADER = "x-deepline-internal-token";
@@ -326,7 +276,7 @@ var HttpClient = class {
326
276
  const response = await fetch(candidateUrl, {
327
277
  method,
328
278
  headers,
329
- 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,
330
280
  signal: controller.signal
331
281
  });
332
282
  clearTimeout(timeoutId);
@@ -409,10 +359,13 @@ var HttpClient = class {
409
359
  throw new AuthError();
410
360
  }
411
361
  if (!response.ok) {
362
+ const body = await response.text();
363
+ const parsed = parseResponseBody(body);
412
364
  throw new DeeplineError(
413
- `HTTP ${response.status}`,
365
+ apiErrorMessage(parsed, response.status),
414
366
  response.status,
415
- "API_ERROR"
367
+ "API_ERROR",
368
+ { response: parsed }
416
369
  );
417
370
  }
418
371
  if (!response.body) {
@@ -462,6 +415,26 @@ var HttpClient = class {
462
415
  return this.request(path, { method: "DELETE" });
463
416
  }
464
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
+ }
465
438
  function parseRetryAfter(response) {
466
439
  const header = response.headers.get("retry-after");
467
440
  if (header) {
@@ -525,15 +498,17 @@ function decodeSseFrame(frame) {
525
498
  return parsed;
526
499
  }
527
500
  function sleep(ms) {
528
- return new Promise((resolve10) => setTimeout(resolve10, ms));
501
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
529
502
  }
530
503
 
531
504
  // src/client.ts
532
505
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
533
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";
534
509
  var COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1e3];
535
510
  function sleep2(ms) {
536
- return new Promise((resolve10) => setTimeout(resolve10, ms));
511
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
537
512
  }
538
513
  function isTransientCompileManifestError(error) {
539
514
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
@@ -799,13 +774,16 @@ var DeeplineClient = class {
799
774
  /**
800
775
  * Execute a tool and return the standard execution envelope.
801
776
  *
802
- * The `result.data` field contains the provider payload. `result.meta`
803
- * 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.
804
779
  * Top-level fields such as `status`, `job_id`, and `billing` describe the
805
- * Deepline execution.
780
+ * Deepline execution envelope.
806
781
  */
807
782
  async executeTool(toolId, input, options) {
808
- 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
+ };
809
787
  return this.http.post(
810
788
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
811
789
  { payload: input },
@@ -1103,34 +1081,37 @@ var DeeplineClient = class {
1103
1081
  * ```
1104
1082
  */
1105
1083
  async stagePlayFiles(files) {
1106
- const formData = new FormData();
1107
- formData.set(
1108
- "metadata",
1109
- JSON.stringify({
1110
- files: files.map((file, index) => ({
1111
- index,
1112
- logicalPath: file.logicalPath,
1113
- contentHash: file.contentHash,
1114
- contentType: file.contentType,
1115
- bytes: file.bytes
1116
- }))
1117
- })
1118
- );
1119
- for (const [index, file] of files.entries()) {
1120
- const bytes = decodeBase64Bytes(file.contentBase64);
1121
- const body = bytes.buffer.slice(
1122
- bytes.byteOffset,
1123
- bytes.byteOffset + bytes.byteLength
1124
- );
1084
+ const buildFormData = () => {
1085
+ const formData = new FormData();
1125
1086
  formData.set(
1126
- `file:${index}`,
1127
- new Blob([body], { type: file.contentType }),
1128
- 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
+ })
1129
1097
  );
1130
- }
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
+ };
1131
1112
  const response = await this.http.postFormData(
1132
1113
  "/api/v2/plays/files/stage",
1133
- formData
1114
+ buildFormData
1134
1115
  );
1135
1116
  return response.files;
1136
1117
  }
@@ -1654,7 +1635,7 @@ async function enforceSdkCompatibility(baseUrl) {
1654
1635
  }
1655
1636
 
1656
1637
  // src/cli/commands/auth.ts
1657
- import { writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
1638
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
1658
1639
  import { hostname } from "os";
1659
1640
  import { dirname as dirname3 } from "path";
1660
1641
 
@@ -1668,10 +1649,11 @@ import {
1668
1649
  import { mkdir, writeFile } from "fs/promises";
1669
1650
  import { homedir as homedir2 } from "os";
1670
1651
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
1671
- import { execFileSync, spawnSync } from "child_process";
1652
+ import * as childProcess from "child_process";
1672
1653
  import { parse } from "csv-parse/sync";
1673
1654
  import { stringify } from "csv-stringify/sync";
1674
1655
  var BROWSER_FOCUS_COOLDOWN_MS = 3e4;
1656
+ var defaultBrowserCommandRunner = childProcess;
1675
1657
  function getAuthedHttpClient() {
1676
1658
  const config = resolveConfig();
1677
1659
  return { config, http: new HttpClient(config) };
@@ -1740,9 +1722,9 @@ function browserAppNameFromBundleId(bundleId) {
1740
1722
  };
1741
1723
  return names[bundleId.toLowerCase()] ?? "";
1742
1724
  }
1743
- function readDefaultMacBrowserBundleId() {
1725
+ function readDefaultMacBrowserBundleId(runner = defaultBrowserCommandRunner) {
1744
1726
  try {
1745
- const output = execFileSync(
1727
+ const output = runner.execFileSync(
1746
1728
  "/usr/bin/defaults",
1747
1729
  [
1748
1730
  "read",
@@ -1778,8 +1760,8 @@ function browserStrategyForBundleId(bundleId) {
1778
1760
  }
1779
1761
  return normalized === "com.apple.safari" ? "safari" : "fallback";
1780
1762
  }
1781
- function runAppleScript(script, args) {
1782
- const result = spawnSync("osascript", ["-", ...args], {
1763
+ function runAppleScript(script, args, runner = defaultBrowserCommandRunner) {
1764
+ const result = runner.spawnSync("osascript", ["-", ...args], {
1783
1765
  input: script,
1784
1766
  encoding: "utf-8",
1785
1767
  stdio: ["pipe", "ignore", "ignore"],
@@ -1787,7 +1769,7 @@ function runAppleScript(script, args) {
1787
1769
  });
1788
1770
  return result.status === 0;
1789
1771
  }
1790
- function retargetChromiumMacos(appName, targetUrl, allowFocus) {
1772
+ function retargetChromiumMacos(appName, targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
1791
1773
  const host = extractUrlHost(targetUrl);
1792
1774
  if (!host) return false;
1793
1775
  const escapedAppName = appName.replace(/"/g, '\\"');
@@ -1819,9 +1801,9 @@ ${newTabBlock} end if
1819
1801
  end tell
1820
1802
  end run
1821
1803
  `;
1822
- return runAppleScript(script, [targetUrl, host]);
1804
+ return runAppleScript(script, [targetUrl, host], runner);
1823
1805
  }
1824
- function retargetSafariMacos(appName, targetUrl, allowFocus) {
1806
+ function retargetSafariMacos(appName, targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
1825
1807
  const host = extractUrlHost(targetUrl);
1826
1808
  if (!host) return false;
1827
1809
  const escapedAppName = appName.replace(/"/g, '\\"');
@@ -1853,30 +1835,38 @@ ${newTabBlock} end if
1853
1835
  end tell
1854
1836
  end run
1855
1837
  `;
1856
- return runAppleScript(script, [targetUrl, host]);
1838
+ return runAppleScript(script, [targetUrl, host], runner);
1857
1839
  }
1858
- function openUrlMacos(targetUrl, allowFocus) {
1859
- const defaultBundleId = readDefaultMacBrowserBundleId();
1840
+ function openUrlMacos(targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
1841
+ const defaultBundleId = readDefaultMacBrowserBundleId(runner);
1860
1842
  const appName = defaultBundleId ? browserAppNameFromBundleId(defaultBundleId) : "";
1861
1843
  const strategy = browserStrategyForBundleId(defaultBundleId);
1862
- if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus)) {
1844
+ if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus, runner)) {
1863
1845
  return true;
1864
1846
  }
1865
- if (appName && strategy === "safari" && retargetSafariMacos(appName, targetUrl, allowFocus)) {
1847
+ if (appName && strategy === "safari" && retargetSafariMacos(appName, targetUrl, allowFocus, runner)) {
1866
1848
  return true;
1867
1849
  }
1868
- if (!allowFocus) {
1869
- return false;
1870
- }
1871
1850
  try {
1872
- execFileSync("open", [targetUrl], { stdio: "ignore" });
1851
+ runner.execFileSync(
1852
+ "open",
1853
+ [...allowFocus ? [] : ["-g"], targetUrl],
1854
+ { stdio: "ignore" }
1855
+ );
1873
1856
  return true;
1874
1857
  } catch {
1875
1858
  return false;
1876
1859
  }
1877
1860
  }
1861
+ function browserOpeningDisabled() {
1862
+ const value = String(
1863
+ process.env.DEEPLINE_NO_BROWSER ?? process.env.PLAYGROUND_HEADLESS ?? ""
1864
+ ).trim().toLowerCase();
1865
+ return value === "1" || value === "true" || value === "yes" || value === "on";
1866
+ }
1878
1867
  function openInBrowser(url) {
1879
1868
  try {
1869
+ if (browserOpeningDisabled()) return;
1880
1870
  const targetUrl = String(url || "").trim();
1881
1871
  if (!targetUrl) return;
1882
1872
  const allowFocus = claimBrowserFocus();
@@ -1886,12 +1876,12 @@ function openInBrowser(url) {
1886
1876
  }
1887
1877
  if (!allowFocus) return;
1888
1878
  if (process.platform === "win32") {
1889
- execFileSync("cmd.exe", ["/c", "start", "", targetUrl], {
1879
+ childProcess.execFileSync("cmd.exe", ["/c", "start", "", targetUrl], {
1890
1880
  stdio: "ignore"
1891
1881
  });
1892
1882
  return;
1893
1883
  }
1894
- execFileSync("xdg-open", [targetUrl], { stdio: "ignore" });
1884
+ childProcess.execFileSync("xdg-open", [targetUrl], { stdio: "ignore" });
1895
1885
  } catch {
1896
1886
  }
1897
1887
  }
@@ -1915,6 +1905,103 @@ function csvStringFromRows(rows, columns) {
1915
1905
  ...columns?.length ? { columns } : {}
1916
1906
  });
1917
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
+ }
1918
2005
  function printJson(value) {
1919
2006
  process.stdout.write(`${JSON.stringify(value, null, 2)}
1920
2007
  `);
@@ -2005,17 +2092,39 @@ var EXIT_SERVER = 2;
2005
2092
  function envFilePath(baseUrl) {
2006
2093
  return hostEnvFilePath(baseUrl);
2007
2094
  }
2008
- function saveEnvValues(values, baseUrl) {
2009
- const filePath = envFilePath(baseUrl);
2095
+ function pendingClaimTokenPath(baseUrl) {
2096
+ return `${hostConfigDirPath(baseUrl)}/pending-claim-token`;
2097
+ }
2098
+ function savePendingClaimToken(baseUrl, claimToken) {
2099
+ const filePath = pendingClaimTokenPath(baseUrl);
2010
2100
  const dir = dirname3(filePath);
2011
2101
  if (!existsSync3(dir)) {
2012
2102
  mkdirSync3(dir, { recursive: true });
2013
2103
  }
2014
- const existing = existsSync3(filePath) ? parseEnvFile(filePath) : {};
2015
- const merged = { ...existing, ...values };
2016
- const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
2017
- writeFileSync3(filePath, lines.join("\n") + "\n", "utf-8");
2018
- saveProjectDeeplineEnvValues(baseUrl, values);
2104
+ writeFileSync3(filePath, `${claimToken}
2105
+ `, "utf-8");
2106
+ }
2107
+ function readPendingClaimToken(baseUrl) {
2108
+ const filePath = pendingClaimTokenPath(baseUrl);
2109
+ if (!existsSync3(filePath)) return "";
2110
+ try {
2111
+ return readFileSync3(filePath, "utf-8").trim();
2112
+ } catch {
2113
+ return "";
2114
+ }
2115
+ }
2116
+ function clearPendingClaimToken(baseUrl) {
2117
+ try {
2118
+ rmSync(pendingClaimTokenPath(baseUrl), { force: true });
2119
+ } catch {
2120
+ }
2121
+ }
2122
+ function saveEnvValues(values, baseUrl) {
2123
+ const filtered = {
2124
+ ...values[HOST_URL_ENV] ? { [HOST_URL_ENV]: values[HOST_URL_ENV] } : {},
2125
+ ...values[API_KEY_ENV] ? { [API_KEY_ENV]: values[API_KEY_ENV] } : {}
2126
+ };
2127
+ saveHostEnvValues(baseUrl, filtered);
2019
2128
  }
2020
2129
  async function httpJson(method, url, apiKey, body) {
2021
2130
  const headers = { "Content-Type": "application/json" };
@@ -2069,7 +2178,7 @@ function buildCandidateUrls2(url) {
2069
2178
  }
2070
2179
  }
2071
2180
  function sleep3(ms) {
2072
- return new Promise((resolve10) => setTimeout(resolve10, ms));
2181
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
2073
2182
  }
2074
2183
  function printDeeplineLogo() {
2075
2184
  if (process.stdout.isTTY && (process.stdout.columns ?? 80) >= 70) {
@@ -2121,9 +2230,9 @@ async function handleRegister(args) {
2121
2230
  const claimUrl = String(data.claim_url || "");
2122
2231
  const claimToken = String(data.claim_token || "");
2123
2232
  if (claimToken) {
2233
+ savePendingClaimToken(baseUrl, claimToken);
2124
2234
  saveEnvValues({
2125
- DEEPLINE_ORIGIN_URL: baseUrl,
2126
- DEEPLINE_CLAIM_TOKEN: claimToken
2235
+ [HOST_URL_ENV]: baseUrl
2127
2236
  }, baseUrl);
2128
2237
  }
2129
2238
  if (claimUrl) {
@@ -2147,6 +2256,7 @@ async function handleRegister(args) {
2147
2256
  { claim_token: claimToken, reveal: true }
2148
2257
  );
2149
2258
  if (s === 401 || s === 403) {
2259
+ clearPendingClaimToken(baseUrl);
2150
2260
  console.log("Status: unauthorized");
2151
2261
  return EXIT_AUTH;
2152
2262
  }
@@ -2163,15 +2273,16 @@ async function handleRegister(args) {
2163
2273
  const apiKey = String(statusData.api_key || "");
2164
2274
  if (apiKey) {
2165
2275
  saveEnvValues({
2166
- DEEPLINE_ORIGIN_URL: baseUrl,
2167
- DEEPLINE_API_KEY: apiKey,
2168
- DEEPLINE_CLAIM_TOKEN: ""
2276
+ [HOST_URL_ENV]: baseUrl,
2277
+ [API_KEY_ENV]: apiKey
2169
2278
  }, baseUrl);
2279
+ clearPendingClaimToken(baseUrl);
2170
2280
  printClaimSuccessBanner(statusData);
2171
2281
  return EXIT_OK;
2172
2282
  }
2173
2283
  }
2174
2284
  if (state === "expired") {
2285
+ clearPendingClaimToken(baseUrl);
2175
2286
  console.log("That approval link expired. Please run: deepline auth register");
2176
2287
  return EXIT_AUTH;
2177
2288
  }
@@ -2189,13 +2300,12 @@ async function handleWait(args) {
2189
2300
  }
2190
2301
  }
2191
2302
  }
2192
- const env = loadCliEnv(baseUrl);
2193
- if (env.DEEPLINE_API_KEY?.trim()) {
2194
- console.log("Already connected.");
2195
- return EXIT_OK;
2196
- }
2197
- const claimToken = env.DEEPLINE_CLAIM_TOKEN?.trim() || "";
2303
+ const claimToken = readPendingClaimToken(baseUrl);
2198
2304
  if (!claimToken) {
2305
+ if (resolveApiKeyForBaseUrl(baseUrl)) {
2306
+ console.log("Already connected.");
2307
+ return EXIT_OK;
2308
+ }
2199
2309
  console.error("No pending approval. Run: deepline auth register --no-wait");
2200
2310
  return EXIT_AUTH;
2201
2311
  }
@@ -2208,6 +2318,7 @@ async function handleWait(args) {
2208
2318
  { claim_token: claimToken, reveal: true }
2209
2319
  );
2210
2320
  if (status === 401 || status === 403) {
2321
+ clearPendingClaimToken(baseUrl);
2211
2322
  console.error("Claim is invalid. Run: deepline auth register");
2212
2323
  return EXIT_AUTH;
2213
2324
  }
@@ -2224,15 +2335,16 @@ async function handleWait(args) {
2224
2335
  const apiKey = String(data.api_key || "");
2225
2336
  if (apiKey) {
2226
2337
  saveEnvValues({
2227
- DEEPLINE_ORIGIN_URL: baseUrl,
2228
- DEEPLINE_API_KEY: apiKey,
2229
- DEEPLINE_CLAIM_TOKEN: ""
2338
+ [HOST_URL_ENV]: baseUrl,
2339
+ [API_KEY_ENV]: apiKey
2230
2340
  }, baseUrl);
2341
+ clearPendingClaimToken(baseUrl);
2231
2342
  printClaimSuccessBanner(data);
2232
2343
  return EXIT_OK;
2233
2344
  }
2234
2345
  }
2235
2346
  if (state === "expired") {
2347
+ clearPendingClaimToken(baseUrl);
2236
2348
  console.error("That approval link expired. Run: deepline auth register");
2237
2349
  return EXIT_AUTH;
2238
2350
  }
@@ -2267,10 +2379,9 @@ async function handleStatus(args) {
2267
2379
  };
2268
2380
  hostLines.push(`Host: ${baseUrl} (unreachable)`);
2269
2381
  }
2270
- const env = loadCliEnv(baseUrl);
2271
- const apiKey = process.env.DEEPLINE_API_KEY?.trim() || env.DEEPLINE_API_KEY || "";
2382
+ const apiKey = resolveApiKeyForBaseUrl(baseUrl);
2272
2383
  if (!apiKey) {
2273
- if (env.DEEPLINE_CLAIM_TOKEN?.trim()) {
2384
+ if (readPendingClaimToken(baseUrl)) {
2274
2385
  printCommandEnvelope({
2275
2386
  ...hostStatusPayload ?? { host: baseUrl },
2276
2387
  status: "pending",
@@ -2316,6 +2427,7 @@ async function handleStatus(args) {
2316
2427
  console.error(`Auth status error (status ${status}).`);
2317
2428
  return EXIT_SERVER;
2318
2429
  }
2430
+ clearPendingClaimToken(baseUrl);
2319
2431
  const payload = {
2320
2432
  ...hostStatusPayload ?? { host: baseUrl },
2321
2433
  status: data.status || "(unknown)",
@@ -2336,9 +2448,8 @@ async function handleStatus(args) {
2336
2448
  const apiKeyResp = String(data.api_key || apiKey);
2337
2449
  if (apiKeyResp) {
2338
2450
  saveEnvValues({
2339
- DEEPLINE_ORIGIN_URL: baseUrl,
2340
- DEEPLINE_API_KEY: apiKeyResp,
2341
- DEEPLINE_CLAIM_TOKEN: ""
2451
+ [HOST_URL_ENV]: baseUrl,
2452
+ [API_KEY_ENV]: apiKeyResp
2342
2453
  }, baseUrl);
2343
2454
  savedApiKeyPath = envFilePath(baseUrl);
2344
2455
  }
@@ -3267,10 +3378,12 @@ function writeCanonicalRowsCsv(rowsInfo, outPath) {
3267
3378
  rows: rowsInfo.rows,
3268
3379
  columns: rowsInfo.columns
3269
3380
  });
3381
+ const rows = dataExportRows(sanitized.rows);
3382
+ const columns = dataExportColumns(rows, sanitized.columns);
3270
3383
  const resolved = resolve4(outPath);
3271
3384
  writeFileSync4(
3272
3385
  resolved,
3273
- csvStringFromRows(sanitized.rows, sanitized.columns),
3386
+ csvStringFromRows(rows, columns),
3274
3387
  "utf-8"
3275
3388
  );
3276
3389
  return resolved;
@@ -3391,6 +3504,14 @@ Examples:
3391
3504
  }
3392
3505
 
3393
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
+ ]);
3394
3515
  function parsePositiveInteger(value, flagName) {
3395
3516
  const parsed = Number.parseInt(value, 10);
3396
3517
  if (!Number.isFinite(parsed) || parsed <= 0) {
@@ -3404,10 +3525,8 @@ function formatCell(value) {
3404
3525
  return text.length > 80 ? `${text.slice(0, 77)}...` : text;
3405
3526
  }
3406
3527
  function tableLines(result) {
3407
- const rows = result.rows.filter(
3408
- (row) => Boolean(row) && typeof row === "object" && !Array.isArray(row)
3409
- );
3410
- 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));
3411
3530
  const businessColumns = responseColumns.filter((column) => !column.startsWith("_"));
3412
3531
  const columns = businessColumns.length > 0 ? businessColumns : responseColumns;
3413
3532
  const hiddenColumns = responseColumns.filter((column) => !columns.includes(column));
@@ -3441,22 +3560,146 @@ function tableLines(result) {
3441
3560
  }
3442
3561
  return lines;
3443
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
+ }
3444
3601
  async function handleDbQuery(args) {
3445
3602
  const sqlIndex = args.indexOf("--sql");
3446
3603
  const sql = sqlIndex >= 0 ? args[sqlIndex + 1]?.trim() : "";
3447
3604
  if (!sql) {
3448
- 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
+ );
3449
3608
  return 1;
3450
3609
  }
3451
3610
  const maxRowsIndex = args.indexOf("--max-rows");
3452
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
+ }
3453
3626
  const jsonOutput = argsWantJson(args);
3627
+ const explicitJsonOutput = args.includes("--json");
3454
3628
  const client = new DeeplineClient();
3455
3629
  const result = await client.queryCustomerDb({ sql, maxRows });
3456
3630
  const toolCommand = `deepline tools execute query_customer_db --payload ${JSON.stringify({
3457
3631
  sql,
3458
3632
  ...maxRows ? { max_rows: maxRows } : {}
3459
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
+ }
3460
3703
  printCommandEnvelope({
3461
3704
  ...result,
3462
3705
  next: { toolEquivalent: toolCommand },
@@ -3464,7 +3707,7 @@ async function handleDbQuery(args) {
3464
3707
  sections: [{ title: "customer db query", lines: tableLines(result) }],
3465
3708
  actions: [{ label: "Tool equivalent", command: toolCommand }]
3466
3709
  }
3467
- }, { json: jsonOutput });
3710
+ }, { json: jsonOutput || format === "json" });
3468
3711
  return 0;
3469
3712
  }
3470
3713
  function registerDbCommands(program) {
@@ -3474,11 +3717,14 @@ function registerDbCommands(program) {
3474
3717
  Notes:
3475
3718
  Runs SQL against the active workspace customer database through Deepline APIs.
3476
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.
3477
3721
 
3478
3722
  Examples:
3479
3723
  deepline db query --sql "select * from companies limit 20"
3480
3724
  deepline db query --sql "select domain, name from companies limit 20" --json
3481
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
3482
3728
  `
3483
3729
  );
3484
3730
  db.command("query").alias("psql").description("Run SQL against the tenant customer database.").addHelpText(
@@ -3487,17 +3733,23 @@ Examples:
3487
3733
  Notes:
3488
3734
  Requires --sql. Output is a compact table in a terminal and raw JSON with
3489
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.
3490
3738
 
3491
3739
  Examples:
3492
3740
  deepline db query --sql "select * from companies limit 20"
3493
3741
  deepline db query --sql "select domain, name from companies limit 20" --json
3494
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
3495
3745
  `
3496
- ).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) => {
3497
3747
  process.exitCode = await handleDbQuery([
3498
3748
  "--sql",
3499
3749
  options.sql,
3500
3750
  ...options.maxRows ? ["--max-rows", options.maxRows] : [],
3751
+ ...options.format ? ["--format", options.format] : [],
3752
+ ...options.out ? ["--out", options.out] : [],
3501
3753
  ...options.json ? ["--json"] : []
3502
3754
  ]);
3503
3755
  });
@@ -3675,25 +3927,25 @@ Examples:
3675
3927
  import { createHash as createHash3 } from "crypto";
3676
3928
  import {
3677
3929
  existsSync as existsSync6,
3678
- readFileSync as readFileSync4,
3930
+ readFileSync as readFileSync5,
3679
3931
  readdirSync,
3680
3932
  realpathSync,
3681
- writeFileSync as writeFileSync5
3933
+ writeFileSync as writeFileSync6
3682
3934
  } from "fs";
3683
- 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";
3684
3936
 
3685
3937
  // src/plays/bundle-play-file.ts
3686
3938
  import { tmpdir as tmpdir2 } from "os";
3687
- 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";
3688
3940
  import { fileURLToPath } from "url";
3689
3941
  import { existsSync as existsSync5 } from "fs";
3690
3942
 
3691
3943
  // ../shared_libs/plays/bundling/index.ts
3692
3944
  import { createHash } from "crypto";
3693
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
3945
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
3694
3946
  import { mkdir as mkdir3, readFile, realpath, stat, writeFile as writeFile3 } from "fs/promises";
3695
3947
  import { tmpdir } from "os";
3696
- 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";
3697
3949
  import { builtinModules } from "module";
3698
3950
  import { build } from "esbuild";
3699
3951
 
@@ -3785,7 +4037,7 @@ async function normalizeLocalPath(filePath) {
3785
4037
  try {
3786
4038
  return await realpath(filePath);
3787
4039
  } catch {
3788
- return resolve5(filePath);
4040
+ return resolve6(filePath);
3789
4041
  }
3790
4042
  }
3791
4043
  function createPlayWorkspace(entryFile) {
@@ -3944,7 +4196,7 @@ function extractDefinedPlayName(sourceCode) {
3944
4196
  }
3945
4197
  function readPackageVersionFromPackageJson(packageJsonPath, packageName) {
3946
4198
  try {
3947
- const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
4199
+ const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
3948
4200
  if (packageJson.name === packageName && typeof packageJson.version === "string") {
3949
4201
  return packageJson.version;
3950
4202
  }
@@ -3954,7 +4206,7 @@ function readPackageVersionFromPackageJson(packageJsonPath, packageName) {
3954
4206
  return null;
3955
4207
  }
3956
4208
  function findPackageJsonPathFrom(startDir, packageName) {
3957
- let current = resolve5(startDir);
4209
+ let current = resolve6(startDir);
3958
4210
  while (true) {
3959
4211
  const packageJsonPath = join3(
3960
4212
  current,
@@ -3981,7 +4233,7 @@ function findPackageJsonPath(packageName, fromFile, adapter) {
3981
4233
  ];
3982
4234
  const seen = /* @__PURE__ */ new Set();
3983
4235
  for (const startDir of startDirs) {
3984
- const normalized = resolve5(startDir);
4236
+ const normalized = resolve6(startDir);
3985
4237
  if (seen.has(normalized)) continue;
3986
4238
  seen.add(normalized);
3987
4239
  const packageJsonPath = findPackageJsonPathFrom(normalized, packageName);
@@ -4218,7 +4470,7 @@ async function resolveLocalImport(fromFile, specifier) {
4218
4470
  if (specifier.startsWith("file:")) {
4219
4471
  return normalizeLocalPath(new URL(specifier).pathname);
4220
4472
  }
4221
- const base = isAbsolute(specifier) ? resolve5(specifier) : resolve5(dirname5(fromFile), specifier);
4473
+ const base = isAbsolute(specifier) ? resolve6(specifier) : resolve6(dirname5(fromFile), specifier);
4222
4474
  const candidates = [base];
4223
4475
  const explicitExtension = extname(base).toLowerCase();
4224
4476
  if (!explicitExtension) {
@@ -4239,7 +4491,7 @@ function resolvePackageImport(specifier, fromFile, adapter) {
4239
4491
  const packageName = getPackageName(specifier);
4240
4492
  if (packageName === "deepline" && existsSync4(adapter.sdkPackageJson)) {
4241
4493
  const packageJson = JSON.parse(
4242
- readFileSync3(adapter.sdkPackageJson, "utf-8")
4494
+ readFileSync4(adapter.sdkPackageJson, "utf-8")
4243
4495
  );
4244
4496
  return {
4245
4497
  name: "deepline",
@@ -4416,7 +4668,7 @@ function normalizeSourceMapForRuntime(sourceMapText) {
4416
4668
  if (sourcePath.startsWith("data:") || sourcePath.startsWith("node:") || sourcePath.startsWith("/") || /^[a-zA-Z]+:\/\//.test(sourcePath)) {
4417
4669
  return sourcePath;
4418
4670
  }
4419
- return resolve5(process.cwd(), sourcePath);
4671
+ return resolve6(process.cwd(), sourcePath);
4420
4672
  });
4421
4673
  parsed.sourceRoot = void 0;
4422
4674
  return JSON.stringify(parsed);
@@ -4738,7 +4990,7 @@ function resolveExecutionProfile(override) {
4738
4990
  // src/plays/local-file-discovery.ts
4739
4991
  import { createHash as createHash2 } from "crypto";
4740
4992
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
4741
- 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";
4742
4994
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
4743
4995
  function sha2562(buffer) {
4744
4996
  return createHash2("sha256").update(buffer).digest("hex");
@@ -4939,7 +5191,7 @@ function isPathInsideDirectory2(filePath, directory) {
4939
5191
  return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute2(relativePath);
4940
5192
  }
4941
5193
  async function resolveLocalImport2(fromFile, specifier) {
4942
- const base = isAbsolute2(specifier) ? resolve6(specifier) : resolve6(dirname6(fromFile), specifier);
5194
+ const base = isAbsolute2(specifier) ? resolve7(specifier) : resolve7(dirname6(fromFile), specifier);
4943
5195
  const candidates = [base];
4944
5196
  const explicitExtension = extname2(base).toLowerCase();
4945
5197
  if (!explicitExtension) {
@@ -4957,13 +5209,13 @@ async function resolveLocalImport2(fromFile, specifier) {
4957
5209
  throw new Error(`Could not resolve local import "${specifier}" from ${fromFile}`);
4958
5210
  }
4959
5211
  async function discoverPackagedLocalFiles(entryFile) {
4960
- const absoluteEntryFile = resolve6(entryFile);
5212
+ const absoluteEntryFile = resolve7(entryFile);
4961
5213
  const packagingRoot = dirname6(absoluteEntryFile);
4962
5214
  const files = /* @__PURE__ */ new Map();
4963
5215
  const unresolved = [];
4964
5216
  const visitedFiles = /* @__PURE__ */ new Set();
4965
5217
  const visitSourceFile = async (filePath) => {
4966
- const absolutePath = resolve6(filePath);
5218
+ const absolutePath = resolve7(filePath);
4967
5219
  if (visitedFiles.has(absolutePath)) {
4968
5220
  return;
4969
5221
  }
@@ -4995,7 +5247,7 @@ async function discoverPackagedLocalFiles(entryFile) {
4995
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."
4996
5248
  });
4997
5249
  } else {
4998
- const absoluteCsvPath = resolve6(dirname6(absolutePath), resolvedPath);
5250
+ const absoluteCsvPath = resolve7(dirname6(absolutePath), resolvedPath);
4999
5251
  if (isAbsolute2(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
5000
5252
  unresolved.push({
5001
5253
  sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
@@ -5035,23 +5287,23 @@ async function discoverPackagedLocalFiles(entryFile) {
5035
5287
  // src/plays/bundle-play-file.ts
5036
5288
  var PLAY_BUNDLE_CACHE_VERSION2 = 30;
5037
5289
  var MODULE_DIR = dirname7(fileURLToPath(import.meta.url));
5038
- var SDK_PACKAGE_ROOT = resolve7(MODULE_DIR, "..", "..");
5039
- 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, "..");
5040
5292
  var HAS_SOURCE_BUNDLING_SOURCES = existsSync5(
5041
- resolve7(SOURCE_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5293
+ resolve8(SOURCE_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5042
5294
  );
5043
- var PACKAGED_REPO_ROOT = resolve7(SDK_PACKAGE_ROOT, "dist", "repo");
5295
+ var PACKAGED_REPO_ROOT = resolve8(SDK_PACKAGE_ROOT, "dist", "repo");
5044
5296
  var HAS_PACKAGED_BUNDLING_SOURCES = existsSync5(
5045
- resolve7(PACKAGED_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5297
+ resolve8(PACKAGED_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5046
5298
  );
5047
- var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED_BUNDLING_SOURCES ? PACKAGED_REPO_ROOT : resolve7(SDK_PACKAGE_ROOT, "..");
5048
- 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");
5049
- var SDK_PACKAGE_JSON = resolve7(SDK_PACKAGE_ROOT, "package.json");
5050
- var SDK_ENTRY_FILE = resolve7(SDK_SOURCE_ROOT, "index.ts");
5051
- var SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : resolve7(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
5052
- var SDK_WORKERS_ENTRY_FILE = resolve7(SDK_SOURCE_ROOT, "worker-play-entry.ts");
5053
- var WORKERS_HARNESS_ENTRY_FILE = resolve7(PROJECT_ROOT, "apps", "play-runner-workers", "src", "entry.ts");
5054
- 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");
5055
5307
  var hasWarnedAboutNonDevelopmentBundling = false;
5056
5308
  function warnAboutNonDevelopmentBundling(filePath) {
5057
5309
  if (hasWarnedAboutNonDevelopmentBundling) {
@@ -5075,7 +5327,7 @@ function defaultPlayBundleTarget() {
5075
5327
  function createSdkPlayBundlingAdapter() {
5076
5328
  return {
5077
5329
  projectRoot: PROJECT_ROOT,
5078
- nodeModulesDir: resolve7(PROJECT_ROOT, "node_modules"),
5330
+ nodeModulesDir: resolve8(PROJECT_ROOT, "node_modules"),
5079
5331
  cacheDir: join5(tmpdir2(), `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION2}`),
5080
5332
  sdkSourceRoot: SDK_SOURCE_ROOT,
5081
5333
  sdkPackageJson: SDK_PACKAGE_JSON,
@@ -5313,7 +5565,7 @@ function traceCliSync(phase, fields, run) {
5313
5565
  }
5314
5566
  }
5315
5567
  function sleep4(ms) {
5316
- return new Promise((resolve10) => setTimeout(resolve10, ms));
5568
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
5317
5569
  }
5318
5570
  function parseReferencedPlayTarget(target) {
5319
5571
  const trimmed = target.trim();
@@ -5359,7 +5611,7 @@ function formatPlayListReference(play) {
5359
5611
  function defaultMaterializedPlayPath(reference) {
5360
5612
  const playName = parseReferencedPlayTarget(reference).unqualifiedPlayName;
5361
5613
  const safeName = playName.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
5362
- return resolve8(`${safeName || "play"}.play.ts`);
5614
+ return resolve9(`${safeName || "play"}.play.ts`);
5363
5615
  }
5364
5616
  function materializeRemotePlaySource(input) {
5365
5617
  if (isFileTarget(input.target)) {
@@ -5370,14 +5622,14 @@ function materializeRemotePlaySource(input) {
5370
5622
  }
5371
5623
  const outputPath = input.outPath ?? defaultMaterializedPlayPath(input.playName);
5372
5624
  if (existsSync6(outputPath)) {
5373
- const existingSource = readFileSync4(outputPath, "utf-8");
5625
+ const existingSource = readFileSync5(outputPath, "utf-8");
5374
5626
  if (existingSource === input.sourceCode) {
5375
5627
  return { path: outputPath, status: "unchanged", created: false };
5376
5628
  }
5377
- writeFileSync5(outputPath, input.sourceCode, "utf-8");
5629
+ writeFileSync6(outputPath, input.sourceCode, "utf-8");
5378
5630
  return { path: outputPath, status: "updated", created: false };
5379
5631
  }
5380
- writeFileSync5(outputPath, input.sourceCode, "utf-8");
5632
+ writeFileSync6(outputPath, input.sourceCode, "utf-8");
5381
5633
  return { path: outputPath, status: "created", created: true };
5382
5634
  }
5383
5635
  function formatLoadedPlayMessage(materializedFile) {
@@ -5422,7 +5674,7 @@ function extractPlayName(code, filePath) {
5422
5674
  throw buildMissingDefinePlayError(filePath);
5423
5675
  }
5424
5676
  function isFileTarget(target) {
5425
- return existsSync6(resolve8(target));
5677
+ return existsSync6(resolve9(target));
5426
5678
  }
5427
5679
  function looksLikeFilePath(target) {
5428
5680
  if (target.trim().toLowerCase().startsWith("prebuilt/")) {
@@ -5441,7 +5693,7 @@ function parsePositiveInteger2(value, flagName) {
5441
5693
  return parsed;
5442
5694
  }
5443
5695
  function parseJsonInput(raw) {
5444
- const source = raw.startsWith("@") ? readFileSync4(resolve8(raw.slice(1)), "utf-8") : raw;
5696
+ const source = raw.startsWith("@") ? readFileSync5(resolve9(raw.slice(1)), "utf-8") : raw;
5445
5697
  const parsed = JSON.parse(source);
5446
5698
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
5447
5699
  throw new Error("--input must be a JSON object.");
@@ -5542,7 +5794,7 @@ function fileInputBindingsFromStaticPipeline(staticPipeline) {
5542
5794
  function isLocalFilePathValue(value) {
5543
5795
  if (typeof value !== "string" || !value.trim()) return false;
5544
5796
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
5545
- return existsSync6(resolve8(value));
5797
+ return existsSync6(resolve9(value));
5546
5798
  }
5547
5799
  function inputContainsLocalFilePath(value) {
5548
5800
  if (isLocalFilePathValue(value)) {
@@ -5568,7 +5820,7 @@ async function stageFileInputArgs(input) {
5568
5820
  const localFiles = uniqueBindings.flatMap((binding) => {
5569
5821
  const value = getDottedInputValue(input.runtimeInput, binding.inputPath);
5570
5822
  if (!isLocalFilePathValue(value)) return [];
5571
- const absolutePath = resolve8(value);
5823
+ const absolutePath = resolve9(value);
5572
5824
  return [{ binding, absolutePath, logicalPath: basename3(absolutePath) }];
5573
5825
  });
5574
5826
  if (localFiles.length === 0) {
@@ -5593,7 +5845,7 @@ async function stageFileInputArgs(input) {
5593
5845
  };
5594
5846
  }
5595
5847
  function stageFile(logicalPath, absolutePath) {
5596
- const buffer = readFileSync4(absolutePath);
5848
+ const buffer = readFileSync5(absolutePath);
5597
5849
  return {
5598
5850
  logicalPath,
5599
5851
  contentBase64: buffer.toString("base64"),
@@ -5604,9 +5856,9 @@ function stageFile(logicalPath, absolutePath) {
5604
5856
  }
5605
5857
  function normalizePlayPath(filePath) {
5606
5858
  try {
5607
- return realpathSync.native(resolve8(filePath));
5859
+ return realpathSync.native(resolve9(filePath));
5608
5860
  } catch {
5609
- return resolve8(filePath);
5861
+ return resolve9(filePath);
5610
5862
  }
5611
5863
  }
5612
5864
  function formatBundlingErrors(filePath, errors) {
@@ -5759,11 +6011,42 @@ function isTransientPlayStreamError(error) {
5759
6011
  text
5760
6012
  );
5761
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
+ }
5762
6044
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
5763
6045
  "completed",
5764
6046
  "failed",
5765
6047
  "cancelled"
5766
6048
  ]);
6049
+ var PLAY_START_TRANSIENT_RETRY_DELAYS_MS = [500, 1500];
5767
6050
  function getEventPayload(event) {
5768
6051
  return event.payload && typeof event.payload === "object" ? event.payload : {};
5769
6052
  }
@@ -5830,10 +6113,6 @@ function openPlayDashboard(input) {
5830
6113
  }
5831
6114
  openInBrowser(input.dashboardUrl);
5832
6115
  }
5833
- function getDashboardUrlFromLiveEvent(event) {
5834
- const dashboardUrl = getEventPayload(event).dashboardUrl;
5835
- return typeof dashboardUrl === "string" && dashboardUrl.trim() ? dashboardUrl.trim() : null;
5836
- }
5837
6116
  function printPlayLogLines(input) {
5838
6117
  for (const line of input.lines) {
5839
6118
  if (input.emitLogs) {
@@ -5902,7 +6181,7 @@ async function waitForPlayCompletionByStream(input) {
5902
6181
  billing: false
5903
6182
  });
5904
6183
  if (TERMINAL_PLAY_STATUSES2.has(finalStatus.status)) {
5905
- return finalStatus;
6184
+ return input.dashboardUrl ? { ...finalStatus, dashboardUrl: input.dashboardUrl } : finalStatus;
5906
6185
  }
5907
6186
  }
5908
6187
  }
@@ -5929,7 +6208,39 @@ async function waitForPlayCompletionByStream(input) {
5929
6208
  );
5930
6209
  }
5931
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) {
5932
6239
  const startedAt = Date.now();
6240
+ const dashboardUrl = buildPlayDashboardUrl(
6241
+ input.client.baseUrl,
6242
+ input.playName
6243
+ );
5933
6244
  const state = {
5934
6245
  lastLogIndex: 0,
5935
6246
  emittedRunnerStarted: false
@@ -5960,7 +6271,6 @@ async function startAndWaitForPlayCompletionByStream(input) {
5960
6271
  }
5961
6272
  const workflowId = lastKnownWorkflowId || "pending";
5962
6273
  if (workflowId !== "pending" && !emittedDashboardUrl) {
5963
- const dashboardUrl = getDashboardUrlFromLiveEvent(event) ?? buildPlayDashboardUrl(input.client.baseUrl, input.playName);
5964
6274
  openPlayDashboard({
5965
6275
  dashboardUrl,
5966
6276
  jsonOutput: input.jsonOutput,
@@ -6009,7 +6319,7 @@ async function startAndWaitForPlayCompletionByStream(input) {
6009
6319
  firstRunIdMs,
6010
6320
  lastPhase
6011
6321
  });
6012
- return finalStatus;
6322
+ return { ...finalStatus, dashboardUrl };
6013
6323
  }
6014
6324
  }
6015
6325
  } catch (error) {
@@ -6046,6 +6356,7 @@ async function startAndWaitForPlayCompletionByStream(input) {
6046
6356
  return waitForPlayCompletionByStream({
6047
6357
  client: input.client,
6048
6358
  workflowId: lastKnownWorkflowId,
6359
+ dashboardUrl,
6049
6360
  jsonOutput: input.jsonOutput,
6050
6361
  emitLogs: input.emitLogs,
6051
6362
  waitTimeoutMs: input.waitTimeoutMs,
@@ -6080,6 +6391,7 @@ async function startAndWaitForPlayCompletionByStream(input) {
6080
6391
  return waitForPlayCompletionByStream({
6081
6392
  client: input.client,
6082
6393
  workflowId: lastKnownWorkflowId,
6394
+ dashboardUrl,
6083
6395
  jsonOutput: input.jsonOutput,
6084
6396
  emitLogs: input.emitLogs,
6085
6397
  waitTimeoutMs: input.waitTimeoutMs,
@@ -6250,12 +6562,25 @@ function buildRunWarnings(status, rowsInfo) {
6250
6562
  }
6251
6563
  return [];
6252
6564
  }
6253
- function buildRunNextCommands(runId) {
6254
- return {
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
+ }
6575
+ const commands = {
6255
6576
  get: `deepline runs get ${runId} --json`,
6256
6577
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
6257
6578
  logs: `deepline runs logs ${runId} --out run.log --json`
6258
6579
  };
6580
+ if (status.dashboardUrl) {
6581
+ commands.open = `Open ${status.dashboardUrl} to see results.`;
6582
+ }
6583
+ return commands;
6259
6584
  }
6260
6585
  var RUN_LOG_PREVIEW_LIMIT = 20;
6261
6586
  function getRecordField(value, key) {
@@ -6276,6 +6601,121 @@ function getTimestampField(value, key) {
6276
6601
  }
6277
6602
  return typeof field === "string" && field.trim() ? field : null;
6278
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
+ }
6279
6719
  function normalizeRunStatusForEnvelope(status) {
6280
6720
  const run = status.run ?? null;
6281
6721
  return {
@@ -6326,18 +6766,33 @@ function normalizeErrorsForEnvelope(status, error) {
6326
6766
  if (Array.isArray(directErrors)) {
6327
6767
  return directErrors.filter(
6328
6768
  (entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)
6329
- );
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
+ });
6330
6781
  }
6331
6782
  if (!error) {
6332
6783
  return [];
6333
6784
  }
6785
+ const nextCommands = buildRunNextCommands(status);
6786
+ const billing = extractBillingForStatus(status, error);
6787
+ const message = formatPlayErrorForDisplay(status, error) ?? error;
6334
6788
  return [
6335
6789
  {
6336
6790
  code: getStringField(status, "errorCode") ?? "RUN_FAILED",
6337
6791
  phase: getStringField(status, "errorPhase") ?? "runtime",
6338
- message: error,
6792
+ message,
6339
6793
  retryable: typeof getRecordField(status, "retryable") === "boolean" ? getRecordField(status, "retryable") : null,
6340
- nextAction: `deepline runs get ${status.runId} --json`
6794
+ ...billing ? { billing: stripProviderSpendFromBilling(billing) } : {},
6795
+ nextAction: nextCommands.get ?? nextCommands.list
6341
6796
  }
6342
6797
  ];
6343
6798
  }
@@ -6386,24 +6841,26 @@ function compactPlayStatus(status) {
6386
6841
  ) : null;
6387
6842
  const progressError = status.progress?.error;
6388
6843
  const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
6844
+ const displayError = formatPlayErrorForDisplay(status, error);
6389
6845
  return {
6390
6846
  runId: status.runId,
6391
6847
  apiVersion: status.apiVersion ?? 1,
6392
6848
  ...typeof status.name === "string" ? { name: status.name } : {},
6393
6849
  ...typeof status.playName === "string" ? { playName: status.playName } : {},
6850
+ ...status.dashboardUrl ? { dashboardUrl: status.dashboardUrl } : {},
6394
6851
  status: status.status,
6395
6852
  run: normalizeRunStatusForEnvelope(status),
6396
6853
  progress: normalizeProgressForEnvelope(status, rowsInfo),
6397
6854
  steps: normalizeStepsForEnvelope(status),
6398
6855
  errors: normalizeErrorsForEnvelope(status, error),
6399
6856
  logs: normalizeLogsForEnvelope(status),
6400
- ...error ? { error } : {},
6857
+ ...displayError ? { error: displayError } : {},
6401
6858
  ...warnings.length > 0 ? { warnings } : {},
6402
6859
  ...result !== void 0 ? { result } : {},
6403
6860
  ...status.resultView ? { resultView: status.resultView } : {},
6404
6861
  ...datasetStats ? { dataset_stats: datasetStats } : {},
6405
6862
  ...billing ? { billing } : {},
6406
- next: buildRunNextCommands(status.runId)
6863
+ next: buildRunNextCommands(status)
6407
6864
  };
6408
6865
  }
6409
6866
  function enrichPlayStatusWithDatasetStats(status) {
@@ -6477,7 +6934,8 @@ function writePlayResult(status, jsonOutput, options) {
6477
6934
  lines.push(...formatDatasetStatsLines(datasetStats));
6478
6935
  const progressError = status.progress?.error;
6479
6936
  if (progressError && typeof progressError === "string") {
6480
- lines.push(` error: ${progressError.slice(0, 200)}`);
6937
+ const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
6938
+ lines.push(` error: ${displayError.slice(0, 200)}`);
6481
6939
  }
6482
6940
  const renderedServerView = renderServerResultView(status.resultView);
6483
6941
  if (result) {
@@ -6501,8 +6959,11 @@ var RUN_EXPORT_PAGE_SIZE = 5e3;
6501
6959
  function shellSingleQuote(value) {
6502
6960
  return `'${value.replace(/'/g, `'\\''`)}'`;
6503
6961
  }
6962
+ function sqlStringLiteral(value) {
6963
+ return `'${value.replace(/'/g, "''")}'`;
6964
+ }
6504
6965
  function runExportRetryCommand(runId, outPath, datasetPath) {
6505
- 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))}`;
6506
6967
  }
6507
6968
  function extractRunPlayName(status) {
6508
6969
  const run = status.run;
@@ -6519,6 +6980,26 @@ function extractRunPlayName(status) {
6519
6980
  }
6520
6981
  return null;
6521
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
+ }
6522
7003
  function exportableSheetRow(row) {
6523
7004
  if (!row || typeof row !== "object" || Array.isArray(row)) {
6524
7005
  return null;
@@ -6616,7 +7097,7 @@ async function exportPlayStatusRows(client, status, outPath, options = {}) {
6616
7097
  return null;
6617
7098
  }
6618
7099
  const availableRows = collectSerializedDatasetRowsInfos(status);
6619
- let rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
7100
+ const rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
6620
7101
  if (!rowsInfo && options.datasetPath) {
6621
7102
  const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
6622
7103
  throw new DeeplineError(
@@ -6690,6 +7171,60 @@ async function exportPlayStatusRows(client, status, outPath, options = {}) {
6690
7171
  }
6691
7172
  return { path: writeCanonicalRowsCsv(rowsInfo, outPath), rowsInfo };
6692
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
+ }
6693
7228
  function renderServerResultView(value) {
6694
7229
  if (!value || typeof value !== "object" || Array.isArray(value)) {
6695
7230
  return { lines: [], actions: [] };
@@ -6925,12 +7460,12 @@ function shouldUseLocalOnlyPlayCheck() {
6925
7460
  async function handlePlayCheck(args) {
6926
7461
  const options = parsePlayCheckOptions(args);
6927
7462
  if (!isFileTarget(options.target)) {
6928
- const resolved = resolve8(options.target);
7463
+ const resolved = resolve9(options.target);
6929
7464
  console.error(`File not found: ${resolved}`);
6930
7465
  return 1;
6931
7466
  }
6932
- const absolutePlayPath = resolve8(options.target);
6933
- const sourceCode = readFileSync4(absolutePlayPath, "utf-8");
7467
+ const absolutePlayPath = resolve9(options.target);
7468
+ const sourceCode = readFileSync5(absolutePlayPath, "utf-8");
6934
7469
  let graph;
6935
7470
  try {
6936
7471
  graph = await collectBundledPlayGraph(absolutePlayPath);
@@ -6993,12 +7528,12 @@ async function handleFileBackedRun(options) {
6993
7528
  }
6994
7529
  const client = new DeeplineClient();
6995
7530
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
6996
- const absolutePlayPath = resolve8(options.target.path);
7531
+ const absolutePlayPath = resolve9(options.target.path);
6997
7532
  progress.phase("compiling play");
6998
7533
  const sourceCode = traceCliSync(
6999
7534
  "cli.play_file_read_source",
7000
7535
  { targetKind: "file" },
7001
- () => readFileSync4(absolutePlayPath, "utf-8")
7536
+ () => readFileSync5(absolutePlayPath, "utf-8")
7002
7537
  );
7003
7538
  const runtimeInput = options.input ? { ...options.input } : {};
7004
7539
  let graph;
@@ -7079,6 +7614,8 @@ async function handleFileBackedRun(options) {
7079
7614
  waitTimeoutMs: options.waitTimeoutMs,
7080
7615
  noOpen: options.noOpen,
7081
7616
  progress
7617
+ }).catch((error) => {
7618
+ throw normalizePlayStartError(error, playName);
7082
7619
  })
7083
7620
  );
7084
7621
  if (finalStatus.status === "completed") {
@@ -7097,10 +7634,11 @@ async function handleFileBackedRun(options) {
7097
7634
  const started = await traceCliSpan(
7098
7635
  "cli.play_start_unwatched",
7099
7636
  { targetKind: "file", playName },
7100
- () => client.startPlayRun(startRequest)
7637
+ () => client.startPlayRun(startRequest).catch((error) => {
7638
+ throw normalizePlayStartError(error, playName);
7639
+ })
7101
7640
  );
7102
- const dashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7103
- const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
7641
+ const resolvedDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7104
7642
  openPlayDashboard({
7105
7643
  dashboardUrl: resolvedDashboardUrl,
7106
7644
  jsonOutput: options.jsonOutput,
@@ -7215,6 +7753,8 @@ async function handleNamedRun(options) {
7215
7753
  waitTimeoutMs: options.waitTimeoutMs,
7216
7754
  noOpen: options.noOpen,
7217
7755
  progress
7756
+ }).catch((error) => {
7757
+ throw normalizePlayStartError(error, playName);
7218
7758
  })
7219
7759
  );
7220
7760
  if (finalStatus.status === "completed") {
@@ -7233,13 +7773,11 @@ async function handleNamedRun(options) {
7233
7773
  const started = await traceCliSpan(
7234
7774
  "cli.play_start_unwatched",
7235
7775
  { targetKind: "name", playName },
7236
- () => client.startPlayRun(startRequest)
7237
- );
7238
- const dashboardUrl = buildPlayDashboardUrl(
7239
- client.baseUrl,
7240
- playName
7776
+ () => client.startPlayRun(startRequest).catch((error) => {
7777
+ throw normalizePlayStartError(error, playName);
7778
+ })
7241
7779
  );
7242
- const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
7780
+ const resolvedDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7243
7781
  openPlayDashboard({
7244
7782
  dashboardUrl: resolvedDashboardUrl,
7245
7783
  jsonOutput: options.jsonOutput,
@@ -7263,7 +7801,7 @@ async function handlePlayRun(args) {
7263
7801
  if (isFileTarget(options.target.path)) {
7264
7802
  return handleFileBackedRun(options);
7265
7803
  }
7266
- const resolved = resolve8(options.target.path);
7804
+ const resolved = resolve9(options.target.path);
7267
7805
  console.error(`File not found: ${resolved}`);
7268
7806
  const dir = dirname8(resolved);
7269
7807
  if (existsSync6(dir)) {
@@ -7410,14 +7948,14 @@ async function handleRunLogs(args) {
7410
7948
  continue;
7411
7949
  }
7412
7950
  if (arg === "--out" && args[index + 1]) {
7413
- outPath = resolve8(args[++index]);
7951
+ outPath = resolve9(args[++index]);
7414
7952
  }
7415
7953
  }
7416
7954
  const client = new DeeplineClient();
7417
7955
  const status = await client.runs.get(runId);
7418
7956
  const logs = status.progress?.logs ?? [];
7419
7957
  if (outPath) {
7420
- writeFileSync5(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
7958
+ writeFileSync6(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
7421
7959
  printCommandEnvelope({
7422
7960
  runId: status.runId,
7423
7961
  log_path: outPath,
@@ -7473,7 +8011,7 @@ async function handleRunStop(args) {
7473
8011
  return 0;
7474
8012
  }
7475
8013
  async function handleRunExport(args) {
7476
- 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]";
7477
8015
  let runId;
7478
8016
  try {
7479
8017
  runId = parseRunIdPositional(args, usage);
@@ -7483,14 +8021,19 @@ async function handleRunExport(args) {
7483
8021
  }
7484
8022
  let outPath = null;
7485
8023
  let datasetPath = null;
8024
+ let metadataOutPath = null;
7486
8025
  for (let index = 0; index < args.length; index += 1) {
7487
8026
  const arg = args[index];
7488
8027
  if (arg === "--out" && args[index + 1]) {
7489
- outPath = resolve8(args[++index]);
8028
+ outPath = resolve9(args[++index]);
7490
8029
  continue;
7491
8030
  }
7492
8031
  if (arg === "--dataset" && args[index + 1]) {
7493
8032
  datasetPath = args[++index];
8033
+ continue;
8034
+ }
8035
+ if (arg === "--metadata-out" && args[index + 1]) {
8036
+ metadataOutPath = resolve9(args[++index]);
7494
8037
  }
7495
8038
  }
7496
8039
  if (!outPath) {
@@ -7502,15 +8045,59 @@ async function handleRunExport(args) {
7502
8045
  const exportResult = await exportPlayStatusRows(client, status, outPath, {
7503
8046
  datasetPath
7504
8047
  });
7505
- 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 = {
7506
8063
  runId: status.runId,
7507
8064
  ...datasetPath ? { dataset: datasetPath } : {},
7508
8065
  csv_path: exportResult?.path ?? null,
8066
+ source,
7509
8067
  rowCount: exportResult?.rowsInfo.totalRows ?? null,
7510
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 } : {},
7511
8077
  local: { csv_path: exportResult?.path ?? null },
7512
- render: { sections: [{ title: "run export", lines: [`Exported ${status.runId} to ${exportResult?.path ?? outPath}`] }] }
7513
- }, { 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) });
7514
8101
  return 0;
7515
8102
  }
7516
8103
  async function handlePlayGet(args) {
@@ -7527,10 +8114,10 @@ async function handlePlayGet(args) {
7527
8114
  for (let index = 1; index < args.length; index += 1) {
7528
8115
  const arg = args[index];
7529
8116
  if (arg === "--out" && args[index + 1]) {
7530
- outPath = resolve8(args[++index]);
8117
+ outPath = resolve9(args[++index]);
7531
8118
  }
7532
8119
  }
7533
- const playName = isFileTarget(target) ? extractPlayName(readFileSync4(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;
7534
8121
  const detail = isFileTarget(target) ? await client.getPlay(playName) : await assertCanonicalNamedPlayReference(client, target);
7535
8122
  const resolvedSource = detail.play.workingRevision?.sourceCode ?? detail.play.liveRevision?.sourceCode ?? detail.play.currentRevision?.sourceCode ?? detail.play.sourceCode ?? "";
7536
8123
  const materializedFile = outPath ? materializeRemotePlaySource({
@@ -7822,7 +8409,7 @@ async function handlePlayPublish(args) {
7822
8409
  }
7823
8410
  let graph;
7824
8411
  try {
7825
- graph = await collectBundledPlayGraph(resolve8(playName));
8412
+ graph = await collectBundledPlayGraph(resolve9(playName));
7826
8413
  await compileBundledPlayGraphManifests(client, graph);
7827
8414
  await publishImportedPlayDependencies(client, graph);
7828
8415
  } catch (error) {
@@ -8308,30 +8895,34 @@ Examples:
8308
8895
  Notes:
8309
8896
  Writes a returned dataset handle to the requested local CSV path. Use runs get
8310
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.
8311
8900
 
8312
8901
  Examples:
8313
8902
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
8314
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
8315
8905
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv --json
8316
8906
  `
8317
- ).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) => {
8318
8908
  process.exitCode = await handleRunExport([
8319
8909
  runId,
8320
8910
  ...options.dataset ? ["--dataset", options.dataset] : [],
8321
8911
  "--out",
8322
8912
  options.out,
8913
+ ...options.metadataOut ? ["--metadata-out", options.metadataOut] : [],
8323
8914
  ...options.json ? ["--json"] : []
8324
8915
  ]);
8325
8916
  });
8326
8917
  }
8327
8918
 
8328
8919
  // src/cli/commands/tools.ts
8329
- import { chmodSync, mkdtempSync, writeFileSync as writeFileSync7 } from "fs";
8920
+ import { chmodSync, mkdtempSync, writeFileSync as writeFileSync8 } from "fs";
8330
8921
  import { tmpdir as tmpdir3 } from "os";
8331
8922
  import { join as join8 } from "path";
8332
8923
 
8333
8924
  // src/tool-output.ts
8334
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
8925
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
8335
8926
  import { homedir as homedir3 } from "os";
8336
8927
  import { join as join7 } from "path";
8337
8928
  function isPlainObject(value) {
@@ -8356,6 +8947,25 @@ function normalizeRows(value) {
8356
8947
  }
8357
8948
  function candidateRoots(payload) {
8358
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
+ }
8359
8969
  if (isPlainObject(payload) && isPlainObject(payload.result)) {
8360
8970
  roots.push({ path: "result", value: payload.result });
8361
8971
  if (isPlainObject(payload.result.data)) {
@@ -8415,7 +9025,7 @@ function ensureOutputDir() {
8415
9025
  function writeJsonOutputFile(payload, stem) {
8416
9026
  const outputDir = ensureOutputDir();
8417
9027
  const outputPath = join7(outputDir, `${stem}_${Date.now()}.json`);
8418
- writeFileSync6(outputPath, JSON.stringify(payload, null, 2), "utf-8");
9028
+ writeFileSync7(outputPath, JSON.stringify(payload, null, 2), "utf-8");
8419
9029
  return outputPath;
8420
9030
  }
8421
9031
  function writeCsvOutputFile(rows, stem) {
@@ -8443,7 +9053,7 @@ function writeCsvOutputFile(rows, stem) {
8443
9053
  for (const row of rows) {
8444
9054
  lines.push(columns.map((column) => escapeCell(row[column])).join(","));
8445
9055
  }
8446
- writeFileSync6(outputPath, `${lines.join("\n")}
9056
+ writeFileSync7(outputPath, `${lines.join("\n")}
8447
9057
  `, "utf-8");
8448
9058
  const previewRows = rows.slice(0, 5);
8449
9059
  const previewColumns = columns.slice(0, 5);
@@ -8489,8 +9099,7 @@ async function listTools(args) {
8489
9099
  title: `${items.length} tools available:`,
8490
9100
  lines: items.flatMap((item) => {
8491
9101
  const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8492
- const listHint = item.listExtractorPaths?.length ? ` listExtractorPaths=${item.listExtractorPaths.join(",")}` : "";
8493
- return [`${item.toolId}${cats}`, ` ${item.description}${listHint}`];
9102
+ return [`${item.toolId}${cats}`, ` ${item.description}`];
8494
9103
  })
8495
9104
  }
8496
9105
  ]
@@ -8569,7 +9178,7 @@ Common commands:
8569
9178
  deepline tools execute hunter_email_verifier --input '{"email":"a@b.com"}'
8570
9179
 
8571
9180
  Output:
8572
- Use describe for tool contracts. get is accepted as a compatibility alias.
9181
+ Use describe for tool contracts.
8573
9182
  Use execute to run a tool. run is accepted as a compatibility alias.
8574
9183
  `
8575
9184
  );
@@ -8616,8 +9225,8 @@ Examples:
8616
9225
  `
8617
9226
  Notes:
8618
9227
  Shows the tool contract, input schema, output schema, Deepline cost, aliases,
8619
- and metadata. describe is the preferred discovery verb. get is kept as a
8620
- 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.
8621
9230
 
8622
9231
  Examples:
8623
9232
  deepline tools describe hunter_email_verifier
@@ -8630,7 +9239,22 @@ Examples:
8630
9239
  ...options.json ? ["--json"] : []
8631
9240
  ]);
8632
9241
  });
8633
- 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
+ });
8634
9258
  tools.command("execute <toolId>").alias("run").description("Execute a tool by id.").addHelpText(
8635
9259
  "after",
8636
9260
  `
@@ -8711,6 +9335,7 @@ function printToolDetails(tool, requestedToolId) {
8711
9335
  const stepContributions = arrayField(tool, "stepContributions", "step_contributions");
8712
9336
  const playExpansion = recordField(tool, "playExpansion", "play_expansion");
8713
9337
  const samples = recordField(tool, "samples");
9338
+ const usageGuidance = recordField(tool, "usageGuidance", "usage_guidance");
8714
9339
  console.log(`Tool: ${toolId}`);
8715
9340
  if (displayName) {
8716
9341
  console.log(" Display name:");
@@ -8774,14 +9399,16 @@ function printToolDetails(tool, requestedToolId) {
8774
9399
  console.log(" Tip: pass --payload with a JSON object.");
8775
9400
  }
8776
9401
  printSamples(samples);
9402
+ printUsageGuidance(usageGuidance);
8777
9403
  if (isPlayTool(tool)) {
8778
9404
  console.log(" Play contract:");
8779
9405
  console.log(" - This is a deepline-native waterfall; the returned rows are extracted by target getters, not by hand-authored payload shape.");
8780
9406
  if (playExpansion && typeof playExpansion.group === "string" && playExpansion.group.trim()) {
8781
9407
  console.log(` - Output alias/runtime group is: ${playExpansion.group.trim()}`);
8782
9408
  }
8783
- const getters = recordField(tool, "resultIdentityGetters", "result_identity_getters");
8784
- 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();
8785
9412
  if (targets.length) {
8786
9413
  console.log(` - Built-in extract targets: ${targets.join(", ")}`);
8787
9414
  }
@@ -8800,7 +9427,53 @@ function printToolDetails(tool, requestedToolId) {
8800
9427
  } else {
8801
9428
  console.log(` deepline tools execute ${toolId} --payload '{...}'`);
8802
9429
  }
8803
- 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
+ }
8804
9477
  }
8805
9478
  function printToolCost(input) {
8806
9479
  const { cost, billingSource, deeplineCredits, deeplineUsdPerPricingUnit } = input;
@@ -8868,6 +9541,17 @@ function samplePayload(samples, key) {
8868
9541
  function commandEnvelopeFromRawResponse(rawResponse) {
8869
9542
  return isRecord3(rawResponse) ? { ...rawResponse } : { status: "completed", result: rawResponse };
8870
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
+ }
8871
9555
  function isPlayTool(tool) {
8872
9556
  const provider = typeof tool.provider === "string" ? tool.provider : "";
8873
9557
  return provider === "deepline_native" && Boolean(recordField(tool, "playExpansion", "play_expansion"));
@@ -8941,6 +9625,7 @@ function parseExecuteOptions(args) {
8941
9625
  const params = {};
8942
9626
  let outputFormat = "auto";
8943
9627
  let noPreview = false;
9628
+ let fullOutput = false;
8944
9629
  for (let index = 1; index < args.length; index += 1) {
8945
9630
  const arg = args[index];
8946
9631
  if ((arg === "--param" || arg === "-p") && args[index + 1]) {
@@ -8967,6 +9652,7 @@ function parseExecuteOptions(args) {
8967
9652
  }
8968
9653
  if (arg === "--full-output") {
8969
9654
  outputFormat = "json";
9655
+ fullOutput = true;
8970
9656
  continue;
8971
9657
  }
8972
9658
  if (arg === "--no-preview") {
@@ -8975,7 +9661,7 @@ function parseExecuteOptions(args) {
8975
9661
  }
8976
9662
  throw new Error(`Unknown option: ${arg}`);
8977
9663
  }
8978
- return { toolId, params, outputFormat, noPreview };
9664
+ return { toolId, params, outputFormat, noPreview, fullOutput };
8979
9665
  }
8980
9666
  function safeFileStem(value) {
8981
9667
  return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
@@ -9007,7 +9693,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
9007
9693
  description: ${JSON.stringify(`Seed ${input.toolId} rows for workflow expansion.`)},
9008
9694
  });
9009
9695
 
9010
- const list = Object.values(result.lists)[0];
9696
+ const list = Object.values(result.extractedLists)[0];
9011
9697
  const rows = (list?.get() ?? []).slice(0, 100);
9012
9698
  // ${sampleRows}
9013
9699
  // columns: ${columns}
@@ -9024,7 +9710,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
9024
9710
  };
9025
9711
  });
9026
9712
  `;
9027
- writeFileSync7(scriptPath, script, { encoding: "utf-8", mode: 384 });
9713
+ writeFileSync8(scriptPath, script, { encoding: "utf-8", mode: 384 });
9028
9714
  return {
9029
9715
  path: scriptPath,
9030
9716
  projectDir,
@@ -9035,7 +9721,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
9035
9721
  function buildToolExecuteBaseEnvelope(input) {
9036
9722
  const envelope = commandEnvelopeFromRawResponse(input.rawResponse);
9037
9723
  const summaryEntries = Object.entries(input.summary);
9038
- const output = input.listConversion ? {
9724
+ const outputPreview = input.listConversion ? {
9039
9725
  kind: "list",
9040
9726
  rowCount: input.listConversion.rows.length,
9041
9727
  columns: Object.keys(input.listConversion.rows[0] ?? {}),
@@ -9046,6 +9732,7 @@ function buildToolExecuteBaseEnvelope(input) {
9046
9732
  kind: summaryEntries.length > 0 ? "object" : "raw",
9047
9733
  summary: input.summary
9048
9734
  };
9735
+ const envelopeHasCanonicalOutput = isRecord3(envelope.toolExecutionResult) && isRecord3(envelope.toolExecutionResult.toolOutput) && Object.prototype.hasOwnProperty.call(envelope.toolExecutionResult.toolOutput, "raw");
9049
9736
  const actions = input.listConversion ? [
9050
9737
  {
9051
9738
  label: "next",
@@ -9054,7 +9741,7 @@ function buildToolExecuteBaseEnvelope(input) {
9054
9741
  ] : [];
9055
9742
  return {
9056
9743
  ...envelope,
9057
- output,
9744
+ ...envelopeHasCanonicalOutput ? { output_preview: outputPreview } : { output: outputPreview },
9058
9745
  ...summaryEntries.length > 0 ? { summary: input.summary } : {},
9059
9746
  next: input.listConversion ? {
9060
9747
  expandToPlay: "Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
@@ -9107,8 +9794,12 @@ async function executeTool(args) {
9107
9794
  throw error;
9108
9795
  }
9109
9796
  const rawResponse = await client.executeTool(parsed.toolId, parsed.params);
9797
+ if (parsed.fullOutput) {
9798
+ printJson(rawResponse);
9799
+ return 0;
9800
+ }
9110
9801
  const listConversion = tryConvertToList(rawResponse, {
9111
- listExtractorPaths: metadata.listExtractorPaths ?? []
9802
+ listExtractorPaths: listExtractorPathsFromUsageGuidance(metadata)
9112
9803
  });
9113
9804
  const summary = extractSummaryFields(rawResponse);
9114
9805
  const baseEnvelope = buildToolExecuteBaseEnvelope({
@@ -9240,7 +9931,7 @@ async function executeTool(args) {
9240
9931
  // src/cli/commands/update.ts
9241
9932
  import { spawn } from "child_process";
9242
9933
  import { existsSync as existsSync7 } from "fs";
9243
- 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";
9244
9935
  function posixShellQuote(value) {
9245
9936
  return `'${value.replace(/'/g, `'\\''`)}'`;
9246
9937
  }
@@ -9259,7 +9950,7 @@ function buildSourceUpdateCommand(sourceRoot) {
9259
9950
  return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
9260
9951
  }
9261
9952
  function findRepoBackedSdkRoot(startPath) {
9262
- let current = resolve9(startPath);
9953
+ let current = resolve10(startPath);
9263
9954
  while (true) {
9264
9955
  if (existsSync7(join9(current, "sdk", "package.json")) && existsSync7(join9(current, "sdk", "bin", "deepline-dev.ts"))) {
9265
9956
  return current;
@@ -9270,7 +9961,7 @@ function findRepoBackedSdkRoot(startPath) {
9270
9961
  }
9271
9962
  }
9272
9963
  function resolveUpdatePlan() {
9273
- const entrypoint = process.argv[1] ? resolve9(process.argv[1]) : "";
9964
+ const entrypoint = process.argv[1] ? resolve10(process.argv[1]) : "";
9274
9965
  const sourceRoot = entrypoint ? findRepoBackedSdkRoot(dirname9(entrypoint)) : null;
9275
9966
  if (sourceRoot) {
9276
9967
  return {
@@ -9363,8 +10054,8 @@ Examples:
9363
10054
  }
9364
10055
 
9365
10056
  // src/cli/skills-sync.ts
9366
- import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
9367
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync8 } from "fs";
10057
+ import { spawn as spawn2, spawnSync } from "child_process";
10058
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync9 } from "fs";
9368
10059
  import { homedir as homedir4 } from "os";
9369
10060
  import { dirname as dirname10, join as join10 } from "path";
9370
10061
  var CHECK_TIMEOUT_MS2 = 3e3;
@@ -9383,7 +10074,7 @@ function readLocalSkillsVersion(baseUrl) {
9383
10074
  const path = sdkSkillsVersionPath(baseUrl);
9384
10075
  if (!existsSync8(path)) return "";
9385
10076
  try {
9386
- return readFileSync5(path, "utf-8").trim();
10077
+ return readFileSync6(path, "utf-8").trim();
9387
10078
  } catch {
9388
10079
  return "";
9389
10080
  }
@@ -9391,9 +10082,45 @@ function readLocalSkillsVersion(baseUrl) {
9391
10082
  function writeLocalSkillsVersion(baseUrl, version) {
9392
10083
  const path = sdkSkillsVersionPath(baseUrl);
9393
10084
  mkdirSync5(dirname10(path), { recursive: true });
9394
- writeFileSync8(path, `${version}
10085
+ writeFileSync9(path, `${version}
9395
10086
  `, "utf-8");
9396
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
+ }
9397
10124
  async function fetchSkillsUpdate(baseUrl, localVersion) {
9398
10125
  const controller = new AbortController();
9399
10126
  const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
@@ -9429,7 +10156,7 @@ function buildSkillsInstallArgs(baseUrl) {
9429
10156
  "skills",
9430
10157
  "add",
9431
10158
  packageUrl,
9432
- "--agents",
10159
+ "--agent",
9433
10160
  ...SKILL_AGENTS,
9434
10161
  "--global",
9435
10162
  "--yes",
@@ -9445,7 +10172,7 @@ function buildBunxSkillsInstallArgs(baseUrl) {
9445
10172
  "skills",
9446
10173
  "add",
9447
10174
  packageUrl,
9448
- "--agents",
10175
+ "--agent",
9449
10176
  ...SKILL_AGENTS,
9450
10177
  "--global",
9451
10178
  "--yes",
@@ -9455,7 +10182,7 @@ function buildBunxSkillsInstallArgs(baseUrl) {
9455
10182
  ];
9456
10183
  }
9457
10184
  function hasCommand(command) {
9458
- const result = spawnSync2(command, ["--version"], {
10185
+ const result = spawnSync(command, ["--version"], {
9459
10186
  stdio: "ignore",
9460
10187
  shell: process.platform === "win32"
9461
10188
  });
@@ -9485,7 +10212,7 @@ function resolveSkillsInstallCommands(baseUrl) {
9485
10212
  return [npxInstall];
9486
10213
  }
9487
10214
  function runOneSkillsInstall(install) {
9488
- return new Promise((resolve10) => {
10215
+ return new Promise((resolve11) => {
9489
10216
  const child = spawn2(install.command, install.args, {
9490
10217
  stdio: ["ignore", "ignore", "pipe"],
9491
10218
  env: process.env
@@ -9495,7 +10222,7 @@ function runOneSkillsInstall(install) {
9495
10222
  stderr += chunk.toString("utf-8");
9496
10223
  });
9497
10224
  child.on("error", (error) => {
9498
- resolve10({
10225
+ resolve11({
9499
10226
  ok: false,
9500
10227
  detail: `failed to start ${install.command}: ${error.message}`,
9501
10228
  manualCommand: install.manualCommand
@@ -9503,11 +10230,11 @@ function runOneSkillsInstall(install) {
9503
10230
  });
9504
10231
  child.on("close", (code) => {
9505
10232
  if (code === 0) {
9506
- resolve10({ ok: true, detail: "", manualCommand: install.manualCommand });
10233
+ resolve11({ ok: true, detail: "", manualCommand: install.manualCommand });
9507
10234
  return;
9508
10235
  }
9509
10236
  const detail = stderr.trim();
9510
- resolve10({
10237
+ resolve11({
9511
10238
  ok: false,
9512
10239
  detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
9513
10240
  manualCommand: install.manualCommand
@@ -9546,10 +10273,19 @@ async function syncSdkSkillsIfNeeded(baseUrl) {
9546
10273
  attemptedSync = true;
9547
10274
  const localVersion = readLocalSkillsVersion(baseUrl);
9548
10275
  const update = await fetchSkillsUpdate(baseUrl, localVersion);
9549
- if (!update?.needsUpdate || !update.remoteVersion) return;
10276
+ const hasStaleInstalledSkill = installedSdkSkillHasStalePositionalExecuteExamples();
10277
+ if (!update?.needsUpdate && !hasStaleInstalledSkill || !update?.remoteVersion) {
10278
+ return;
10279
+ }
9550
10280
  writeSdkSkillsStatusLine("SDK skills changed; syncing deepline-sdk skill...");
9551
10281
  const installed = await runSkillsInstall(baseUrl);
9552
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
+ }
9553
10289
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
9554
10290
  writeSdkSkillsStatusLine("SDK skills are up to date.");
9555
10291
  }