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.
package/dist/cli/index.js CHANGED
@@ -67,19 +67,12 @@ var ConfigError = class extends DeeplineError {
67
67
  };
68
68
 
69
69
  // src/config.ts
70
+ var HOST_URL_ENV = "DEEPLINE_HOST_URL";
71
+ var API_KEY_ENV = "DEEPLINE_API_KEY";
70
72
  var PROD_URL = "https://code.deepline.com";
71
73
  var DEFAULT_TIMEOUT = 6e4;
72
74
  var DEFAULT_MAX_RETRIES = 3;
73
- var ACTIVE_DEEPLINE_ENV_FILE = ".env.deepline";
74
- function isProdBaseUrl(baseUrl) {
75
- return baseUrl.trim().replace(/\/$/, "") === PROD_URL;
76
- }
77
- function profileNameForBaseUrl(baseUrl) {
78
- return isProdBaseUrl(baseUrl) ? "prod" : "dev";
79
- }
80
- function projectEnvStartDir() {
81
- return process.env.DEEPLINE_PROJECT_ENV_DIR?.trim() || process.cwd();
82
- }
75
+ var PROJECT_DEEPLINE_ENV_FILE = ".env.deepline";
83
76
  function baseUrlSlug(baseUrl) {
84
77
  let url;
85
78
  try {
@@ -88,7 +81,7 @@ function baseUrlSlug(baseUrl) {
88
81
  return "unknown";
89
82
  }
90
83
  const host = url.hostname || "unknown";
91
- const port = url.port ? parseInt(url.port, 10) : null;
84
+ const port = url.port ? Number.parseInt(url.port, 10) : null;
92
85
  let slug = host.replace(/[^a-zA-Z0-9]/g, "-");
93
86
  if (port && port !== 80 && port !== 443) {
94
87
  slug = `${slug}-${port}`;
@@ -115,79 +108,52 @@ function parseEnvFile(filePath) {
115
108
  }
116
109
  return env;
117
110
  }
118
- function findNearestEnvFile(names, startDir = process.cwd()) {
111
+ function findNearestEnvFile(name, startDir = process.cwd()) {
119
112
  let current = (0, import_node_path.resolve)(startDir);
120
113
  while (true) {
121
- for (const name of names) {
122
- const filePath = (0, import_node_path.join)(current, name);
123
- if ((0, import_node_fs.existsSync)(filePath)) return filePath;
124
- }
114
+ const filePath = (0, import_node_path.join)(current, name);
115
+ if ((0, import_node_fs.existsSync)(filePath)) return filePath;
125
116
  const parent = (0, import_node_path.dirname)(current);
126
117
  if (parent === current) return null;
127
118
  current = parent;
128
119
  }
129
120
  }
130
- function findNearestEnv(names, startDir = process.cwd()) {
131
- const filePath = findNearestEnvFile(names, startDir);
121
+ function loadProjectDeeplineEnv(startDir = process.cwd()) {
122
+ const filePath = findNearestEnvFile(PROJECT_DEEPLINE_ENV_FILE, startDir);
132
123
  return filePath ? parseEnvFile(filePath) : {};
133
124
  }
134
- function findNearestWorktreeEnv(startDir = process.cwd()) {
135
- return findNearestEnv([".env.worktree"], startDir);
136
- }
137
- function resolveProfileEnvFileNames() {
138
- const explicitProfile = process.env.DEEPLINE_ENV_PROFILE?.trim() || process.env.DEEPLINE_PROFILE?.trim() || "";
139
- const names = [];
140
- if (explicitProfile) names.push(`.env.deepline.${explicitProfile}`);
141
- const nodeEnv = process.env.NODE_ENV?.trim();
142
- if (nodeEnv === "production") names.push(".env.deepline.prod");
143
- else if (nodeEnv === "staging") names.push(".env.deepline.staging");
144
- names.push(ACTIVE_DEEPLINE_ENV_FILE);
145
- return names;
146
- }
147
- function resolveProjectAppEnvFileNames() {
148
- const nodeEnv = process.env.NODE_ENV?.trim();
149
- const names = [];
150
- if (nodeEnv === "production") names.push(".env.prod");
151
- if (nodeEnv === "staging") names.push(".env.staging");
152
- names.push(".env.local", ".env");
153
- return names;
154
- }
155
- function resolveBaseUrlFromEnvValues(env) {
156
- return env.DEEPLINE_ORIGIN_URL?.trim() || env.DEEPLINE_API_BASE_URL?.trim() || "";
157
- }
158
- function loadProjectDeeplineEnv() {
159
- return findNearestEnv(resolveProfileEnvFileNames(), projectEnvStartDir());
160
- }
161
- function loadProjectAppEnv() {
162
- return findNearestEnv(resolveProjectAppEnvFileNames(), projectEnvStartDir());
163
- }
164
- function normalizeWorktreeBaseUrl(baseUrl, worktreeEnv = findNearestWorktreeEnv()) {
165
- const trimmed = baseUrl.trim().replace(/\/$/, "");
166
- if (!trimmed) return trimmed;
125
+ function normalizeBaseUrl(baseUrl) {
126
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
127
+ if (!trimmed) return "";
167
128
  try {
168
129
  const parsed = new URL(trimmed);
169
- if (parsed.hostname.endsWith(".localhost") && parsed.port === "1355") {
170
- const port = worktreeEnv.WORKTREE_APP_PORT || worktreeEnv.PORT;
171
- if (port) return `${parsed.protocol}//localhost:${port}`;
130
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
131
+ return "";
172
132
  }
133
+ return parsed.toString().replace(/\/+$/, "");
173
134
  } catch {
135
+ return "";
174
136
  }
175
- return trimmed;
176
137
  }
177
- function resolveWorktreeBaseUrl() {
178
- const worktreeEnv = findNearestWorktreeEnv();
179
- const declared = worktreeEnv.DEEPLINE_API_BASE_URL || worktreeEnv.WORKTREE_PUBLIC_APP_URL || worktreeEnv.APP_URL || "";
180
- if (declared) return normalizeWorktreeBaseUrl(declared, worktreeEnv);
181
- const port = worktreeEnv.WORKTREE_APP_PORT || worktreeEnv.PORT || "";
182
- return port ? `http://localhost:${port}` : "";
138
+ function firstNonEmpty(...values) {
139
+ for (const value of values) {
140
+ const trimmed = value?.trim();
141
+ if (trimmed) return trimmed;
142
+ }
143
+ return "";
183
144
  }
184
- function sdkCliEnvFilePath(baseUrl) {
145
+ function sdkCliConfigDir(baseUrl) {
185
146
  const home = process.env.HOME?.trim() || (0, import_node_os.homedir)();
186
- return (0, import_node_path.join)(home, ".local", "deepline", baseUrlSlug(baseUrl || PROD_URL), ".env");
147
+ return (0, import_node_path.join)(home, ".local", "deepline", baseUrlSlug(baseUrl || PROD_URL));
148
+ }
149
+ function sdkCliEnvFilePath(baseUrl) {
150
+ return (0, import_node_path.join)(sdkCliConfigDir(baseUrl), ".env");
187
151
  }
188
152
  function loadCliEnv(baseUrl = PROD_URL) {
189
- const envPath = sdkCliEnvFilePath(baseUrl);
190
- return parseEnvFile(envPath);
153
+ return parseEnvFile(sdkCliEnvFilePath(baseUrl));
154
+ }
155
+ function hostConfigDirPath(baseUrl) {
156
+ return sdkCliConfigDir(baseUrl);
191
157
  }
192
158
  function hostEnvFilePath(baseUrl) {
193
159
  return sdkCliEnvFilePath(baseUrl);
@@ -198,9 +164,10 @@ function saveHostEnvValues(baseUrl, values) {
198
164
  if (!(0, import_node_fs.existsSync)(dir)) {
199
165
  (0, import_node_fs.mkdirSync)(dir, { recursive: true });
200
166
  }
201
- const existing = (0, import_node_fs.existsSync)(filePath) ? parseEnvFile(filePath) : {};
167
+ const existing = parseEnvFile(filePath);
202
168
  const merged = { ...existing, ...values };
203
- const lines = Object.entries(merged).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${value}`);
169
+ const allowedKeys = /* @__PURE__ */ new Set([HOST_URL_ENV, API_KEY_ENV]);
170
+ const lines = Object.entries(merged).filter(([key, value]) => allowedKeys.has(key) && value !== "").map(([key, value]) => `${key}=${value}`);
204
171
  (0, import_node_fs.writeFileSync)(filePath, `${lines.join("\n")}
205
172
  `, "utf-8");
206
173
  }
@@ -208,31 +175,36 @@ function loadGlobalCliEnv() {
208
175
  return loadCliEnv(PROD_URL);
209
176
  }
210
177
  function autoDetectBaseUrl() {
211
- const envOrigin = process.env.DEEPLINE_ORIGIN_URL?.trim();
212
- if (envOrigin) return normalizeWorktreeBaseUrl(envOrigin);
213
- const envBase = process.env.DEEPLINE_API_BASE_URL?.trim();
214
- if (envBase) return normalizeWorktreeBaseUrl(envBase);
215
- const projectDeeplineBaseUrl = resolveBaseUrlFromEnvValues(loadProjectDeeplineEnv());
216
- if (projectDeeplineBaseUrl) return normalizeWorktreeBaseUrl(projectDeeplineBaseUrl);
217
- const projectAppBaseUrl = resolveBaseUrlFromEnvValues(loadProjectAppEnv());
218
- if (projectAppBaseUrl) return normalizeWorktreeBaseUrl(projectAppBaseUrl);
219
- const worktreeBaseUrl = resolveWorktreeBaseUrl();
220
- if (worktreeBaseUrl) return worktreeBaseUrl;
178
+ const projectEnv = loadProjectDeeplineEnv();
221
179
  const globalEnv = loadGlobalCliEnv();
222
- const globalOrigin = globalEnv.DEEPLINE_ORIGIN_URL?.trim();
223
- if (globalOrigin) return normalizeWorktreeBaseUrl(globalOrigin);
224
- return PROD_URL;
180
+ return normalizeBaseUrl(process.env[HOST_URL_ENV] ?? "") || normalizeBaseUrl(projectEnv[HOST_URL_ENV] ?? "") || normalizeBaseUrl(globalEnv[HOST_URL_ENV] ?? "") || PROD_URL;
181
+ }
182
+ function resolveApiKeyForBaseUrl(baseUrl, explicitApiKey) {
183
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
184
+ const projectEnv = loadProjectDeeplineEnv();
185
+ const cliEnv = loadCliEnv(normalizedBaseUrl || baseUrl);
186
+ const projectBaseUrl = normalizeBaseUrl(projectEnv[HOST_URL_ENV] ?? "");
187
+ const projectKeyApplies = projectBaseUrl === normalizedBaseUrl;
188
+ return firstNonEmpty(
189
+ explicitApiKey,
190
+ process.env[API_KEY_ENV],
191
+ projectKeyApplies ? projectEnv[API_KEY_ENV] : "",
192
+ cliEnv[API_KEY_ENV]
193
+ );
225
194
  }
226
195
  function resolveConfig(options) {
227
- const requestedBaseUrl = options?.baseUrl?.trim() || autoDetectBaseUrl();
228
- const baseUrl = normalizeWorktreeBaseUrl(requestedBaseUrl);
229
- const cliEnv = loadCliEnv(baseUrl);
230
- const projectDeeplineEnv = loadProjectDeeplineEnv();
231
- const projectAppEnv = loadProjectAppEnv();
232
- const apiKey = options?.apiKey?.trim() || process.env.DEEPLINE_API_KEY?.trim() || projectDeeplineEnv.DEEPLINE_API_KEY || projectAppEnv.DEEPLINE_API_KEY || cliEnv.DEEPLINE_API_KEY || "";
196
+ const baseUrl = normalizeBaseUrl(
197
+ options?.baseUrl?.trim() || autoDetectBaseUrl()
198
+ );
199
+ if (!baseUrl) {
200
+ throw new ConfigError(
201
+ `Invalid ${HOST_URL_ENV}. Expected an http(s) URL such as https://code.deepline.com.`
202
+ );
203
+ }
204
+ const apiKey = resolveApiKeyForBaseUrl(baseUrl, options?.apiKey);
233
205
  if (!apiKey) {
234
206
  throw new ConfigError(
235
- `No API key found. Set DEEPLINE_API_KEY env var, pass apiKey option, or run: deepline auth register`
207
+ `No API key found. Set ${API_KEY_ENV}, add it to .env.deepline, or run: deepline auth register`
236
208
  );
237
209
  }
238
210
  return {
@@ -242,32 +214,10 @@ function resolveConfig(options) {
242
214
  maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES
243
215
  };
244
216
  }
245
- function mergeEnvFile(filePath, values) {
246
- const existing = (0, import_node_fs.existsSync)(filePath) ? parseEnvFile(filePath) : {};
247
- const merged = { ...existing, ...values };
248
- const dir = (0, import_node_path.dirname)(filePath);
249
- if (!(0, import_node_fs.existsSync)(dir)) (0, import_node_fs.mkdirSync)(dir, { recursive: true });
250
- const lines = Object.entries(merged).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${value}`);
251
- (0, import_node_fs.writeFileSync)(filePath, `${lines.join("\n")}
252
- `, "utf-8");
253
- }
254
- function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStartDir()) {
255
- const root = (0, import_node_path.resolve)(startDir);
256
- const profile = profileNameForBaseUrl(baseUrl);
257
- const files = [
258
- (0, import_node_path.join)(root, ACTIVE_DEEPLINE_ENV_FILE),
259
- (0, import_node_path.join)(root, `.env.deepline.${profile}`)
260
- ];
261
- if (profile === "dev") files.push((0, import_node_path.join)(root, ".env"));
262
- for (const filePath of files) {
263
- mergeEnvFile(filePath, values);
264
- }
265
- return files;
266
- }
267
217
 
268
218
  // src/version.ts
269
- var SDK_VERSION = "0.1.32";
270
- var SDK_API_CONTRACT = "2026-05-generic-play-input-flags";
219
+ var SDK_VERSION = "0.1.35";
220
+ var SDK_API_CONTRACT = "2026-05-v2-tool-result-contract";
271
221
 
272
222
  // ../shared_libs/play-runtime/coordinator-headers.ts
273
223
  var COORDINATOR_INTERNAL_TOKEN_HEADER = "x-deepline-internal-token";
@@ -349,7 +299,7 @@ var HttpClient = class {
349
299
  const response = await fetch(candidateUrl, {
350
300
  method,
351
301
  headers,
352
- body: options?.formData !== void 0 ? options.formData : options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
302
+ body: options?.formData !== void 0 ? typeof options.formData === "function" ? options.formData() : options.formData : options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
353
303
  signal: controller.signal
354
304
  });
355
305
  clearTimeout(timeoutId);
@@ -432,10 +382,13 @@ var HttpClient = class {
432
382
  throw new AuthError();
433
383
  }
434
384
  if (!response.ok) {
385
+ const body = await response.text();
386
+ const parsed = parseResponseBody(body);
435
387
  throw new DeeplineError(
436
- `HTTP ${response.status}`,
388
+ apiErrorMessage(parsed, response.status),
437
389
  response.status,
438
- "API_ERROR"
390
+ "API_ERROR",
391
+ { response: parsed }
439
392
  );
440
393
  }
441
394
  if (!response.body) {
@@ -485,6 +438,26 @@ var HttpClient = class {
485
438
  return this.request(path, { method: "DELETE" });
486
439
  }
487
440
  };
441
+ function parseResponseBody(body) {
442
+ try {
443
+ return JSON.parse(body);
444
+ } catch {
445
+ return body;
446
+ }
447
+ }
448
+ function apiErrorMessage(parsed, status) {
449
+ const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
450
+ if (typeof errorValue === "string") {
451
+ return errorValue;
452
+ }
453
+ if (errorValue && typeof errorValue === "object" && "message" in errorValue && typeof errorValue.message === "string") {
454
+ return errorValue.message;
455
+ }
456
+ if (typeof parsed === "object" && parsed && "message" in parsed && typeof parsed.message === "string") {
457
+ return parsed.message;
458
+ }
459
+ return `HTTP ${status}`;
460
+ }
488
461
  function parseRetryAfter(response) {
489
462
  const header = response.headers.get("retry-after");
490
463
  if (header) {
@@ -548,15 +521,17 @@ function decodeSseFrame(frame) {
548
521
  return parsed;
549
522
  }
550
523
  function sleep(ms) {
551
- return new Promise((resolve10) => setTimeout(resolve10, ms));
524
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
552
525
  }
553
526
 
554
527
  // src/client.ts
555
528
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
556
529
  var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
530
+ var EXECUTE_RESPONSE_CONTRACT_HEADER = "x-deepline-execute-response-contract";
531
+ var V2_EXECUTE_RESPONSE_CONTRACT = "v2-tool-execution-result";
557
532
  var COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1e3];
558
533
  function sleep2(ms) {
559
- return new Promise((resolve10) => setTimeout(resolve10, ms));
534
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
560
535
  }
561
536
  function isTransientCompileManifestError(error) {
562
537
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
@@ -822,13 +797,16 @@ var DeeplineClient = class {
822
797
  /**
823
798
  * Execute a tool and return the standard execution envelope.
824
799
  *
825
- * The `result.data` field contains the provider payload. `result.meta`
826
- * contains provider/upstream metadata such as HTTP status or paging details.
800
+ * The `toolExecutionResult.toolOutput.raw` field contains the raw tool output.
801
+ * `toolExecutionResult.toolOutput.meta` contains tool/provider metadata.
827
802
  * Top-level fields such as `status`, `job_id`, and `billing` describe the
828
- * Deepline execution.
803
+ * Deepline execution envelope.
829
804
  */
830
805
  async executeTool(toolId, input, options) {
831
- const headers = options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : void 0;
806
+ const headers = {
807
+ [EXECUTE_RESPONSE_CONTRACT_HEADER]: V2_EXECUTE_RESPONSE_CONTRACT,
808
+ ...options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : {}
809
+ };
832
810
  return this.http.post(
833
811
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
834
812
  { payload: input },
@@ -1126,34 +1104,37 @@ var DeeplineClient = class {
1126
1104
  * ```
1127
1105
  */
1128
1106
  async stagePlayFiles(files) {
1129
- const formData = new FormData();
1130
- formData.set(
1131
- "metadata",
1132
- JSON.stringify({
1133
- files: files.map((file, index) => ({
1134
- index,
1135
- logicalPath: file.logicalPath,
1136
- contentHash: file.contentHash,
1137
- contentType: file.contentType,
1138
- bytes: file.bytes
1139
- }))
1140
- })
1141
- );
1142
- for (const [index, file] of files.entries()) {
1143
- const bytes = decodeBase64Bytes(file.contentBase64);
1144
- const body = bytes.buffer.slice(
1145
- bytes.byteOffset,
1146
- bytes.byteOffset + bytes.byteLength
1147
- );
1107
+ const buildFormData = () => {
1108
+ const formData = new FormData();
1148
1109
  formData.set(
1149
- `file:${index}`,
1150
- new Blob([body], { type: file.contentType }),
1151
- file.logicalPath
1110
+ "metadata",
1111
+ JSON.stringify({
1112
+ files: files.map((file, index) => ({
1113
+ index,
1114
+ logicalPath: file.logicalPath,
1115
+ contentHash: file.contentHash,
1116
+ contentType: file.contentType,
1117
+ bytes: file.bytes
1118
+ }))
1119
+ })
1152
1120
  );
1153
- }
1121
+ for (const [index, file] of files.entries()) {
1122
+ const bytes = decodeBase64Bytes(file.contentBase64);
1123
+ const body = bytes.buffer.slice(
1124
+ bytes.byteOffset,
1125
+ bytes.byteOffset + bytes.byteLength
1126
+ );
1127
+ formData.set(
1128
+ `file:${index}`,
1129
+ new Blob([body], { type: file.contentType }),
1130
+ file.logicalPath
1131
+ );
1132
+ }
1133
+ return formData;
1134
+ };
1154
1135
  const response = await this.http.postFormData(
1155
1136
  "/api/v2/plays/files/stage",
1156
- formData
1137
+ buildFormData
1157
1138
  );
1158
1139
  return response.files;
1159
1140
  }
@@ -1686,10 +1667,11 @@ var import_node_fs2 = require("fs");
1686
1667
  var import_promises = require("fs/promises");
1687
1668
  var import_node_os2 = require("os");
1688
1669
  var import_node_path2 = require("path");
1689
- var import_node_child_process = require("child_process");
1670
+ var childProcess = __toESM(require("child_process"));
1690
1671
  var import_sync = require("csv-parse/sync");
1691
1672
  var import_sync2 = require("csv-stringify/sync");
1692
1673
  var BROWSER_FOCUS_COOLDOWN_MS = 3e4;
1674
+ var defaultBrowserCommandRunner = childProcess;
1693
1675
  function getAuthedHttpClient() {
1694
1676
  const config = resolveConfig();
1695
1677
  return { config, http: new HttpClient(config) };
@@ -1758,9 +1740,9 @@ function browserAppNameFromBundleId(bundleId) {
1758
1740
  };
1759
1741
  return names[bundleId.toLowerCase()] ?? "";
1760
1742
  }
1761
- function readDefaultMacBrowserBundleId() {
1743
+ function readDefaultMacBrowserBundleId(runner = defaultBrowserCommandRunner) {
1762
1744
  try {
1763
- const output = (0, import_node_child_process.execFileSync)(
1745
+ const output = runner.execFileSync(
1764
1746
  "/usr/bin/defaults",
1765
1747
  [
1766
1748
  "read",
@@ -1796,8 +1778,8 @@ function browserStrategyForBundleId(bundleId) {
1796
1778
  }
1797
1779
  return normalized === "com.apple.safari" ? "safari" : "fallback";
1798
1780
  }
1799
- function runAppleScript(script, args) {
1800
- const result = (0, import_node_child_process.spawnSync)("osascript", ["-", ...args], {
1781
+ function runAppleScript(script, args, runner = defaultBrowserCommandRunner) {
1782
+ const result = runner.spawnSync("osascript", ["-", ...args], {
1801
1783
  input: script,
1802
1784
  encoding: "utf-8",
1803
1785
  stdio: ["pipe", "ignore", "ignore"],
@@ -1805,7 +1787,7 @@ function runAppleScript(script, args) {
1805
1787
  });
1806
1788
  return result.status === 0;
1807
1789
  }
1808
- function retargetChromiumMacos(appName, targetUrl, allowFocus) {
1790
+ function retargetChromiumMacos(appName, targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
1809
1791
  const host = extractUrlHost(targetUrl);
1810
1792
  if (!host) return false;
1811
1793
  const escapedAppName = appName.replace(/"/g, '\\"');
@@ -1837,9 +1819,9 @@ ${newTabBlock} end if
1837
1819
  end tell
1838
1820
  end run
1839
1821
  `;
1840
- return runAppleScript(script, [targetUrl, host]);
1822
+ return runAppleScript(script, [targetUrl, host], runner);
1841
1823
  }
1842
- function retargetSafariMacos(appName, targetUrl, allowFocus) {
1824
+ function retargetSafariMacos(appName, targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
1843
1825
  const host = extractUrlHost(targetUrl);
1844
1826
  if (!host) return false;
1845
1827
  const escapedAppName = appName.replace(/"/g, '\\"');
@@ -1871,30 +1853,38 @@ ${newTabBlock} end if
1871
1853
  end tell
1872
1854
  end run
1873
1855
  `;
1874
- return runAppleScript(script, [targetUrl, host]);
1856
+ return runAppleScript(script, [targetUrl, host], runner);
1875
1857
  }
1876
- function openUrlMacos(targetUrl, allowFocus) {
1877
- const defaultBundleId = readDefaultMacBrowserBundleId();
1858
+ function openUrlMacos(targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
1859
+ const defaultBundleId = readDefaultMacBrowserBundleId(runner);
1878
1860
  const appName = defaultBundleId ? browserAppNameFromBundleId(defaultBundleId) : "";
1879
1861
  const strategy = browserStrategyForBundleId(defaultBundleId);
1880
- if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus)) {
1862
+ if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus, runner)) {
1881
1863
  return true;
1882
1864
  }
1883
- if (appName && strategy === "safari" && retargetSafariMacos(appName, targetUrl, allowFocus)) {
1865
+ if (appName && strategy === "safari" && retargetSafariMacos(appName, targetUrl, allowFocus, runner)) {
1884
1866
  return true;
1885
1867
  }
1886
- if (!allowFocus) {
1887
- return false;
1888
- }
1889
1868
  try {
1890
- (0, import_node_child_process.execFileSync)("open", [targetUrl], { stdio: "ignore" });
1869
+ runner.execFileSync(
1870
+ "open",
1871
+ [...allowFocus ? [] : ["-g"], targetUrl],
1872
+ { stdio: "ignore" }
1873
+ );
1891
1874
  return true;
1892
1875
  } catch {
1893
1876
  return false;
1894
1877
  }
1895
1878
  }
1879
+ function browserOpeningDisabled() {
1880
+ const value = String(
1881
+ process.env.DEEPLINE_NO_BROWSER ?? process.env.PLAYGROUND_HEADLESS ?? ""
1882
+ ).trim().toLowerCase();
1883
+ return value === "1" || value === "true" || value === "yes" || value === "on";
1884
+ }
1896
1885
  function openInBrowser(url) {
1897
1886
  try {
1887
+ if (browserOpeningDisabled()) return;
1898
1888
  const targetUrl = String(url || "").trim();
1899
1889
  if (!targetUrl) return;
1900
1890
  const allowFocus = claimBrowserFocus();
@@ -1904,12 +1894,12 @@ function openInBrowser(url) {
1904
1894
  }
1905
1895
  if (!allowFocus) return;
1906
1896
  if (process.platform === "win32") {
1907
- (0, import_node_child_process.execFileSync)("cmd.exe", ["/c", "start", "", targetUrl], {
1897
+ childProcess.execFileSync("cmd.exe", ["/c", "start", "", targetUrl], {
1908
1898
  stdio: "ignore"
1909
1899
  });
1910
1900
  return;
1911
1901
  }
1912
- (0, import_node_child_process.execFileSync)("xdg-open", [targetUrl], { stdio: "ignore" });
1902
+ childProcess.execFileSync("xdg-open", [targetUrl], { stdio: "ignore" });
1913
1903
  } catch {
1914
1904
  }
1915
1905
  }
@@ -1933,6 +1923,103 @@ function csvStringFromRows(rows, columns) {
1933
1923
  ...columns?.length ? { columns } : {}
1934
1924
  });
1935
1925
  }
1926
+ function parseMaybeJsonObject(value) {
1927
+ if (typeof value !== "string") {
1928
+ return value;
1929
+ }
1930
+ const trimmed = value.trim();
1931
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
1932
+ return value;
1933
+ }
1934
+ try {
1935
+ return JSON.parse(trimmed);
1936
+ } catch {
1937
+ return value;
1938
+ }
1939
+ }
1940
+ function flattenObjectColumns(row) {
1941
+ const flattened = {};
1942
+ for (const [key, rawValue] of Object.entries(row)) {
1943
+ const value = parseMaybeJsonObject(rawValue);
1944
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1945
+ for (const [nestedKey, nestedValue] of Object.entries(
1946
+ value
1947
+ )) {
1948
+ flattened[`${key}.${nestedKey}`] = nestedValue && typeof nestedValue === "object" ? JSON.stringify(nestedValue) : nestedValue;
1949
+ }
1950
+ continue;
1951
+ }
1952
+ flattened[key] = Array.isArray(value) ? JSON.stringify(value) : value;
1953
+ }
1954
+ return flattened;
1955
+ }
1956
+ function recordRows(value) {
1957
+ return value.filter(
1958
+ (row) => Boolean(row) && typeof row === "object" && !Array.isArray(row)
1959
+ );
1960
+ }
1961
+ function dataExportRows(rows) {
1962
+ return rows.map((row) => flattenObjectColumns(row));
1963
+ }
1964
+ function dataExportColumns(rows, preferredColumns = []) {
1965
+ const discoveredColumns = [
1966
+ ...new Set(rows.flatMap((row) => Object.keys(row)))
1967
+ ];
1968
+ if (rows.length === 0) {
1969
+ return [...new Set(preferredColumns.filter(Boolean))];
1970
+ }
1971
+ const discovered = new Set(discoveredColumns);
1972
+ const columns = [];
1973
+ const seen = /* @__PURE__ */ new Set();
1974
+ for (const column of preferredColumns) {
1975
+ if (!column) {
1976
+ continue;
1977
+ }
1978
+ const expandedColumns = discovered.has(column) ? [column] : discoveredColumns.filter(
1979
+ (discoveredColumn) => discoveredColumn.startsWith(`${column}.`)
1980
+ );
1981
+ for (const expandedColumn of expandedColumns) {
1982
+ if (seen.has(expandedColumn)) {
1983
+ continue;
1984
+ }
1985
+ seen.add(expandedColumn);
1986
+ columns.push(expandedColumn);
1987
+ }
1988
+ }
1989
+ for (const column of discoveredColumns) {
1990
+ if (seen.has(column)) {
1991
+ continue;
1992
+ }
1993
+ seen.add(column);
1994
+ columns.push(column);
1995
+ }
1996
+ return columns;
1997
+ }
1998
+ function dataExportCsvString(rows, preferredColumns = []) {
1999
+ const flattenedRows = dataExportRows(rows);
2000
+ return csvStringFromRows(
2001
+ flattenedRows,
2002
+ dataExportColumns(flattenedRows, preferredColumns)
2003
+ );
2004
+ }
2005
+ function markdownCell(value) {
2006
+ if (value === null || value === void 0) {
2007
+ return "";
2008
+ }
2009
+ const text = typeof value === "object" ? JSON.stringify(value) : String(value);
2010
+ return text.replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
2011
+ }
2012
+ function markdownTableFromRows(rows, preferredColumns = []) {
2013
+ const flattenedRows = dataExportRows(rows);
2014
+ const columns = dataExportColumns(flattenedRows, preferredColumns);
2015
+ const header = `| ${columns.map(markdownCell).join(" | ")} |`;
2016
+ const separator = `| ${columns.map(() => "---").join(" | ")} |`;
2017
+ const body = flattenedRows.map(
2018
+ (row) => `| ${columns.map((column) => markdownCell(row[column])).join(" | ")} |`
2019
+ );
2020
+ return `${[header, separator, ...body].join("\n")}
2021
+ `;
2022
+ }
1936
2023
  function printJson(value) {
1937
2024
  process.stdout.write(`${JSON.stringify(value, null, 2)}
1938
2025
  `);
@@ -2023,17 +2110,39 @@ var EXIT_SERVER = 2;
2023
2110
  function envFilePath(baseUrl) {
2024
2111
  return hostEnvFilePath(baseUrl);
2025
2112
  }
2026
- function saveEnvValues(values, baseUrl) {
2027
- const filePath = envFilePath(baseUrl);
2113
+ function pendingClaimTokenPath(baseUrl) {
2114
+ return `${hostConfigDirPath(baseUrl)}/pending-claim-token`;
2115
+ }
2116
+ function savePendingClaimToken(baseUrl, claimToken) {
2117
+ const filePath = pendingClaimTokenPath(baseUrl);
2028
2118
  const dir = (0, import_node_path3.dirname)(filePath);
2029
2119
  if (!(0, import_node_fs3.existsSync)(dir)) {
2030
2120
  (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
2031
2121
  }
2032
- const existing = (0, import_node_fs3.existsSync)(filePath) ? parseEnvFile(filePath) : {};
2033
- const merged = { ...existing, ...values };
2034
- const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
2035
- (0, import_node_fs3.writeFileSync)(filePath, lines.join("\n") + "\n", "utf-8");
2036
- saveProjectDeeplineEnvValues(baseUrl, values);
2122
+ (0, import_node_fs3.writeFileSync)(filePath, `${claimToken}
2123
+ `, "utf-8");
2124
+ }
2125
+ function readPendingClaimToken(baseUrl) {
2126
+ const filePath = pendingClaimTokenPath(baseUrl);
2127
+ if (!(0, import_node_fs3.existsSync)(filePath)) return "";
2128
+ try {
2129
+ return (0, import_node_fs3.readFileSync)(filePath, "utf-8").trim();
2130
+ } catch {
2131
+ return "";
2132
+ }
2133
+ }
2134
+ function clearPendingClaimToken(baseUrl) {
2135
+ try {
2136
+ (0, import_node_fs3.rmSync)(pendingClaimTokenPath(baseUrl), { force: true });
2137
+ } catch {
2138
+ }
2139
+ }
2140
+ function saveEnvValues(values, baseUrl) {
2141
+ const filtered = {
2142
+ ...values[HOST_URL_ENV] ? { [HOST_URL_ENV]: values[HOST_URL_ENV] } : {},
2143
+ ...values[API_KEY_ENV] ? { [API_KEY_ENV]: values[API_KEY_ENV] } : {}
2144
+ };
2145
+ saveHostEnvValues(baseUrl, filtered);
2037
2146
  }
2038
2147
  async function httpJson(method, url, apiKey, body) {
2039
2148
  const headers = { "Content-Type": "application/json" };
@@ -2087,7 +2196,7 @@ function buildCandidateUrls2(url) {
2087
2196
  }
2088
2197
  }
2089
2198
  function sleep3(ms) {
2090
- return new Promise((resolve10) => setTimeout(resolve10, ms));
2199
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
2091
2200
  }
2092
2201
  function printDeeplineLogo() {
2093
2202
  if (process.stdout.isTTY && (process.stdout.columns ?? 80) >= 70) {
@@ -2139,9 +2248,9 @@ async function handleRegister(args) {
2139
2248
  const claimUrl = String(data.claim_url || "");
2140
2249
  const claimToken = String(data.claim_token || "");
2141
2250
  if (claimToken) {
2251
+ savePendingClaimToken(baseUrl, claimToken);
2142
2252
  saveEnvValues({
2143
- DEEPLINE_ORIGIN_URL: baseUrl,
2144
- DEEPLINE_CLAIM_TOKEN: claimToken
2253
+ [HOST_URL_ENV]: baseUrl
2145
2254
  }, baseUrl);
2146
2255
  }
2147
2256
  if (claimUrl) {
@@ -2165,6 +2274,7 @@ async function handleRegister(args) {
2165
2274
  { claim_token: claimToken, reveal: true }
2166
2275
  );
2167
2276
  if (s === 401 || s === 403) {
2277
+ clearPendingClaimToken(baseUrl);
2168
2278
  console.log("Status: unauthorized");
2169
2279
  return EXIT_AUTH;
2170
2280
  }
@@ -2181,15 +2291,16 @@ async function handleRegister(args) {
2181
2291
  const apiKey = String(statusData.api_key || "");
2182
2292
  if (apiKey) {
2183
2293
  saveEnvValues({
2184
- DEEPLINE_ORIGIN_URL: baseUrl,
2185
- DEEPLINE_API_KEY: apiKey,
2186
- DEEPLINE_CLAIM_TOKEN: ""
2294
+ [HOST_URL_ENV]: baseUrl,
2295
+ [API_KEY_ENV]: apiKey
2187
2296
  }, baseUrl);
2297
+ clearPendingClaimToken(baseUrl);
2188
2298
  printClaimSuccessBanner(statusData);
2189
2299
  return EXIT_OK;
2190
2300
  }
2191
2301
  }
2192
2302
  if (state === "expired") {
2303
+ clearPendingClaimToken(baseUrl);
2193
2304
  console.log("That approval link expired. Please run: deepline auth register");
2194
2305
  return EXIT_AUTH;
2195
2306
  }
@@ -2207,13 +2318,12 @@ async function handleWait(args) {
2207
2318
  }
2208
2319
  }
2209
2320
  }
2210
- const env = loadCliEnv(baseUrl);
2211
- if (env.DEEPLINE_API_KEY?.trim()) {
2212
- console.log("Already connected.");
2213
- return EXIT_OK;
2214
- }
2215
- const claimToken = env.DEEPLINE_CLAIM_TOKEN?.trim() || "";
2321
+ const claimToken = readPendingClaimToken(baseUrl);
2216
2322
  if (!claimToken) {
2323
+ if (resolveApiKeyForBaseUrl(baseUrl)) {
2324
+ console.log("Already connected.");
2325
+ return EXIT_OK;
2326
+ }
2217
2327
  console.error("No pending approval. Run: deepline auth register --no-wait");
2218
2328
  return EXIT_AUTH;
2219
2329
  }
@@ -2226,6 +2336,7 @@ async function handleWait(args) {
2226
2336
  { claim_token: claimToken, reveal: true }
2227
2337
  );
2228
2338
  if (status === 401 || status === 403) {
2339
+ clearPendingClaimToken(baseUrl);
2229
2340
  console.error("Claim is invalid. Run: deepline auth register");
2230
2341
  return EXIT_AUTH;
2231
2342
  }
@@ -2242,15 +2353,16 @@ async function handleWait(args) {
2242
2353
  const apiKey = String(data.api_key || "");
2243
2354
  if (apiKey) {
2244
2355
  saveEnvValues({
2245
- DEEPLINE_ORIGIN_URL: baseUrl,
2246
- DEEPLINE_API_KEY: apiKey,
2247
- DEEPLINE_CLAIM_TOKEN: ""
2356
+ [HOST_URL_ENV]: baseUrl,
2357
+ [API_KEY_ENV]: apiKey
2248
2358
  }, baseUrl);
2359
+ clearPendingClaimToken(baseUrl);
2249
2360
  printClaimSuccessBanner(data);
2250
2361
  return EXIT_OK;
2251
2362
  }
2252
2363
  }
2253
2364
  if (state === "expired") {
2365
+ clearPendingClaimToken(baseUrl);
2254
2366
  console.error("That approval link expired. Run: deepline auth register");
2255
2367
  return EXIT_AUTH;
2256
2368
  }
@@ -2285,10 +2397,9 @@ async function handleStatus(args) {
2285
2397
  };
2286
2398
  hostLines.push(`Host: ${baseUrl} (unreachable)`);
2287
2399
  }
2288
- const env = loadCliEnv(baseUrl);
2289
- const apiKey = process.env.DEEPLINE_API_KEY?.trim() || env.DEEPLINE_API_KEY || "";
2400
+ const apiKey = resolveApiKeyForBaseUrl(baseUrl);
2290
2401
  if (!apiKey) {
2291
- if (env.DEEPLINE_CLAIM_TOKEN?.trim()) {
2402
+ if (readPendingClaimToken(baseUrl)) {
2292
2403
  printCommandEnvelope({
2293
2404
  ...hostStatusPayload ?? { host: baseUrl },
2294
2405
  status: "pending",
@@ -2334,6 +2445,7 @@ async function handleStatus(args) {
2334
2445
  console.error(`Auth status error (status ${status}).`);
2335
2446
  return EXIT_SERVER;
2336
2447
  }
2448
+ clearPendingClaimToken(baseUrl);
2337
2449
  const payload = {
2338
2450
  ...hostStatusPayload ?? { host: baseUrl },
2339
2451
  status: data.status || "(unknown)",
@@ -2354,9 +2466,8 @@ async function handleStatus(args) {
2354
2466
  const apiKeyResp = String(data.api_key || apiKey);
2355
2467
  if (apiKeyResp) {
2356
2468
  saveEnvValues({
2357
- DEEPLINE_ORIGIN_URL: baseUrl,
2358
- DEEPLINE_API_KEY: apiKeyResp,
2359
- DEEPLINE_CLAIM_TOKEN: ""
2469
+ [HOST_URL_ENV]: baseUrl,
2470
+ [API_KEY_ENV]: apiKeyResp
2360
2471
  }, baseUrl);
2361
2472
  savedApiKeyPath = envFilePath(baseUrl);
2362
2473
  }
@@ -3285,10 +3396,12 @@ function writeCanonicalRowsCsv(rowsInfo, outPath) {
3285
3396
  rows: rowsInfo.rows,
3286
3397
  columns: rowsInfo.columns
3287
3398
  });
3399
+ const rows = dataExportRows(sanitized.rows);
3400
+ const columns = dataExportColumns(rows, sanitized.columns);
3288
3401
  const resolved = (0, import_node_path5.resolve)(outPath);
3289
3402
  (0, import_node_fs4.writeFileSync)(
3290
3403
  resolved,
3291
- csvStringFromRows(sanitized.rows, sanitized.columns),
3404
+ csvStringFromRows(rows, columns),
3292
3405
  "utf-8"
3293
3406
  );
3294
3407
  return resolved;
@@ -3409,6 +3522,14 @@ Examples:
3409
3522
  }
3410
3523
 
3411
3524
  // src/cli/commands/db.ts
3525
+ var import_node_fs5 = require("fs");
3526
+ var import_node_path6 = require("path");
3527
+ var CUSTOMER_DB_QUERY_FORMATS = /* @__PURE__ */ new Set([
3528
+ "table",
3529
+ "json",
3530
+ "csv",
3531
+ "markdown"
3532
+ ]);
3412
3533
  function parsePositiveInteger(value, flagName) {
3413
3534
  const parsed = Number.parseInt(value, 10);
3414
3535
  if (!Number.isFinite(parsed) || parsed <= 0) {
@@ -3422,10 +3543,8 @@ function formatCell(value) {
3422
3543
  return text.length > 80 ? `${text.slice(0, 77)}...` : text;
3423
3544
  }
3424
3545
  function tableLines(result) {
3425
- const rows = result.rows.filter(
3426
- (row) => Boolean(row) && typeof row === "object" && !Array.isArray(row)
3427
- );
3428
- const responseColumns = result.columns.length > 0 ? result.columns.map((column) => column.name) : [...new Set(rows.flatMap((row) => Object.keys(row)))];
3546
+ const rows = dataExportRows(customerDbRows(result));
3547
+ const responseColumns = dataExportColumns(rows, customerDbColumnNames(result));
3429
3548
  const businessColumns = responseColumns.filter((column) => !column.startsWith("_"));
3430
3549
  const columns = businessColumns.length > 0 ? businessColumns : responseColumns;
3431
3550
  const hiddenColumns = responseColumns.filter((column) => !columns.includes(column));
@@ -3459,22 +3578,146 @@ function tableLines(result) {
3459
3578
  }
3460
3579
  return lines;
3461
3580
  }
3581
+ function customerDbRows(result) {
3582
+ return recordRows(result.rows);
3583
+ }
3584
+ function customerDbColumnNames(result) {
3585
+ return result.columns.map((column) => column.name).filter(Boolean);
3586
+ }
3587
+ function writeCustomerDbCsv(result, outPath) {
3588
+ const resolved = (0, import_node_path6.resolve)(outPath);
3589
+ (0, import_node_fs5.writeFileSync)(
3590
+ resolved,
3591
+ dataExportCsvString(customerDbRows(result), customerDbColumnNames(result)),
3592
+ "utf-8"
3593
+ );
3594
+ return resolved;
3595
+ }
3596
+ function dbQueryExportEnvelope(input) {
3597
+ const destination = input.outPath ?? "stdout";
3598
+ return {
3599
+ command: input.result.command,
3600
+ format: input.format,
3601
+ row_count: input.result.row_count,
3602
+ row_count_returned: input.result.row_count_returned,
3603
+ truncated: input.result.truncated,
3604
+ ...input.outPath ? { file: input.outPath, local: { file: input.outPath } } : {},
3605
+ next: { toolEquivalent: input.toolCommand },
3606
+ render: {
3607
+ sections: [
3608
+ {
3609
+ title: "customer db export",
3610
+ lines: [
3611
+ `Rendered ${input.result.row_count_returned} row(s) as ${input.format} to ${destination}`
3612
+ ]
3613
+ }
3614
+ ],
3615
+ actions: [{ label: "Tool equivalent", command: input.toolCommand }]
3616
+ }
3617
+ };
3618
+ }
3462
3619
  async function handleDbQuery(args) {
3463
3620
  const sqlIndex = args.indexOf("--sql");
3464
3621
  const sql = sqlIndex >= 0 ? args[sqlIndex + 1]?.trim() : "";
3465
3622
  if (!sql) {
3466
- console.error('Usage: deepline db query --sql "select * from table limit 20" [--max-rows N] [--json]');
3623
+ console.error(
3624
+ 'Usage: deepline db query --sql "select * from table limit 20" [--max-rows N] [--json]'
3625
+ );
3467
3626
  return 1;
3468
3627
  }
3469
3628
  const maxRowsIndex = args.indexOf("--max-rows");
3470
3629
  const maxRows = maxRowsIndex >= 0 && args[maxRowsIndex + 1] ? parsePositiveInteger(args[maxRowsIndex + 1], "--max-rows") : void 0;
3630
+ const formatIndex = args.indexOf("--format");
3631
+ const format = formatIndex >= 0 ? args[formatIndex + 1]?.trim().toLowerCase() : "";
3632
+ if (format && !CUSTOMER_DB_QUERY_FORMATS.has(format)) {
3633
+ console.error(
3634
+ 'Usage: deepline db query --sql "select * from table limit 20" [--format table|json|csv|markdown] [--out path]'
3635
+ );
3636
+ return 1;
3637
+ }
3638
+ const outIndex = args.indexOf("--out");
3639
+ const outPath = outIndex >= 0 ? args[outIndex + 1]?.trim() : "";
3640
+ if (outIndex >= 0 && !outPath) {
3641
+ console.error("--out requires a path.");
3642
+ return 1;
3643
+ }
3471
3644
  const jsonOutput = argsWantJson(args);
3645
+ const explicitJsonOutput = args.includes("--json");
3472
3646
  const client = new DeeplineClient();
3473
3647
  const result = await client.queryCustomerDb({ sql, maxRows });
3474
3648
  const toolCommand = `deepline tools execute query_customer_db --payload ${JSON.stringify({
3475
3649
  sql,
3476
3650
  ...maxRows ? { max_rows: maxRows } : {}
3477
3651
  })} --json`;
3652
+ if (format === "csv") {
3653
+ if (outPath) {
3654
+ const exportedPath = writeCustomerDbCsv(result, outPath);
3655
+ printCommandEnvelope(
3656
+ dbQueryExportEnvelope({
3657
+ result,
3658
+ format,
3659
+ outPath: exportedPath,
3660
+ toolCommand
3661
+ }),
3662
+ {
3663
+ json: jsonOutput,
3664
+ text: `Exported ${result.row_count_returned} row(s) to ${exportedPath}
3665
+ `
3666
+ }
3667
+ );
3668
+ return 0;
3669
+ }
3670
+ printCommandEnvelope(
3671
+ dbQueryExportEnvelope({
3672
+ result,
3673
+ format,
3674
+ outPath: null,
3675
+ toolCommand
3676
+ }),
3677
+ {
3678
+ json: explicitJsonOutput,
3679
+ text: dataExportCsvString(customerDbRows(result), customerDbColumnNames(result))
3680
+ }
3681
+ );
3682
+ return 0;
3683
+ }
3684
+ if (format === "markdown") {
3685
+ const content = markdownTableFromRows(
3686
+ customerDbRows(result),
3687
+ customerDbColumnNames(result)
3688
+ );
3689
+ if (outPath) {
3690
+ const exportedPath = (0, import_node_path6.resolve)(outPath);
3691
+ (0, import_node_fs5.writeFileSync)(exportedPath, content, "utf-8");
3692
+ printCommandEnvelope(
3693
+ dbQueryExportEnvelope({
3694
+ result,
3695
+ format,
3696
+ outPath: exportedPath,
3697
+ toolCommand
3698
+ }),
3699
+ {
3700
+ json: jsonOutput,
3701
+ text: `Exported ${result.row_count_returned} row(s) to ${exportedPath}
3702
+ `
3703
+ }
3704
+ );
3705
+ return 0;
3706
+ }
3707
+ printCommandEnvelope(
3708
+ dbQueryExportEnvelope({
3709
+ result,
3710
+ format,
3711
+ outPath: null,
3712
+ toolCommand
3713
+ }),
3714
+ {
3715
+ json: explicitJsonOutput,
3716
+ text: content
3717
+ }
3718
+ );
3719
+ return 0;
3720
+ }
3478
3721
  printCommandEnvelope({
3479
3722
  ...result,
3480
3723
  next: { toolEquivalent: toolCommand },
@@ -3482,7 +3725,7 @@ async function handleDbQuery(args) {
3482
3725
  sections: [{ title: "customer db query", lines: tableLines(result) }],
3483
3726
  actions: [{ label: "Tool equivalent", command: toolCommand }]
3484
3727
  }
3485
- }, { json: jsonOutput });
3728
+ }, { json: jsonOutput || format === "json" });
3486
3729
  return 0;
3487
3730
  }
3488
3731
  function registerDbCommands(program) {
@@ -3492,11 +3735,14 @@ function registerDbCommands(program) {
3492
3735
  Notes:
3493
3736
  Runs SQL against the active workspace customer database through Deepline APIs.
3494
3737
  Results are bounded by the server and --max-rows. Use --json for stable output.
3738
+ Use --format csv or --format markdown for agent-readable exports and display tables.
3495
3739
 
3496
3740
  Examples:
3497
3741
  deepline db query --sql "select * from companies limit 20"
3498
3742
  deepline db query --sql "select domain, name from companies limit 20" --json
3499
3743
  deepline db query --sql "select * from contacts" --max-rows 100 --json
3744
+ deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
3745
+ deepline db query --sql "select domain, name from companies limit 20" --format markdown
3500
3746
  `
3501
3747
  );
3502
3748
  db.command("query").alias("psql").description("Run SQL against the tenant customer database.").addHelpText(
@@ -3505,17 +3751,23 @@ Examples:
3505
3751
  Notes:
3506
3752
  Requires --sql. Output is a compact table in a terminal and raw JSON with
3507
3753
  --json or when stdout is piped. The active auth workspace determines scope.
3754
+ --format csv and --format markdown are explicit data/display formats and can
3755
+ be written directly with --out.
3508
3756
 
3509
3757
  Examples:
3510
3758
  deepline db query --sql "select * from companies limit 20"
3511
3759
  deepline db query --sql "select domain, name from companies limit 20" --json
3512
3760
  deepline db psql --sql "select count(*) from contacts" --json
3761
+ deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
3762
+ deepline db query --sql "select domain, name from companies limit 20" --format markdown
3513
3763
  `
3514
- ).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) => {
3764
+ ).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) => {
3515
3765
  process.exitCode = await handleDbQuery([
3516
3766
  "--sql",
3517
3767
  options.sql,
3518
3768
  ...options.maxRows ? ["--max-rows", options.maxRows] : [],
3769
+ ...options.format ? ["--format", options.format] : [],
3770
+ ...options.out ? ["--out", options.out] : [],
3519
3771
  ...options.json ? ["--json"] : []
3520
3772
  ]);
3521
3773
  });
@@ -3691,21 +3943,21 @@ Examples:
3691
3943
 
3692
3944
  // src/cli/commands/play.ts
3693
3945
  var import_node_crypto3 = require("crypto");
3694
- var import_node_fs7 = require("fs");
3695
- var import_node_path9 = require("path");
3946
+ var import_node_fs8 = require("fs");
3947
+ var import_node_path10 = require("path");
3696
3948
 
3697
3949
  // src/plays/bundle-play-file.ts
3698
3950
  var import_node_os5 = require("os");
3699
- var import_node_path8 = require("path");
3951
+ var import_node_path9 = require("path");
3700
3952
  var import_node_url = require("url");
3701
- var import_node_fs6 = require("fs");
3953
+ var import_node_fs7 = require("fs");
3702
3954
 
3703
3955
  // ../shared_libs/plays/bundling/index.ts
3704
3956
  var import_node_crypto = require("crypto");
3705
- var import_node_fs5 = require("fs");
3957
+ var import_node_fs6 = require("fs");
3706
3958
  var import_promises3 = require("fs/promises");
3707
3959
  var import_node_os4 = require("os");
3708
- var import_node_path6 = require("path");
3960
+ var import_node_path7 = require("path");
3709
3961
  var import_node_module = require("module");
3710
3962
  var import_esbuild = require("esbuild");
3711
3963
 
@@ -3764,7 +4016,7 @@ function buildPlayContractCompatibility(input) {
3764
4016
  var PLAY_BUNDLE_CACHE_VERSION = 24;
3765
4017
  var MAX_PLAY_BUNDLE_BYTES = 30 * 1024 * 1024;
3766
4018
  var MAX_ESM_WORKERS_BUNDLE_BYTES = 115e4;
3767
- var PLAY_ARTIFACT_CACHE_DIR = (0, import_node_path6.join)(
4019
+ var PLAY_ARTIFACT_CACHE_DIR = (0, import_node_path7.join)(
3768
4020
  (0, import_node_os4.tmpdir)(),
3769
4021
  `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION}`
3770
4022
  );
@@ -3797,13 +4049,13 @@ async function normalizeLocalPath(filePath) {
3797
4049
  try {
3798
4050
  return await (0, import_promises3.realpath)(filePath);
3799
4051
  } catch {
3800
- return (0, import_node_path6.resolve)(filePath);
4052
+ return (0, import_node_path7.resolve)(filePath);
3801
4053
  }
3802
4054
  }
3803
4055
  function createPlayWorkspace(entryFile) {
3804
4056
  return {
3805
4057
  entryFile,
3806
- rootDir: (0, import_node_path6.dirname)(entryFile)
4058
+ rootDir: (0, import_node_path7.dirname)(entryFile)
3807
4059
  };
3808
4060
  }
3809
4061
  function isPathInsideDirectory(filePath, directory) {
@@ -3956,7 +4208,7 @@ function extractDefinedPlayName(sourceCode) {
3956
4208
  }
3957
4209
  function readPackageVersionFromPackageJson(packageJsonPath, packageName) {
3958
4210
  try {
3959
- const packageJson = JSON.parse((0, import_node_fs5.readFileSync)(packageJsonPath, "utf-8"));
4211
+ const packageJson = JSON.parse((0, import_node_fs6.readFileSync)(packageJsonPath, "utf-8"));
3960
4212
  if (packageJson.name === packageName && typeof packageJson.version === "string") {
3961
4213
  return packageJson.version;
3962
4214
  }
@@ -3966,18 +4218,18 @@ function readPackageVersionFromPackageJson(packageJsonPath, packageName) {
3966
4218
  return null;
3967
4219
  }
3968
4220
  function findPackageJsonPathFrom(startDir, packageName) {
3969
- let current = (0, import_node_path6.resolve)(startDir);
4221
+ let current = (0, import_node_path7.resolve)(startDir);
3970
4222
  while (true) {
3971
- const packageJsonPath = (0, import_node_path6.join)(
4223
+ const packageJsonPath = (0, import_node_path7.join)(
3972
4224
  current,
3973
4225
  "node_modules",
3974
4226
  packageName,
3975
4227
  "package.json"
3976
4228
  );
3977
- if ((0, import_node_fs5.existsSync)(packageJsonPath)) {
4229
+ if ((0, import_node_fs6.existsSync)(packageJsonPath)) {
3978
4230
  return packageJsonPath;
3979
4231
  }
3980
- const parent = (0, import_node_path6.dirname)(current);
4232
+ const parent = (0, import_node_path7.dirname)(current);
3981
4233
  if (parent === current) {
3982
4234
  return null;
3983
4235
  }
@@ -3986,29 +4238,29 @@ function findPackageJsonPathFrom(startDir, packageName) {
3986
4238
  }
3987
4239
  function findPackageJsonPath(packageName, fromFile, adapter) {
3988
4240
  const startDirs = [
3989
- (0, import_node_path6.dirname)(fromFile),
4241
+ (0, import_node_path7.dirname)(fromFile),
3990
4242
  adapter.projectRoot,
3991
- (0, import_node_path6.dirname)(adapter.sdkPackageJson),
4243
+ (0, import_node_path7.dirname)(adapter.sdkPackageJson),
3992
4244
  process.cwd()
3993
4245
  ];
3994
4246
  const seen = /* @__PURE__ */ new Set();
3995
4247
  for (const startDir of startDirs) {
3996
- const normalized = (0, import_node_path6.resolve)(startDir);
4248
+ const normalized = (0, import_node_path7.resolve)(startDir);
3997
4249
  if (seen.has(normalized)) continue;
3998
4250
  seen.add(normalized);
3999
4251
  const packageJsonPath = findPackageJsonPathFrom(normalized, packageName);
4000
4252
  if (packageJsonPath) return packageJsonPath;
4001
4253
  }
4002
- const adapterNodeModulesPackageJson = (0, import_node_path6.join)(
4254
+ const adapterNodeModulesPackageJson = (0, import_node_path7.join)(
4003
4255
  adapter.nodeModulesDir,
4004
4256
  packageName,
4005
4257
  "package.json"
4006
4258
  );
4007
- return (0, import_node_fs5.existsSync)(adapterNodeModulesPackageJson) ? adapterNodeModulesPackageJson : null;
4259
+ return (0, import_node_fs6.existsSync)(adapterNodeModulesPackageJson) ? adapterNodeModulesPackageJson : null;
4008
4260
  }
4009
4261
  function localSdkAliasPlugin(adapter, options) {
4010
4262
  const entryFile = options?.workersRuntime ? adapter.sdkWorkersEntryFile : adapter.sdkEntryFile;
4011
- if (!(0, import_node_fs5.existsSync)(entryFile)) {
4263
+ if (!(0, import_node_fs6.existsSync)(entryFile)) {
4012
4264
  return null;
4013
4265
  }
4014
4266
  return {
@@ -4048,7 +4300,7 @@ function workersNamedPlayEntryAliasPlugin(playFilePath, exportName) {
4048
4300
  contents: `export { ${exportName} as default } from ${JSON.stringify(playFilePath)};
4049
4301
  `,
4050
4302
  loader: "ts",
4051
- resolveDir: (0, import_node_path6.dirname)(playFilePath)
4303
+ resolveDir: (0, import_node_path7.dirname)(playFilePath)
4052
4304
  })
4053
4305
  );
4054
4306
  }
@@ -4212,7 +4464,7 @@ function importedPlayProxyPlugin(importedPlayDependencies) {
4212
4464
  return {
4213
4465
  contents: buildImportedPlayProxyModule(dependency.playName),
4214
4466
  loader: "ts",
4215
- resolveDir: (0, import_node_path6.dirname)(args.path)
4467
+ resolveDir: (0, import_node_path7.dirname)(args.path)
4216
4468
  };
4217
4469
  });
4218
4470
  }
@@ -4230,12 +4482,12 @@ async function resolveLocalImport(fromFile, specifier) {
4230
4482
  if (specifier.startsWith("file:")) {
4231
4483
  return normalizeLocalPath(new URL(specifier).pathname);
4232
4484
  }
4233
- const base = (0, import_node_path6.isAbsolute)(specifier) ? (0, import_node_path6.resolve)(specifier) : (0, import_node_path6.resolve)((0, import_node_path6.dirname)(fromFile), specifier);
4485
+ const base = (0, import_node_path7.isAbsolute)(specifier) ? (0, import_node_path7.resolve)(specifier) : (0, import_node_path7.resolve)((0, import_node_path7.dirname)(fromFile), specifier);
4234
4486
  const candidates = [base];
4235
- const explicitExtension = (0, import_node_path6.extname)(base).toLowerCase();
4487
+ const explicitExtension = (0, import_node_path7.extname)(base).toLowerCase();
4236
4488
  if (!explicitExtension) {
4237
4489
  candidates.push(...SOURCE_EXTENSIONS.map((extension) => `${base}${extension}`));
4238
- candidates.push(...SOURCE_EXTENSIONS.map((extension) => (0, import_node_path6.join)(base, `index${extension}`)));
4490
+ candidates.push(...SOURCE_EXTENSIONS.map((extension) => (0, import_node_path7.join)(base, `index${extension}`)));
4239
4491
  } else if ([".js", ".jsx", ".mjs", ".cjs"].includes(explicitExtension)) {
4240
4492
  const stem = base.slice(0, -explicitExtension.length);
4241
4493
  candidates.push(...SOURCE_EXTENSIONS.map((extension) => `${stem}${extension}`));
@@ -4249,9 +4501,9 @@ async function resolveLocalImport(fromFile, specifier) {
4249
4501
  }
4250
4502
  function resolvePackageImport(specifier, fromFile, adapter) {
4251
4503
  const packageName = getPackageName(specifier);
4252
- if (packageName === "deepline" && (0, import_node_fs5.existsSync)(adapter.sdkPackageJson)) {
4504
+ if (packageName === "deepline" && (0, import_node_fs6.existsSync)(adapter.sdkPackageJson)) {
4253
4505
  const packageJson = JSON.parse(
4254
- (0, import_node_fs5.readFileSync)(adapter.sdkPackageJson, "utf-8")
4506
+ (0, import_node_fs6.readFileSync)(adapter.sdkPackageJson, "utf-8")
4255
4507
  );
4256
4508
  return {
4257
4509
  name: "deepline",
@@ -4283,7 +4535,7 @@ async function analyzeSourceGraph(entryFile, adapter) {
4283
4535
  visited.add(absolutePath);
4284
4536
  const sourceCode2 = await (0, import_promises3.readFile)(absolutePath, "utf-8");
4285
4537
  localFiles.set(absolutePath, sourceCode2);
4286
- if ((0, import_node_path6.extname)(absolutePath).toLowerCase() === ".json") {
4538
+ if ((0, import_node_path7.extname)(absolutePath).toLowerCase() === ".json") {
4287
4539
  return;
4288
4540
  }
4289
4541
  const handleSpecifier = async (specifier, line, column, kind) => {
@@ -4387,13 +4639,13 @@ async function computeWorkersHarnessFingerprintWithAdapter(adapter) {
4387
4639
  const tsFiles = entries.filter((e) => e.isFile() && /\.[cm]?ts$/.test(e.name)).map((e) => e.name).sort();
4388
4640
  const parts = [];
4389
4641
  for (const name of tsFiles) {
4390
- const contents = await (0, import_promises3.readFile)((0, import_node_path6.join)(adapter.workersHarnessFilesDir, name), "utf-8");
4642
+ const contents = await (0, import_promises3.readFile)((0, import_node_path7.join)(adapter.workersHarnessFilesDir, name), "utf-8");
4391
4643
  parts.push({ name, hash: sha256(contents) });
4392
4644
  }
4393
4645
  return sha256(JSON.stringify(parts));
4394
4646
  }
4395
4647
  function artifactCachePath(graphHash, artifactKind, adapter) {
4396
- return (0, import_node_path6.join)(
4648
+ return (0, import_node_path7.join)(
4397
4649
  adapter.cacheDir ?? PLAY_ARTIFACT_CACHE_DIR,
4398
4650
  `${graphHash}.${artifactKind}.json`
4399
4651
  );
@@ -4428,7 +4680,7 @@ function normalizeSourceMapForRuntime(sourceMapText) {
4428
4680
  if (sourcePath.startsWith("data:") || sourcePath.startsWith("node:") || sourcePath.startsWith("/") || /^[a-zA-Z]+:\/\//.test(sourcePath)) {
4429
4681
  return sourcePath;
4430
4682
  }
4431
- return (0, import_node_path6.resolve)(process.cwd(), sourcePath);
4683
+ return (0, import_node_path7.resolve)(process.cwd(), sourcePath);
4432
4684
  });
4433
4685
  parsed.sourceRoot = void 0;
4434
4686
  return JSON.stringify(parsed);
@@ -4460,8 +4712,8 @@ async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter
4460
4712
  ...namedExportShim ? {
4461
4713
  stdin: {
4462
4714
  contents: namedExportShim,
4463
- resolveDir: (0, import_node_path6.dirname)(entryFile),
4464
- sourcefile: `${(0, import_node_path6.basename)(entryFile)}.${exportName}.entry.ts`,
4715
+ resolveDir: (0, import_node_path7.dirname)(entryFile),
4716
+ sourcefile: `${(0, import_node_path7.basename)(entryFile)}.${exportName}.entry.ts`,
4465
4717
  loader: "ts"
4466
4718
  }
4467
4719
  } : { entryPoints: [entryFile] },
@@ -4637,10 +4889,10 @@ workers-harness:${harnessFingerprint}`
4637
4889
  }
4638
4890
  const { bundledCode, sourceMapText, outputExtension } = buildOutcome;
4639
4891
  const normalizedSourceMap = normalizeSourceMapForRuntime(sourceMapText);
4640
- const virtualBaseName = exportName === "default" ? (0, import_node_path6.basename)(absolutePath).replace(/\.[^.]+$/, "") : `${(0, import_node_path6.basename)(absolutePath).replace(/\.[^.]+$/, "")}.${exportName}`;
4892
+ const virtualBaseName = exportName === "default" ? (0, import_node_path7.basename)(absolutePath).replace(/\.[^.]+$/, "") : `${(0, import_node_path7.basename)(absolutePath).replace(/\.[^.]+$/, "")}.${exportName}`;
4641
4893
  const virtualFilename = `/virtual/deepline-plays/${analysis.graphHash}/${virtualBaseName}.${outputExtension}`;
4642
4894
  const executableCode = `${bundledCode}
4643
- //# sourceMappingURL=${(0, import_node_path6.basename)(virtualFilename)}.map
4895
+ //# sourceMappingURL=${(0, import_node_path7.basename)(virtualFilename)}.map
4644
4896
  `;
4645
4897
  const bundleSizeError = getBundleSizeError(
4646
4898
  absolutePath,
@@ -4750,13 +5002,13 @@ function resolveExecutionProfile(override) {
4750
5002
  // src/plays/local-file-discovery.ts
4751
5003
  var import_node_crypto2 = require("crypto");
4752
5004
  var import_promises4 = require("fs/promises");
4753
- var import_node_path7 = require("path");
5005
+ var import_node_path8 = require("path");
4754
5006
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
4755
5007
  function sha2562(buffer) {
4756
5008
  return (0, import_node_crypto2.createHash)("sha256").update(buffer).digest("hex");
4757
5009
  }
4758
5010
  function contentTypeForFile(filePath) {
4759
- const extension = (0, import_node_path7.extname)(filePath).toLowerCase();
5011
+ const extension = (0, import_node_path8.extname)(filePath).toLowerCase();
4760
5012
  if (extension === ".csv") return "text/csv";
4761
5013
  if (extension === ".json") return "application/json";
4762
5014
  if (extension === ".txt") return "text/plain";
@@ -4947,16 +5199,16 @@ async function fileExists2(filePath) {
4947
5199
  }
4948
5200
  }
4949
5201
  function isPathInsideDirectory2(filePath, directory) {
4950
- const relativePath = (0, import_node_path7.relative)(directory, filePath);
4951
- return relativePath === "" || !relativePath.startsWith("..") && !(0, import_node_path7.isAbsolute)(relativePath);
5202
+ const relativePath = (0, import_node_path8.relative)(directory, filePath);
5203
+ return relativePath === "" || !relativePath.startsWith("..") && !(0, import_node_path8.isAbsolute)(relativePath);
4952
5204
  }
4953
5205
  async function resolveLocalImport2(fromFile, specifier) {
4954
- const base = (0, import_node_path7.isAbsolute)(specifier) ? (0, import_node_path7.resolve)(specifier) : (0, import_node_path7.resolve)((0, import_node_path7.dirname)(fromFile), specifier);
5206
+ const base = (0, import_node_path8.isAbsolute)(specifier) ? (0, import_node_path8.resolve)(specifier) : (0, import_node_path8.resolve)((0, import_node_path8.dirname)(fromFile), specifier);
4955
5207
  const candidates = [base];
4956
- const explicitExtension = (0, import_node_path7.extname)(base).toLowerCase();
5208
+ const explicitExtension = (0, import_node_path8.extname)(base).toLowerCase();
4957
5209
  if (!explicitExtension) {
4958
5210
  candidates.push(...SOURCE_EXTENSIONS2.map((extension) => `${base}${extension}`));
4959
- candidates.push(...SOURCE_EXTENSIONS2.map((extension) => (0, import_node_path7.join)(base, `index${extension}`)));
5211
+ candidates.push(...SOURCE_EXTENSIONS2.map((extension) => (0, import_node_path8.join)(base, `index${extension}`)));
4960
5212
  } else if ([".js", ".jsx", ".mjs", ".cjs"].includes(explicitExtension)) {
4961
5213
  const stem = base.slice(0, -explicitExtension.length);
4962
5214
  candidates.push(...SOURCE_EXTENSIONS2.map((extension) => `${stem}${extension}`));
@@ -4969,13 +5221,13 @@ async function resolveLocalImport2(fromFile, specifier) {
4969
5221
  throw new Error(`Could not resolve local import "${specifier}" from ${fromFile}`);
4970
5222
  }
4971
5223
  async function discoverPackagedLocalFiles(entryFile) {
4972
- const absoluteEntryFile = (0, import_node_path7.resolve)(entryFile);
4973
- const packagingRoot = (0, import_node_path7.dirname)(absoluteEntryFile);
5224
+ const absoluteEntryFile = (0, import_node_path8.resolve)(entryFile);
5225
+ const packagingRoot = (0, import_node_path8.dirname)(absoluteEntryFile);
4974
5226
  const files = /* @__PURE__ */ new Map();
4975
5227
  const unresolved = [];
4976
5228
  const visitedFiles = /* @__PURE__ */ new Set();
4977
5229
  const visitSourceFile = async (filePath) => {
4978
- const absolutePath = (0, import_node_path7.resolve)(filePath);
5230
+ const absolutePath = (0, import_node_path8.resolve)(filePath);
4979
5231
  if (visitedFiles.has(absolutePath)) {
4980
5232
  return;
4981
5233
  }
@@ -5007,8 +5259,8 @@ async function discoverPackagedLocalFiles(entryFile) {
5007
5259
  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."
5008
5260
  });
5009
5261
  } else {
5010
- const absoluteCsvPath = (0, import_node_path7.resolve)((0, import_node_path7.dirname)(absolutePath), resolvedPath);
5011
- if ((0, import_node_path7.isAbsolute)(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
5262
+ const absoluteCsvPath = (0, import_node_path8.resolve)((0, import_node_path8.dirname)(absolutePath), resolvedPath);
5263
+ if ((0, import_node_path8.isAbsolute)(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
5012
5264
  unresolved.push({
5013
5265
  sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
5014
5266
  message: "ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead."
@@ -5047,24 +5299,24 @@ async function discoverPackagedLocalFiles(entryFile) {
5047
5299
  // src/plays/bundle-play-file.ts
5048
5300
  var import_meta = {};
5049
5301
  var PLAY_BUNDLE_CACHE_VERSION2 = 30;
5050
- var MODULE_DIR = (0, import_node_path8.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
5051
- var SDK_PACKAGE_ROOT = (0, import_node_path8.resolve)(MODULE_DIR, "..", "..");
5052
- var SOURCE_REPO_ROOT = (0, import_node_path8.resolve)(SDK_PACKAGE_ROOT, "..");
5053
- var HAS_SOURCE_BUNDLING_SOURCES = (0, import_node_fs6.existsSync)(
5054
- (0, import_node_path8.resolve)(SOURCE_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5302
+ var MODULE_DIR = (0, import_node_path9.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
5303
+ var SDK_PACKAGE_ROOT = (0, import_node_path9.resolve)(MODULE_DIR, "..", "..");
5304
+ var SOURCE_REPO_ROOT = (0, import_node_path9.resolve)(SDK_PACKAGE_ROOT, "..");
5305
+ var HAS_SOURCE_BUNDLING_SOURCES = (0, import_node_fs7.existsSync)(
5306
+ (0, import_node_path9.resolve)(SOURCE_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5055
5307
  );
5056
- var PACKAGED_REPO_ROOT = (0, import_node_path8.resolve)(SDK_PACKAGE_ROOT, "dist", "repo");
5057
- var HAS_PACKAGED_BUNDLING_SOURCES = (0, import_node_fs6.existsSync)(
5058
- (0, import_node_path8.resolve)(PACKAGED_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5308
+ var PACKAGED_REPO_ROOT = (0, import_node_path9.resolve)(SDK_PACKAGE_ROOT, "dist", "repo");
5309
+ var HAS_PACKAGED_BUNDLING_SOURCES = (0, import_node_fs7.existsSync)(
5310
+ (0, import_node_path9.resolve)(PACKAGED_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
5059
5311
  );
5060
- var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED_BUNDLING_SOURCES ? PACKAGED_REPO_ROOT : (0, import_node_path8.resolve)(SDK_PACKAGE_ROOT, "..");
5061
- var SDK_SOURCE_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? (0, import_node_path8.resolve)(SOURCE_REPO_ROOT, "sdk", "src") : HAS_PACKAGED_BUNDLING_SOURCES ? (0, import_node_path8.resolve)(PACKAGED_REPO_ROOT, "sdk", "src") : (0, import_node_path8.resolve)(SDK_PACKAGE_ROOT, "src");
5062
- var SDK_PACKAGE_JSON = (0, import_node_path8.resolve)(SDK_PACKAGE_ROOT, "package.json");
5063
- var SDK_ENTRY_FILE = (0, import_node_path8.resolve)(SDK_SOURCE_ROOT, "index.ts");
5064
- var SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : (0, import_node_path8.resolve)(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
5065
- var SDK_WORKERS_ENTRY_FILE = (0, import_node_path8.resolve)(SDK_SOURCE_ROOT, "worker-play-entry.ts");
5066
- var WORKERS_HARNESS_ENTRY_FILE = (0, import_node_path8.resolve)(PROJECT_ROOT, "apps", "play-runner-workers", "src", "entry.ts");
5067
- var WORKERS_HARNESS_FILES_DIR = (0, import_node_path8.resolve)(PROJECT_ROOT, "apps", "play-runner-workers", "src");
5312
+ var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED_BUNDLING_SOURCES ? PACKAGED_REPO_ROOT : (0, import_node_path9.resolve)(SDK_PACKAGE_ROOT, "..");
5313
+ var SDK_SOURCE_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? (0, import_node_path9.resolve)(SOURCE_REPO_ROOT, "sdk", "src") : HAS_PACKAGED_BUNDLING_SOURCES ? (0, import_node_path9.resolve)(PACKAGED_REPO_ROOT, "sdk", "src") : (0, import_node_path9.resolve)(SDK_PACKAGE_ROOT, "src");
5314
+ var SDK_PACKAGE_JSON = (0, import_node_path9.resolve)(SDK_PACKAGE_ROOT, "package.json");
5315
+ var SDK_ENTRY_FILE = (0, import_node_path9.resolve)(SDK_SOURCE_ROOT, "index.ts");
5316
+ var SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : (0, import_node_path9.resolve)(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
5317
+ var SDK_WORKERS_ENTRY_FILE = (0, import_node_path9.resolve)(SDK_SOURCE_ROOT, "worker-play-entry.ts");
5318
+ var WORKERS_HARNESS_ENTRY_FILE = (0, import_node_path9.resolve)(PROJECT_ROOT, "apps", "play-runner-workers", "src", "entry.ts");
5319
+ var WORKERS_HARNESS_FILES_DIR = (0, import_node_path9.resolve)(PROJECT_ROOT, "apps", "play-runner-workers", "src");
5068
5320
  var hasWarnedAboutNonDevelopmentBundling = false;
5069
5321
  function warnAboutNonDevelopmentBundling(filePath) {
5070
5322
  if (hasWarnedAboutNonDevelopmentBundling) {
@@ -5088,12 +5340,12 @@ function defaultPlayBundleTarget() {
5088
5340
  function createSdkPlayBundlingAdapter() {
5089
5341
  return {
5090
5342
  projectRoot: PROJECT_ROOT,
5091
- nodeModulesDir: (0, import_node_path8.resolve)(PROJECT_ROOT, "node_modules"),
5092
- cacheDir: (0, import_node_path8.join)((0, import_node_os5.tmpdir)(), `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION2}`),
5343
+ nodeModulesDir: (0, import_node_path9.resolve)(PROJECT_ROOT, "node_modules"),
5344
+ cacheDir: (0, import_node_path9.join)((0, import_node_os5.tmpdir)(), `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION2}`),
5093
5345
  sdkSourceRoot: SDK_SOURCE_ROOT,
5094
5346
  sdkPackageJson: SDK_PACKAGE_JSON,
5095
5347
  sdkEntryFile: SDK_ENTRY_FILE,
5096
- sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !(0, import_node_fs6.existsSync)(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
5348
+ sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !(0, import_node_fs7.existsSync)(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
5097
5349
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
5098
5350
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
5099
5351
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
@@ -5326,7 +5578,7 @@ function traceCliSync(phase, fields, run) {
5326
5578
  }
5327
5579
  }
5328
5580
  function sleep4(ms) {
5329
- return new Promise((resolve10) => setTimeout(resolve10, ms));
5581
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
5330
5582
  }
5331
5583
  function parseReferencedPlayTarget(target) {
5332
5584
  const trimmed = target.trim();
@@ -5372,7 +5624,7 @@ function formatPlayListReference(play) {
5372
5624
  function defaultMaterializedPlayPath(reference) {
5373
5625
  const playName = parseReferencedPlayTarget(reference).unqualifiedPlayName;
5374
5626
  const safeName = playName.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
5375
- return (0, import_node_path9.resolve)(`${safeName || "play"}.play.ts`);
5627
+ return (0, import_node_path10.resolve)(`${safeName || "play"}.play.ts`);
5376
5628
  }
5377
5629
  function materializeRemotePlaySource(input) {
5378
5630
  if (isFileTarget(input.target)) {
@@ -5382,15 +5634,15 @@ function materializeRemotePlaySource(input) {
5382
5634
  return null;
5383
5635
  }
5384
5636
  const outputPath = input.outPath ?? defaultMaterializedPlayPath(input.playName);
5385
- if ((0, import_node_fs7.existsSync)(outputPath)) {
5386
- const existingSource = (0, import_node_fs7.readFileSync)(outputPath, "utf-8");
5637
+ if ((0, import_node_fs8.existsSync)(outputPath)) {
5638
+ const existingSource = (0, import_node_fs8.readFileSync)(outputPath, "utf-8");
5387
5639
  if (existingSource === input.sourceCode) {
5388
5640
  return { path: outputPath, status: "unchanged", created: false };
5389
5641
  }
5390
- (0, import_node_fs7.writeFileSync)(outputPath, input.sourceCode, "utf-8");
5642
+ (0, import_node_fs8.writeFileSync)(outputPath, input.sourceCode, "utf-8");
5391
5643
  return { path: outputPath, status: "updated", created: false };
5392
5644
  }
5393
- (0, import_node_fs7.writeFileSync)(outputPath, input.sourceCode, "utf-8");
5645
+ (0, import_node_fs8.writeFileSync)(outputPath, input.sourceCode, "utf-8");
5394
5646
  return { path: outputPath, status: "created", created: true };
5395
5647
  }
5396
5648
  function formatLoadedPlayMessage(materializedFile) {
@@ -5435,7 +5687,7 @@ function extractPlayName(code, filePath) {
5435
5687
  throw buildMissingDefinePlayError(filePath);
5436
5688
  }
5437
5689
  function isFileTarget(target) {
5438
- return (0, import_node_fs7.existsSync)((0, import_node_path9.resolve)(target));
5690
+ return (0, import_node_fs8.existsSync)((0, import_node_path10.resolve)(target));
5439
5691
  }
5440
5692
  function looksLikeFilePath(target) {
5441
5693
  if (target.trim().toLowerCase().startsWith("prebuilt/")) {
@@ -5454,7 +5706,7 @@ function parsePositiveInteger2(value, flagName) {
5454
5706
  return parsed;
5455
5707
  }
5456
5708
  function parseJsonInput(raw) {
5457
- const source = raw.startsWith("@") ? (0, import_node_fs7.readFileSync)((0, import_node_path9.resolve)(raw.slice(1)), "utf-8") : raw;
5709
+ const source = raw.startsWith("@") ? (0, import_node_fs8.readFileSync)((0, import_node_path10.resolve)(raw.slice(1)), "utf-8") : raw;
5458
5710
  const parsed = JSON.parse(source);
5459
5711
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
5460
5712
  throw new Error("--input must be a JSON object.");
@@ -5555,7 +5807,7 @@ function fileInputBindingsFromStaticPipeline(staticPipeline) {
5555
5807
  function isLocalFilePathValue(value) {
5556
5808
  if (typeof value !== "string" || !value.trim()) return false;
5557
5809
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
5558
- return (0, import_node_fs7.existsSync)((0, import_node_path9.resolve)(value));
5810
+ return (0, import_node_fs8.existsSync)((0, import_node_path10.resolve)(value));
5559
5811
  }
5560
5812
  function inputContainsLocalFilePath(value) {
5561
5813
  if (isLocalFilePathValue(value)) {
@@ -5581,8 +5833,8 @@ async function stageFileInputArgs(input) {
5581
5833
  const localFiles = uniqueBindings.flatMap((binding) => {
5582
5834
  const value = getDottedInputValue(input.runtimeInput, binding.inputPath);
5583
5835
  if (!isLocalFilePathValue(value)) return [];
5584
- const absolutePath = (0, import_node_path9.resolve)(value);
5585
- return [{ binding, absolutePath, logicalPath: (0, import_node_path9.basename)(absolutePath) }];
5836
+ const absolutePath = (0, import_node_path10.resolve)(value);
5837
+ return [{ binding, absolutePath, logicalPath: (0, import_node_path10.basename)(absolutePath) }];
5586
5838
  });
5587
5839
  if (localFiles.length === 0) {
5588
5840
  return { inputFile: null, packagedFiles: [] };
@@ -5606,7 +5858,7 @@ async function stageFileInputArgs(input) {
5606
5858
  };
5607
5859
  }
5608
5860
  function stageFile(logicalPath, absolutePath) {
5609
- const buffer = (0, import_node_fs7.readFileSync)(absolutePath);
5861
+ const buffer = (0, import_node_fs8.readFileSync)(absolutePath);
5610
5862
  return {
5611
5863
  logicalPath,
5612
5864
  contentBase64: buffer.toString("base64"),
@@ -5617,9 +5869,9 @@ function stageFile(logicalPath, absolutePath) {
5617
5869
  }
5618
5870
  function normalizePlayPath(filePath) {
5619
5871
  try {
5620
- return import_node_fs7.realpathSync.native((0, import_node_path9.resolve)(filePath));
5872
+ return import_node_fs8.realpathSync.native((0, import_node_path10.resolve)(filePath));
5621
5873
  } catch {
5622
- return (0, import_node_path9.resolve)(filePath);
5874
+ return (0, import_node_path10.resolve)(filePath);
5623
5875
  }
5624
5876
  }
5625
5877
  function formatBundlingErrors(filePath, errors) {
@@ -5772,11 +6024,42 @@ function isTransientPlayStreamError(error) {
5772
6024
  text
5773
6025
  );
5774
6026
  }
6027
+ function playStatusErrorText(status) {
6028
+ const chunks = [];
6029
+ const progressError = status.progress?.error;
6030
+ if (typeof progressError === "string" && progressError.trim()) {
6031
+ chunks.push(progressError.trim());
6032
+ }
6033
+ const errorValue = status.error;
6034
+ if (typeof errorValue === "string" && errorValue.trim()) {
6035
+ chunks.push(errorValue.trim());
6036
+ }
6037
+ const errors = status.errors;
6038
+ if (Array.isArray(errors)) {
6039
+ for (const error of errors) {
6040
+ if (typeof error === "string" && error.trim()) {
6041
+ chunks.push(error.trim());
6042
+ } else if (error && typeof error === "object") {
6043
+ const message = error.message;
6044
+ if (typeof message === "string" && message.trim()) {
6045
+ chunks.push(message.trim());
6046
+ }
6047
+ }
6048
+ }
6049
+ }
6050
+ return chunks.join("; ") || status.status;
6051
+ }
6052
+ function isRetryablePendingStartFailure(status) {
6053
+ if (status.status !== "failed") return false;
6054
+ if (status.runId && status.runId !== "pending") return false;
6055
+ return isTransientPlayStreamError(new Error(playStatusErrorText(status)));
6056
+ }
5775
6057
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
5776
6058
  "completed",
5777
6059
  "failed",
5778
6060
  "cancelled"
5779
6061
  ]);
6062
+ var PLAY_START_TRANSIENT_RETRY_DELAYS_MS = [500, 1500];
5780
6063
  function getEventPayload(event) {
5781
6064
  return event.payload && typeof event.payload === "object" ? event.payload : {};
5782
6065
  }
@@ -5843,10 +6126,6 @@ function openPlayDashboard(input) {
5843
6126
  }
5844
6127
  openInBrowser(input.dashboardUrl);
5845
6128
  }
5846
- function getDashboardUrlFromLiveEvent(event) {
5847
- const dashboardUrl = getEventPayload(event).dashboardUrl;
5848
- return typeof dashboardUrl === "string" && dashboardUrl.trim() ? dashboardUrl.trim() : null;
5849
- }
5850
6129
  function printPlayLogLines(input) {
5851
6130
  for (const line of input.lines) {
5852
6131
  if (input.emitLogs) {
@@ -5915,7 +6194,7 @@ async function waitForPlayCompletionByStream(input) {
5915
6194
  billing: false
5916
6195
  });
5917
6196
  if (TERMINAL_PLAY_STATUSES2.has(finalStatus.status)) {
5918
- return finalStatus;
6197
+ return input.dashboardUrl ? { ...finalStatus, dashboardUrl: input.dashboardUrl } : finalStatus;
5919
6198
  }
5920
6199
  }
5921
6200
  }
@@ -5942,7 +6221,39 @@ async function waitForPlayCompletionByStream(input) {
5942
6221
  );
5943
6222
  }
5944
6223
  async function startAndWaitForPlayCompletionByStream(input) {
6224
+ for (let attempt = 0; attempt <= PLAY_START_TRANSIENT_RETRY_DELAYS_MS.length; attempt += 1) {
6225
+ const status = await startAndWaitForPlayCompletionByStreamOnce(input);
6226
+ const retryDelayMs = PLAY_START_TRANSIENT_RETRY_DELAYS_MS[attempt];
6227
+ if (retryDelayMs === void 0 || !isRetryablePendingStartFailure(status)) {
6228
+ return status;
6229
+ }
6230
+ if (!input.jsonOutput) {
6231
+ input.progress.writeLine(
6232
+ `[play watch] start failed before run id with a transient error; retrying (${playStatusErrorText(status)})`
6233
+ );
6234
+ }
6235
+ recordCliTrace({
6236
+ phase: "cli.play_start_stream_retry",
6237
+ ms: 0,
6238
+ ok: true,
6239
+ playName: input.playName,
6240
+ attempt: attempt + 1,
6241
+ reason: playStatusErrorText(status)
6242
+ });
6243
+ await sleep4(retryDelayMs);
6244
+ }
6245
+ throw new DeeplineError(
6246
+ `Play ${input.playName} did not start after retrying transient start failures.`,
6247
+ void 0,
6248
+ "PLAY_START_RETRY_EXHAUSTED"
6249
+ );
6250
+ }
6251
+ async function startAndWaitForPlayCompletionByStreamOnce(input) {
5945
6252
  const startedAt = Date.now();
6253
+ const dashboardUrl = buildPlayDashboardUrl(
6254
+ input.client.baseUrl,
6255
+ input.playName
6256
+ );
5946
6257
  const state = {
5947
6258
  lastLogIndex: 0,
5948
6259
  emittedRunnerStarted: false
@@ -5973,7 +6284,6 @@ async function startAndWaitForPlayCompletionByStream(input) {
5973
6284
  }
5974
6285
  const workflowId = lastKnownWorkflowId || "pending";
5975
6286
  if (workflowId !== "pending" && !emittedDashboardUrl) {
5976
- const dashboardUrl = getDashboardUrlFromLiveEvent(event) ?? buildPlayDashboardUrl(input.client.baseUrl, input.playName);
5977
6287
  openPlayDashboard({
5978
6288
  dashboardUrl,
5979
6289
  jsonOutput: input.jsonOutput,
@@ -6022,7 +6332,7 @@ async function startAndWaitForPlayCompletionByStream(input) {
6022
6332
  firstRunIdMs,
6023
6333
  lastPhase
6024
6334
  });
6025
- return finalStatus;
6335
+ return { ...finalStatus, dashboardUrl };
6026
6336
  }
6027
6337
  }
6028
6338
  } catch (error) {
@@ -6059,6 +6369,7 @@ async function startAndWaitForPlayCompletionByStream(input) {
6059
6369
  return waitForPlayCompletionByStream({
6060
6370
  client: input.client,
6061
6371
  workflowId: lastKnownWorkflowId,
6372
+ dashboardUrl,
6062
6373
  jsonOutput: input.jsonOutput,
6063
6374
  emitLogs: input.emitLogs,
6064
6375
  waitTimeoutMs: input.waitTimeoutMs,
@@ -6093,6 +6404,7 @@ async function startAndWaitForPlayCompletionByStream(input) {
6093
6404
  return waitForPlayCompletionByStream({
6094
6405
  client: input.client,
6095
6406
  workflowId: lastKnownWorkflowId,
6407
+ dashboardUrl,
6096
6408
  jsonOutput: input.jsonOutput,
6097
6409
  emitLogs: input.emitLogs,
6098
6410
  waitTimeoutMs: input.waitTimeoutMs,
@@ -6263,12 +6575,25 @@ function buildRunWarnings(status, rowsInfo) {
6263
6575
  }
6264
6576
  return [];
6265
6577
  }
6266
- function buildRunNextCommands(runId) {
6267
- return {
6578
+ function buildRunNextCommands(status) {
6579
+ const runId = status.runId?.trim();
6580
+ if (!runId) {
6581
+ const playName = extractRunPlayName(status);
6582
+ return playName ? {
6583
+ list: `deepline runs list --play ${playName} --json`
6584
+ } : {
6585
+ list: "deepline runs list --json"
6586
+ };
6587
+ }
6588
+ const commands = {
6268
6589
  get: `deepline runs get ${runId} --json`,
6269
6590
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
6270
6591
  logs: `deepline runs logs ${runId} --out run.log --json`
6271
6592
  };
6593
+ if (status.dashboardUrl) {
6594
+ commands.open = `Open ${status.dashboardUrl} to see results.`;
6595
+ }
6596
+ return commands;
6272
6597
  }
6273
6598
  var RUN_LOG_PREVIEW_LIMIT = 20;
6274
6599
  function getRecordField(value, key) {
@@ -6289,6 +6614,121 @@ function getTimestampField(value, key) {
6289
6614
  }
6290
6615
  return typeof field === "string" && field.trim() ? field : null;
6291
6616
  }
6617
+ function getObjectField(value, key) {
6618
+ const field = getRecordField(value, key);
6619
+ return field && typeof field === "object" && !Array.isArray(field) ? field : null;
6620
+ }
6621
+ function formatCreditAmount(value) {
6622
+ if (typeof value !== "number" || !Number.isFinite(value)) {
6623
+ return String(value ?? "-");
6624
+ }
6625
+ return Number(value.toFixed(8)).toString();
6626
+ }
6627
+ function extractJsonObjectFromText(text) {
6628
+ const start = text.indexOf("{");
6629
+ if (start < 0) {
6630
+ return null;
6631
+ }
6632
+ const suffix = text.slice(start);
6633
+ try {
6634
+ const parsed = JSON.parse(suffix);
6635
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
6636
+ } catch {
6637
+ let depth = 0;
6638
+ let inString = false;
6639
+ let escaped = false;
6640
+ for (let index = start; index < text.length; index += 1) {
6641
+ const char = text[index];
6642
+ if (inString) {
6643
+ if (escaped) {
6644
+ escaped = false;
6645
+ } else if (char === "\\") {
6646
+ escaped = true;
6647
+ } else if (char === '"') {
6648
+ inString = false;
6649
+ }
6650
+ continue;
6651
+ }
6652
+ if (char === '"') {
6653
+ inString = true;
6654
+ } else if (char === "{") {
6655
+ depth += 1;
6656
+ } else if (char === "}") {
6657
+ depth -= 1;
6658
+ if (depth === 0) {
6659
+ try {
6660
+ const parsed = JSON.parse(text.slice(start, index + 1));
6661
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
6662
+ } catch {
6663
+ return null;
6664
+ }
6665
+ }
6666
+ }
6667
+ }
6668
+ }
6669
+ return null;
6670
+ }
6671
+ function extractBillingFromText(text) {
6672
+ if (!text) {
6673
+ return null;
6674
+ }
6675
+ const parsed = extractJsonObjectFromText(text);
6676
+ return getObjectField(parsed, "billing");
6677
+ }
6678
+ function extractToolIdFromErrorText(text) {
6679
+ if (!text) {
6680
+ return null;
6681
+ }
6682
+ const lowerToolMatch = /\btool\s+([A-Za-z0-9_.:-]+)\s+\d{3}\b/.exec(text);
6683
+ if (lowerToolMatch?.[1]) {
6684
+ return lowerToolMatch[1];
6685
+ }
6686
+ const upperToolMatch = /\bTool\s+([A-Za-z0-9_.:-]+)\s+failed\b/.exec(text);
6687
+ return upperToolMatch?.[1] ?? null;
6688
+ }
6689
+ function isInsufficientCreditsBilling(billing) {
6690
+ return billing?.kind === "insufficient_credits";
6691
+ }
6692
+ function extractBillingForStatus(status, error) {
6693
+ const errorBilling = getObjectField(status, "errorBilling");
6694
+ if (errorBilling) {
6695
+ return errorBilling;
6696
+ }
6697
+ const directErrors = getRecordField(status, "errors");
6698
+ if (Array.isArray(directErrors)) {
6699
+ for (const entry of directErrors) {
6700
+ const billing = getObjectField(entry, "billing");
6701
+ if (billing) {
6702
+ return billing;
6703
+ }
6704
+ }
6705
+ }
6706
+ const progressError = getStringField(status.progress, "error");
6707
+ return extractBillingFromText(error) ?? extractBillingFromText(progressError) ?? getObjectField(status, "billing");
6708
+ }
6709
+ function formatInsufficientCreditsMessage(input) {
6710
+ const operation = getStringField(input.billing, "operation_id") ?? getStringField(input.billing, "operation") ?? extractToolIdFromErrorText(input.error) ?? getStringField(input.billing, "provider") ?? "tool call";
6711
+ const balance = formatCreditAmount(input.billing.balance_credits);
6712
+ const required = formatCreditAmount(input.billing.required_credits);
6713
+ const recommended = formatCreditAmount(
6714
+ input.billing.recommended_add_credits ?? input.billing.needed_credits
6715
+ );
6716
+ const billingUrl = getStringField(input.billing, "billing_url");
6717
+ const workspace = getStringField(input.billing, "workspace_id") ?? getStringField(input.billing, "workspaceId");
6718
+ const workspaceSuffix = workspace ? ` (workspace=${workspace})` : "";
6719
+ const addSuffix = billingUrl && recommended !== "-" ? ` Add >=${recommended} at ${billingUrl}.` : billingUrl ? ` Add credits at ${billingUrl}.` : "";
6720
+ return `Workspace balance ${balance} < required ${required} for ${operation}${workspaceSuffix}.${addSuffix}`;
6721
+ }
6722
+ function formatPlayErrorForDisplay(status, error) {
6723
+ if (!error) {
6724
+ return null;
6725
+ }
6726
+ const billing = extractBillingForStatus(status, error);
6727
+ if (isInsufficientCreditsBilling(billing)) {
6728
+ return formatInsufficientCreditsMessage({ billing, error });
6729
+ }
6730
+ return error;
6731
+ }
6292
6732
  function normalizeRunStatusForEnvelope(status) {
6293
6733
  const run = status.run ?? null;
6294
6734
  return {
@@ -6339,18 +6779,33 @@ function normalizeErrorsForEnvelope(status, error) {
6339
6779
  if (Array.isArray(directErrors)) {
6340
6780
  return directErrors.filter(
6341
6781
  (entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)
6342
- );
6782
+ ).map((entry) => {
6783
+ const message2 = typeof entry.message === "string" && entry.message.trim() ? entry.message : error;
6784
+ const billing2 = getObjectField(entry, "billing");
6785
+ if (!isInsufficientCreditsBilling(billing2) || !message2) {
6786
+ return entry;
6787
+ }
6788
+ return {
6789
+ ...entry,
6790
+ message: formatInsufficientCreditsMessage({ billing: billing2, error: message2 }),
6791
+ billing: stripProviderSpendFromBilling(billing2)
6792
+ };
6793
+ });
6343
6794
  }
6344
6795
  if (!error) {
6345
6796
  return [];
6346
6797
  }
6798
+ const nextCommands = buildRunNextCommands(status);
6799
+ const billing = extractBillingForStatus(status, error);
6800
+ const message = formatPlayErrorForDisplay(status, error) ?? error;
6347
6801
  return [
6348
6802
  {
6349
6803
  code: getStringField(status, "errorCode") ?? "RUN_FAILED",
6350
6804
  phase: getStringField(status, "errorPhase") ?? "runtime",
6351
- message: error,
6805
+ message,
6352
6806
  retryable: typeof getRecordField(status, "retryable") === "boolean" ? getRecordField(status, "retryable") : null,
6353
- nextAction: `deepline runs get ${status.runId} --json`
6807
+ ...billing ? { billing: stripProviderSpendFromBilling(billing) } : {},
6808
+ nextAction: nextCommands.get ?? nextCommands.list
6354
6809
  }
6355
6810
  ];
6356
6811
  }
@@ -6399,24 +6854,26 @@ function compactPlayStatus(status) {
6399
6854
  ) : null;
6400
6855
  const progressError = status.progress?.error;
6401
6856
  const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
6857
+ const displayError = formatPlayErrorForDisplay(status, error);
6402
6858
  return {
6403
6859
  runId: status.runId,
6404
6860
  apiVersion: status.apiVersion ?? 1,
6405
6861
  ...typeof status.name === "string" ? { name: status.name } : {},
6406
6862
  ...typeof status.playName === "string" ? { playName: status.playName } : {},
6863
+ ...status.dashboardUrl ? { dashboardUrl: status.dashboardUrl } : {},
6407
6864
  status: status.status,
6408
6865
  run: normalizeRunStatusForEnvelope(status),
6409
6866
  progress: normalizeProgressForEnvelope(status, rowsInfo),
6410
6867
  steps: normalizeStepsForEnvelope(status),
6411
6868
  errors: normalizeErrorsForEnvelope(status, error),
6412
6869
  logs: normalizeLogsForEnvelope(status),
6413
- ...error ? { error } : {},
6870
+ ...displayError ? { error: displayError } : {},
6414
6871
  ...warnings.length > 0 ? { warnings } : {},
6415
6872
  ...result !== void 0 ? { result } : {},
6416
6873
  ...status.resultView ? { resultView: status.resultView } : {},
6417
6874
  ...datasetStats ? { dataset_stats: datasetStats } : {},
6418
6875
  ...billing ? { billing } : {},
6419
- next: buildRunNextCommands(status.runId)
6876
+ next: buildRunNextCommands(status)
6420
6877
  };
6421
6878
  }
6422
6879
  function enrichPlayStatusWithDatasetStats(status) {
@@ -6490,7 +6947,8 @@ function writePlayResult(status, jsonOutput, options) {
6490
6947
  lines.push(...formatDatasetStatsLines(datasetStats));
6491
6948
  const progressError = status.progress?.error;
6492
6949
  if (progressError && typeof progressError === "string") {
6493
- lines.push(` error: ${progressError.slice(0, 200)}`);
6950
+ const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
6951
+ lines.push(` error: ${displayError.slice(0, 200)}`);
6494
6952
  }
6495
6953
  const renderedServerView = renderServerResultView(status.resultView);
6496
6954
  if (result) {
@@ -6514,8 +6972,11 @@ var RUN_EXPORT_PAGE_SIZE = 5e3;
6514
6972
  function shellSingleQuote(value) {
6515
6973
  return `'${value.replace(/'/g, `'\\''`)}'`;
6516
6974
  }
6975
+ function sqlStringLiteral(value) {
6976
+ return `'${value.replace(/'/g, "''")}'`;
6977
+ }
6517
6978
  function runExportRetryCommand(runId, outPath, datasetPath) {
6518
- return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote((0, import_node_path9.resolve)(outPath))}`;
6979
+ return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote((0, import_node_path10.resolve)(outPath))}`;
6519
6980
  }
6520
6981
  function extractRunPlayName(status) {
6521
6982
  const run = status.run;
@@ -6532,6 +6993,26 @@ function extractRunPlayName(status) {
6532
6993
  }
6533
6994
  return null;
6534
6995
  }
6996
+ function normalizeCustomerDbIdentifier(value) {
6997
+ return value.split("/").pop().replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
6998
+ }
6999
+ function buildCustomerDbQueryPlan(input) {
7000
+ const playName = extractRunPlayName(input.status);
7001
+ const tableNamespace = input.rowsInfo.tableNamespace?.trim();
7002
+ if (!playName || !tableNamespace || input.rowsInfo.totalRows <= 0) {
7003
+ return null;
7004
+ }
7005
+ const tableName = `${normalizeCustomerDbIdentifier(playName)}_${normalizeCustomerDbIdentifier(
7006
+ tableNamespace
7007
+ )}`;
7008
+ const sql = `select * from "storage"."${tableName}" where _run_id = ${sqlStringLiteral(input.status.runId)} limit ${input.rowsInfo.totalRows}`;
7009
+ const base = `deepline customer-db query --sql ${shellSingleQuote(sql)} --max-rows ${input.rowsInfo.totalRows}`;
7010
+ return {
7011
+ sql,
7012
+ json: `${base} --json`,
7013
+ csv: `${base} --format csv --out ${shellSingleQuote((0, import_node_path10.resolve)(input.outPath))}`
7014
+ };
7015
+ }
6535
7016
  function exportableSheetRow(row) {
6536
7017
  if (!row || typeof row !== "object" || Array.isArray(row)) {
6537
7018
  return null;
@@ -6629,7 +7110,7 @@ async function exportPlayStatusRows(client, status, outPath, options = {}) {
6629
7110
  return null;
6630
7111
  }
6631
7112
  const availableRows = collectSerializedDatasetRowsInfos(status);
6632
- let rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
7113
+ const rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
6633
7114
  if (!rowsInfo && options.datasetPath) {
6634
7115
  const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
6635
7116
  throw new DeeplineError(
@@ -6703,6 +7184,60 @@ async function exportPlayStatusRows(client, status, outPath, options = {}) {
6703
7184
  }
6704
7185
  return { path: writeCanonicalRowsCsv(rowsInfo, outPath), rowsInfo };
6705
7186
  }
7187
+ function extractActiveRunsFromError(error) {
7188
+ if (!(error instanceof DeeplineError)) {
7189
+ return [];
7190
+ }
7191
+ const response = error.details?.response;
7192
+ if (!response || typeof response !== "object" || Array.isArray(response)) {
7193
+ return [];
7194
+ }
7195
+ const details = response.details;
7196
+ if (!details || typeof details !== "object" || Array.isArray(details)) {
7197
+ return [];
7198
+ }
7199
+ const activeRuns = details.activeRuns;
7200
+ return Array.isArray(activeRuns) ? activeRuns.filter(
7201
+ (run) => Boolean(run) && typeof run === "object" && !Array.isArray(run)
7202
+ ) : [];
7203
+ }
7204
+ function activeRunId(run) {
7205
+ return getStringField(run, "workflowId") ?? getStringField(run, "runId");
7206
+ }
7207
+ function formatActiveRunConflictError(input) {
7208
+ const lines = [
7209
+ `Active run exists for ${input.playName}. Use --force to supersede, or inspect/stop the active run first.`
7210
+ ];
7211
+ for (const run of input.activeRuns.slice(0, 3)) {
7212
+ const runId = activeRunId(run);
7213
+ if (!runId) {
7214
+ continue;
7215
+ }
7216
+ const status = getStringField(run, "status");
7217
+ const startedAt = getStringField(run, "startedAt") ?? getStringField(run, "startTime");
7218
+ lines.push(
7219
+ ` active: ${runId}${status ? ` status=${status}` : ""}${startedAt ? ` startedAt=${startedAt}` : ""}`
7220
+ );
7221
+ lines.push(` get: deepline runs get ${runId} --json`);
7222
+ lines.push(
7223
+ ` stop: deepline runs stop ${runId} --reason "stale lock" --json`
7224
+ );
7225
+ }
7226
+ lines.push(` rerun: add --force to the same deepline plays run command`);
7227
+ return lines.join("\n");
7228
+ }
7229
+ function normalizePlayStartError(error, playName) {
7230
+ const activeRuns = extractActiveRunsFromError(error);
7231
+ if (activeRuns.length === 0) {
7232
+ return error;
7233
+ }
7234
+ return new DeeplineError(
7235
+ formatActiveRunConflictError({ playName, activeRuns }),
7236
+ error instanceof DeeplineError ? error.statusCode : 409,
7237
+ "ACTIVE_RUN_EXISTS",
7238
+ error instanceof DeeplineError ? error.details : void 0
7239
+ );
7240
+ }
6706
7241
  function renderServerResultView(value) {
6707
7242
  if (!value || typeof value !== "object" || Array.isArray(value)) {
6708
7243
  return { lines: [], actions: [] };
@@ -6938,12 +7473,12 @@ function shouldUseLocalOnlyPlayCheck() {
6938
7473
  async function handlePlayCheck(args) {
6939
7474
  const options = parsePlayCheckOptions(args);
6940
7475
  if (!isFileTarget(options.target)) {
6941
- const resolved = (0, import_node_path9.resolve)(options.target);
7476
+ const resolved = (0, import_node_path10.resolve)(options.target);
6942
7477
  console.error(`File not found: ${resolved}`);
6943
7478
  return 1;
6944
7479
  }
6945
- const absolutePlayPath = (0, import_node_path9.resolve)(options.target);
6946
- const sourceCode = (0, import_node_fs7.readFileSync)(absolutePlayPath, "utf-8");
7480
+ const absolutePlayPath = (0, import_node_path10.resolve)(options.target);
7481
+ const sourceCode = (0, import_node_fs8.readFileSync)(absolutePlayPath, "utf-8");
6947
7482
  let graph;
6948
7483
  try {
6949
7484
  graph = await collectBundledPlayGraph(absolutePlayPath);
@@ -7006,12 +7541,12 @@ async function handleFileBackedRun(options) {
7006
7541
  }
7007
7542
  const client = new DeeplineClient();
7008
7543
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
7009
- const absolutePlayPath = (0, import_node_path9.resolve)(options.target.path);
7544
+ const absolutePlayPath = (0, import_node_path10.resolve)(options.target.path);
7010
7545
  progress.phase("compiling play");
7011
7546
  const sourceCode = traceCliSync(
7012
7547
  "cli.play_file_read_source",
7013
7548
  { targetKind: "file" },
7014
- () => (0, import_node_fs7.readFileSync)(absolutePlayPath, "utf-8")
7549
+ () => (0, import_node_fs8.readFileSync)(absolutePlayPath, "utf-8")
7015
7550
  );
7016
7551
  const runtimeInput = options.input ? { ...options.input } : {};
7017
7552
  let graph;
@@ -7092,6 +7627,8 @@ async function handleFileBackedRun(options) {
7092
7627
  waitTimeoutMs: options.waitTimeoutMs,
7093
7628
  noOpen: options.noOpen,
7094
7629
  progress
7630
+ }).catch((error) => {
7631
+ throw normalizePlayStartError(error, playName);
7095
7632
  })
7096
7633
  );
7097
7634
  if (finalStatus.status === "completed") {
@@ -7110,10 +7647,11 @@ async function handleFileBackedRun(options) {
7110
7647
  const started = await traceCliSpan(
7111
7648
  "cli.play_start_unwatched",
7112
7649
  { targetKind: "file", playName },
7113
- () => client.startPlayRun(startRequest)
7650
+ () => client.startPlayRun(startRequest).catch((error) => {
7651
+ throw normalizePlayStartError(error, playName);
7652
+ })
7114
7653
  );
7115
- const dashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7116
- const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
7654
+ const resolvedDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7117
7655
  openPlayDashboard({
7118
7656
  dashboardUrl: resolvedDashboardUrl,
7119
7657
  jsonOutput: options.jsonOutput,
@@ -7228,6 +7766,8 @@ async function handleNamedRun(options) {
7228
7766
  waitTimeoutMs: options.waitTimeoutMs,
7229
7767
  noOpen: options.noOpen,
7230
7768
  progress
7769
+ }).catch((error) => {
7770
+ throw normalizePlayStartError(error, playName);
7231
7771
  })
7232
7772
  );
7233
7773
  if (finalStatus.status === "completed") {
@@ -7246,13 +7786,11 @@ async function handleNamedRun(options) {
7246
7786
  const started = await traceCliSpan(
7247
7787
  "cli.play_start_unwatched",
7248
7788
  { targetKind: "name", playName },
7249
- () => client.startPlayRun(startRequest)
7250
- );
7251
- const dashboardUrl = buildPlayDashboardUrl(
7252
- client.baseUrl,
7253
- playName
7789
+ () => client.startPlayRun(startRequest).catch((error) => {
7790
+ throw normalizePlayStartError(error, playName);
7791
+ })
7254
7792
  );
7255
- const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
7793
+ const resolvedDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7256
7794
  openPlayDashboard({
7257
7795
  dashboardUrl: resolvedDashboardUrl,
7258
7796
  jsonOutput: options.jsonOutput,
@@ -7276,19 +7814,19 @@ async function handlePlayRun(args) {
7276
7814
  if (isFileTarget(options.target.path)) {
7277
7815
  return handleFileBackedRun(options);
7278
7816
  }
7279
- const resolved = (0, import_node_path9.resolve)(options.target.path);
7817
+ const resolved = (0, import_node_path10.resolve)(options.target.path);
7280
7818
  console.error(`File not found: ${resolved}`);
7281
- const dir = (0, import_node_path9.dirname)(resolved);
7282
- if ((0, import_node_fs7.existsSync)(dir)) {
7283
- const base = (0, import_node_path9.basename)(resolved);
7819
+ const dir = (0, import_node_path10.dirname)(resolved);
7820
+ if ((0, import_node_fs8.existsSync)(dir)) {
7821
+ const base = (0, import_node_path10.basename)(resolved);
7284
7822
  try {
7285
- const siblings = (0, import_node_fs7.readdirSync)(dir).filter(
7823
+ const siblings = (0, import_node_fs8.readdirSync)(dir).filter(
7286
7824
  (f) => f.includes(base.replace(/\.(play\.)?ts$/, "")) || f.endsWith(".play.ts")
7287
7825
  );
7288
7826
  if (siblings.length > 0) {
7289
7827
  console.error(`Did you mean one of these?`);
7290
7828
  for (const s of siblings.slice(0, 5)) {
7291
- console.error(` ${(0, import_node_path9.join)(dir, s)}`);
7829
+ console.error(` ${(0, import_node_path10.join)(dir, s)}`);
7292
7830
  }
7293
7831
  }
7294
7832
  } catch {
@@ -7423,14 +7961,14 @@ async function handleRunLogs(args) {
7423
7961
  continue;
7424
7962
  }
7425
7963
  if (arg === "--out" && args[index + 1]) {
7426
- outPath = (0, import_node_path9.resolve)(args[++index]);
7964
+ outPath = (0, import_node_path10.resolve)(args[++index]);
7427
7965
  }
7428
7966
  }
7429
7967
  const client = new DeeplineClient();
7430
7968
  const status = await client.runs.get(runId);
7431
7969
  const logs = status.progress?.logs ?? [];
7432
7970
  if (outPath) {
7433
- (0, import_node_fs7.writeFileSync)(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
7971
+ (0, import_node_fs8.writeFileSync)(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
7434
7972
  printCommandEnvelope({
7435
7973
  runId: status.runId,
7436
7974
  log_path: outPath,
@@ -7486,7 +8024,7 @@ async function handleRunStop(args) {
7486
8024
  return 0;
7487
8025
  }
7488
8026
  async function handleRunExport(args) {
7489
- const usage = "Usage: deepline runs export <run-id> [--dataset result.rows] --out output.csv [--json]";
8027
+ const usage = "Usage: deepline runs export <run-id> [--dataset result.rows] --out output.csv [--metadata-out export.json] [--json]";
7490
8028
  let runId;
7491
8029
  try {
7492
8030
  runId = parseRunIdPositional(args, usage);
@@ -7496,14 +8034,19 @@ async function handleRunExport(args) {
7496
8034
  }
7497
8035
  let outPath = null;
7498
8036
  let datasetPath = null;
8037
+ let metadataOutPath = null;
7499
8038
  for (let index = 0; index < args.length; index += 1) {
7500
8039
  const arg = args[index];
7501
8040
  if (arg === "--out" && args[index + 1]) {
7502
- outPath = (0, import_node_path9.resolve)(args[++index]);
8041
+ outPath = (0, import_node_path10.resolve)(args[++index]);
7503
8042
  continue;
7504
8043
  }
7505
8044
  if (arg === "--dataset" && args[index + 1]) {
7506
8045
  datasetPath = args[++index];
8046
+ continue;
8047
+ }
8048
+ if (arg === "--metadata-out" && args[index + 1]) {
8049
+ metadataOutPath = (0, import_node_path10.resolve)(args[++index]);
7507
8050
  }
7508
8051
  }
7509
8052
  if (!outPath) {
@@ -7515,15 +8058,59 @@ async function handleRunExport(args) {
7515
8058
  const exportResult = await exportPlayStatusRows(client, status, outPath, {
7516
8059
  datasetPath
7517
8060
  });
7518
- printCommandEnvelope({
8061
+ const source = exportResult?.rowsInfo.source ?? datasetPath ?? null;
8062
+ const queryPlan = exportResult && outPath ? buildCustomerDbQueryPlan({
8063
+ status,
8064
+ rowsInfo: exportResult.rowsInfo,
8065
+ outPath
8066
+ }) : null;
8067
+ const next = {
8068
+ ...buildRunNextCommands(status),
8069
+ export: runExportRetryCommand(status.runId, outPath, datasetPath ?? source),
8070
+ ...queryPlan ? {
8071
+ queryJson: queryPlan.json,
8072
+ queryCsv: queryPlan.csv
8073
+ } : {}
8074
+ };
8075
+ const payload = {
7519
8076
  runId: status.runId,
7520
8077
  ...datasetPath ? { dataset: datasetPath } : {},
7521
8078
  csv_path: exportResult?.path ?? null,
8079
+ source,
7522
8080
  rowCount: exportResult?.rowsInfo.totalRows ?? null,
7523
8081
  columns: exportResult?.rowsInfo.columns ?? [],
8082
+ ...queryPlan ? {
8083
+ query: {
8084
+ sql: queryPlan.sql,
8085
+ json: queryPlan.json,
8086
+ csv: queryPlan.csv
8087
+ }
8088
+ } : {},
8089
+ ...metadataOutPath ? { metadata_path: metadataOutPath } : {},
7524
8090
  local: { csv_path: exportResult?.path ?? null },
7525
- render: { sections: [{ title: "run export", lines: [`Exported ${status.runId} to ${exportResult?.path ?? outPath}`] }] }
7526
- }, { json: argsWantJson(args) });
8091
+ next,
8092
+ render: {
8093
+ sections: [
8094
+ {
8095
+ title: "run export",
8096
+ lines: [
8097
+ `Exported ${status.runId} to ${exportResult?.path ?? outPath}`,
8098
+ ...source ? [`source=${source}`] : [],
8099
+ ...queryPlan ? [`query=${queryPlan.json}`] : []
8100
+ ]
8101
+ }
8102
+ ]
8103
+ }
8104
+ };
8105
+ if (metadataOutPath) {
8106
+ (0, import_node_fs8.writeFileSync)(
8107
+ metadataOutPath,
8108
+ `${JSON.stringify(payload, null, 2)}
8109
+ `,
8110
+ "utf-8"
8111
+ );
8112
+ }
8113
+ printCommandEnvelope(payload, { json: argsWantJson(args) });
7527
8114
  return 0;
7528
8115
  }
7529
8116
  async function handlePlayGet(args) {
@@ -7540,10 +8127,10 @@ async function handlePlayGet(args) {
7540
8127
  for (let index = 1; index < args.length; index += 1) {
7541
8128
  const arg = args[index];
7542
8129
  if (arg === "--out" && args[index + 1]) {
7543
- outPath = (0, import_node_path9.resolve)(args[++index]);
8130
+ outPath = (0, import_node_path10.resolve)(args[++index]);
7544
8131
  }
7545
8132
  }
7546
- const playName = isFileTarget(target) ? extractPlayName((0, import_node_fs7.readFileSync)((0, import_node_path9.resolve)(target), "utf-8"), (0, import_node_path9.resolve)(target)) : parseReferencedPlayTarget(target).playName;
8133
+ const playName = isFileTarget(target) ? extractPlayName((0, import_node_fs8.readFileSync)((0, import_node_path10.resolve)(target), "utf-8"), (0, import_node_path10.resolve)(target)) : parseReferencedPlayTarget(target).playName;
7547
8134
  const detail = isFileTarget(target) ? await client.getPlay(playName) : await assertCanonicalNamedPlayReference(client, target);
7548
8135
  const resolvedSource = detail.play.workingRevision?.sourceCode ?? detail.play.liveRevision?.sourceCode ?? detail.play.currentRevision?.sourceCode ?? detail.play.sourceCode ?? "";
7549
8136
  const materializedFile = outPath ? materializeRemotePlaySource({
@@ -7835,7 +8422,7 @@ async function handlePlayPublish(args) {
7835
8422
  }
7836
8423
  let graph;
7837
8424
  try {
7838
- graph = await collectBundledPlayGraph((0, import_node_path9.resolve)(playName));
8425
+ graph = await collectBundledPlayGraph((0, import_node_path10.resolve)(playName));
7839
8426
  await compileBundledPlayGraphManifests(client, graph);
7840
8427
  await publishImportedPlayDependencies(client, graph);
7841
8428
  } catch (error) {
@@ -8321,32 +8908,36 @@ Examples:
8321
8908
  Notes:
8322
8909
  Writes a returned dataset handle to the requested local CSV path. Use runs get
8323
8910
  first to inspect dataset paths like result.rows or result.nested.contacts.
8911
+ --metadata-out writes the same export metadata object returned by --json,
8912
+ including source and follow-on customer-db query commands when available.
8324
8913
 
8325
8914
  Examples:
8326
8915
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
8327
8916
  deepline runs export play/my-play/run/20260501t000000-000 --dataset result.rows --out output.csv
8917
+ deepline runs export play/my-play/run/20260501t000000-000 --out output.csv --metadata-out output.meta.json
8328
8918
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv --json
8329
8919
  `
8330
- ).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) => {
8920
+ ).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) => {
8331
8921
  process.exitCode = await handleRunExport([
8332
8922
  runId,
8333
8923
  ...options.dataset ? ["--dataset", options.dataset] : [],
8334
8924
  "--out",
8335
8925
  options.out,
8926
+ ...options.metadataOut ? ["--metadata-out", options.metadataOut] : [],
8336
8927
  ...options.json ? ["--json"] : []
8337
8928
  ]);
8338
8929
  });
8339
8930
  }
8340
8931
 
8341
8932
  // src/cli/commands/tools.ts
8342
- var import_node_fs9 = require("fs");
8933
+ var import_node_fs10 = require("fs");
8343
8934
  var import_node_os7 = require("os");
8344
- var import_node_path11 = require("path");
8935
+ var import_node_path12 = require("path");
8345
8936
 
8346
8937
  // src/tool-output.ts
8347
- var import_node_fs8 = require("fs");
8938
+ var import_node_fs9 = require("fs");
8348
8939
  var import_node_os6 = require("os");
8349
- var import_node_path10 = require("path");
8940
+ var import_node_path11 = require("path");
8350
8941
  function isPlainObject(value) {
8351
8942
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8352
8943
  }
@@ -8369,6 +8960,25 @@ function normalizeRows(value) {
8369
8960
  }
8370
8961
  function candidateRoots(payload) {
8371
8962
  const roots = [{ path: null, value: payload }];
8963
+ if (isPlainObject(payload) && isPlainObject(payload.toolExecutionResult)) {
8964
+ roots.push({ path: "toolExecutionResult", value: payload.toolExecutionResult });
8965
+ const toolOutput = payload.toolExecutionResult.toolOutput;
8966
+ if (isPlainObject(toolOutput)) {
8967
+ roots.push({ path: "toolExecutionResult.toolOutput", value: toolOutput });
8968
+ if (Object.prototype.hasOwnProperty.call(toolOutput, "raw")) {
8969
+ roots.push({
8970
+ path: "toolExecutionResult.toolOutput.raw",
8971
+ value: toolOutput.raw
8972
+ });
8973
+ }
8974
+ }
8975
+ }
8976
+ if (isPlainObject(payload) && isPlainObject(payload.output)) {
8977
+ roots.push({ path: "output", value: payload.output });
8978
+ if (Object.prototype.hasOwnProperty.call(payload.output, "body")) {
8979
+ roots.push({ path: "output.body", value: payload.output.body });
8980
+ }
8981
+ }
8372
8982
  if (isPlainObject(payload) && isPlainObject(payload.result)) {
8373
8983
  roots.push({ path: "result", value: payload.result });
8374
8984
  if (isPlainObject(payload.result.data)) {
@@ -8421,19 +9031,19 @@ function tryConvertToList(payload, options) {
8421
9031
  return null;
8422
9032
  }
8423
9033
  function ensureOutputDir() {
8424
- const outputDir = (0, import_node_path10.join)((0, import_node_os6.homedir)(), ".local", "share", "deepline", "data");
8425
- (0, import_node_fs8.mkdirSync)(outputDir, { recursive: true });
9034
+ const outputDir = (0, import_node_path11.join)((0, import_node_os6.homedir)(), ".local", "share", "deepline", "data");
9035
+ (0, import_node_fs9.mkdirSync)(outputDir, { recursive: true });
8426
9036
  return outputDir;
8427
9037
  }
8428
9038
  function writeJsonOutputFile(payload, stem) {
8429
9039
  const outputDir = ensureOutputDir();
8430
- const outputPath = (0, import_node_path10.join)(outputDir, `${stem}_${Date.now()}.json`);
8431
- (0, import_node_fs8.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf-8");
9040
+ const outputPath = (0, import_node_path11.join)(outputDir, `${stem}_${Date.now()}.json`);
9041
+ (0, import_node_fs9.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf-8");
8432
9042
  return outputPath;
8433
9043
  }
8434
9044
  function writeCsvOutputFile(rows, stem) {
8435
9045
  const outputDir = ensureOutputDir();
8436
- const outputPath = (0, import_node_path10.join)(outputDir, `${stem}_${Date.now()}.csv`);
9046
+ const outputPath = (0, import_node_path11.join)(outputDir, `${stem}_${Date.now()}.csv`);
8437
9047
  const seen = /* @__PURE__ */ new Set();
8438
9048
  const columns = [];
8439
9049
  for (const row of rows) {
@@ -8456,7 +9066,7 @@ function writeCsvOutputFile(rows, stem) {
8456
9066
  for (const row of rows) {
8457
9067
  lines.push(columns.map((column) => escapeCell(row[column])).join(","));
8458
9068
  }
8459
- (0, import_node_fs8.writeFileSync)(outputPath, `${lines.join("\n")}
9069
+ (0, import_node_fs9.writeFileSync)(outputPath, `${lines.join("\n")}
8460
9070
  `, "utf-8");
8461
9071
  const previewRows = rows.slice(0, 5);
8462
9072
  const previewColumns = columns.slice(0, 5);
@@ -8502,8 +9112,7 @@ async function listTools(args) {
8502
9112
  title: `${items.length} tools available:`,
8503
9113
  lines: items.flatMap((item) => {
8504
9114
  const cats = item.categories.length ? ` [${item.categories.join(", ")}]` : "";
8505
- const listHint = item.listExtractorPaths?.length ? ` listExtractorPaths=${item.listExtractorPaths.join(",")}` : "";
8506
- return [`${item.toolId}${cats}`, ` ${item.description}${listHint}`];
9115
+ return [`${item.toolId}${cats}`, ` ${item.description}`];
8507
9116
  })
8508
9117
  }
8509
9118
  ]
@@ -8582,7 +9191,7 @@ Common commands:
8582
9191
  deepline tools execute hunter_email_verifier --input '{"email":"a@b.com"}'
8583
9192
 
8584
9193
  Output:
8585
- Use describe for tool contracts. get is accepted as a compatibility alias.
9194
+ Use describe for tool contracts.
8586
9195
  Use execute to run a tool. run is accepted as a compatibility alias.
8587
9196
  `
8588
9197
  );
@@ -8629,8 +9238,8 @@ Examples:
8629
9238
  `
8630
9239
  Notes:
8631
9240
  Shows the tool contract, input schema, output schema, Deepline cost, aliases,
8632
- and metadata. describe is the preferred discovery verb. get is kept as a
8633
- compatibility alias for the same metadata surface.
9241
+ and metadata. describe is the supported discovery verb. get is removed in
9242
+ the V2 SDK CLI; use describe for the same metadata surface.
8634
9243
 
8635
9244
  Examples:
8636
9245
  deepline tools describe hunter_email_verifier
@@ -8643,7 +9252,22 @@ Examples:
8643
9252
  ...options.json ? ["--json"] : []
8644
9253
  ]);
8645
9254
  });
8646
- addToolMetadataCommand(tools.command("describe <toolId>").alias("get"));
9255
+ addToolMetadataCommand(tools.command("describe <toolId>"));
9256
+ tools.command("get <toolId>").description("Deprecated. Use tools describe.").addHelpText(
9257
+ "after",
9258
+ `
9259
+ Examples:
9260
+ deepline tools describe hunter_email_verifier --json
9261
+ `
9262
+ ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (toolId, options) => {
9263
+ const message = `tools get has been removed from the V2 SDK CLI. Use: deepline tools describe ${toolId} --json`;
9264
+ if (options.json || shouldEmitJson()) {
9265
+ printJsonError({ message, code: "TOOLS_GET_REMOVED" });
9266
+ } else {
9267
+ console.error(message);
9268
+ }
9269
+ process.exitCode = 2;
9270
+ });
8647
9271
  tools.command("execute <toolId>").alias("run").description("Execute a tool by id.").addHelpText(
8648
9272
  "after",
8649
9273
  `
@@ -8724,6 +9348,7 @@ function printToolDetails(tool, requestedToolId) {
8724
9348
  const stepContributions = arrayField(tool, "stepContributions", "step_contributions");
8725
9349
  const playExpansion = recordField(tool, "playExpansion", "play_expansion");
8726
9350
  const samples = recordField(tool, "samples");
9351
+ const usageGuidance = recordField(tool, "usageGuidance", "usage_guidance");
8727
9352
  console.log(`Tool: ${toolId}`);
8728
9353
  if (displayName) {
8729
9354
  console.log(" Display name:");
@@ -8787,14 +9412,16 @@ function printToolDetails(tool, requestedToolId) {
8787
9412
  console.log(" Tip: pass --payload with a JSON object.");
8788
9413
  }
8789
9414
  printSamples(samples);
9415
+ printUsageGuidance(usageGuidance);
8790
9416
  if (isPlayTool(tool)) {
8791
9417
  console.log(" Play contract:");
8792
9418
  console.log(" - This is a deepline-native waterfall; the returned rows are extracted by target getters, not by hand-authored payload shape.");
8793
9419
  if (playExpansion && typeof playExpansion.group === "string" && playExpansion.group.trim()) {
8794
9420
  console.log(` - Output alias/runtime group is: ${playExpansion.group.trim()}`);
8795
9421
  }
8796
- const getters = recordField(tool, "resultIdentityGetters", "result_identity_getters");
8797
- const targets = Object.keys(getters).filter(Boolean).sort();
9422
+ const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult");
9423
+ const extractedValues = arrayField(toolExecutionResult, "extractedValues", "extracted_values");
9424
+ const targets = extractedValues.map((entry) => isRecord3(entry) && typeof entry.name === "string" ? entry.name : "").filter(Boolean).sort();
8798
9425
  if (targets.length) {
8799
9426
  console.log(` - Built-in extract targets: ${targets.join(", ")}`);
8800
9427
  }
@@ -8813,7 +9440,53 @@ function printToolDetails(tool, requestedToolId) {
8813
9440
  } else {
8814
9441
  console.log(` deepline tools execute ${toolId} --payload '{...}'`);
8815
9442
  }
8816
- console.log(" deepline tools get <tool_id> --json # full machine-readable output");
9443
+ console.log(" deepline tools describe <tool_id> --json");
9444
+ }
9445
+ function printUsageGuidance(usageGuidance) {
9446
+ if (Object.keys(usageGuidance).length === 0) return;
9447
+ const execute = stringField(usageGuidance, "execute");
9448
+ const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult", "tool_execution_result");
9449
+ const toolOutput = recordField(toolExecutionResult, "toolOutput", "tool_output");
9450
+ const extractedLists = extractionEntries(toolExecutionResult, "extractedLists", "extracted_lists");
9451
+ const extractedValues = extractionEntries(toolExecutionResult, "extractedValues", "extracted_values");
9452
+ console.log(" Usage guidance:");
9453
+ if (execute) console.log(` ${execute}`);
9454
+ const raw = pathField(toolOutput, "raw");
9455
+ const meta = pathField(toolOutput, "meta");
9456
+ if (raw) console.log(` Raw tool output: ${raw}`);
9457
+ if (meta) console.log(` Tool output metadata: ${meta}`);
9458
+ printExtractions("Extracted lists", extractedLists);
9459
+ printExtractions("Extracted values", extractedValues);
9460
+ }
9461
+ function pathField(record, key) {
9462
+ const value = record[key];
9463
+ if (typeof value === "string") return value.trim();
9464
+ if (isRecord3(value)) return stringField(value, "path");
9465
+ return "";
9466
+ }
9467
+ function extractionEntries(record, camelKey, snakeKey) {
9468
+ const value = record[camelKey] ?? record[snakeKey];
9469
+ if (Array.isArray(value)) return value;
9470
+ if (!isRecord3(value)) return [];
9471
+ return Object.entries(value).map(
9472
+ ([name, entry]) => isRecord3(entry) ? { name, ...entry } : { name }
9473
+ );
9474
+ }
9475
+ function printExtractions(label, entries) {
9476
+ if (!entries.length) return;
9477
+ console.log(` ${label}:`);
9478
+ for (const entry of entries) {
9479
+ if (!isRecord3(entry)) continue;
9480
+ const name = stringField(entry, "name");
9481
+ const expression = stringField(entry, "expression");
9482
+ const details = recordField(entry, "details");
9483
+ const rawToolOutputPaths = arrayField(details, "rawToolOutputPaths", "raw_tool_output_paths").map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
9484
+ const candidatePaths = arrayField(details, "candidatePaths", "candidate_paths").map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
9485
+ if (!name || !expression) continue;
9486
+ const paths = candidatePaths.length ? candidatePaths : rawToolOutputPaths;
9487
+ const pathSuffix = paths.length ? ` from ${paths.join(", ")}` : "";
9488
+ console.log(` - ${name}: ${expression}${pathSuffix}`);
9489
+ }
8817
9490
  }
8818
9491
  function printToolCost(input) {
8819
9492
  const { cost, billingSource, deeplineCredits, deeplineUsdPerPricingUnit } = input;
@@ -8881,6 +9554,17 @@ function samplePayload(samples, key) {
8881
9554
  function commandEnvelopeFromRawResponse(rawResponse) {
8882
9555
  return isRecord3(rawResponse) ? { ...rawResponse } : { status: "completed", result: rawResponse };
8883
9556
  }
9557
+ function listExtractorPathsFromUsageGuidance(tool) {
9558
+ const toolExecutionResult = tool.usageGuidance?.toolExecutionResult;
9559
+ const extractedLists = Array.isArray(toolExecutionResult?.extractedLists) ? toolExecutionResult.extractedLists : isRecord3(toolExecutionResult?.extractedLists) ? Object.values(toolExecutionResult.extractedLists) : [];
9560
+ return extractedLists.flatMap((entry) => {
9561
+ const paths = entry.details?.candidatePaths ?? entry.details?.rawToolOutputPaths;
9562
+ if (!Array.isArray(paths)) return [];
9563
+ return paths.map(
9564
+ (path) => path.trim().replace(/^toolExecutionResult\.toolOutput\.raw\.?/, "").replace(/^\./, "")
9565
+ ).filter(Boolean);
9566
+ });
9567
+ }
8884
9568
  function isPlayTool(tool) {
8885
9569
  const provider = typeof tool.provider === "string" ? tool.provider : "";
8886
9570
  return provider === "deepline_native" && Boolean(recordField(tool, "playExpansion", "play_expansion"));
@@ -8954,6 +9638,7 @@ function parseExecuteOptions(args) {
8954
9638
  const params = {};
8955
9639
  let outputFormat = "auto";
8956
9640
  let noPreview = false;
9641
+ let fullOutput = false;
8957
9642
  for (let index = 1; index < args.length; index += 1) {
8958
9643
  const arg = args[index];
8959
9644
  if ((arg === "--param" || arg === "-p") && args[index + 1]) {
@@ -8980,6 +9665,7 @@ function parseExecuteOptions(args) {
8980
9665
  }
8981
9666
  if (arg === "--full-output") {
8982
9667
  outputFormat = "json";
9668
+ fullOutput = true;
8983
9669
  continue;
8984
9670
  }
8985
9671
  if (arg === "--no-preview") {
@@ -8988,7 +9674,7 @@ function parseExecuteOptions(args) {
8988
9674
  }
8989
9675
  throw new Error(`Unknown option: ${arg}`);
8990
9676
  }
8991
- return { toolId, params, outputFormat, noPreview };
9677
+ return { toolId, params, outputFormat, noPreview, fullOutput };
8992
9678
  }
8993
9679
  function safeFileStem(value) {
8994
9680
  return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
@@ -9002,9 +9688,9 @@ function powerShellQuote(value) {
9002
9688
  function seedToolListScript(input) {
9003
9689
  const stem = safeFileStem(input.toolId);
9004
9690
  const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
9005
- const scriptDir = (0, import_node_fs9.mkdtempSync)((0, import_node_path11.join)((0, import_node_os7.tmpdir)(), "deepline-workflow-seed-"));
9006
- (0, import_node_fs9.chmodSync)(scriptDir, 448);
9007
- const scriptPath = (0, import_node_path11.join)(scriptDir, fileName);
9691
+ const scriptDir = (0, import_node_fs10.mkdtempSync)((0, import_node_path12.join)((0, import_node_os7.tmpdir)(), "deepline-workflow-seed-"));
9692
+ (0, import_node_fs10.chmodSync)(scriptDir, 448);
9693
+ const scriptPath = (0, import_node_path12.join)(scriptDir, fileName);
9008
9694
  const projectDir = `deepline/projects/${stem}-workflow`;
9009
9695
  const playName = `${stem}-workflow`;
9010
9696
  const sampleRows = input.rows.length > 0 ? `${JSON.stringify(input.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
@@ -9020,7 +9706,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
9020
9706
  description: ${JSON.stringify(`Seed ${input.toolId} rows for workflow expansion.`)},
9021
9707
  });
9022
9708
 
9023
- const list = Object.values(result.lists)[0];
9709
+ const list = Object.values(result.extractedLists)[0];
9024
9710
  const rows = (list?.get() ?? []).slice(0, 100);
9025
9711
  // ${sampleRows}
9026
9712
  // columns: ${columns}
@@ -9037,7 +9723,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
9037
9723
  };
9038
9724
  });
9039
9725
  `;
9040
- (0, import_node_fs9.writeFileSync)(scriptPath, script, { encoding: "utf-8", mode: 384 });
9726
+ (0, import_node_fs10.writeFileSync)(scriptPath, script, { encoding: "utf-8", mode: 384 });
9041
9727
  return {
9042
9728
  path: scriptPath,
9043
9729
  projectDir,
@@ -9048,7 +9734,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
9048
9734
  function buildToolExecuteBaseEnvelope(input) {
9049
9735
  const envelope = commandEnvelopeFromRawResponse(input.rawResponse);
9050
9736
  const summaryEntries = Object.entries(input.summary);
9051
- const output = input.listConversion ? {
9737
+ const outputPreview = input.listConversion ? {
9052
9738
  kind: "list",
9053
9739
  rowCount: input.listConversion.rows.length,
9054
9740
  columns: Object.keys(input.listConversion.rows[0] ?? {}),
@@ -9059,6 +9745,7 @@ function buildToolExecuteBaseEnvelope(input) {
9059
9745
  kind: summaryEntries.length > 0 ? "object" : "raw",
9060
9746
  summary: input.summary
9061
9747
  };
9748
+ const envelopeHasCanonicalOutput = isRecord3(envelope.toolExecutionResult) && isRecord3(envelope.toolExecutionResult.toolOutput) && Object.prototype.hasOwnProperty.call(envelope.toolExecutionResult.toolOutput, "raw");
9062
9749
  const actions = input.listConversion ? [
9063
9750
  {
9064
9751
  label: "next",
@@ -9067,7 +9754,7 @@ function buildToolExecuteBaseEnvelope(input) {
9067
9754
  ] : [];
9068
9755
  return {
9069
9756
  ...envelope,
9070
- output,
9757
+ ...envelopeHasCanonicalOutput ? { output_preview: outputPreview } : { output: outputPreview },
9071
9758
  ...summaryEntries.length > 0 ? { summary: input.summary } : {},
9072
9759
  next: input.listConversion ? {
9073
9760
  expandToPlay: "Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
@@ -9120,8 +9807,12 @@ async function executeTool(args) {
9120
9807
  throw error;
9121
9808
  }
9122
9809
  const rawResponse = await client.executeTool(parsed.toolId, parsed.params);
9810
+ if (parsed.fullOutput) {
9811
+ printJson(rawResponse);
9812
+ return 0;
9813
+ }
9123
9814
  const listConversion = tryConvertToList(rawResponse, {
9124
- listExtractorPaths: metadata.listExtractorPaths ?? []
9815
+ listExtractorPaths: listExtractorPathsFromUsageGuidance(metadata)
9125
9816
  });
9126
9817
  const summary = extractSummaryFields(rawResponse);
9127
9818
  const baseEnvelope = buildToolExecuteBaseEnvelope({
@@ -9251,9 +9942,9 @@ async function executeTool(args) {
9251
9942
  }
9252
9943
 
9253
9944
  // src/cli/commands/update.ts
9254
- var import_node_child_process2 = require("child_process");
9255
- var import_node_fs10 = require("fs");
9256
- var import_node_path12 = require("path");
9945
+ var import_node_child_process = require("child_process");
9946
+ var import_node_fs11 = require("fs");
9947
+ var import_node_path13 = require("path");
9257
9948
  function posixShellQuote(value) {
9258
9949
  return `'${value.replace(/'/g, `'\\''`)}'`;
9259
9950
  }
@@ -9272,19 +9963,19 @@ function buildSourceUpdateCommand(sourceRoot) {
9272
9963
  return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
9273
9964
  }
9274
9965
  function findRepoBackedSdkRoot(startPath) {
9275
- let current = (0, import_node_path12.resolve)(startPath);
9966
+ let current = (0, import_node_path13.resolve)(startPath);
9276
9967
  while (true) {
9277
- if ((0, import_node_fs10.existsSync)((0, import_node_path12.join)(current, "sdk", "package.json")) && (0, import_node_fs10.existsSync)((0, import_node_path12.join)(current, "sdk", "bin", "deepline-dev.ts"))) {
9968
+ if ((0, import_node_fs11.existsSync)((0, import_node_path13.join)(current, "sdk", "package.json")) && (0, import_node_fs11.existsSync)((0, import_node_path13.join)(current, "sdk", "bin", "deepline-dev.ts"))) {
9278
9969
  return current;
9279
9970
  }
9280
- const parent = (0, import_node_path12.dirname)(current);
9971
+ const parent = (0, import_node_path13.dirname)(current);
9281
9972
  if (parent === current) return null;
9282
9973
  current = parent;
9283
9974
  }
9284
9975
  }
9285
9976
  function resolveUpdatePlan() {
9286
- const entrypoint = process.argv[1] ? (0, import_node_path12.resolve)(process.argv[1]) : "";
9287
- const sourceRoot = entrypoint ? findRepoBackedSdkRoot((0, import_node_path12.dirname)(entrypoint)) : null;
9977
+ const entrypoint = process.argv[1] ? (0, import_node_path13.resolve)(process.argv[1]) : "";
9978
+ const sourceRoot = entrypoint ? findRepoBackedSdkRoot((0, import_node_path13.dirname)(entrypoint)) : null;
9288
9979
  if (sourceRoot) {
9289
9980
  return {
9290
9981
  kind: "source",
@@ -9303,7 +9994,7 @@ function resolveUpdatePlan() {
9303
9994
  }
9304
9995
  function runCommand(command, args) {
9305
9996
  return new Promise((resolveExitCode) => {
9306
- const child = (0, import_node_child_process2.spawn)(command, args, {
9997
+ const child = (0, import_node_child_process.spawn)(command, args, {
9307
9998
  stdio: "inherit",
9308
9999
  shell: process.platform === "win32",
9309
10000
  env: process.env
@@ -9376,10 +10067,10 @@ Examples:
9376
10067
  }
9377
10068
 
9378
10069
  // src/cli/skills-sync.ts
9379
- var import_node_child_process3 = require("child_process");
9380
- var import_node_fs11 = require("fs");
10070
+ var import_node_child_process2 = require("child_process");
10071
+ var import_node_fs12 = require("fs");
9381
10072
  var import_node_os8 = require("os");
9382
- var import_node_path13 = require("path");
10073
+ var import_node_path14 = require("path");
9383
10074
  var CHECK_TIMEOUT_MS2 = 3e3;
9384
10075
  var SDK_SKILL_NAME = "deepline-sdk";
9385
10076
  var SKILL_AGENTS = ["codex", "claude-code", "cursor"];
@@ -9390,23 +10081,59 @@ function shouldSkipSkillsSync() {
9390
10081
  }
9391
10082
  function sdkSkillsVersionPath(baseUrl) {
9392
10083
  const home = process.env.HOME?.trim() || (0, import_node_os8.homedir)();
9393
- return (0, import_node_path13.join)(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
10084
+ return (0, import_node_path14.join)(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
9394
10085
  }
9395
10086
  function readLocalSkillsVersion(baseUrl) {
9396
10087
  const path = sdkSkillsVersionPath(baseUrl);
9397
- if (!(0, import_node_fs11.existsSync)(path)) return "";
10088
+ if (!(0, import_node_fs12.existsSync)(path)) return "";
9398
10089
  try {
9399
- return (0, import_node_fs11.readFileSync)(path, "utf-8").trim();
10090
+ return (0, import_node_fs12.readFileSync)(path, "utf-8").trim();
9400
10091
  } catch {
9401
10092
  return "";
9402
10093
  }
9403
10094
  }
9404
10095
  function writeLocalSkillsVersion(baseUrl, version) {
9405
10096
  const path = sdkSkillsVersionPath(baseUrl);
9406
- (0, import_node_fs11.mkdirSync)((0, import_node_path13.dirname)(path), { recursive: true });
9407
- (0, import_node_fs11.writeFileSync)(path, `${version}
10097
+ (0, import_node_fs12.mkdirSync)((0, import_node_path14.dirname)(path), { recursive: true });
10098
+ (0, import_node_fs12.writeFileSync)(path, `${version}
9408
10099
  `, "utf-8");
9409
10100
  }
10101
+ function installedSdkSkillHasStalePositionalExecuteExamples() {
10102
+ const home = process.env.HOME?.trim() || (0, import_node_os8.homedir)();
10103
+ const roots = [
10104
+ (0, import_node_path14.join)(home, ".claude", "skills", SDK_SKILL_NAME),
10105
+ (0, import_node_path14.join)(home, ".agents", "skills", SDK_SKILL_NAME)
10106
+ ];
10107
+ const staleMarkers = [
10108
+ "ctx.tools.execute(key",
10109
+ "ctx.tools.execute('",
10110
+ 'ctx.tools.execute("',
10111
+ "rowCtx.tools.execute('",
10112
+ 'rowCtx.tools.execute("'
10113
+ ];
10114
+ const scan = (dir) => {
10115
+ for (const entry of (0, import_node_fs12.readdirSync)(dir)) {
10116
+ const path = (0, import_node_path14.join)(dir, entry);
10117
+ const stat3 = (0, import_node_fs12.statSync)(path);
10118
+ if (stat3.isDirectory()) {
10119
+ if (scan(path)) return true;
10120
+ continue;
10121
+ }
10122
+ if (!entry.endsWith(".md")) continue;
10123
+ const text = (0, import_node_fs12.readFileSync)(path, "utf-8");
10124
+ if (staleMarkers.some((marker) => text.includes(marker))) return true;
10125
+ }
10126
+ return false;
10127
+ };
10128
+ for (const root of roots) {
10129
+ try {
10130
+ if ((0, import_node_fs12.existsSync)(root) && scan(root)) return true;
10131
+ } catch {
10132
+ continue;
10133
+ }
10134
+ }
10135
+ return false;
10136
+ }
9410
10137
  async function fetchSkillsUpdate(baseUrl, localVersion) {
9411
10138
  const controller = new AbortController();
9412
10139
  const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
@@ -9442,7 +10169,7 @@ function buildSkillsInstallArgs(baseUrl) {
9442
10169
  "skills",
9443
10170
  "add",
9444
10171
  packageUrl,
9445
- "--agents",
10172
+ "--agent",
9446
10173
  ...SKILL_AGENTS,
9447
10174
  "--global",
9448
10175
  "--yes",
@@ -9458,7 +10185,7 @@ function buildBunxSkillsInstallArgs(baseUrl) {
9458
10185
  "skills",
9459
10186
  "add",
9460
10187
  packageUrl,
9461
- "--agents",
10188
+ "--agent",
9462
10189
  ...SKILL_AGENTS,
9463
10190
  "--global",
9464
10191
  "--yes",
@@ -9468,7 +10195,7 @@ function buildBunxSkillsInstallArgs(baseUrl) {
9468
10195
  ];
9469
10196
  }
9470
10197
  function hasCommand(command) {
9471
- const result = (0, import_node_child_process3.spawnSync)(command, ["--version"], {
10198
+ const result = (0, import_node_child_process2.spawnSync)(command, ["--version"], {
9472
10199
  stdio: "ignore",
9473
10200
  shell: process.platform === "win32"
9474
10201
  });
@@ -9498,8 +10225,8 @@ function resolveSkillsInstallCommands(baseUrl) {
9498
10225
  return [npxInstall];
9499
10226
  }
9500
10227
  function runOneSkillsInstall(install) {
9501
- return new Promise((resolve10) => {
9502
- const child = (0, import_node_child_process3.spawn)(install.command, install.args, {
10228
+ return new Promise((resolve11) => {
10229
+ const child = (0, import_node_child_process2.spawn)(install.command, install.args, {
9503
10230
  stdio: ["ignore", "ignore", "pipe"],
9504
10231
  env: process.env
9505
10232
  });
@@ -9508,7 +10235,7 @@ function runOneSkillsInstall(install) {
9508
10235
  stderr += chunk.toString("utf-8");
9509
10236
  });
9510
10237
  child.on("error", (error) => {
9511
- resolve10({
10238
+ resolve11({
9512
10239
  ok: false,
9513
10240
  detail: `failed to start ${install.command}: ${error.message}`,
9514
10241
  manualCommand: install.manualCommand
@@ -9516,11 +10243,11 @@ function runOneSkillsInstall(install) {
9516
10243
  });
9517
10244
  child.on("close", (code) => {
9518
10245
  if (code === 0) {
9519
- resolve10({ ok: true, detail: "", manualCommand: install.manualCommand });
10246
+ resolve11({ ok: true, detail: "", manualCommand: install.manualCommand });
9520
10247
  return;
9521
10248
  }
9522
10249
  const detail = stderr.trim();
9523
- resolve10({
10250
+ resolve11({
9524
10251
  ok: false,
9525
10252
  detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
9526
10253
  manualCommand: install.manualCommand
@@ -9559,10 +10286,19 @@ async function syncSdkSkillsIfNeeded(baseUrl) {
9559
10286
  attemptedSync = true;
9560
10287
  const localVersion = readLocalSkillsVersion(baseUrl);
9561
10288
  const update = await fetchSkillsUpdate(baseUrl, localVersion);
9562
- if (!update?.needsUpdate || !update.remoteVersion) return;
10289
+ const hasStaleInstalledSkill = installedSdkSkillHasStalePositionalExecuteExamples();
10290
+ if (!update?.needsUpdate && !hasStaleInstalledSkill || !update?.remoteVersion) {
10291
+ return;
10292
+ }
9563
10293
  writeSdkSkillsStatusLine("SDK skills changed; syncing deepline-sdk skill...");
9564
10294
  const installed = await runSkillsInstall(baseUrl);
9565
10295
  if (!installed) return;
10296
+ if (installedSdkSkillHasStalePositionalExecuteExamples()) {
10297
+ process.stderr.write(
10298
+ "SDK skills sync completed, but installed deepline-sdk docs still contain stale positional ctx.tools.execute examples.\n"
10299
+ );
10300
+ return;
10301
+ }
9566
10302
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
9567
10303
  writeSdkSkillsStatusLine("SDK skills are up to date.");
9568
10304
  }