deepline 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -243,7 +243,7 @@ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStar
243
243
  }
244
244
 
245
245
  // src/version.ts
246
- var SDK_VERSION = "0.1.20";
246
+ var SDK_VERSION = "0.1.22";
247
247
  var SDK_API_CONTRACT = "2026-05-runs-v2";
248
248
 
249
249
  // ../shared_libs/play-runtime/coordinator-headers.ts
@@ -326,7 +326,7 @@ var HttpClient = class {
326
326
  const response = await fetch(candidateUrl, {
327
327
  method,
328
328
  headers,
329
- body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
329
+ body: options?.formData !== void 0 ? options.formData : options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
330
330
  signal: controller.signal
331
331
  });
332
332
  clearTimeout(timeoutId);
@@ -445,6 +445,13 @@ var HttpClient = class {
445
445
  headers
446
446
  });
447
447
  }
448
+ async postFormData(path, formData, headers) {
449
+ return this.request(path, {
450
+ method: "POST",
451
+ formData,
452
+ headers
453
+ });
454
+ }
448
455
  /**
449
456
  * Send a DELETE request.
450
457
  *
@@ -528,7 +535,7 @@ function isRecord(value) {
528
535
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
529
536
  }
530
537
  function normalizePlayStatus(raw) {
531
- const status = typeof raw.status === "string" ? raw.status : typeof raw.temporalStatus === "string" ? mapLegacyTemporalStatus(raw.temporalStatus) : "running";
538
+ const status = typeof raw.status === "string" ? raw.status : "running";
532
539
  const runId = typeof raw.runId === "string" ? raw.runId : typeof raw.workflowId === "string" ? raw.workflowId : "";
533
540
  return {
534
541
  ...raw,
@@ -536,22 +543,86 @@ function normalizePlayStatus(raw) {
536
543
  status
537
544
  };
538
545
  }
539
- function mapLegacyTemporalStatus(status) {
540
- switch (status.trim().toUpperCase()) {
541
- case "PENDING":
542
- return "queued";
543
- case "COMPLETED":
544
- return "completed";
545
- case "FAILED":
546
- return "failed";
547
- case "CANCELLED":
548
- case "TERMINATED":
549
- case "TIMED_OUT":
550
- return "cancelled";
551
- case "RUNNING":
552
- default:
553
- return "running";
546
+ function decodeBase64Bytes(value) {
547
+ const binary = atob(value);
548
+ const bytes = new Uint8Array(binary.length);
549
+ for (let index = 0; index < binary.length; index += 1) {
550
+ bytes[index] = binary.charCodeAt(index);
551
+ }
552
+ return bytes;
553
+ }
554
+ function readStringArray(value) {
555
+ return Array.isArray(value) ? value.filter((line) => typeof line === "string") : [];
556
+ }
557
+ function getPlayLiveEventPayload(event) {
558
+ return event.payload && typeof event.payload === "object" ? event.payload : {};
559
+ }
560
+ function normalizeLiveStatus(value) {
561
+ if (value === "queued" || value === "running" || value === "waiting" || value === "completed" || value === "failed" || value === "cancelled") {
562
+ return value;
563
+ }
564
+ return null;
565
+ }
566
+ function updatePlayLiveStatusState(state, event) {
567
+ const payload = getPlayLiveEventPayload(event);
568
+ if (event.type === "play.run.log") {
569
+ state.logs.push(...readStringArray(payload.lines));
570
+ return null;
571
+ }
572
+ if (event.type !== "play.run.snapshot" && event.type !== "play.run.status" && event.type !== "play.run.final_status") {
573
+ return null;
574
+ }
575
+ const runId = typeof payload.runId === "string" && payload.runId ? payload.runId : state.runId;
576
+ const status = normalizeLiveStatus(payload.status) ?? state.status;
577
+ const logs = readStringArray(payload.logs);
578
+ if (logs.length > 0 || event.type === "play.run.snapshot") {
579
+ state.logs = logs;
580
+ }
581
+ if ("result" in payload) {
582
+ state.result = payload.result;
583
+ }
584
+ if (typeof payload.error === "string" && payload.error.trim()) {
585
+ state.error = payload.error;
554
586
  }
587
+ state.runId = runId;
588
+ state.status = status;
589
+ const progressRecord = payload.progress && typeof payload.progress === "object" && !Array.isArray(payload.progress) ? payload.progress : {};
590
+ const next = {
591
+ ...payload,
592
+ runId,
593
+ status,
594
+ progress: {
595
+ ...progressRecord,
596
+ status: typeof progressRecord.status === "string" ? progressRecord.status : status,
597
+ logs: state.logs,
598
+ ...state.error ? { error: state.error } : {}
599
+ },
600
+ ..."result" in state ? { result: state.result } : {}
601
+ };
602
+ state.latest = next;
603
+ return next;
604
+ }
605
+ function playRunResultFromStatus(status, startedAt, fallbackRunId) {
606
+ return {
607
+ success: status.status === "completed",
608
+ runId: status.runId || fallbackRunId,
609
+ result: status.result,
610
+ logs: status.progress?.logs ?? [],
611
+ durationMs: Date.now() - startedAt,
612
+ error: status.progress?.error ?? (status.status !== "completed" ? status.status : void 0)
613
+ };
614
+ }
615
+ function playRunStatusFromState(state) {
616
+ return {
617
+ runId: state.runId,
618
+ status: state.status,
619
+ progress: {
620
+ status: state.status,
621
+ logs: state.logs,
622
+ ...state.error ? { error: state.error } : {}
623
+ },
624
+ ..."result" in state ? { result: state.result } : {}
625
+ };
555
626
  }
556
627
  var DeeplineClient = class {
557
628
  http;
@@ -662,7 +733,7 @@ var DeeplineClient = class {
662
733
  /**
663
734
  * Search available tools using Deepline's ranked backend search.
664
735
  *
665
- * This is the same discovery surface used by the legacy CLI: it ranks across
736
+ * This is the same discovery surface used by the CLI: it ranks across
666
737
  * tool metadata, categories, agent guidance, and input schema fields.
667
738
  */
668
739
  async searchTools(options = {}) {
@@ -755,7 +826,7 @@ var DeeplineClient = class {
755
826
  * `progress.logs`; they are not part of the user output object.
756
827
  *
757
828
  * @param request - Play run configuration (name, code, input, etc.)
758
- * @returns Workflow metadata including the `workflowId` for status polling
829
+ * @returns Run metadata including the public `workflowId`
759
830
  *
760
831
  * @example
761
832
  * ```typescript
@@ -1004,9 +1075,34 @@ var DeeplineClient = class {
1004
1075
  * ```
1005
1076
  */
1006
1077
  async stagePlayFiles(files) {
1007
- const response = await this.http.post(
1078
+ const formData = new FormData();
1079
+ formData.set(
1080
+ "metadata",
1081
+ JSON.stringify({
1082
+ files: files.map((file, index) => ({
1083
+ index,
1084
+ logicalPath: file.logicalPath,
1085
+ contentHash: file.contentHash,
1086
+ contentType: file.contentType,
1087
+ bytes: file.bytes
1088
+ }))
1089
+ })
1090
+ );
1091
+ for (const [index, file] of files.entries()) {
1092
+ const bytes = decodeBase64Bytes(file.contentBase64);
1093
+ const body = bytes.buffer.slice(
1094
+ bytes.byteOffset,
1095
+ bytes.byteOffset + bytes.byteLength
1096
+ );
1097
+ formData.set(
1098
+ `file:${index}`,
1099
+ new Blob([body], { type: file.contentType }),
1100
+ file.logicalPath
1101
+ );
1102
+ }
1103
+ const response = await this.http.postFormData(
1008
1104
  "/api/v2/plays/files/stage",
1009
- { files }
1105
+ formData
1010
1106
  );
1011
1107
  return response.files;
1012
1108
  }
@@ -1022,9 +1118,6 @@ var DeeplineClient = class {
1022
1118
  * Internal/advanced primitive. Public callers should usually prefer
1023
1119
  * {@link runPlay}, {@link PlayJob.get}, or `deepline play run --watch`.
1024
1120
  *
1025
- * Poll this method until `status` reaches a terminal state:
1026
- * `'completed'`, `'failed'`, or `'cancelled'`.
1027
- *
1028
1121
  * @param workflowId - Play-run id from {@link startPlayRun}
1029
1122
  * @returns Current status with progress logs and partial results
1030
1123
  *
@@ -1035,41 +1128,22 @@ var DeeplineClient = class {
1035
1128
  * console.log(`Logs: ${status.progress?.logs.length ?? 0} lines`);
1036
1129
  * ```
1037
1130
  */
1038
- async getPlayStatus(workflowId) {
1039
- const response = await this.http.get(
1040
- `/api/v2/plays/run/${encodeURIComponent(workflowId)}`
1041
- );
1042
- return normalizePlayStatus(response);
1043
- }
1044
- /**
1045
- * Get the lightweight tail-polling status for a play execution.
1046
- *
1047
- * This is intentionally smaller than {@link getPlayStatus}: it returns the
1048
- * fields needed for CLI log tailing while the run is in flight, without
1049
- * forcing the API to rebuild final result views on every poll. Call
1050
- * {@link getPlayStatus} once after a terminal state for the full result.
1051
- */
1052
- async getPlayTailStatus(workflowId, options) {
1053
- const params = new URLSearchParams({ mode: "tail" });
1054
- if (typeof options?.afterLogIndex === "number") {
1055
- params.set("afterLogIndex", String(options.afterLogIndex));
1056
- }
1057
- if (typeof options?.waitMs === "number") {
1058
- params.set("waitMs", String(options.waitMs));
1059
- }
1060
- if (options?.terminalOnly) {
1061
- params.set("terminalOnly", "true");
1131
+ async getPlayStatus(workflowId, options) {
1132
+ const params = new URLSearchParams();
1133
+ if (options?.billing === false) {
1134
+ params.set("billing", "false");
1062
1135
  }
1136
+ const query = params.size > 0 ? `?${params.toString()}` : "";
1063
1137
  const response = await this.http.get(
1064
- `/api/v2/plays/run/${encodeURIComponent(workflowId)}?${params.toString()}`
1138
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}${query}`
1065
1139
  );
1066
1140
  return normalizePlayStatus(response);
1067
1141
  }
1068
1142
  /**
1069
1143
  * Stream semantic play-run events using the same SSE feed as the dashboard.
1070
1144
  *
1071
- * Consumers should still keep a polling fallback: SSE is the fast live-update
1072
- * transport, while the status endpoints remain the authoritative recovery path.
1145
+ * The server emits a canonical `play.run.snapshot` event first for every
1146
+ * connection, then incremental live events until terminal state or reconnect.
1073
1147
  */
1074
1148
  async *streamPlayRunEvents(workflowId, options) {
1075
1149
  const headers = options?.lastEventId && options.lastEventId.trim() ? { "Last-Event-ID": options.lastEventId.trim() } : void 0;
@@ -1089,7 +1163,7 @@ var DeeplineClient = class {
1089
1163
  *
1090
1164
  * Sends a stop request for the run.
1091
1165
  *
1092
- * @param workflowId - Temporal workflow ID to cancel
1166
+ * @param workflowId - Public Deepline play-run id to cancel
1093
1167
  *
1094
1168
  * @example
1095
1169
  * ```typescript
@@ -1105,7 +1179,7 @@ var DeeplineClient = class {
1105
1179
  /**
1106
1180
  * Stop a running play execution, including open HITL waits.
1107
1181
  *
1108
- * @param workflowId - Temporal workflow ID to stop
1182
+ * @param workflowId - Public Deepline play-run id to stop
1109
1183
  * @param options.reason - Optional audit/debug reason
1110
1184
  */
1111
1185
  async stopPlay(workflowId, options) {
@@ -1177,32 +1251,42 @@ var DeeplineClient = class {
1177
1251
  );
1178
1252
  return response.runs ?? [];
1179
1253
  }
1180
- /**
1181
- * Fetch the lightweight tail status for a run using the public runs resource model.
1182
- *
1183
- * This is the SDK equivalent of:
1184
- *
1185
- * ```bash
1186
- * deepline runs tail <run-id> --json
1187
- * ```
1188
- */
1254
+ /** Read the canonical run stream and return the latest run snapshot. */
1189
1255
  async tailRun(runId, options) {
1190
- const afterLogIndex = typeof options?.afterLogIndex === "number" ? options.afterLogIndex : typeof options?.cursor === "number" ? options.cursor : typeof options?.cursor === "string" && options.cursor.trim() ? Number(options.cursor) : void 0;
1191
- const params = new URLSearchParams();
1192
- if (Number.isFinite(afterLogIndex)) {
1193
- params.set("afterLogIndex", String(Number(afterLogIndex)));
1256
+ const state = {
1257
+ runId,
1258
+ status: "running",
1259
+ logs: [],
1260
+ latest: null
1261
+ };
1262
+ let terminal = false;
1263
+ for await (const event of this.streamPlayRunEvents(runId, {
1264
+ mode: "cli",
1265
+ signal: options?.signal
1266
+ })) {
1267
+ const status = updatePlayLiveStatusState(state, event);
1268
+ if (!status) {
1269
+ continue;
1270
+ }
1271
+ terminal = TERMINAL_PLAY_STATUSES.has(status.status);
1272
+ if (terminal) {
1273
+ break;
1274
+ }
1194
1275
  }
1195
- if (typeof options?.waitMs === "number") {
1196
- params.set("waitMs", String(options.waitMs));
1276
+ if (terminal && state.latest) {
1277
+ return await this.getRunStatus(state.latest.runId || runId).catch(
1278
+ () => state.latest ?? playRunStatusFromState(state)
1279
+ );
1197
1280
  }
1198
- if (options?.terminalOnly) {
1199
- params.set("terminalOnly", "true");
1281
+ if (state.latest) {
1282
+ return state.latest;
1200
1283
  }
1201
- const suffix = params.toString() ? `?${params.toString()}` : "";
1202
- const response = await this.http.get(
1203
- `/api/v2/runs/${encodeURIComponent(runId)}/tail${suffix}`
1284
+ throw new DeeplineError(
1285
+ `Run stream for ${runId} ended before the initial snapshot.`,
1286
+ void 0,
1287
+ "PLAY_RUN_STREAM_EMPTY",
1288
+ { runId }
1204
1289
  );
1205
- return normalizePlayStatus(response);
1206
1290
  }
1207
1291
  /**
1208
1292
  * Fetch persisted logs for a run using the public runs resource model.
@@ -1359,11 +1443,11 @@ var DeeplineClient = class {
1359
1443
  // Plays — high-level orchestration
1360
1444
  // ——————————————————————————————————————————————————————————
1361
1445
  /**
1362
- * Run a play end-to-end: submit, poll until terminal, return result.
1446
+ * Run a play end-to-end: submit, stream until terminal, return result.
1363
1447
  *
1364
1448
  * This is the highest-level play execution method. It submits the play,
1365
- * polls for status updates, and returns a structured result with logs
1366
- * and timing. Supports cancellation via `AbortSignal`.
1449
+ * reads the canonical run stream for status updates, and returns a structured
1450
+ * result with logs and timing. Supports cancellation via `AbortSignal`.
1367
1451
  *
1368
1452
  * @param code - Source string fallback; pass the bundled artifact in `options.artifact`
1369
1453
  * @param csvPath - Input CSV path, or `null`
@@ -1379,7 +1463,6 @@ var DeeplineClient = class {
1379
1463
  * const logs = status.progress?.logs ?? [];
1380
1464
  * console.log(`[${status.status}] ${logs.length} log lines`);
1381
1465
  * },
1382
- * pollIntervalMs: 1000,
1383
1466
  * });
1384
1467
  *
1385
1468
  * if (result.success) {
@@ -1410,33 +1493,53 @@ var DeeplineClient = class {
1410
1493
  packagedFiles: options?.packagedFiles,
1411
1494
  force: options?.force
1412
1495
  });
1413
- const pollInterval = options?.pollIntervalMs ?? 500;
1414
1496
  const start = Date.now();
1415
- while (true) {
1497
+ const state = {
1498
+ runId: workflowId,
1499
+ status: "running",
1500
+ logs: [],
1501
+ latest: null
1502
+ };
1503
+ if (options?.signal?.aborted) {
1504
+ await this.cancelPlay(workflowId);
1505
+ return {
1506
+ success: false,
1507
+ runId: workflowId,
1508
+ logs: [],
1509
+ durationMs: Date.now() - start,
1510
+ error: "Cancelled by user"
1511
+ };
1512
+ }
1513
+ for await (const event of this.streamPlayRunEvents(workflowId, {
1514
+ mode: "cli",
1515
+ signal: options?.signal
1516
+ })) {
1416
1517
  if (options?.signal?.aborted) {
1417
1518
  await this.cancelPlay(workflowId);
1418
1519
  return {
1419
1520
  success: false,
1420
1521
  runId: workflowId,
1421
- logs: [],
1522
+ logs: state.logs,
1422
1523
  durationMs: Date.now() - start,
1423
1524
  error: "Cancelled by user"
1424
1525
  };
1425
1526
  }
1426
- const status = await this.getPlayStatus(workflowId);
1527
+ const status = updatePlayLiveStatusState(state, event);
1528
+ if (!status) {
1529
+ continue;
1530
+ }
1427
1531
  options?.onProgress?.(status);
1428
1532
  if (TERMINAL_PLAY_STATUSES.has(status.status)) {
1429
- return {
1430
- success: status.status === "completed",
1431
- runId: status.runId || workflowId,
1432
- result: status.result,
1433
- logs: status.progress?.logs ?? [],
1434
- durationMs: Date.now() - start,
1435
- error: status.progress?.error ?? (status.status !== "completed" ? status.status : void 0)
1436
- };
1533
+ const finalStatus = await this.getPlayStatus(status.runId || workflowId).catch(() => status);
1534
+ return playRunResultFromStatus(finalStatus, start, workflowId);
1437
1535
  }
1438
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
1439
1536
  }
1537
+ throw new DeeplineError(
1538
+ `Run stream for ${workflowId} ended before the run reached a terminal state.`,
1539
+ void 0,
1540
+ "PLAY_RUN_STREAM_ENDED",
1541
+ { runId: workflowId, workflowId }
1542
+ );
1440
1543
  }
1441
1544
  // ——————————————————————————————————————————————————————————
1442
1545
  // Health
@@ -1510,18 +1613,24 @@ async function enforceSdkCompatibility(baseUrl) {
1510
1613
  }
1511
1614
 
1512
1615
  // src/cli/commands/auth.ts
1513
- import { writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
1616
+ import { writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
1514
1617
  import { hostname } from "os";
1515
- import { dirname as dirname2 } from "path";
1618
+ import { dirname as dirname3 } from "path";
1516
1619
 
1517
1620
  // src/cli/utils.ts
1518
- import { readFileSync as readFileSync2 } from "fs";
1621
+ import {
1622
+ existsSync as existsSync2,
1623
+ mkdirSync as mkdirSync2,
1624
+ readFileSync as readFileSync2,
1625
+ writeFileSync as writeFileSync2
1626
+ } from "fs";
1519
1627
  import { mkdir, writeFile } from "fs/promises";
1520
1628
  import { homedir as homedir2 } from "os";
1521
- import { join as join2, resolve as resolve2 } from "path";
1522
- import { execSync } from "child_process";
1629
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
1630
+ import { execFileSync, spawnSync } from "child_process";
1523
1631
  import { parse } from "csv-parse/sync";
1524
1632
  import { stringify } from "csv-stringify/sync";
1633
+ var BROWSER_FOCUS_COOLDOWN_MS = 3e4;
1525
1634
  function getAuthedHttpClient() {
1526
1635
  const config = resolveConfig();
1527
1636
  return { config, http: new HttpClient(config) };
@@ -1533,12 +1642,215 @@ async function writeOutputFile(filename, content) {
1533
1642
  await writeFile(fullPath, content, "utf-8");
1534
1643
  return fullPath;
1535
1644
  }
1645
+ function browserFocusStateFile() {
1646
+ const homeDir = process.env.HOME || homedir2();
1647
+ return join2(
1648
+ homeDir,
1649
+ ".local",
1650
+ "deepline",
1651
+ "runtime",
1652
+ "state",
1653
+ "browser-focus.json"
1654
+ );
1655
+ }
1656
+ function claimBrowserFocus(now = Date.now()) {
1657
+ const statePath = browserFocusStateFile();
1658
+ try {
1659
+ mkdirSync2(dirname2(statePath), { recursive: true });
1660
+ let lastFocusedAt = 0;
1661
+ if (existsSync2(statePath)) {
1662
+ const payload = JSON.parse(readFileSync2(statePath, "utf-8"));
1663
+ const value = payload.lastFocusedAt ?? payload.last_focused_at;
1664
+ if (typeof value === "number" && Number.isFinite(value)) {
1665
+ lastFocusedAt = value;
1666
+ }
1667
+ }
1668
+ if (lastFocusedAt > now) {
1669
+ lastFocusedAt = 0;
1670
+ }
1671
+ if (now - lastFocusedAt < BROWSER_FOCUS_COOLDOWN_MS) {
1672
+ return false;
1673
+ }
1674
+ writeFileSync2(statePath, JSON.stringify({ lastFocusedAt: now }), "utf-8");
1675
+ return true;
1676
+ } catch {
1677
+ return true;
1678
+ }
1679
+ }
1680
+ function extractUrlHost(raw) {
1681
+ try {
1682
+ const parsed = new URL(raw.includes("://") ? raw : `https://${raw}`);
1683
+ return parsed.port ? `${parsed.hostname.toLowerCase()}:${parsed.port}` : parsed.hostname.toLowerCase();
1684
+ } catch {
1685
+ return "";
1686
+ }
1687
+ }
1688
+ function browserAppNameFromBundleId(bundleId) {
1689
+ const names = {
1690
+ "com.google.chrome": "Google Chrome",
1691
+ "com.google.chrome.canary": "Google Chrome Canary",
1692
+ "com.microsoft.edgemac": "Microsoft Edge",
1693
+ "com.brave.browser": "Brave Browser",
1694
+ "com.operasoftware.opera": "Opera",
1695
+ "com.operasoftware.operagx": "Opera GX",
1696
+ "com.vivaldi.vivaldi": "Vivaldi",
1697
+ "company.thebrowser.browser": "Arc",
1698
+ "com.apple.safari": "Safari"
1699
+ };
1700
+ return names[bundleId.toLowerCase()] ?? "";
1701
+ }
1702
+ function readDefaultMacBrowserBundleId() {
1703
+ try {
1704
+ const output = execFileSync(
1705
+ "/usr/bin/defaults",
1706
+ [
1707
+ "read",
1708
+ `${homedir2()}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`,
1709
+ "LSHandlers"
1710
+ ],
1711
+ { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
1712
+ );
1713
+ const httpsMatch = output.match(
1714
+ /LSHandlerURLScheme\s*=\s*https;[\s\S]*?LSHandlerRole(?:All|Viewer|Editor)\s*=\s*"([^"]+)"/
1715
+ );
1716
+ const httpMatch = output.match(
1717
+ /LSHandlerURLScheme\s*=\s*http;[\s\S]*?LSHandlerRole(?:All|Viewer|Editor)\s*=\s*"([^"]+)"/
1718
+ );
1719
+ return (httpsMatch?.[1] ?? httpMatch?.[1] ?? "").trim();
1720
+ } catch {
1721
+ return "";
1722
+ }
1723
+ }
1724
+ function browserStrategyForBundleId(bundleId) {
1725
+ const normalized = bundleId.toLowerCase();
1726
+ if ((/* @__PURE__ */ new Set([
1727
+ "com.google.chrome",
1728
+ "com.google.chrome.canary",
1729
+ "com.microsoft.edgemac",
1730
+ "com.brave.browser",
1731
+ "com.operasoftware.opera",
1732
+ "com.operasoftware.operagx",
1733
+ "com.vivaldi.vivaldi",
1734
+ "company.thebrowser.browser"
1735
+ ])).has(normalized)) {
1736
+ return "chromium";
1737
+ }
1738
+ return normalized === "com.apple.safari" ? "safari" : "fallback";
1739
+ }
1740
+ function runAppleScript(script, args) {
1741
+ const result = spawnSync("osascript", ["-", ...args], {
1742
+ input: script,
1743
+ encoding: "utf-8",
1744
+ stdio: ["pipe", "ignore", "ignore"],
1745
+ timeout: 5e3
1746
+ });
1747
+ return result.status === 0;
1748
+ }
1749
+ function retargetChromiumMacos(appName, targetUrl, allowFocus) {
1750
+ const host = extractUrlHost(targetUrl);
1751
+ if (!host) return false;
1752
+ const escapedAppName = appName.replace(/"/g, '\\"');
1753
+ const activateBlock = allowFocus ? " activate\n" : "";
1754
+ const foundTabBlock = allowFocus ? " set active tab index of w to i\n set index of w to 1\n set URL of t to targetUrl\n" : " set URL of t to targetUrl\n";
1755
+ const newTabBlock = allowFocus ? " tell window 1\n make new tab with properties {URL:targetUrl}\n set active tab index to (count of tabs)\n set index to 1\n end tell\n" : " tell window 1\n make new tab with properties {URL:targetUrl}\n end tell\n";
1756
+ const script = `
1757
+ on run argv
1758
+ set targetUrl to item 1 of argv
1759
+ set targetHost to item 2 of argv
1760
+ tell application "${escapedAppName}"
1761
+ ${activateBlock} if (count of windows) is 0 then
1762
+ make new window
1763
+ end if
1764
+ set foundTab to false
1765
+ repeat with w in windows
1766
+ set tabCount to count of tabs of w
1767
+ repeat with i from 1 to tabCount
1768
+ set t to tab i of w
1769
+ if (URL of t) contains targetHost then
1770
+ ${foundTabBlock} set foundTab to true
1771
+ exit repeat
1772
+ end if
1773
+ end repeat
1774
+ if foundTab then exit repeat
1775
+ end repeat
1776
+ if not foundTab then
1777
+ ${newTabBlock} end if
1778
+ end tell
1779
+ end run
1780
+ `;
1781
+ return runAppleScript(script, [targetUrl, host]);
1782
+ }
1783
+ function retargetSafariMacos(appName, targetUrl, allowFocus) {
1784
+ const host = extractUrlHost(targetUrl);
1785
+ if (!host) return false;
1786
+ const escapedAppName = appName.replace(/"/g, '\\"');
1787
+ const activateBlock = allowFocus ? " activate\n" : "";
1788
+ const foundTabBlock = allowFocus ? " set current tab of w to t\n set index of w to 1\n set URL of t to targetUrl\n" : " set URL of t to targetUrl\n";
1789
+ const newTabBlock = allowFocus ? " tell window 1\n set current tab to (make new tab with properties {URL:targetUrl})\n set index to 1\n end tell\n" : " tell window 1\n make new tab with properties {URL:targetUrl}\n end tell\n";
1790
+ const script = `
1791
+ on run argv
1792
+ set targetUrl to item 1 of argv
1793
+ set targetHost to item 2 of argv
1794
+ tell application "${escapedAppName}"
1795
+ ${activateBlock} if (count of windows) is 0 then
1796
+ make new document
1797
+ end if
1798
+ set foundTab to false
1799
+ repeat with w in windows
1800
+ set tabCount to count of tabs of w
1801
+ repeat with i from 1 to tabCount
1802
+ set t to tab i of w
1803
+ if (URL of t) contains targetHost then
1804
+ ${foundTabBlock} set foundTab to true
1805
+ exit repeat
1806
+ end if
1807
+ end repeat
1808
+ if foundTab then exit repeat
1809
+ end repeat
1810
+ if not foundTab then
1811
+ ${newTabBlock} end if
1812
+ end tell
1813
+ end run
1814
+ `;
1815
+ return runAppleScript(script, [targetUrl, host]);
1816
+ }
1817
+ function openUrlMacos(targetUrl, allowFocus) {
1818
+ const defaultBundleId = readDefaultMacBrowserBundleId();
1819
+ const appName = defaultBundleId ? browserAppNameFromBundleId(defaultBundleId) : "";
1820
+ const strategy = browserStrategyForBundleId(defaultBundleId);
1821
+ if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus)) {
1822
+ return true;
1823
+ }
1824
+ if (appName && strategy === "safari" && retargetSafariMacos(appName, targetUrl, allowFocus)) {
1825
+ return true;
1826
+ }
1827
+ if (!allowFocus) {
1828
+ return false;
1829
+ }
1830
+ try {
1831
+ execFileSync("open", [targetUrl], { stdio: "ignore" });
1832
+ return true;
1833
+ } catch {
1834
+ return false;
1835
+ }
1836
+ }
1536
1837
  function openInBrowser(url) {
1537
1838
  try {
1538
- if (process.platform === "darwin") execSync(`open "${url}"`, { stdio: "ignore" });
1539
- else if (process.platform === "win32")
1540
- execSync(`start "" "${url}"`, { stdio: "ignore" });
1541
- else execSync(`xdg-open "${url}"`, { stdio: "ignore" });
1839
+ const targetUrl = String(url || "").trim();
1840
+ if (!targetUrl) return;
1841
+ const allowFocus = claimBrowserFocus();
1842
+ if (process.platform === "darwin") {
1843
+ openUrlMacos(targetUrl, allowFocus);
1844
+ return;
1845
+ }
1846
+ if (!allowFocus) return;
1847
+ if (process.platform === "win32") {
1848
+ execFileSync("cmd.exe", ["/c", "start", "", targetUrl], {
1849
+ stdio: "ignore"
1850
+ });
1851
+ return;
1852
+ }
1853
+ execFileSync("xdg-open", [targetUrl], { stdio: "ignore" });
1542
1854
  } catch {
1543
1855
  }
1544
1856
  }
@@ -1622,14 +1934,14 @@ function envFilePath(baseUrl) {
1622
1934
  }
1623
1935
  function saveEnvValues(values, baseUrl) {
1624
1936
  const filePath = envFilePath(baseUrl);
1625
- const dir = dirname2(filePath);
1626
- if (!existsSync2(dir)) {
1627
- mkdirSync2(dir, { recursive: true });
1937
+ const dir = dirname3(filePath);
1938
+ if (!existsSync3(dir)) {
1939
+ mkdirSync3(dir, { recursive: true });
1628
1940
  }
1629
- const existing = existsSync2(filePath) ? parseEnvFile(filePath) : {};
1941
+ const existing = existsSync3(filePath) ? parseEnvFile(filePath) : {};
1630
1942
  const merged = { ...existing, ...values };
1631
1943
  const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
1632
- writeFileSync2(filePath, lines.join("\n") + "\n", "utf-8");
1944
+ writeFileSync3(filePath, lines.join("\n") + "\n", "utf-8");
1633
1945
  saveProjectDeeplineEnvValues(baseUrl, values);
1634
1946
  }
1635
1947
  async function httpJson(method, url, apiKey, body) {
@@ -2168,7 +2480,7 @@ function registerBillingCommands(program) {
2168
2480
  }
2169
2481
 
2170
2482
  // src/cli/dataset-stats.ts
2171
- import { writeFileSync as writeFileSync3 } from "fs";
2483
+ import { writeFileSync as writeFileSync4 } from "fs";
2172
2484
  import { resolve as resolve3 } from "path";
2173
2485
  var CSV_PROJECTED_FIELDS_KEY = "__deeplineCsvProjectedFields";
2174
2486
  function csvProjectedFields(row) {
@@ -2500,7 +2812,7 @@ function writeCanonicalRowsCsv(rowsInfo, outPath) {
2500
2812
  columns: rowsInfo.columns
2501
2813
  });
2502
2814
  const resolved = resolve3(outPath);
2503
- writeFileSync3(
2815
+ writeFileSync4(
2504
2816
  resolved,
2505
2817
  csvStringFromRows(sanitized.rows, sanitized.columns),
2506
2818
  "utf-8"
@@ -2793,25 +3105,25 @@ function registerOrgCommands(program) {
2793
3105
  // src/cli/commands/play.ts
2794
3106
  import { createHash as createHash3 } from "crypto";
2795
3107
  import {
2796
- existsSync as existsSync4,
3108
+ existsSync as existsSync5,
2797
3109
  readFileSync as readFileSync3,
2798
3110
  readdirSync,
2799
3111
  realpathSync,
2800
- writeFileSync as writeFileSync4
3112
+ writeFileSync as writeFileSync5
2801
3113
  } from "fs";
2802
- import { basename as basename3, dirname as dirname6, join as join6, resolve as resolve7 } from "path";
3114
+ import { basename as basename3, dirname as dirname7, join as join6, resolve as resolve7 } from "path";
2803
3115
 
2804
3116
  // src/plays/bundle-play-file.ts
2805
3117
  import { tmpdir as tmpdir2 } from "os";
2806
- import { dirname as dirname5, join as join5, resolve as resolve6 } from "path";
3118
+ import { dirname as dirname6, join as join5, resolve as resolve6 } from "path";
2807
3119
  import { fileURLToPath } from "url";
2808
- import { existsSync as existsSync3 } from "fs";
3120
+ import { existsSync as existsSync4 } from "fs";
2809
3121
 
2810
3122
  // ../shared_libs/plays/bundling/index.ts
2811
3123
  import { createHash } from "crypto";
2812
3124
  import { mkdir as mkdir2, readFile, realpath, stat, writeFile as writeFile2 } from "fs/promises";
2813
3125
  import { tmpdir } from "os";
2814
- import { basename, dirname as dirname3, extname, isAbsolute, join as join3, resolve as resolve4 } from "path";
3126
+ import { basename, dirname as dirname4, extname, isAbsolute, join as join3, resolve as resolve4 } from "path";
2815
3127
  import { builtinModules, createRequire } from "module";
2816
3128
  import { build } from "esbuild";
2817
3129
 
@@ -2910,7 +3222,7 @@ async function normalizeLocalPath(filePath) {
2910
3222
  function createPlayWorkspace(entryFile) {
2911
3223
  return {
2912
3224
  entryFile,
2913
- rootDir: dirname3(entryFile)
3225
+ rootDir: dirname4(entryFile)
2914
3226
  };
2915
3227
  }
2916
3228
  function isPathInsideDirectory(filePath, directory) {
@@ -3111,7 +3423,7 @@ function workersNamedPlayEntryAliasPlugin(playFilePath, exportName) {
3111
3423
  contents: `export { ${exportName} as default } from ${JSON.stringify(playFilePath)};
3112
3424
  `,
3113
3425
  loader: "ts",
3114
- resolveDir: dirname3(playFilePath)
3426
+ resolveDir: dirname4(playFilePath)
3115
3427
  })
3116
3428
  );
3117
3429
  }
@@ -3275,7 +3587,7 @@ function importedPlayProxyPlugin(importedPlayDependencies) {
3275
3587
  return {
3276
3588
  contents: buildImportedPlayProxyModule(dependency.playName),
3277
3589
  loader: "ts",
3278
- resolveDir: dirname3(args.path)
3590
+ resolveDir: dirname4(args.path)
3279
3591
  };
3280
3592
  });
3281
3593
  }
@@ -3293,7 +3605,7 @@ async function resolveLocalImport(fromFile, specifier) {
3293
3605
  if (specifier.startsWith("file:")) {
3294
3606
  return normalizeLocalPath(new URL(specifier).pathname);
3295
3607
  }
3296
- const base = isAbsolute(specifier) ? resolve4(specifier) : resolve4(dirname3(fromFile), specifier);
3608
+ const base = isAbsolute(specifier) ? resolve4(specifier) : resolve4(dirname4(fromFile), specifier);
3297
3609
  const candidates = [base];
3298
3610
  const explicitExtension = extname(base).toLowerCase();
3299
3611
  if (!explicitExtension) {
@@ -3543,7 +3855,7 @@ async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter
3543
3855
  ...namedExportShim ? {
3544
3856
  stdin: {
3545
3857
  contents: namedExportShim,
3546
- resolveDir: dirname3(entryFile),
3858
+ resolveDir: dirname4(entryFile),
3547
3859
  sourcefile: `${basename(entryFile)}.${exportName}.entry.ts`,
3548
3860
  loader: "ts"
3549
3861
  }
@@ -3799,13 +4111,6 @@ var PLAY_DEDUP_BACKENDS = {
3799
4111
 
3800
4112
  // ../shared_libs/play-runtime/profiles.ts
3801
4113
  var PLAY_EXECUTION_PROFILES = {
3802
- legacy: {
3803
- id: "legacy",
3804
- scheduler: PLAY_SCHEDULER_BACKENDS.temporal,
3805
- runner: PLAY_RUNTIME_BACKENDS.daytona,
3806
- dedup: PLAY_DEDUP_BACKENDS.inMemory,
3807
- label: "Daytona + Temporal (production today)"
3808
- },
3809
4114
  workers_edge: {
3810
4115
  id: "workers_edge",
3811
4116
  scheduler: PLAY_SCHEDULER_BACKENDS.cfWorkflows,
@@ -3840,7 +4145,7 @@ function resolveExecutionProfile(override) {
3840
4145
  // src/plays/local-file-discovery.ts
3841
4146
  import { createHash as createHash2 } from "crypto";
3842
4147
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
3843
- import { basename as basename2, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve5 } from "path";
4148
+ import { basename as basename2, dirname as dirname5, extname as extname2, isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve5 } from "path";
3844
4149
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
3845
4150
  function sha2562(buffer) {
3846
4151
  return createHash2("sha256").update(buffer).digest("hex");
@@ -4041,7 +4346,7 @@ function isPathInsideDirectory2(filePath, directory) {
4041
4346
  return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute2(relativePath);
4042
4347
  }
4043
4348
  async function resolveLocalImport2(fromFile, specifier) {
4044
- const base = isAbsolute2(specifier) ? resolve5(specifier) : resolve5(dirname4(fromFile), specifier);
4349
+ const base = isAbsolute2(specifier) ? resolve5(specifier) : resolve5(dirname5(fromFile), specifier);
4045
4350
  const candidates = [base];
4046
4351
  const explicitExtension = extname2(base).toLowerCase();
4047
4352
  if (!explicitExtension) {
@@ -4060,7 +4365,7 @@ async function resolveLocalImport2(fromFile, specifier) {
4060
4365
  }
4061
4366
  async function discoverPackagedLocalFiles(entryFile) {
4062
4367
  const absoluteEntryFile = resolve5(entryFile);
4063
- const packagingRoot = dirname4(absoluteEntryFile);
4368
+ const packagingRoot = dirname5(absoluteEntryFile);
4064
4369
  const files = /* @__PURE__ */ new Map();
4065
4370
  const unresolved = [];
4066
4371
  const visitedFiles = /* @__PURE__ */ new Set();
@@ -4097,7 +4402,7 @@ async function discoverPackagedLocalFiles(entryFile) {
4097
4402
  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."
4098
4403
  });
4099
4404
  } else {
4100
- const absoluteCsvPath = resolve5(dirname4(absolutePath), resolvedPath);
4405
+ const absoluteCsvPath = resolve5(dirname5(absolutePath), resolvedPath);
4101
4406
  if (isAbsolute2(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
4102
4407
  unresolved.push({
4103
4408
  sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
@@ -4136,14 +4441,14 @@ async function discoverPackagedLocalFiles(entryFile) {
4136
4441
 
4137
4442
  // src/plays/bundle-play-file.ts
4138
4443
  var PLAY_BUNDLE_CACHE_VERSION2 = 26;
4139
- var MODULE_DIR = dirname5(fileURLToPath(import.meta.url));
4444
+ var MODULE_DIR = dirname6(fileURLToPath(import.meta.url));
4140
4445
  var SDK_PACKAGE_ROOT = resolve6(MODULE_DIR, "..", "..");
4141
4446
  var SOURCE_REPO_ROOT = resolve6(SDK_PACKAGE_ROOT, "..");
4142
- var HAS_SOURCE_BUNDLING_SOURCES = existsSync3(
4447
+ var HAS_SOURCE_BUNDLING_SOURCES = existsSync4(
4143
4448
  resolve6(SOURCE_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
4144
4449
  );
4145
4450
  var PACKAGED_REPO_ROOT = resolve6(SDK_PACKAGE_ROOT, "dist", "repo");
4146
- var HAS_PACKAGED_BUNDLING_SOURCES = existsSync3(
4451
+ var HAS_PACKAGED_BUNDLING_SOURCES = existsSync4(
4147
4452
  resolve6(PACKAGED_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
4148
4453
  );
4149
4454
  var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED_BUNDLING_SOURCES ? PACKAGED_REPO_ROOT : resolve6(SDK_PACKAGE_ROOT, "..");
@@ -4182,7 +4487,7 @@ function createSdkPlayBundlingAdapter() {
4182
4487
  sdkSourceRoot: SDK_SOURCE_ROOT,
4183
4488
  sdkPackageJson: SDK_PACKAGE_JSON,
4184
4489
  sdkEntryFile: SDK_ENTRY_FILE,
4185
- sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !existsSync3(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
4490
+ sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !existsSync4(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
4186
4491
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
4187
4492
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
4188
4493
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
@@ -4343,7 +4648,77 @@ function createCliProgress(enabled) {
4343
4648
  return progress;
4344
4649
  }
4345
4650
 
4651
+ // src/cli/trace.ts
4652
+ var cliTraceStartedAt = Date.now();
4653
+ function isTruthyEnv(value) {
4654
+ return value === "1" || value === "true" || value === "yes";
4655
+ }
4656
+ function isCliTraceEnabled() {
4657
+ return isTruthyEnv(process.env.DEEPLINE_CLI_TRACE);
4658
+ }
4659
+ function recordCliTrace(event) {
4660
+ if (!isCliTraceEnabled()) {
4661
+ return;
4662
+ }
4663
+ const now = Date.now();
4664
+ const payload = {
4665
+ ts: now,
4666
+ source: "cli",
4667
+ sinceStartMs: now - cliTraceStartedAt,
4668
+ ...event
4669
+ };
4670
+ process.stderr.write(`[cli-trace] ${JSON.stringify(payload)}
4671
+ `);
4672
+ }
4673
+ async function traceCliSpan(phase, fields, run) {
4674
+ if (!isCliTraceEnabled()) {
4675
+ return run();
4676
+ }
4677
+ const startedAt = Date.now();
4678
+ try {
4679
+ const result = await run();
4680
+ recordCliTrace({
4681
+ phase,
4682
+ ms: Date.now() - startedAt,
4683
+ ok: true,
4684
+ ...fields
4685
+ });
4686
+ return result;
4687
+ } catch (error) {
4688
+ recordCliTrace({
4689
+ phase,
4690
+ ms: Date.now() - startedAt,
4691
+ ok: false,
4692
+ error: error instanceof Error ? error.message : String(error),
4693
+ ...fields
4694
+ });
4695
+ throw error;
4696
+ }
4697
+ }
4698
+
4346
4699
  // src/cli/commands/play.ts
4700
+ function traceCliSync(phase, fields, run) {
4701
+ const startedAt = Date.now();
4702
+ try {
4703
+ const result = run();
4704
+ recordCliTrace({
4705
+ phase,
4706
+ ms: Date.now() - startedAt,
4707
+ ok: true,
4708
+ ...fields
4709
+ });
4710
+ return result;
4711
+ } catch (error) {
4712
+ recordCliTrace({
4713
+ phase,
4714
+ ms: Date.now() - startedAt,
4715
+ ok: false,
4716
+ error: error instanceof Error ? error.message : String(error),
4717
+ ...fields
4718
+ });
4719
+ throw error;
4720
+ }
4721
+ }
4347
4722
  function parseReferencedPlayTarget(target) {
4348
4723
  const trimmed = target.trim();
4349
4724
  const slashIndex = trimmed.indexOf("/");
@@ -4398,15 +4773,15 @@ function materializeRemotePlaySource(input) {
4398
4773
  return null;
4399
4774
  }
4400
4775
  const outputPath = input.outPath ?? defaultMaterializedPlayPath(input.playName);
4401
- if (existsSync4(outputPath)) {
4776
+ if (existsSync5(outputPath)) {
4402
4777
  const existingSource = readFileSync3(outputPath, "utf-8");
4403
4778
  if (existingSource === input.sourceCode) {
4404
4779
  return { path: outputPath, status: "unchanged", created: false };
4405
4780
  }
4406
- writeFileSync4(outputPath, input.sourceCode, "utf-8");
4781
+ writeFileSync5(outputPath, input.sourceCode, "utf-8");
4407
4782
  return { path: outputPath, status: "updated", created: false };
4408
4783
  }
4409
- writeFileSync4(outputPath, input.sourceCode, "utf-8");
4784
+ writeFileSync5(outputPath, input.sourceCode, "utf-8");
4410
4785
  return { path: outputPath, status: "created", created: true };
4411
4786
  }
4412
4787
  function formatLoadedPlayMessage(materializedFile) {
@@ -4451,7 +4826,7 @@ function extractPlayName(code, filePath) {
4451
4826
  throw buildMissingDefinePlayError(filePath);
4452
4827
  }
4453
4828
  function isFileTarget(target) {
4454
- return existsSync4(resolve7(target));
4829
+ return existsSync5(resolve7(target));
4455
4830
  }
4456
4831
  function looksLikeFilePath(target) {
4457
4832
  if (target.trim().toLowerCase().startsWith("prebuilt/")) {
@@ -4580,7 +4955,24 @@ function applyCsvShortcutInput(input) {
4580
4955
  function isLocalFilePathValue(value) {
4581
4956
  if (typeof value !== "string" || !value.trim()) return false;
4582
4957
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
4583
- return existsSync4(resolve7(value));
4958
+ return existsSync5(resolve7(value));
4959
+ }
4960
+ function inputContainsLocalFilePath(value) {
4961
+ if (isLocalFilePathValue(value)) {
4962
+ return true;
4963
+ }
4964
+ if (Array.isArray(value)) {
4965
+ return value.some((entry) => inputContainsLocalFilePath(entry));
4966
+ }
4967
+ if (value && typeof value === "object") {
4968
+ return Object.values(value).some(
4969
+ (entry) => inputContainsLocalFilePath(entry)
4970
+ );
4971
+ }
4972
+ return false;
4973
+ }
4974
+ function namedRunNeedsPlayDefinition(input) {
4975
+ return input.revisionSelector === "latest" || getDottedInputValue(input.runtimeInput, "csv") != null || inputContainsLocalFilePath(input.runtimeInput);
4584
4976
  }
4585
4977
  async function stageFileInputArgs(input) {
4586
4978
  const uniqueBindings = [
@@ -4768,7 +5160,7 @@ function formatTimestamp(value) {
4768
5160
  function formatRunLine(run) {
4769
5161
  return `${run.workflowId} ${run.status} ${formatTimestamp(run.startTime)}`;
4770
5162
  }
4771
- function isTransientPlayStatusPollError(error) {
5163
+ function isTransientPlayStreamError(error) {
4772
5164
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
4773
5165
  return error.statusCode >= 500 && error.statusCode < 600;
4774
5166
  }
@@ -4777,12 +5169,6 @@ function isTransientPlayStatusPollError(error) {
4777
5169
  text
4778
5170
  );
4779
5171
  }
4780
- function isTerminalPlayStatusPollError(input) {
4781
- if (input.error instanceof DeeplineError && input.error.statusCode === 404 && input.hasSeenRun) {
4782
- return true;
4783
- }
4784
- return false;
4785
- }
4786
5172
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
4787
5173
  "completed",
4788
5174
  "failed",
@@ -4848,6 +5234,12 @@ function buildPlayDashboardUrl(baseUrl, playName) {
4848
5234
  const encodedPlayName = encodeURIComponent(playName);
4849
5235
  return `${trimmedBase}/dashboard/plays/${encodedPlayName}`;
4850
5236
  }
5237
+ function openPlayDashboard(input) {
5238
+ if (input.jsonOutput || input.noOpen || !input.dashboardUrl) {
5239
+ return;
5240
+ }
5241
+ openInBrowser(input.dashboardUrl);
5242
+ }
4851
5243
  function getDashboardUrlFromLiveEvent(event) {
4852
5244
  const dashboardUrl = getEventPayload(event).dashboardUrl;
4853
5245
  return typeof dashboardUrl === "string" && dashboardUrl.trim() ? dashboardUrl.trim() : null;
@@ -4884,6 +5276,68 @@ function assertPlayWaitNotTimedOut(input) {
4884
5276
  );
4885
5277
  }
4886
5278
  }
5279
+ async function waitForPlayCompletionByStream(input) {
5280
+ const controller = new AbortController();
5281
+ let timedOut = false;
5282
+ let lastPhase = null;
5283
+ const timeout = input.waitTimeoutMs === null ? null : setTimeout(
5284
+ () => {
5285
+ timedOut = true;
5286
+ controller.abort();
5287
+ },
5288
+ Math.max(1, input.waitTimeoutMs - (Date.now() - input.startedAt))
5289
+ );
5290
+ try {
5291
+ for await (const event of input.client.streamPlayRunEvents(
5292
+ input.workflowId,
5293
+ { signal: controller.signal }
5294
+ )) {
5295
+ assertPlayWaitNotTimedOut({ ...input, lastPhase });
5296
+ const phase = describeLiveEventPhase(event);
5297
+ if (phase) {
5298
+ lastPhase = phase;
5299
+ input.progress.phase(phase);
5300
+ }
5301
+ printPlayLogLines({
5302
+ lines: getLogLinesFromLiveEvent(event),
5303
+ status: null,
5304
+ jsonOutput: input.jsonOutput,
5305
+ emitLogs: input.emitLogs,
5306
+ state: input.state,
5307
+ progress: input.progress
5308
+ });
5309
+ const status = getStatusFromLiveEvent(event);
5310
+ if (status && TERMINAL_PLAY_STATUSES2.has(status)) {
5311
+ const finalStatus = await input.client.getPlayStatus(input.workflowId, {
5312
+ billing: false
5313
+ });
5314
+ if (TERMINAL_PLAY_STATUSES2.has(finalStatus.status)) {
5315
+ return finalStatus;
5316
+ }
5317
+ }
5318
+ }
5319
+ } catch (error) {
5320
+ if (timedOut) {
5321
+ assertPlayWaitNotTimedOut({ ...input, lastPhase });
5322
+ }
5323
+ throw error;
5324
+ } finally {
5325
+ if (timeout) {
5326
+ clearTimeout(timeout);
5327
+ }
5328
+ }
5329
+ const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
5330
+ throw new DeeplineError(
5331
+ `Play live stream ended before the run reached a terminal state runId=${input.workflowId}${phaseSuffix}.`,
5332
+ void 0,
5333
+ "PLAY_LIVE_STREAM_ENDED",
5334
+ {
5335
+ runId: input.workflowId,
5336
+ workflowId: input.workflowId,
5337
+ ...lastPhase ? { phase: lastPhase } : {}
5338
+ }
5339
+ );
5340
+ }
4887
5341
  async function startAndWaitForPlayCompletionByStream(input) {
4888
5342
  const startedAt = Date.now();
4889
5343
  const state = {
@@ -4894,6 +5348,8 @@ async function startAndWaitForPlayCompletionByStream(input) {
4894
5348
  let timedOut = false;
4895
5349
  let emittedDashboardUrl = false;
4896
5350
  let lastKnownWorkflowId = "";
5351
+ let eventCount = 0;
5352
+ let firstRunIdMs = null;
4897
5353
  let lastPhase = null;
4898
5354
  const timeout = input.waitTimeoutMs === null ? null : setTimeout(
4899
5355
  () => {
@@ -4906,13 +5362,20 @@ async function startAndWaitForPlayCompletionByStream(input) {
4906
5362
  for await (const event of input.client.startPlayRunStream(input.request, {
4907
5363
  signal: controller.signal
4908
5364
  })) {
5365
+ eventCount += 1;
4909
5366
  const eventRunId = getEventPayload(event).runId;
4910
5367
  if (typeof eventRunId === "string" && eventRunId && eventRunId !== "pending") {
4911
5368
  lastKnownWorkflowId = eventRunId;
5369
+ firstRunIdMs ??= Date.now() - startedAt;
4912
5370
  }
4913
5371
  const workflowId = lastKnownWorkflowId || "pending";
4914
5372
  if (workflowId !== "pending" && !emittedDashboardUrl) {
4915
5373
  const dashboardUrl = getDashboardUrlFromLiveEvent(event) ?? buildPlayDashboardUrl(input.client.baseUrl, input.playName);
5374
+ openPlayDashboard({
5375
+ dashboardUrl,
5376
+ jsonOutput: input.jsonOutput,
5377
+ noOpen: input.noOpen
5378
+ });
4916
5379
  if (!input.jsonOutput) {
4917
5380
  writeStartedPlayRun({
4918
5381
  runId: workflowId,
@@ -4946,6 +5409,16 @@ async function startAndWaitForPlayCompletionByStream(input) {
4946
5409
  });
4947
5410
  const finalStatus = getFinalStatusFromLiveEvent(event);
4948
5411
  if (finalStatus) {
5412
+ recordCliTrace({
5413
+ phase: "cli.play_start_stream_terminal",
5414
+ ms: Date.now() - startedAt,
5415
+ ok: true,
5416
+ playName: input.playName,
5417
+ workflowId: finalStatus.runId || lastKnownWorkflowId || null,
5418
+ eventCount,
5419
+ firstRunIdMs,
5420
+ lastPhase
5421
+ });
4949
5422
  return finalStatus;
4950
5423
  }
4951
5424
  }
@@ -4958,21 +5431,31 @@ async function startAndWaitForPlayCompletionByStream(input) {
4958
5431
  lastPhase
4959
5432
  });
4960
5433
  }
4961
- if (lastKnownWorkflowId && isTransientPlayStatusPollError(error)) {
5434
+ if (lastKnownWorkflowId && isTransientPlayStreamError(error)) {
4962
5435
  if (timeout) {
4963
5436
  clearTimeout(timeout);
4964
5437
  }
4965
5438
  const reason = error instanceof Error ? error.message : String(error);
4966
5439
  if (!input.jsonOutput) {
4967
5440
  process.stderr.write(
4968
- `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
5441
+ `[play watch] start stream failed after run ${lastKnownWorkflowId}; reconnecting to run stream (${reason})
4969
5442
  `
4970
5443
  );
4971
5444
  }
4972
- return waitForPlayCompletionByPolling({
5445
+ recordCliTrace({
5446
+ phase: "cli.play_start_stream_reconnect",
5447
+ ms: Date.now() - startedAt,
5448
+ ok: true,
5449
+ playName: input.playName,
5450
+ workflowId: lastKnownWorkflowId,
5451
+ eventCount,
5452
+ firstRunIdMs,
5453
+ lastPhase,
5454
+ reason
5455
+ });
5456
+ return waitForPlayCompletionByStream({
4973
5457
  client: input.client,
4974
5458
  workflowId: lastKnownWorkflowId,
4975
- pollIntervalMs: 500,
4976
5459
  jsonOutput: input.jsonOutput,
4977
5460
  emitLogs: input.emitLogs,
4978
5461
  waitTimeoutMs: input.waitTimeoutMs,
@@ -4990,13 +5473,23 @@ async function startAndWaitForPlayCompletionByStream(input) {
4990
5473
  if (lastKnownWorkflowId) {
4991
5474
  if (!input.jsonOutput) {
4992
5475
  input.progress.writeLine(
4993
- `[play watch] start stream ended after run ${lastKnownWorkflowId}; falling back to polling`
5476
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; reconnecting to run stream`
4994
5477
  );
4995
5478
  }
4996
- return waitForPlayCompletionByPolling({
5479
+ recordCliTrace({
5480
+ phase: "cli.play_start_stream_reconnect",
5481
+ ms: Date.now() - startedAt,
5482
+ ok: true,
5483
+ playName: input.playName,
5484
+ workflowId: lastKnownWorkflowId,
5485
+ eventCount,
5486
+ firstRunIdMs,
5487
+ lastPhase,
5488
+ reason: "stream ended before terminal event"
5489
+ });
5490
+ return waitForPlayCompletionByStream({
4997
5491
  client: input.client,
4998
5492
  workflowId: lastKnownWorkflowId,
4999
- pollIntervalMs: 500,
5000
5493
  jsonOutput: input.jsonOutput,
5001
5494
  emitLogs: input.emitLogs,
5002
5495
  waitTimeoutMs: input.waitTimeoutMs,
@@ -5017,71 +5510,6 @@ async function startAndWaitForPlayCompletionByStream(input) {
5017
5510
  }
5018
5511
  );
5019
5512
  }
5020
- async function waitForPlayCompletionByPolling(input) {
5021
- let lastTransientPollWarningAt = 0;
5022
- let hasSeenRun = false;
5023
- while (true) {
5024
- assertPlayWaitNotTimedOut(input);
5025
- let status;
5026
- try {
5027
- status = await input.client.getPlayTailStatus(input.workflowId, {
5028
- afterLogIndex: input.state.lastLogIndex,
5029
- // Keep the server-side tail wait close to the caller's requested poll
5030
- // cadence. A long wait makes tiny remote runs look slow whenever the
5031
- // terminal update lands just after the held request starts.
5032
- waitMs: Math.max(50, Math.min(input.pollIntervalMs, 1e3))
5033
- });
5034
- } catch (error) {
5035
- if (isTerminalPlayStatusPollError({ error, hasSeenRun })) {
5036
- throw new DeeplineError(
5037
- `Play run ${input.workflowId} no longer exists on the server (404). The run was deleted or the backend lost it.`,
5038
- 404,
5039
- "PLAY_RUN_NOT_FOUND"
5040
- );
5041
- }
5042
- if (!isTransientPlayStatusPollError(error)) {
5043
- throw error;
5044
- }
5045
- const now = Date.now();
5046
- if (now - lastTransientPollWarningAt >= 3e4) {
5047
- const message = error instanceof Error ? error.message : String(error);
5048
- input.progress.writeLine(
5049
- `[play tail] transient status poll failed; retrying: ${message}`
5050
- );
5051
- lastTransientPollWarningAt = now;
5052
- }
5053
- await new Promise(
5054
- (resolvePromise) => setTimeout(resolvePromise, input.pollIntervalMs)
5055
- );
5056
- continue;
5057
- }
5058
- hasSeenRun = true;
5059
- const logs = status.progress?.logs ?? [];
5060
- input.progress.phase(status.status);
5061
- printPlayLogLines({
5062
- lines: logs.slice(input.state.lastLogIndex),
5063
- status,
5064
- jsonOutput: input.jsonOutput,
5065
- emitLogs: input.emitLogs,
5066
- state: input.state,
5067
- progress: input.progress
5068
- });
5069
- if (TERMINAL_PLAY_STATUSES2.has(status.status)) {
5070
- return status.result !== void 0 ? status : await input.client.getPlayStatus(input.workflowId);
5071
- }
5072
- const authoritativeStatus = await input.client.getPlayStatus(
5073
- input.workflowId
5074
- );
5075
- if (TERMINAL_PLAY_STATUSES2.has(authoritativeStatus.status)) {
5076
- return authoritativeStatus;
5077
- }
5078
- if ((status.progress?.logs ?? []).length === input.state.lastLogIndex) {
5079
- await new Promise(
5080
- (resolvePromise) => setTimeout(resolvePromise, input.pollIntervalMs)
5081
- );
5082
- }
5083
- }
5084
- }
5085
5513
  function formatInteger(value) {
5086
5514
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
5087
5515
  }
@@ -5222,7 +5650,6 @@ function buildRunWarnings(status, rowsInfo) {
5222
5650
  function buildRunNextCommands(runId, rowsInfo) {
5223
5651
  const commands = {
5224
5652
  get: `deepline runs get ${runId} --json`,
5225
- tail: `deepline runs tail ${runId} --json`,
5226
5653
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
5227
5654
  logs: `deepline runs logs ${runId} --out run.log --json`
5228
5655
  };
@@ -5338,10 +5765,9 @@ function normalizeErrorsForEnvelope(status, error) {
5338
5765
  }
5339
5766
  function normalizeLogsForEnvelope(status) {
5340
5767
  const logs = Array.isArray(status.progress?.logs) ? status.progress.logs : [];
5341
- const offset = typeof status.progress?.logOffset === "number" && Number.isFinite(status.progress.logOffset) ? Math.max(0, Math.trunc(status.progress.logOffset)) : 0;
5342
- const totalCount = offset + logs.length;
5768
+ const totalCount = logs.length;
5343
5769
  const entries = logs.slice(Math.max(0, logs.length - RUN_LOG_PREVIEW_LIMIT));
5344
- const firstSequence = entries.length === 0 ? null : offset + logs.length - entries.length + 1;
5770
+ const firstSequence = entries.length === 0 ? null : logs.length - entries.length + 1;
5345
5771
  const lastSequence = totalCount === 0 ? null : totalCount;
5346
5772
  return {
5347
5773
  totalCount,
@@ -5587,7 +6013,6 @@ function writeStartedPlayRun(input) {
5587
6013
  workflowId: input.runId,
5588
6014
  name: input.playName,
5589
6015
  status: input.status ?? "started",
5590
- statusUrl: input.statusUrl,
5591
6016
  dashboardUrl: input.dashboardUrl
5592
6017
  };
5593
6018
  if (input.jsonOutput) {
@@ -5599,7 +6024,7 @@ function writeStartedPlayRun(input) {
5599
6024
  `Started ${input.playName}`,
5600
6025
  ` run id: ${input.runId}`,
5601
6026
  ` get status: deepline runs get ${input.runId} --json`,
5602
- ` tail logs: deepline runs tail ${input.runId} --json`,
6027
+ ` logs: deepline runs logs ${input.runId} --json`,
5603
6028
  ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
5604
6029
  ` result JSON: deepline runs get ${input.runId} --json`
5605
6030
  ];
@@ -5614,7 +6039,7 @@ function writeStartedPlayRun(input) {
5614
6039
  console.log(output);
5615
6040
  }
5616
6041
  function parsePlayRunOptions(args) {
5617
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--json] [--<input> value]";
6042
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--<input> value]\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.map guidance.";
5618
6043
  let filePath = null;
5619
6044
  let playName = null;
5620
6045
  let input = null;
@@ -5624,8 +6049,8 @@ function parsePlayRunOptions(args) {
5624
6049
  let jsonOutput = watch ? args.includes("--json") : argsWantJson(args);
5625
6050
  const emitLogs = !jsonOutput || args.includes("--logs");
5626
6051
  const force = args.includes("--force");
6052
+ const noOpen = args.includes("--no-open");
5627
6053
  let outPath = null;
5628
- let pollIntervalMs = 500;
5629
6054
  let waitTimeoutMs = null;
5630
6055
  for (let index = 0; index < args.length; index += 1) {
5631
6056
  const arg = args[index];
@@ -5657,15 +6082,16 @@ function parsePlayRunOptions(args) {
5657
6082
  outPath = resolve7(args[++index]);
5658
6083
  continue;
5659
6084
  }
5660
- if ((arg === "--poll-interval-ms" || arg === "--interval-ms") && args[index + 1]) {
5661
- pollIntervalMs = parsePositiveInteger2(args[++index], arg);
5662
- continue;
6085
+ if (arg === "--poll-interval-ms" || arg === "--interval-ms") {
6086
+ throw new Error(
6087
+ `${arg} was removed. --watch uses the canonical run stream directly.`
6088
+ );
5663
6089
  }
5664
6090
  if ((arg === "--tail-timeout-ms" || arg === "--timeout-ms") && args[index + 1]) {
5665
6091
  waitTimeoutMs = parsePositiveInteger2(args[++index], arg);
5666
6092
  continue;
5667
6093
  }
5668
- if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--logs" || arg === "--force") {
6094
+ if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--logs" || arg === "--force" || arg === "--no-open") {
5669
6095
  if (arg === "--watch") {
5670
6096
  continue;
5671
6097
  }
@@ -5734,10 +6160,10 @@ function parsePlayRunOptions(args) {
5734
6160
  watch,
5735
6161
  emitLogs,
5736
6162
  jsonOutput,
5737
- pollIntervalMs,
5738
6163
  waitTimeoutMs,
5739
6164
  force,
5740
- outPath
6165
+ outPath,
6166
+ noOpen
5741
6167
  };
5742
6168
  }
5743
6169
  function parsePlayCheckOptions(args) {
@@ -5825,11 +6251,24 @@ async function handleFileBackedRun(options) {
5825
6251
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
5826
6252
  const absolutePlayPath = resolve7(options.target.path);
5827
6253
  progress.phase("compiling play");
5828
- const sourceCode = readFileSync3(absolutePlayPath, "utf-8");
6254
+ const sourceCode = traceCliSync(
6255
+ "cli.play_file_read_source",
6256
+ { targetKind: "file" },
6257
+ () => readFileSync3(absolutePlayPath, "utf-8")
6258
+ );
6259
+ const runtimeInput = options.input ? { ...options.input } : {};
5829
6260
  let graph;
5830
6261
  try {
5831
- graph = await collectBundledPlayGraph(absolutePlayPath);
5832
- await compileBundledPlayGraphManifests(client, graph);
6262
+ graph = await traceCliSpan(
6263
+ "cli.play_file_bundle_graph",
6264
+ { targetKind: "file" },
6265
+ () => collectBundledPlayGraph(absolutePlayPath)
6266
+ );
6267
+ await traceCliSpan(
6268
+ "cli.play_file_compile_manifests",
6269
+ { targetKind: "file" },
6270
+ () => compileBundledPlayGraphManifests(client, graph)
6271
+ );
5833
6272
  progress.phase("compiled play");
5834
6273
  } catch (error) {
5835
6274
  progress.fail();
@@ -5840,36 +6279,47 @@ async function handleFileBackedRun(options) {
5840
6279
  const playName = bundleResult.playName ?? extractPlayName(sourceCode, absolutePlayPath);
5841
6280
  try {
5842
6281
  progress.phase("publishing imported plays");
5843
- await publishImportedPlayDependencies(client, graph);
6282
+ await traceCliSpan(
6283
+ "cli.play_file_publish_imports",
6284
+ { targetKind: "file" },
6285
+ () => publishImportedPlayDependencies(client, graph)
6286
+ );
5844
6287
  } catch (error) {
5845
6288
  progress.fail();
5846
6289
  console.error(error instanceof Error ? error.message : String(error));
5847
6290
  return 1;
5848
6291
  }
5849
- const runtimeInput = options.input ? { ...options.input } : {};
5850
6292
  const packagedFileUploads = bundleResult.packagedFiles.map(
5851
6293
  (file) => stageFile(file.logicalPath, file.absolutePath)
5852
6294
  );
6295
+ const compilerManifest = requireCompilerManifest(bundleResult);
5853
6296
  const fileInputBindings = fileInputBindingsFromStaticPipeline(
5854
- requireCompilerManifest(bundleResult).staticPipeline
6297
+ compilerManifest.staticPipeline
5855
6298
  );
5856
6299
  applyCsvShortcutInput({
5857
6300
  runtimeInput,
5858
6301
  bindings: fileInputBindings,
5859
6302
  fallbackInputPath: "file"
5860
6303
  });
5861
- const stagedFileInputs = await stageFileInputArgs({
5862
- client,
5863
- runtimeInput,
5864
- bindings: fileInputBindings,
5865
- progress
5866
- });
6304
+ const stagedFileInputs = await traceCliSpan(
6305
+ "cli.play_stage_inputs",
6306
+ {
6307
+ targetKind: "file",
6308
+ bindingCount: fileInputBindings.length
6309
+ },
6310
+ () => stageFileInputArgs({
6311
+ client,
6312
+ runtimeInput,
6313
+ bindings: fileInputBindings,
6314
+ progress
6315
+ })
6316
+ );
5867
6317
  const startRequest = {
5868
6318
  name: playName,
5869
6319
  sourceCode: bundleResult.sourceCode,
5870
6320
  sourceFiles: bundleResult.sourceFiles,
5871
6321
  runtimeArtifact: bundleResult.artifact,
5872
- compilerManifest: requireCompilerManifest(bundleResult),
6322
+ compilerManifest,
5873
6323
  packagedFileUploads,
5874
6324
  ...Object.keys(runtimeInput).length > 0 ? { input: runtimeInput } : {},
5875
6325
  ...stagedFileInputs.inputFile ? { inputFile: stagedFileInputs.inputFile } : {},
@@ -5878,35 +6328,57 @@ async function handleFileBackedRun(options) {
5878
6328
  };
5879
6329
  if (options.watch) {
5880
6330
  progress.phase("starting run");
5881
- const finalStatus = await startAndWaitForPlayCompletionByStream({
5882
- client,
5883
- request: startRequest,
5884
- playName,
5885
- jsonOutput: options.jsonOutput,
5886
- emitLogs: options.emitLogs,
5887
- waitTimeoutMs: options.waitTimeoutMs,
5888
- progress
5889
- });
5890
- const exportedPath = exportPlayStatusRows(finalStatus, options.outPath);
6331
+ const finalStatus = await traceCliSpan(
6332
+ "cli.play_start_watch",
6333
+ { targetKind: "file", playName },
6334
+ () => startAndWaitForPlayCompletionByStream({
6335
+ client,
6336
+ request: startRequest,
6337
+ playName,
6338
+ jsonOutput: options.jsonOutput,
6339
+ emitLogs: options.emitLogs,
6340
+ waitTimeoutMs: options.waitTimeoutMs,
6341
+ noOpen: options.noOpen,
6342
+ progress
6343
+ })
6344
+ );
6345
+ const exportedPath = traceCliSync(
6346
+ "cli.play_export_rows",
6347
+ { targetKind: "file", playName },
6348
+ () => exportPlayStatusRows(finalStatus, options.outPath)
6349
+ );
5891
6350
  if (finalStatus.status === "completed") {
5892
6351
  progress.complete();
5893
6352
  } else {
5894
6353
  progress.fail();
5895
6354
  }
5896
- writePlayResult(finalStatus, options.jsonOutput, { exportedPath });
6355
+ traceCliSync(
6356
+ "cli.play_write_result",
6357
+ { targetKind: "file", playName },
6358
+ () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath })
6359
+ );
5897
6360
  return finalStatus.status === "completed" ? 0 : 1;
5898
6361
  }
5899
6362
  progress.phase("starting run");
5900
- const started = await client.startPlayRun(startRequest);
6363
+ const started = await traceCliSpan(
6364
+ "cli.play_start_unwatched",
6365
+ { targetKind: "file", playName },
6366
+ () => client.startPlayRun(startRequest)
6367
+ );
5901
6368
  const dashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
5902
- progress.phase(`loading play on ${dashboardUrl}`);
6369
+ const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
6370
+ openPlayDashboard({
6371
+ dashboardUrl: resolvedDashboardUrl,
6372
+ jsonOutput: options.jsonOutput,
6373
+ noOpen: options.noOpen
6374
+ });
6375
+ progress.phase(`loading play on ${resolvedDashboardUrl}`);
5903
6376
  progress.complete();
5904
6377
  writeStartedPlayRun({
5905
6378
  runId: started.workflowId,
5906
6379
  playName,
5907
6380
  status: started.status,
5908
- statusUrl: started.statusUrl,
5909
- dashboardUrl: started.dashboardUrl ?? dashboardUrl,
6381
+ dashboardUrl: resolvedDashboardUrl,
5910
6382
  jsonOutput: options.jsonOutput,
5911
6383
  progress
5912
6384
  });
@@ -5932,32 +6404,67 @@ async function handleNamedRun(options) {
5932
6404
  }
5933
6405
  const client = new DeeplineClient();
5934
6406
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
5935
- progress.phase("loading play definition");
5936
- const playDetail = await assertCanonicalNamedPlayReference(client, options.target.name);
5937
- progress.phase("selecting revision");
5938
- const selectedRevisionId = await resolveNamedRunRevisionId({
5939
- client,
5940
- playName: options.target.name,
5941
- revisionId: options.revisionId,
5942
- selector: options.revisionSelector
5943
- });
6407
+ const playName = options.target.name;
5944
6408
  const runtimeInput = options.input ? { ...options.input } : {};
5945
- const fileInputBindings = [
6409
+ const needsPlayDefinition = namedRunNeedsPlayDefinition({
6410
+ runtimeInput,
6411
+ revisionSelector: options.revisionSelector
6412
+ });
6413
+ const playDetail = needsPlayDefinition ? await (async () => {
6414
+ progress.phase("loading play definition");
6415
+ return traceCliSpan(
6416
+ "cli.play_load_definition",
6417
+ { targetKind: "name", playName, skipped: false },
6418
+ () => assertCanonicalNamedPlayReference(client, playName)
6419
+ );
6420
+ })() : (recordCliTrace({
6421
+ phase: "cli.play_load_definition",
6422
+ ms: 0,
6423
+ ok: true,
6424
+ targetKind: "name",
6425
+ playName,
6426
+ skipped: true
6427
+ }), null);
6428
+ progress.phase("selecting revision");
6429
+ const selectedRevisionId = await traceCliSpan(
6430
+ "cli.play_select_revision",
6431
+ {
6432
+ targetKind: "name",
6433
+ playName,
6434
+ selector: options.revisionSelector,
6435
+ hasExplicitRevisionId: Boolean(options.revisionId)
6436
+ },
6437
+ () => resolveNamedRunRevisionId({
6438
+ client,
6439
+ playName,
6440
+ revisionId: options.revisionId,
6441
+ selector: options.revisionSelector
6442
+ })
6443
+ );
6444
+ const fileInputBindings = playDetail ? [
5946
6445
  ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5947
6446
  ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5948
- ];
6447
+ ] : [];
5949
6448
  applyCsvShortcutInput({
5950
6449
  runtimeInput,
5951
6450
  bindings: fileInputBindings
5952
6451
  });
5953
- const stagedFileInputs = await stageFileInputArgs({
5954
- client,
5955
- runtimeInput,
5956
- bindings: fileInputBindings,
5957
- progress
5958
- });
6452
+ const stagedFileInputs = await traceCliSpan(
6453
+ "cli.play_stage_inputs",
6454
+ {
6455
+ targetKind: "name",
6456
+ playName,
6457
+ bindingCount: fileInputBindings.length
6458
+ },
6459
+ () => stageFileInputArgs({
6460
+ client,
6461
+ runtimeInput,
6462
+ bindings: fileInputBindings,
6463
+ progress
6464
+ })
6465
+ );
5959
6466
  const startRequest = {
5960
- name: options.target.name,
6467
+ name: playName,
5961
6468
  ...selectedRevisionId ? { revisionId: selectedRevisionId } : {},
5962
6469
  ...Object.keys(runtimeInput).length > 0 ? { input: runtimeInput } : {},
5963
6470
  ...stagedFileInputs.inputFile ? { inputFile: stagedFileInputs.inputFile } : {},
@@ -5966,38 +6473,60 @@ async function handleNamedRun(options) {
5966
6473
  };
5967
6474
  if (options.watch) {
5968
6475
  progress.phase("starting run");
5969
- const finalStatus = await startAndWaitForPlayCompletionByStream({
5970
- client,
5971
- request: startRequest,
5972
- playName: options.target.name,
5973
- jsonOutput: options.jsonOutput,
5974
- emitLogs: options.emitLogs,
5975
- waitTimeoutMs: options.waitTimeoutMs,
5976
- progress
5977
- });
5978
- const exportedPath = exportPlayStatusRows(finalStatus, options.outPath);
6476
+ const finalStatus = await traceCliSpan(
6477
+ "cli.play_start_watch",
6478
+ { targetKind: "name", playName },
6479
+ () => startAndWaitForPlayCompletionByStream({
6480
+ client,
6481
+ request: startRequest,
6482
+ playName,
6483
+ jsonOutput: options.jsonOutput,
6484
+ emitLogs: options.emitLogs,
6485
+ waitTimeoutMs: options.waitTimeoutMs,
6486
+ noOpen: options.noOpen,
6487
+ progress
6488
+ })
6489
+ );
6490
+ const exportedPath = traceCliSync(
6491
+ "cli.play_export_rows",
6492
+ { targetKind: "name", playName },
6493
+ () => exportPlayStatusRows(finalStatus, options.outPath)
6494
+ );
5979
6495
  if (finalStatus.status === "completed") {
5980
6496
  progress.complete();
5981
6497
  } else {
5982
6498
  progress.fail();
5983
6499
  }
5984
- writePlayResult(finalStatus, options.jsonOutput, { exportedPath });
6500
+ traceCliSync(
6501
+ "cli.play_write_result",
6502
+ { targetKind: "name", playName },
6503
+ () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath })
6504
+ );
5985
6505
  return finalStatus.status === "completed" ? 0 : 1;
5986
6506
  }
5987
6507
  progress.phase("starting run");
5988
- const started = await client.startPlayRun(startRequest);
6508
+ const started = await traceCliSpan(
6509
+ "cli.play_start_unwatched",
6510
+ { targetKind: "name", playName },
6511
+ () => client.startPlayRun(startRequest)
6512
+ );
5989
6513
  const dashboardUrl = buildPlayDashboardUrl(
5990
6514
  client.baseUrl,
5991
- options.target.name
6515
+ playName
5992
6516
  );
5993
- progress.phase(`loading play on ${dashboardUrl}`);
6517
+ const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
6518
+ openPlayDashboard({
6519
+ dashboardUrl: resolvedDashboardUrl,
6520
+ jsonOutput: options.jsonOutput,
6521
+ noOpen: options.noOpen
6522
+ });
6523
+ progress.phase(`loading play on ${resolvedDashboardUrl}`);
5994
6524
  progress.complete();
5995
6525
  writeStartedPlayRun({
5996
6526
  runId: started.workflowId,
5997
- playName: started.name ?? options.target.name,
6527
+ playName: started.name ?? playName,
5998
6528
  status: started.status,
5999
- statusUrl: started.statusUrl,
6000
- dashboardUrl: started.dashboardUrl ?? dashboardUrl,
6529
+ dashboardUrl: resolvedDashboardUrl,
6001
6530
  jsonOutput: options.jsonOutput,
6002
6531
  progress
6003
6532
  });
@@ -6011,8 +6540,8 @@ async function handlePlayRun(args) {
6011
6540
  }
6012
6541
  const resolved = resolve7(options.target.path);
6013
6542
  console.error(`File not found: ${resolved}`);
6014
- const dir = dirname6(resolved);
6015
- if (existsSync4(dir)) {
6543
+ const dir = dirname7(resolved);
6544
+ if (existsSync5(dir)) {
6016
6545
  const base = basename3(resolved);
6017
6546
  try {
6018
6547
  const siblings = readdirSync(dir).filter(
@@ -6040,7 +6569,7 @@ function parseRunIdPositional(args, usage) {
6040
6569
  }
6041
6570
  continue;
6042
6571
  }
6043
- if ((arg === "--out" || arg === "--cursor" || arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
6572
+ if ((arg === "--out" || arg === "--reason") && args[index + 1]) {
6044
6573
  index += 1;
6045
6574
  continue;
6046
6575
  }
@@ -6125,7 +6654,7 @@ async function handleRunsList(args) {
6125
6654
  return 0;
6126
6655
  }
6127
6656
  async function handleRunTail(args) {
6128
- const usage = "Usage: deepline runs tail <run-id> [--json] [--compact] [--cursor <cursor>]";
6657
+ const usage = "Usage: deepline runs tail <run-id> [--json] [--compact]";
6129
6658
  let runId;
6130
6659
  try {
6131
6660
  runId = parseRunIdPositional(args, usage);
@@ -6133,20 +6662,19 @@ async function handleRunTail(args) {
6133
6662
  console.error(error instanceof Error ? error.message : usage);
6134
6663
  return 1;
6135
6664
  }
6136
- const client = new DeeplineClient();
6137
- let afterLogIndex;
6138
6665
  for (let index = 0; index < args.length; index += 1) {
6139
6666
  const arg = args[index];
6140
- if (arg === "--cursor" && args[index + 1]) {
6141
- const parsed = Number(args[++index]);
6142
- if (Number.isInteger(parsed) && parsed >= 0) {
6143
- afterLogIndex = parsed;
6144
- }
6667
+ if (arg === "--cursor") {
6668
+ console.error("--cursor was removed. deepline runs tail reads the canonical run stream.");
6669
+ return 1;
6670
+ }
6671
+ if (arg.startsWith("--") && arg !== "--json" && arg !== "--compact") {
6672
+ console.error(`${arg} is not supported by deepline runs tail.`);
6673
+ return 1;
6145
6674
  }
6146
6675
  }
6147
- const status = await client.runs.tail(runId, {
6148
- ...afterLogIndex !== void 0 ? { afterLogIndex } : {}
6149
- });
6676
+ const client = new DeeplineClient();
6677
+ const status = await client.runs.tail(runId);
6150
6678
  writePlayResult(status, argsWantJson(args));
6151
6679
  return status.status === "failed" ? 1 : 0;
6152
6680
  }
@@ -6175,7 +6703,7 @@ async function handleRunLogs(args) {
6175
6703
  const status = await client.runs.get(runId);
6176
6704
  const logs = status.progress?.logs ?? [];
6177
6705
  if (outPath) {
6178
- writeFileSync4(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6706
+ writeFileSync5(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6179
6707
  if (argsWantJson(args)) {
6180
6708
  process.stdout.write(
6181
6709
  `${JSON.stringify({
@@ -6687,8 +7215,9 @@ function registerPlayCommands(program) {
6687
7215
  "after",
6688
7216
  `
6689
7217
  Concepts:
6690
- Plays are durable Deepline cloud workflows. Local .play.ts files are bundled locally,
6691
- then validated and executed in Deepline cloud.
7218
+ Plays are durable cloud workflows.
7219
+ Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
7220
+ ctx.map adds row keys and row progress.
6692
7221
 
6693
7222
  Common commands:
6694
7223
  deepline plays search email --json
@@ -6719,17 +7248,42 @@ Examples:
6719
7248
  "after",
6720
7249
  `
6721
7250
  Notes:
6722
- Local play files are bundled locally, then validated and executed in Deepline cloud.
6723
- Named plays run the stored live cloud revision.
6724
- Unknown --foo and --foo.bar flags are treated as play input args.
6725
- File-like input args accept local paths; the CLI stages those files before submit.
6726
- Run performs server preflight automatically. Use \`deepline plays check <file>\`
6727
- to validate without starting a run.
7251
+ Local files are bundled, preflighted, then run in Deepline cloud.
7252
+ Named plays run the live saved revision.
7253
+ Unknown --foo and --foo.bar flags are treated as play input args.
7254
+ File args accept local paths; the CLI stages files before submit.
7255
+ --watch prints logs, previews, stats, and next commands.
7256
+ The play page opens in your browser as soon as the run starts; use --no-open
7257
+ to only print the URL.
7258
+ --force supersedes active runs; it does not bypass completed reuse.
7259
+
7260
+ Idempotent execution:
7261
+ Stable tool call ids are the reuse key:
7262
+
7263
+ await ctx.tools.execute({ id: 'company_lookup', tool, input });
7264
+
7265
+ For rows, use ctx.map plus a stable row key:
7266
+
7267
+ const rows = await ctx
7268
+ .map('companies_v1', companies)
7269
+ .step('cto', (row, ctx) => ctx.tools.execute({
7270
+ id: 'find_cto',
7271
+ tool: 'apollo_search_people_with_match',
7272
+ input: { q_organization_domains_list: [row.domain], per_page: 1 },
7273
+ }))
7274
+ .run({ key: 'domain' });
7275
+
7276
+ Reuse needs the same play, tool id, map name, row key, and compatible logic.
7277
+ To refresh, change the id/map key or set staleAfterSeconds:
7278
+
7279
+ .run({ key: 'domain', staleAfterSeconds: 86400 })
6728
7280
 
6729
7281
  Examples:
6730
7282
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
6731
7283
  deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}' --watch
6732
7284
  deepline plays run enrich.play.ts --csv leads.csv --watch --out leads-enriched.csv
7285
+ deepline plays run cto-search.play.ts --limit 5 --watch
7286
+ deepline runs get <run-id>
6733
7287
  `
6734
7288
  ).option("--file <path>", "Local play file to run").option("--name <name>", "Saved play name to run").option("-i, --input <json>", "Input JSON object or @file path").option("--live", "Run the current live revision explicitly").option("--latest", "Run the newest saved revision, even if it is not live").option(
6735
7289
  "--revision-id <id>",
@@ -6740,7 +7294,7 @@ Examples:
6740
7294
  ).option("--watch", "Stream logs until completion").option(
6741
7295
  "--logs",
6742
7296
  "When output is non-interactive, stream play logs to stderr while waiting"
6743
- ).option("--poll-interval-ms <ms>", "Polling interval while tailing").option("--tail-timeout-ms <ms>", "Timeout while tailing").option("--force", "Supersede any active runs for this play").option("--json", "Emit JSON output").action(async (target, options, command) => {
7297
+ ).option("--tail-timeout-ms <ms>", "Timeout while watching the run stream").option("--force", "Supersede any active runs for this play").option("--no-open", "Print the play page URL without opening a browser").option("--json", "Emit JSON output").action(async (target, options, command) => {
6744
7298
  const passthroughArgs = [...command.args];
6745
7299
  const explicitTarget = options.file || options.name;
6746
7300
  const targetIsInputFlag = typeof target === "string" && target.startsWith("--");
@@ -6762,9 +7316,9 @@ Examples:
6762
7316
  ...options.out ? ["--out", options.out] : [],
6763
7317
  ...options.watch ? ["--watch"] : [],
6764
7318
  ...options.logs ? ["--logs"] : [],
6765
- ...options.pollIntervalMs ? ["--poll-interval-ms", options.pollIntervalMs] : [],
6766
7319
  ...options.tailTimeoutMs ? ["--tail-timeout-ms", options.tailTimeoutMs] : [],
6767
7320
  ...options.force ? ["--force"] : [],
7321
+ ...options.open === false ? ["--no-open"] : [],
6768
7322
  ...options.json ? ["--json"] : [],
6769
7323
  ...passthroughArgs
6770
7324
  ]);
@@ -6871,12 +7425,11 @@ Examples:
6871
7425
  ...options.json ? ["--json"] : []
6872
7426
  ]);
6873
7427
  });
6874
- runs.command("tail <runId>").description("Tail or fetch recent live events for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--compact", "Drop verbose fields from JSON output").option("--cursor <cursor>", "Resume after a previously returned event cursor").action(async (runId, options) => {
7428
+ runs.command("tail <runId>").description("Read the canonical live stream for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--compact", "Drop verbose fields from JSON output").action(async (runId, options) => {
6875
7429
  process.exitCode = await handleRunTail([
6876
7430
  runId,
6877
7431
  ...options.json ? ["--json"] : [],
6878
- ...options.compact ? ["--compact"] : [],
6879
- ...options.cursor ? ["--cursor", options.cursor] : []
7432
+ ...options.compact ? ["--compact"] : []
6880
7433
  ]);
6881
7434
  });
6882
7435
  runs.command("logs <runId>").description("Fetch persisted logs for a play run.").option("--limit <count>", "Maximum recent log lines to print without --out", "200").option("--out <path>", "Write the full persisted log stream to a file").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
@@ -6905,12 +7458,12 @@ Examples:
6905
7458
  }
6906
7459
 
6907
7460
  // src/cli/commands/tools.ts
6908
- import { chmodSync, mkdtempSync, writeFileSync as writeFileSync6 } from "fs";
7461
+ import { chmodSync, mkdtempSync, writeFileSync as writeFileSync7 } from "fs";
6909
7462
  import { tmpdir as tmpdir3 } from "os";
6910
7463
  import { join as join8 } from "path";
6911
7464
 
6912
7465
  // src/tool-output.ts
6913
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
7466
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
6914
7467
  import { homedir as homedir3 } from "os";
6915
7468
  import { join as join7 } from "path";
6916
7469
  function isPlainObject(value) {
@@ -6988,13 +7541,13 @@ function tryConvertToList(payload, options) {
6988
7541
  }
6989
7542
  function ensureOutputDir() {
6990
7543
  const outputDir = join7(homedir3(), ".local", "share", "deepline", "data");
6991
- mkdirSync3(outputDir, { recursive: true });
7544
+ mkdirSync4(outputDir, { recursive: true });
6992
7545
  return outputDir;
6993
7546
  }
6994
7547
  function writeJsonOutputFile(payload, stem) {
6995
7548
  const outputDir = ensureOutputDir();
6996
7549
  const outputPath = join7(outputDir, `${stem}_${Date.now()}.json`);
6997
- writeFileSync5(outputPath, JSON.stringify(payload, null, 2), "utf-8");
7550
+ writeFileSync6(outputPath, JSON.stringify(payload, null, 2), "utf-8");
6998
7551
  return outputPath;
6999
7552
  }
7000
7553
  function writeCsvOutputFile(rows, stem) {
@@ -7022,7 +7575,7 @@ function writeCsvOutputFile(rows, stem) {
7022
7575
  for (const row of rows) {
7023
7576
  lines.push(columns.map((column) => escapeCell(row[column])).join(","));
7024
7577
  }
7025
- writeFileSync5(outputPath, `${lines.join("\n")}
7578
+ writeFileSync6(outputPath, `${lines.join("\n")}
7026
7579
  `, "utf-8");
7027
7580
  const previewRows = rows.slice(0, 5);
7028
7581
  const previewColumns = columns.slice(0, 5);
@@ -7559,7 +8112,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
7559
8112
  };
7560
8113
  });
7561
8114
  `;
7562
- writeFileSync6(scriptPath, script, { encoding: "utf-8", mode: 384 });
8115
+ writeFileSync7(scriptPath, script, { encoding: "utf-8", mode: 384 });
7563
8116
  return {
7564
8117
  path: scriptPath,
7565
8118
  projectDir,
@@ -7666,10 +8219,10 @@ async function executeTool(args) {
7666
8219
  }
7667
8220
 
7668
8221
  // src/cli/skills-sync.ts
7669
- import { spawn, spawnSync } from "child_process";
7670
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync7 } from "fs";
8222
+ import { spawn, spawnSync as spawnSync2 } from "child_process";
8223
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync8 } from "fs";
7671
8224
  import { homedir as homedir4 } from "os";
7672
- import { dirname as dirname7, join as join9 } from "path";
8225
+ import { dirname as dirname8, join as join9 } from "path";
7673
8226
  var CHECK_TIMEOUT_MS2 = 3e3;
7674
8227
  var SDK_SKILL_NAME = "deepline-sdk";
7675
8228
  var SKILL_AGENTS = ["codex", "claude-code", "cursor"];
@@ -7684,7 +8237,7 @@ function sdkSkillsVersionPath(baseUrl) {
7684
8237
  }
7685
8238
  function readLocalSkillsVersion(baseUrl) {
7686
8239
  const path = sdkSkillsVersionPath(baseUrl);
7687
- if (!existsSync5(path)) return "";
8240
+ if (!existsSync6(path)) return "";
7688
8241
  try {
7689
8242
  return readFileSync4(path, "utf-8").trim();
7690
8243
  } catch {
@@ -7693,8 +8246,8 @@ function readLocalSkillsVersion(baseUrl) {
7693
8246
  }
7694
8247
  function writeLocalSkillsVersion(baseUrl, version) {
7695
8248
  const path = sdkSkillsVersionPath(baseUrl);
7696
- mkdirSync4(dirname7(path), { recursive: true });
7697
- writeFileSync7(path, `${version}
8249
+ mkdirSync5(dirname8(path), { recursive: true });
8250
+ writeFileSync8(path, `${version}
7698
8251
  `, "utf-8");
7699
8252
  }
7700
8253
  async function fetchSkillsUpdate(baseUrl, localVersion) {
@@ -7758,7 +8311,7 @@ function buildBunxSkillsInstallArgs(baseUrl) {
7758
8311
  ];
7759
8312
  }
7760
8313
  function hasCommand(command) {
7761
- const result = spawnSync(command, ["--version"], {
8314
+ const result = spawnSync2(command, ["--version"], {
7762
8315
  stdio: "ignore",
7763
8316
  shell: process.platform === "win32"
7764
8317
  });
@@ -7857,54 +8410,6 @@ async function syncSdkSkillsIfNeeded(baseUrl) {
7857
8410
  writeSdkSkillsStatusLine("SDK skills are up to date.");
7858
8411
  }
7859
8412
 
7860
- // src/cli/trace.ts
7861
- var cliTraceStartedAt = Date.now();
7862
- function isTruthyEnv(value) {
7863
- return value === "1" || value === "true" || value === "yes";
7864
- }
7865
- function isCliTraceEnabled() {
7866
- return isTruthyEnv(process.env.DEEPLINE_CLI_TRACE);
7867
- }
7868
- function recordCliTrace(event) {
7869
- if (!isCliTraceEnabled()) {
7870
- return;
7871
- }
7872
- const now = Date.now();
7873
- const payload = {
7874
- ts: now,
7875
- source: "cli",
7876
- sinceStartMs: now - cliTraceStartedAt,
7877
- ...event
7878
- };
7879
- process.stderr.write(`[cli-trace] ${JSON.stringify(payload)}
7880
- `);
7881
- }
7882
- async function traceCliSpan(phase, fields, run) {
7883
- if (!isCliTraceEnabled()) {
7884
- return run();
7885
- }
7886
- const startedAt = Date.now();
7887
- try {
7888
- const result = await run();
7889
- recordCliTrace({
7890
- phase,
7891
- ms: Date.now() - startedAt,
7892
- ok: true,
7893
- ...fields
7894
- });
7895
- return result;
7896
- } catch (error) {
7897
- recordCliTrace({
7898
- phase,
7899
- ms: Date.now() - startedAt,
7900
- ok: false,
7901
- error: error instanceof Error ? error.message : String(error),
7902
- ...fields
7903
- });
7904
- throw error;
7905
- }
7906
- }
7907
-
7908
8413
  // src/cli/index.ts
7909
8414
  function shouldPrintStartupPhase() {
7910
8415
  if (process.argv.includes("--json")) {
@@ -7915,6 +8420,12 @@ function shouldPrintStartupPhase() {
7915
8420
  const subcommand = args[1];
7916
8421
  return (command === "play" || command === "plays") && subcommand === "run";
7917
8422
  }
8423
+ function shouldDeferSkillsSyncForCommand() {
8424
+ const args = process.argv.slice(2);
8425
+ const command = args[0];
8426
+ const subcommand = args[1];
8427
+ return (command === "play" || command === "plays") && subcommand === "run" && args.includes("--json");
8428
+ }
7918
8429
  async function main() {
7919
8430
  const mainStartedAt = Date.now();
7920
8431
  recordCliTrace({
@@ -7959,11 +8470,13 @@ Output:
7959
8470
  if (printStartupPhase) {
7960
8471
  progress?.phase("checking sdk skills");
7961
8472
  }
7962
- await traceCliSpan(
7963
- "cli.sdk_skills_sync",
7964
- { baseUrl },
7965
- () => syncSdkSkillsIfNeeded(baseUrl)
7966
- );
8473
+ if (!shouldDeferSkillsSyncForCommand()) {
8474
+ await traceCliSpan(
8475
+ "cli.sdk_skills_sync",
8476
+ { baseUrl },
8477
+ () => syncSdkSkillsIfNeeded(baseUrl)
8478
+ );
8479
+ }
7967
8480
  });
7968
8481
  registerAuthCommands(program);
7969
8482
  registerToolsCommands(program);