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.
package/dist/cli/index.js CHANGED
@@ -266,7 +266,7 @@ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStar
266
266
  }
267
267
 
268
268
  // src/version.ts
269
- var SDK_VERSION = "0.1.20";
269
+ var SDK_VERSION = "0.1.22";
270
270
  var SDK_API_CONTRACT = "2026-05-runs-v2";
271
271
 
272
272
  // ../shared_libs/play-runtime/coordinator-headers.ts
@@ -349,7 +349,7 @@ var HttpClient = class {
349
349
  const response = await fetch(candidateUrl, {
350
350
  method,
351
351
  headers,
352
- body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
352
+ body: options?.formData !== void 0 ? options.formData : options?.body !== void 0 ? JSON.stringify(options.body) : void 0,
353
353
  signal: controller.signal
354
354
  });
355
355
  clearTimeout(timeoutId);
@@ -468,6 +468,13 @@ var HttpClient = class {
468
468
  headers
469
469
  });
470
470
  }
471
+ async postFormData(path, formData, headers) {
472
+ return this.request(path, {
473
+ method: "POST",
474
+ formData,
475
+ headers
476
+ });
477
+ }
471
478
  /**
472
479
  * Send a DELETE request.
473
480
  *
@@ -551,7 +558,7 @@ function isRecord(value) {
551
558
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
552
559
  }
553
560
  function normalizePlayStatus(raw) {
554
- const status = typeof raw.status === "string" ? raw.status : typeof raw.temporalStatus === "string" ? mapLegacyTemporalStatus(raw.temporalStatus) : "running";
561
+ const status = typeof raw.status === "string" ? raw.status : "running";
555
562
  const runId = typeof raw.runId === "string" ? raw.runId : typeof raw.workflowId === "string" ? raw.workflowId : "";
556
563
  return {
557
564
  ...raw,
@@ -559,22 +566,86 @@ function normalizePlayStatus(raw) {
559
566
  status
560
567
  };
561
568
  }
562
- function mapLegacyTemporalStatus(status) {
563
- switch (status.trim().toUpperCase()) {
564
- case "PENDING":
565
- return "queued";
566
- case "COMPLETED":
567
- return "completed";
568
- case "FAILED":
569
- return "failed";
570
- case "CANCELLED":
571
- case "TERMINATED":
572
- case "TIMED_OUT":
573
- return "cancelled";
574
- case "RUNNING":
575
- default:
576
- return "running";
569
+ function decodeBase64Bytes(value) {
570
+ const binary = atob(value);
571
+ const bytes = new Uint8Array(binary.length);
572
+ for (let index = 0; index < binary.length; index += 1) {
573
+ bytes[index] = binary.charCodeAt(index);
574
+ }
575
+ return bytes;
576
+ }
577
+ function readStringArray(value) {
578
+ return Array.isArray(value) ? value.filter((line) => typeof line === "string") : [];
579
+ }
580
+ function getPlayLiveEventPayload(event) {
581
+ return event.payload && typeof event.payload === "object" ? event.payload : {};
582
+ }
583
+ function normalizeLiveStatus(value) {
584
+ if (value === "queued" || value === "running" || value === "waiting" || value === "completed" || value === "failed" || value === "cancelled") {
585
+ return value;
586
+ }
587
+ return null;
588
+ }
589
+ function updatePlayLiveStatusState(state, event) {
590
+ const payload = getPlayLiveEventPayload(event);
591
+ if (event.type === "play.run.log") {
592
+ state.logs.push(...readStringArray(payload.lines));
593
+ return null;
594
+ }
595
+ if (event.type !== "play.run.snapshot" && event.type !== "play.run.status" && event.type !== "play.run.final_status") {
596
+ return null;
597
+ }
598
+ const runId = typeof payload.runId === "string" && payload.runId ? payload.runId : state.runId;
599
+ const status = normalizeLiveStatus(payload.status) ?? state.status;
600
+ const logs = readStringArray(payload.logs);
601
+ if (logs.length > 0 || event.type === "play.run.snapshot") {
602
+ state.logs = logs;
603
+ }
604
+ if ("result" in payload) {
605
+ state.result = payload.result;
606
+ }
607
+ if (typeof payload.error === "string" && payload.error.trim()) {
608
+ state.error = payload.error;
577
609
  }
610
+ state.runId = runId;
611
+ state.status = status;
612
+ const progressRecord = payload.progress && typeof payload.progress === "object" && !Array.isArray(payload.progress) ? payload.progress : {};
613
+ const next = {
614
+ ...payload,
615
+ runId,
616
+ status,
617
+ progress: {
618
+ ...progressRecord,
619
+ status: typeof progressRecord.status === "string" ? progressRecord.status : status,
620
+ logs: state.logs,
621
+ ...state.error ? { error: state.error } : {}
622
+ },
623
+ ..."result" in state ? { result: state.result } : {}
624
+ };
625
+ state.latest = next;
626
+ return next;
627
+ }
628
+ function playRunResultFromStatus(status, startedAt, fallbackRunId) {
629
+ return {
630
+ success: status.status === "completed",
631
+ runId: status.runId || fallbackRunId,
632
+ result: status.result,
633
+ logs: status.progress?.logs ?? [],
634
+ durationMs: Date.now() - startedAt,
635
+ error: status.progress?.error ?? (status.status !== "completed" ? status.status : void 0)
636
+ };
637
+ }
638
+ function playRunStatusFromState(state) {
639
+ return {
640
+ runId: state.runId,
641
+ status: state.status,
642
+ progress: {
643
+ status: state.status,
644
+ logs: state.logs,
645
+ ...state.error ? { error: state.error } : {}
646
+ },
647
+ ..."result" in state ? { result: state.result } : {}
648
+ };
578
649
  }
579
650
  var DeeplineClient = class {
580
651
  http;
@@ -685,7 +756,7 @@ var DeeplineClient = class {
685
756
  /**
686
757
  * Search available tools using Deepline's ranked backend search.
687
758
  *
688
- * This is the same discovery surface used by the legacy CLI: it ranks across
759
+ * This is the same discovery surface used by the CLI: it ranks across
689
760
  * tool metadata, categories, agent guidance, and input schema fields.
690
761
  */
691
762
  async searchTools(options = {}) {
@@ -778,7 +849,7 @@ var DeeplineClient = class {
778
849
  * `progress.logs`; they are not part of the user output object.
779
850
  *
780
851
  * @param request - Play run configuration (name, code, input, etc.)
781
- * @returns Workflow metadata including the `workflowId` for status polling
852
+ * @returns Run metadata including the public `workflowId`
782
853
  *
783
854
  * @example
784
855
  * ```typescript
@@ -1027,9 +1098,34 @@ var DeeplineClient = class {
1027
1098
  * ```
1028
1099
  */
1029
1100
  async stagePlayFiles(files) {
1030
- const response = await this.http.post(
1101
+ const formData = new FormData();
1102
+ formData.set(
1103
+ "metadata",
1104
+ JSON.stringify({
1105
+ files: files.map((file, index) => ({
1106
+ index,
1107
+ logicalPath: file.logicalPath,
1108
+ contentHash: file.contentHash,
1109
+ contentType: file.contentType,
1110
+ bytes: file.bytes
1111
+ }))
1112
+ })
1113
+ );
1114
+ for (const [index, file] of files.entries()) {
1115
+ const bytes = decodeBase64Bytes(file.contentBase64);
1116
+ const body = bytes.buffer.slice(
1117
+ bytes.byteOffset,
1118
+ bytes.byteOffset + bytes.byteLength
1119
+ );
1120
+ formData.set(
1121
+ `file:${index}`,
1122
+ new Blob([body], { type: file.contentType }),
1123
+ file.logicalPath
1124
+ );
1125
+ }
1126
+ const response = await this.http.postFormData(
1031
1127
  "/api/v2/plays/files/stage",
1032
- { files }
1128
+ formData
1033
1129
  );
1034
1130
  return response.files;
1035
1131
  }
@@ -1045,9 +1141,6 @@ var DeeplineClient = class {
1045
1141
  * Internal/advanced primitive. Public callers should usually prefer
1046
1142
  * {@link runPlay}, {@link PlayJob.get}, or `deepline play run --watch`.
1047
1143
  *
1048
- * Poll this method until `status` reaches a terminal state:
1049
- * `'completed'`, `'failed'`, or `'cancelled'`.
1050
- *
1051
1144
  * @param workflowId - Play-run id from {@link startPlayRun}
1052
1145
  * @returns Current status with progress logs and partial results
1053
1146
  *
@@ -1058,41 +1151,22 @@ var DeeplineClient = class {
1058
1151
  * console.log(`Logs: ${status.progress?.logs.length ?? 0} lines`);
1059
1152
  * ```
1060
1153
  */
1061
- async getPlayStatus(workflowId) {
1062
- const response = await this.http.get(
1063
- `/api/v2/plays/run/${encodeURIComponent(workflowId)}`
1064
- );
1065
- return normalizePlayStatus(response);
1066
- }
1067
- /**
1068
- * Get the lightweight tail-polling status for a play execution.
1069
- *
1070
- * This is intentionally smaller than {@link getPlayStatus}: it returns the
1071
- * fields needed for CLI log tailing while the run is in flight, without
1072
- * forcing the API to rebuild final result views on every poll. Call
1073
- * {@link getPlayStatus} once after a terminal state for the full result.
1074
- */
1075
- async getPlayTailStatus(workflowId, options) {
1076
- const params = new URLSearchParams({ mode: "tail" });
1077
- if (typeof options?.afterLogIndex === "number") {
1078
- params.set("afterLogIndex", String(options.afterLogIndex));
1079
- }
1080
- if (typeof options?.waitMs === "number") {
1081
- params.set("waitMs", String(options.waitMs));
1082
- }
1083
- if (options?.terminalOnly) {
1084
- params.set("terminalOnly", "true");
1154
+ async getPlayStatus(workflowId, options) {
1155
+ const params = new URLSearchParams();
1156
+ if (options?.billing === false) {
1157
+ params.set("billing", "false");
1085
1158
  }
1159
+ const query = params.size > 0 ? `?${params.toString()}` : "";
1086
1160
  const response = await this.http.get(
1087
- `/api/v2/plays/run/${encodeURIComponent(workflowId)}?${params.toString()}`
1161
+ `/api/v2/plays/run/${encodeURIComponent(workflowId)}${query}`
1088
1162
  );
1089
1163
  return normalizePlayStatus(response);
1090
1164
  }
1091
1165
  /**
1092
1166
  * Stream semantic play-run events using the same SSE feed as the dashboard.
1093
1167
  *
1094
- * Consumers should still keep a polling fallback: SSE is the fast live-update
1095
- * transport, while the status endpoints remain the authoritative recovery path.
1168
+ * The server emits a canonical `play.run.snapshot` event first for every
1169
+ * connection, then incremental live events until terminal state or reconnect.
1096
1170
  */
1097
1171
  async *streamPlayRunEvents(workflowId, options) {
1098
1172
  const headers = options?.lastEventId && options.lastEventId.trim() ? { "Last-Event-ID": options.lastEventId.trim() } : void 0;
@@ -1112,7 +1186,7 @@ var DeeplineClient = class {
1112
1186
  *
1113
1187
  * Sends a stop request for the run.
1114
1188
  *
1115
- * @param workflowId - Temporal workflow ID to cancel
1189
+ * @param workflowId - Public Deepline play-run id to cancel
1116
1190
  *
1117
1191
  * @example
1118
1192
  * ```typescript
@@ -1128,7 +1202,7 @@ var DeeplineClient = class {
1128
1202
  /**
1129
1203
  * Stop a running play execution, including open HITL waits.
1130
1204
  *
1131
- * @param workflowId - Temporal workflow ID to stop
1205
+ * @param workflowId - Public Deepline play-run id to stop
1132
1206
  * @param options.reason - Optional audit/debug reason
1133
1207
  */
1134
1208
  async stopPlay(workflowId, options) {
@@ -1200,32 +1274,42 @@ var DeeplineClient = class {
1200
1274
  );
1201
1275
  return response.runs ?? [];
1202
1276
  }
1203
- /**
1204
- * Fetch the lightweight tail status for a run using the public runs resource model.
1205
- *
1206
- * This is the SDK equivalent of:
1207
- *
1208
- * ```bash
1209
- * deepline runs tail <run-id> --json
1210
- * ```
1211
- */
1277
+ /** Read the canonical run stream and return the latest run snapshot. */
1212
1278
  async tailRun(runId, options) {
1213
- 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;
1214
- const params = new URLSearchParams();
1215
- if (Number.isFinite(afterLogIndex)) {
1216
- params.set("afterLogIndex", String(Number(afterLogIndex)));
1279
+ const state = {
1280
+ runId,
1281
+ status: "running",
1282
+ logs: [],
1283
+ latest: null
1284
+ };
1285
+ let terminal = false;
1286
+ for await (const event of this.streamPlayRunEvents(runId, {
1287
+ mode: "cli",
1288
+ signal: options?.signal
1289
+ })) {
1290
+ const status = updatePlayLiveStatusState(state, event);
1291
+ if (!status) {
1292
+ continue;
1293
+ }
1294
+ terminal = TERMINAL_PLAY_STATUSES.has(status.status);
1295
+ if (terminal) {
1296
+ break;
1297
+ }
1217
1298
  }
1218
- if (typeof options?.waitMs === "number") {
1219
- params.set("waitMs", String(options.waitMs));
1299
+ if (terminal && state.latest) {
1300
+ return await this.getRunStatus(state.latest.runId || runId).catch(
1301
+ () => state.latest ?? playRunStatusFromState(state)
1302
+ );
1220
1303
  }
1221
- if (options?.terminalOnly) {
1222
- params.set("terminalOnly", "true");
1304
+ if (state.latest) {
1305
+ return state.latest;
1223
1306
  }
1224
- const suffix = params.toString() ? `?${params.toString()}` : "";
1225
- const response = await this.http.get(
1226
- `/api/v2/runs/${encodeURIComponent(runId)}/tail${suffix}`
1307
+ throw new DeeplineError(
1308
+ `Run stream for ${runId} ended before the initial snapshot.`,
1309
+ void 0,
1310
+ "PLAY_RUN_STREAM_EMPTY",
1311
+ { runId }
1227
1312
  );
1228
- return normalizePlayStatus(response);
1229
1313
  }
1230
1314
  /**
1231
1315
  * Fetch persisted logs for a run using the public runs resource model.
@@ -1382,11 +1466,11 @@ var DeeplineClient = class {
1382
1466
  // Plays — high-level orchestration
1383
1467
  // ——————————————————————————————————————————————————————————
1384
1468
  /**
1385
- * Run a play end-to-end: submit, poll until terminal, return result.
1469
+ * Run a play end-to-end: submit, stream until terminal, return result.
1386
1470
  *
1387
1471
  * This is the highest-level play execution method. It submits the play,
1388
- * polls for status updates, and returns a structured result with logs
1389
- * and timing. Supports cancellation via `AbortSignal`.
1472
+ * reads the canonical run stream for status updates, and returns a structured
1473
+ * result with logs and timing. Supports cancellation via `AbortSignal`.
1390
1474
  *
1391
1475
  * @param code - Source string fallback; pass the bundled artifact in `options.artifact`
1392
1476
  * @param csvPath - Input CSV path, or `null`
@@ -1402,7 +1486,6 @@ var DeeplineClient = class {
1402
1486
  * const logs = status.progress?.logs ?? [];
1403
1487
  * console.log(`[${status.status}] ${logs.length} log lines`);
1404
1488
  * },
1405
- * pollIntervalMs: 1000,
1406
1489
  * });
1407
1490
  *
1408
1491
  * if (result.success) {
@@ -1433,33 +1516,53 @@ var DeeplineClient = class {
1433
1516
  packagedFiles: options?.packagedFiles,
1434
1517
  force: options?.force
1435
1518
  });
1436
- const pollInterval = options?.pollIntervalMs ?? 500;
1437
1519
  const start = Date.now();
1438
- while (true) {
1520
+ const state = {
1521
+ runId: workflowId,
1522
+ status: "running",
1523
+ logs: [],
1524
+ latest: null
1525
+ };
1526
+ if (options?.signal?.aborted) {
1527
+ await this.cancelPlay(workflowId);
1528
+ return {
1529
+ success: false,
1530
+ runId: workflowId,
1531
+ logs: [],
1532
+ durationMs: Date.now() - start,
1533
+ error: "Cancelled by user"
1534
+ };
1535
+ }
1536
+ for await (const event of this.streamPlayRunEvents(workflowId, {
1537
+ mode: "cli",
1538
+ signal: options?.signal
1539
+ })) {
1439
1540
  if (options?.signal?.aborted) {
1440
1541
  await this.cancelPlay(workflowId);
1441
1542
  return {
1442
1543
  success: false,
1443
1544
  runId: workflowId,
1444
- logs: [],
1545
+ logs: state.logs,
1445
1546
  durationMs: Date.now() - start,
1446
1547
  error: "Cancelled by user"
1447
1548
  };
1448
1549
  }
1449
- const status = await this.getPlayStatus(workflowId);
1550
+ const status = updatePlayLiveStatusState(state, event);
1551
+ if (!status) {
1552
+ continue;
1553
+ }
1450
1554
  options?.onProgress?.(status);
1451
1555
  if (TERMINAL_PLAY_STATUSES.has(status.status)) {
1452
- return {
1453
- success: status.status === "completed",
1454
- runId: status.runId || workflowId,
1455
- result: status.result,
1456
- logs: status.progress?.logs ?? [],
1457
- durationMs: Date.now() - start,
1458
- error: status.progress?.error ?? (status.status !== "completed" ? status.status : void 0)
1459
- };
1556
+ const finalStatus = await this.getPlayStatus(status.runId || workflowId).catch(() => status);
1557
+ return playRunResultFromStatus(finalStatus, start, workflowId);
1460
1558
  }
1461
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
1462
1559
  }
1560
+ throw new DeeplineError(
1561
+ `Run stream for ${workflowId} ended before the run reached a terminal state.`,
1562
+ void 0,
1563
+ "PLAY_RUN_STREAM_ENDED",
1564
+ { runId: workflowId, workflowId }
1565
+ );
1463
1566
  }
1464
1567
  // ——————————————————————————————————————————————————————————
1465
1568
  // Health
@@ -1545,6 +1648,7 @@ var import_node_path2 = require("path");
1545
1648
  var import_node_child_process = require("child_process");
1546
1649
  var import_sync = require("csv-parse/sync");
1547
1650
  var import_sync2 = require("csv-stringify/sync");
1651
+ var BROWSER_FOCUS_COOLDOWN_MS = 3e4;
1548
1652
  function getAuthedHttpClient() {
1549
1653
  const config = resolveConfig();
1550
1654
  return { config, http: new HttpClient(config) };
@@ -1556,12 +1660,215 @@ async function writeOutputFile(filename, content) {
1556
1660
  await (0, import_promises.writeFile)(fullPath, content, "utf-8");
1557
1661
  return fullPath;
1558
1662
  }
1663
+ function browserFocusStateFile() {
1664
+ const homeDir = process.env.HOME || (0, import_node_os2.homedir)();
1665
+ return (0, import_node_path2.join)(
1666
+ homeDir,
1667
+ ".local",
1668
+ "deepline",
1669
+ "runtime",
1670
+ "state",
1671
+ "browser-focus.json"
1672
+ );
1673
+ }
1674
+ function claimBrowserFocus(now = Date.now()) {
1675
+ const statePath = browserFocusStateFile();
1676
+ try {
1677
+ (0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(statePath), { recursive: true });
1678
+ let lastFocusedAt = 0;
1679
+ if ((0, import_node_fs2.existsSync)(statePath)) {
1680
+ const payload = JSON.parse((0, import_node_fs2.readFileSync)(statePath, "utf-8"));
1681
+ const value = payload.lastFocusedAt ?? payload.last_focused_at;
1682
+ if (typeof value === "number" && Number.isFinite(value)) {
1683
+ lastFocusedAt = value;
1684
+ }
1685
+ }
1686
+ if (lastFocusedAt > now) {
1687
+ lastFocusedAt = 0;
1688
+ }
1689
+ if (now - lastFocusedAt < BROWSER_FOCUS_COOLDOWN_MS) {
1690
+ return false;
1691
+ }
1692
+ (0, import_node_fs2.writeFileSync)(statePath, JSON.stringify({ lastFocusedAt: now }), "utf-8");
1693
+ return true;
1694
+ } catch {
1695
+ return true;
1696
+ }
1697
+ }
1698
+ function extractUrlHost(raw) {
1699
+ try {
1700
+ const parsed = new URL(raw.includes("://") ? raw : `https://${raw}`);
1701
+ return parsed.port ? `${parsed.hostname.toLowerCase()}:${parsed.port}` : parsed.hostname.toLowerCase();
1702
+ } catch {
1703
+ return "";
1704
+ }
1705
+ }
1706
+ function browserAppNameFromBundleId(bundleId) {
1707
+ const names = {
1708
+ "com.google.chrome": "Google Chrome",
1709
+ "com.google.chrome.canary": "Google Chrome Canary",
1710
+ "com.microsoft.edgemac": "Microsoft Edge",
1711
+ "com.brave.browser": "Brave Browser",
1712
+ "com.operasoftware.opera": "Opera",
1713
+ "com.operasoftware.operagx": "Opera GX",
1714
+ "com.vivaldi.vivaldi": "Vivaldi",
1715
+ "company.thebrowser.browser": "Arc",
1716
+ "com.apple.safari": "Safari"
1717
+ };
1718
+ return names[bundleId.toLowerCase()] ?? "";
1719
+ }
1720
+ function readDefaultMacBrowserBundleId() {
1721
+ try {
1722
+ const output = (0, import_node_child_process.execFileSync)(
1723
+ "/usr/bin/defaults",
1724
+ [
1725
+ "read",
1726
+ `${(0, import_node_os2.homedir)()}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`,
1727
+ "LSHandlers"
1728
+ ],
1729
+ { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
1730
+ );
1731
+ const httpsMatch = output.match(
1732
+ /LSHandlerURLScheme\s*=\s*https;[\s\S]*?LSHandlerRole(?:All|Viewer|Editor)\s*=\s*"([^"]+)"/
1733
+ );
1734
+ const httpMatch = output.match(
1735
+ /LSHandlerURLScheme\s*=\s*http;[\s\S]*?LSHandlerRole(?:All|Viewer|Editor)\s*=\s*"([^"]+)"/
1736
+ );
1737
+ return (httpsMatch?.[1] ?? httpMatch?.[1] ?? "").trim();
1738
+ } catch {
1739
+ return "";
1740
+ }
1741
+ }
1742
+ function browserStrategyForBundleId(bundleId) {
1743
+ const normalized = bundleId.toLowerCase();
1744
+ if ((/* @__PURE__ */ new Set([
1745
+ "com.google.chrome",
1746
+ "com.google.chrome.canary",
1747
+ "com.microsoft.edgemac",
1748
+ "com.brave.browser",
1749
+ "com.operasoftware.opera",
1750
+ "com.operasoftware.operagx",
1751
+ "com.vivaldi.vivaldi",
1752
+ "company.thebrowser.browser"
1753
+ ])).has(normalized)) {
1754
+ return "chromium";
1755
+ }
1756
+ return normalized === "com.apple.safari" ? "safari" : "fallback";
1757
+ }
1758
+ function runAppleScript(script, args) {
1759
+ const result = (0, import_node_child_process.spawnSync)("osascript", ["-", ...args], {
1760
+ input: script,
1761
+ encoding: "utf-8",
1762
+ stdio: ["pipe", "ignore", "ignore"],
1763
+ timeout: 5e3
1764
+ });
1765
+ return result.status === 0;
1766
+ }
1767
+ function retargetChromiumMacos(appName, targetUrl, allowFocus) {
1768
+ const host = extractUrlHost(targetUrl);
1769
+ if (!host) return false;
1770
+ const escapedAppName = appName.replace(/"/g, '\\"');
1771
+ const activateBlock = allowFocus ? " activate\n" : "";
1772
+ 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";
1773
+ 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";
1774
+ const script = `
1775
+ on run argv
1776
+ set targetUrl to item 1 of argv
1777
+ set targetHost to item 2 of argv
1778
+ tell application "${escapedAppName}"
1779
+ ${activateBlock} if (count of windows) is 0 then
1780
+ make new window
1781
+ end if
1782
+ set foundTab to false
1783
+ repeat with w in windows
1784
+ set tabCount to count of tabs of w
1785
+ repeat with i from 1 to tabCount
1786
+ set t to tab i of w
1787
+ if (URL of t) contains targetHost then
1788
+ ${foundTabBlock} set foundTab to true
1789
+ exit repeat
1790
+ end if
1791
+ end repeat
1792
+ if foundTab then exit repeat
1793
+ end repeat
1794
+ if not foundTab then
1795
+ ${newTabBlock} end if
1796
+ end tell
1797
+ end run
1798
+ `;
1799
+ return runAppleScript(script, [targetUrl, host]);
1800
+ }
1801
+ function retargetSafariMacos(appName, targetUrl, allowFocus) {
1802
+ const host = extractUrlHost(targetUrl);
1803
+ if (!host) return false;
1804
+ const escapedAppName = appName.replace(/"/g, '\\"');
1805
+ const activateBlock = allowFocus ? " activate\n" : "";
1806
+ 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";
1807
+ 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";
1808
+ const script = `
1809
+ on run argv
1810
+ set targetUrl to item 1 of argv
1811
+ set targetHost to item 2 of argv
1812
+ tell application "${escapedAppName}"
1813
+ ${activateBlock} if (count of windows) is 0 then
1814
+ make new document
1815
+ end if
1816
+ set foundTab to false
1817
+ repeat with w in windows
1818
+ set tabCount to count of tabs of w
1819
+ repeat with i from 1 to tabCount
1820
+ set t to tab i of w
1821
+ if (URL of t) contains targetHost then
1822
+ ${foundTabBlock} set foundTab to true
1823
+ exit repeat
1824
+ end if
1825
+ end repeat
1826
+ if foundTab then exit repeat
1827
+ end repeat
1828
+ if not foundTab then
1829
+ ${newTabBlock} end if
1830
+ end tell
1831
+ end run
1832
+ `;
1833
+ return runAppleScript(script, [targetUrl, host]);
1834
+ }
1835
+ function openUrlMacos(targetUrl, allowFocus) {
1836
+ const defaultBundleId = readDefaultMacBrowserBundleId();
1837
+ const appName = defaultBundleId ? browserAppNameFromBundleId(defaultBundleId) : "";
1838
+ const strategy = browserStrategyForBundleId(defaultBundleId);
1839
+ if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus)) {
1840
+ return true;
1841
+ }
1842
+ if (appName && strategy === "safari" && retargetSafariMacos(appName, targetUrl, allowFocus)) {
1843
+ return true;
1844
+ }
1845
+ if (!allowFocus) {
1846
+ return false;
1847
+ }
1848
+ try {
1849
+ (0, import_node_child_process.execFileSync)("open", [targetUrl], { stdio: "ignore" });
1850
+ return true;
1851
+ } catch {
1852
+ return false;
1853
+ }
1854
+ }
1559
1855
  function openInBrowser(url) {
1560
1856
  try {
1561
- if (process.platform === "darwin") (0, import_node_child_process.execSync)(`open "${url}"`, { stdio: "ignore" });
1562
- else if (process.platform === "win32")
1563
- (0, import_node_child_process.execSync)(`start "" "${url}"`, { stdio: "ignore" });
1564
- else (0, import_node_child_process.execSync)(`xdg-open "${url}"`, { stdio: "ignore" });
1857
+ const targetUrl = String(url || "").trim();
1858
+ if (!targetUrl) return;
1859
+ const allowFocus = claimBrowserFocus();
1860
+ if (process.platform === "darwin") {
1861
+ openUrlMacos(targetUrl, allowFocus);
1862
+ return;
1863
+ }
1864
+ if (!allowFocus) return;
1865
+ if (process.platform === "win32") {
1866
+ (0, import_node_child_process.execFileSync)("cmd.exe", ["/c", "start", "", targetUrl], {
1867
+ stdio: "ignore"
1868
+ });
1869
+ return;
1870
+ }
1871
+ (0, import_node_child_process.execFileSync)("xdg-open", [targetUrl], { stdio: "ignore" });
1565
1872
  } catch {
1566
1873
  }
1567
1874
  }
@@ -3817,13 +4124,6 @@ var PLAY_DEDUP_BACKENDS = {
3817
4124
 
3818
4125
  // ../shared_libs/play-runtime/profiles.ts
3819
4126
  var PLAY_EXECUTION_PROFILES = {
3820
- legacy: {
3821
- id: "legacy",
3822
- scheduler: PLAY_SCHEDULER_BACKENDS.temporal,
3823
- runner: PLAY_RUNTIME_BACKENDS.daytona,
3824
- dedup: PLAY_DEDUP_BACKENDS.inMemory,
3825
- label: "Daytona + Temporal (production today)"
3826
- },
3827
4127
  workers_edge: {
3828
4128
  id: "workers_edge",
3829
4129
  scheduler: PLAY_SCHEDULER_BACKENDS.cfWorkflows,
@@ -4362,7 +4662,77 @@ function createCliProgress(enabled) {
4362
4662
  return progress;
4363
4663
  }
4364
4664
 
4665
+ // src/cli/trace.ts
4666
+ var cliTraceStartedAt = Date.now();
4667
+ function isTruthyEnv(value) {
4668
+ return value === "1" || value === "true" || value === "yes";
4669
+ }
4670
+ function isCliTraceEnabled() {
4671
+ return isTruthyEnv(process.env.DEEPLINE_CLI_TRACE);
4672
+ }
4673
+ function recordCliTrace(event) {
4674
+ if (!isCliTraceEnabled()) {
4675
+ return;
4676
+ }
4677
+ const now = Date.now();
4678
+ const payload = {
4679
+ ts: now,
4680
+ source: "cli",
4681
+ sinceStartMs: now - cliTraceStartedAt,
4682
+ ...event
4683
+ };
4684
+ process.stderr.write(`[cli-trace] ${JSON.stringify(payload)}
4685
+ `);
4686
+ }
4687
+ async function traceCliSpan(phase, fields, run) {
4688
+ if (!isCliTraceEnabled()) {
4689
+ return run();
4690
+ }
4691
+ const startedAt = Date.now();
4692
+ try {
4693
+ const result = await run();
4694
+ recordCliTrace({
4695
+ phase,
4696
+ ms: Date.now() - startedAt,
4697
+ ok: true,
4698
+ ...fields
4699
+ });
4700
+ return result;
4701
+ } catch (error) {
4702
+ recordCliTrace({
4703
+ phase,
4704
+ ms: Date.now() - startedAt,
4705
+ ok: false,
4706
+ error: error instanceof Error ? error.message : String(error),
4707
+ ...fields
4708
+ });
4709
+ throw error;
4710
+ }
4711
+ }
4712
+
4365
4713
  // src/cli/commands/play.ts
4714
+ function traceCliSync(phase, fields, run) {
4715
+ const startedAt = Date.now();
4716
+ try {
4717
+ const result = run();
4718
+ recordCliTrace({
4719
+ phase,
4720
+ ms: Date.now() - startedAt,
4721
+ ok: true,
4722
+ ...fields
4723
+ });
4724
+ return result;
4725
+ } catch (error) {
4726
+ recordCliTrace({
4727
+ phase,
4728
+ ms: Date.now() - startedAt,
4729
+ ok: false,
4730
+ error: error instanceof Error ? error.message : String(error),
4731
+ ...fields
4732
+ });
4733
+ throw error;
4734
+ }
4735
+ }
4366
4736
  function parseReferencedPlayTarget(target) {
4367
4737
  const trimmed = target.trim();
4368
4738
  const slashIndex = trimmed.indexOf("/");
@@ -4601,6 +4971,23 @@ function isLocalFilePathValue(value) {
4601
4971
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
4602
4972
  return (0, import_node_fs6.existsSync)((0, import_node_path8.resolve)(value));
4603
4973
  }
4974
+ function inputContainsLocalFilePath(value) {
4975
+ if (isLocalFilePathValue(value)) {
4976
+ return true;
4977
+ }
4978
+ if (Array.isArray(value)) {
4979
+ return value.some((entry) => inputContainsLocalFilePath(entry));
4980
+ }
4981
+ if (value && typeof value === "object") {
4982
+ return Object.values(value).some(
4983
+ (entry) => inputContainsLocalFilePath(entry)
4984
+ );
4985
+ }
4986
+ return false;
4987
+ }
4988
+ function namedRunNeedsPlayDefinition(input) {
4989
+ return input.revisionSelector === "latest" || getDottedInputValue(input.runtimeInput, "csv") != null || inputContainsLocalFilePath(input.runtimeInput);
4990
+ }
4604
4991
  async function stageFileInputArgs(input) {
4605
4992
  const uniqueBindings = [
4606
4993
  ...new Map(input.bindings.map((binding) => [binding.inputPath, binding])).values()
@@ -4787,7 +5174,7 @@ function formatTimestamp(value) {
4787
5174
  function formatRunLine(run) {
4788
5175
  return `${run.workflowId} ${run.status} ${formatTimestamp(run.startTime)}`;
4789
5176
  }
4790
- function isTransientPlayStatusPollError(error) {
5177
+ function isTransientPlayStreamError(error) {
4791
5178
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
4792
5179
  return error.statusCode >= 500 && error.statusCode < 600;
4793
5180
  }
@@ -4796,12 +5183,6 @@ function isTransientPlayStatusPollError(error) {
4796
5183
  text
4797
5184
  );
4798
5185
  }
4799
- function isTerminalPlayStatusPollError(input) {
4800
- if (input.error instanceof DeeplineError && input.error.statusCode === 404 && input.hasSeenRun) {
4801
- return true;
4802
- }
4803
- return false;
4804
- }
4805
5186
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
4806
5187
  "completed",
4807
5188
  "failed",
@@ -4867,6 +5248,12 @@ function buildPlayDashboardUrl(baseUrl, playName) {
4867
5248
  const encodedPlayName = encodeURIComponent(playName);
4868
5249
  return `${trimmedBase}/dashboard/plays/${encodedPlayName}`;
4869
5250
  }
5251
+ function openPlayDashboard(input) {
5252
+ if (input.jsonOutput || input.noOpen || !input.dashboardUrl) {
5253
+ return;
5254
+ }
5255
+ openInBrowser(input.dashboardUrl);
5256
+ }
4870
5257
  function getDashboardUrlFromLiveEvent(event) {
4871
5258
  const dashboardUrl = getEventPayload(event).dashboardUrl;
4872
5259
  return typeof dashboardUrl === "string" && dashboardUrl.trim() ? dashboardUrl.trim() : null;
@@ -4903,6 +5290,68 @@ function assertPlayWaitNotTimedOut(input) {
4903
5290
  );
4904
5291
  }
4905
5292
  }
5293
+ async function waitForPlayCompletionByStream(input) {
5294
+ const controller = new AbortController();
5295
+ let timedOut = false;
5296
+ let lastPhase = null;
5297
+ const timeout = input.waitTimeoutMs === null ? null : setTimeout(
5298
+ () => {
5299
+ timedOut = true;
5300
+ controller.abort();
5301
+ },
5302
+ Math.max(1, input.waitTimeoutMs - (Date.now() - input.startedAt))
5303
+ );
5304
+ try {
5305
+ for await (const event of input.client.streamPlayRunEvents(
5306
+ input.workflowId,
5307
+ { signal: controller.signal }
5308
+ )) {
5309
+ assertPlayWaitNotTimedOut({ ...input, lastPhase });
5310
+ const phase = describeLiveEventPhase(event);
5311
+ if (phase) {
5312
+ lastPhase = phase;
5313
+ input.progress.phase(phase);
5314
+ }
5315
+ printPlayLogLines({
5316
+ lines: getLogLinesFromLiveEvent(event),
5317
+ status: null,
5318
+ jsonOutput: input.jsonOutput,
5319
+ emitLogs: input.emitLogs,
5320
+ state: input.state,
5321
+ progress: input.progress
5322
+ });
5323
+ const status = getStatusFromLiveEvent(event);
5324
+ if (status && TERMINAL_PLAY_STATUSES2.has(status)) {
5325
+ const finalStatus = await input.client.getPlayStatus(input.workflowId, {
5326
+ billing: false
5327
+ });
5328
+ if (TERMINAL_PLAY_STATUSES2.has(finalStatus.status)) {
5329
+ return finalStatus;
5330
+ }
5331
+ }
5332
+ }
5333
+ } catch (error) {
5334
+ if (timedOut) {
5335
+ assertPlayWaitNotTimedOut({ ...input, lastPhase });
5336
+ }
5337
+ throw error;
5338
+ } finally {
5339
+ if (timeout) {
5340
+ clearTimeout(timeout);
5341
+ }
5342
+ }
5343
+ const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
5344
+ throw new DeeplineError(
5345
+ `Play live stream ended before the run reached a terminal state runId=${input.workflowId}${phaseSuffix}.`,
5346
+ void 0,
5347
+ "PLAY_LIVE_STREAM_ENDED",
5348
+ {
5349
+ runId: input.workflowId,
5350
+ workflowId: input.workflowId,
5351
+ ...lastPhase ? { phase: lastPhase } : {}
5352
+ }
5353
+ );
5354
+ }
4906
5355
  async function startAndWaitForPlayCompletionByStream(input) {
4907
5356
  const startedAt = Date.now();
4908
5357
  const state = {
@@ -4913,6 +5362,8 @@ async function startAndWaitForPlayCompletionByStream(input) {
4913
5362
  let timedOut = false;
4914
5363
  let emittedDashboardUrl = false;
4915
5364
  let lastKnownWorkflowId = "";
5365
+ let eventCount = 0;
5366
+ let firstRunIdMs = null;
4916
5367
  let lastPhase = null;
4917
5368
  const timeout = input.waitTimeoutMs === null ? null : setTimeout(
4918
5369
  () => {
@@ -4925,13 +5376,20 @@ async function startAndWaitForPlayCompletionByStream(input) {
4925
5376
  for await (const event of input.client.startPlayRunStream(input.request, {
4926
5377
  signal: controller.signal
4927
5378
  })) {
5379
+ eventCount += 1;
4928
5380
  const eventRunId = getEventPayload(event).runId;
4929
5381
  if (typeof eventRunId === "string" && eventRunId && eventRunId !== "pending") {
4930
5382
  lastKnownWorkflowId = eventRunId;
5383
+ firstRunIdMs ??= Date.now() - startedAt;
4931
5384
  }
4932
5385
  const workflowId = lastKnownWorkflowId || "pending";
4933
5386
  if (workflowId !== "pending" && !emittedDashboardUrl) {
4934
5387
  const dashboardUrl = getDashboardUrlFromLiveEvent(event) ?? buildPlayDashboardUrl(input.client.baseUrl, input.playName);
5388
+ openPlayDashboard({
5389
+ dashboardUrl,
5390
+ jsonOutput: input.jsonOutput,
5391
+ noOpen: input.noOpen
5392
+ });
4935
5393
  if (!input.jsonOutput) {
4936
5394
  writeStartedPlayRun({
4937
5395
  runId: workflowId,
@@ -4965,6 +5423,16 @@ async function startAndWaitForPlayCompletionByStream(input) {
4965
5423
  });
4966
5424
  const finalStatus = getFinalStatusFromLiveEvent(event);
4967
5425
  if (finalStatus) {
5426
+ recordCliTrace({
5427
+ phase: "cli.play_start_stream_terminal",
5428
+ ms: Date.now() - startedAt,
5429
+ ok: true,
5430
+ playName: input.playName,
5431
+ workflowId: finalStatus.runId || lastKnownWorkflowId || null,
5432
+ eventCount,
5433
+ firstRunIdMs,
5434
+ lastPhase
5435
+ });
4968
5436
  return finalStatus;
4969
5437
  }
4970
5438
  }
@@ -4977,21 +5445,31 @@ async function startAndWaitForPlayCompletionByStream(input) {
4977
5445
  lastPhase
4978
5446
  });
4979
5447
  }
4980
- if (lastKnownWorkflowId && isTransientPlayStatusPollError(error)) {
5448
+ if (lastKnownWorkflowId && isTransientPlayStreamError(error)) {
4981
5449
  if (timeout) {
4982
5450
  clearTimeout(timeout);
4983
5451
  }
4984
5452
  const reason = error instanceof Error ? error.message : String(error);
4985
5453
  if (!input.jsonOutput) {
4986
5454
  process.stderr.write(
4987
- `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
5455
+ `[play watch] start stream failed after run ${lastKnownWorkflowId}; reconnecting to run stream (${reason})
4988
5456
  `
4989
5457
  );
4990
5458
  }
4991
- return waitForPlayCompletionByPolling({
5459
+ recordCliTrace({
5460
+ phase: "cli.play_start_stream_reconnect",
5461
+ ms: Date.now() - startedAt,
5462
+ ok: true,
5463
+ playName: input.playName,
5464
+ workflowId: lastKnownWorkflowId,
5465
+ eventCount,
5466
+ firstRunIdMs,
5467
+ lastPhase,
5468
+ reason
5469
+ });
5470
+ return waitForPlayCompletionByStream({
4992
5471
  client: input.client,
4993
5472
  workflowId: lastKnownWorkflowId,
4994
- pollIntervalMs: 500,
4995
5473
  jsonOutput: input.jsonOutput,
4996
5474
  emitLogs: input.emitLogs,
4997
5475
  waitTimeoutMs: input.waitTimeoutMs,
@@ -5009,13 +5487,23 @@ async function startAndWaitForPlayCompletionByStream(input) {
5009
5487
  if (lastKnownWorkflowId) {
5010
5488
  if (!input.jsonOutput) {
5011
5489
  input.progress.writeLine(
5012
- `[play watch] start stream ended after run ${lastKnownWorkflowId}; falling back to polling`
5490
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; reconnecting to run stream`
5013
5491
  );
5014
5492
  }
5015
- return waitForPlayCompletionByPolling({
5493
+ recordCliTrace({
5494
+ phase: "cli.play_start_stream_reconnect",
5495
+ ms: Date.now() - startedAt,
5496
+ ok: true,
5497
+ playName: input.playName,
5498
+ workflowId: lastKnownWorkflowId,
5499
+ eventCount,
5500
+ firstRunIdMs,
5501
+ lastPhase,
5502
+ reason: "stream ended before terminal event"
5503
+ });
5504
+ return waitForPlayCompletionByStream({
5016
5505
  client: input.client,
5017
5506
  workflowId: lastKnownWorkflowId,
5018
- pollIntervalMs: 500,
5019
5507
  jsonOutput: input.jsonOutput,
5020
5508
  emitLogs: input.emitLogs,
5021
5509
  waitTimeoutMs: input.waitTimeoutMs,
@@ -5036,71 +5524,6 @@ async function startAndWaitForPlayCompletionByStream(input) {
5036
5524
  }
5037
5525
  );
5038
5526
  }
5039
- async function waitForPlayCompletionByPolling(input) {
5040
- let lastTransientPollWarningAt = 0;
5041
- let hasSeenRun = false;
5042
- while (true) {
5043
- assertPlayWaitNotTimedOut(input);
5044
- let status;
5045
- try {
5046
- status = await input.client.getPlayTailStatus(input.workflowId, {
5047
- afterLogIndex: input.state.lastLogIndex,
5048
- // Keep the server-side tail wait close to the caller's requested poll
5049
- // cadence. A long wait makes tiny remote runs look slow whenever the
5050
- // terminal update lands just after the held request starts.
5051
- waitMs: Math.max(50, Math.min(input.pollIntervalMs, 1e3))
5052
- });
5053
- } catch (error) {
5054
- if (isTerminalPlayStatusPollError({ error, hasSeenRun })) {
5055
- throw new DeeplineError(
5056
- `Play run ${input.workflowId} no longer exists on the server (404). The run was deleted or the backend lost it.`,
5057
- 404,
5058
- "PLAY_RUN_NOT_FOUND"
5059
- );
5060
- }
5061
- if (!isTransientPlayStatusPollError(error)) {
5062
- throw error;
5063
- }
5064
- const now = Date.now();
5065
- if (now - lastTransientPollWarningAt >= 3e4) {
5066
- const message = error instanceof Error ? error.message : String(error);
5067
- input.progress.writeLine(
5068
- `[play tail] transient status poll failed; retrying: ${message}`
5069
- );
5070
- lastTransientPollWarningAt = now;
5071
- }
5072
- await new Promise(
5073
- (resolvePromise) => setTimeout(resolvePromise, input.pollIntervalMs)
5074
- );
5075
- continue;
5076
- }
5077
- hasSeenRun = true;
5078
- const logs = status.progress?.logs ?? [];
5079
- input.progress.phase(status.status);
5080
- printPlayLogLines({
5081
- lines: logs.slice(input.state.lastLogIndex),
5082
- status,
5083
- jsonOutput: input.jsonOutput,
5084
- emitLogs: input.emitLogs,
5085
- state: input.state,
5086
- progress: input.progress
5087
- });
5088
- if (TERMINAL_PLAY_STATUSES2.has(status.status)) {
5089
- return status.result !== void 0 ? status : await input.client.getPlayStatus(input.workflowId);
5090
- }
5091
- const authoritativeStatus = await input.client.getPlayStatus(
5092
- input.workflowId
5093
- );
5094
- if (TERMINAL_PLAY_STATUSES2.has(authoritativeStatus.status)) {
5095
- return authoritativeStatus;
5096
- }
5097
- if ((status.progress?.logs ?? []).length === input.state.lastLogIndex) {
5098
- await new Promise(
5099
- (resolvePromise) => setTimeout(resolvePromise, input.pollIntervalMs)
5100
- );
5101
- }
5102
- }
5103
- }
5104
5527
  function formatInteger(value) {
5105
5528
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
5106
5529
  }
@@ -5241,7 +5664,6 @@ function buildRunWarnings(status, rowsInfo) {
5241
5664
  function buildRunNextCommands(runId, rowsInfo) {
5242
5665
  const commands = {
5243
5666
  get: `deepline runs get ${runId} --json`,
5244
- tail: `deepline runs tail ${runId} --json`,
5245
5667
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
5246
5668
  logs: `deepline runs logs ${runId} --out run.log --json`
5247
5669
  };
@@ -5357,10 +5779,9 @@ function normalizeErrorsForEnvelope(status, error) {
5357
5779
  }
5358
5780
  function normalizeLogsForEnvelope(status) {
5359
5781
  const logs = Array.isArray(status.progress?.logs) ? status.progress.logs : [];
5360
- const offset = typeof status.progress?.logOffset === "number" && Number.isFinite(status.progress.logOffset) ? Math.max(0, Math.trunc(status.progress.logOffset)) : 0;
5361
- const totalCount = offset + logs.length;
5782
+ const totalCount = logs.length;
5362
5783
  const entries = logs.slice(Math.max(0, logs.length - RUN_LOG_PREVIEW_LIMIT));
5363
- const firstSequence = entries.length === 0 ? null : offset + logs.length - entries.length + 1;
5784
+ const firstSequence = entries.length === 0 ? null : logs.length - entries.length + 1;
5364
5785
  const lastSequence = totalCount === 0 ? null : totalCount;
5365
5786
  return {
5366
5787
  totalCount,
@@ -5606,7 +6027,6 @@ function writeStartedPlayRun(input) {
5606
6027
  workflowId: input.runId,
5607
6028
  name: input.playName,
5608
6029
  status: input.status ?? "started",
5609
- statusUrl: input.statusUrl,
5610
6030
  dashboardUrl: input.dashboardUrl
5611
6031
  };
5612
6032
  if (input.jsonOutput) {
@@ -5618,7 +6038,7 @@ function writeStartedPlayRun(input) {
5618
6038
  `Started ${input.playName}`,
5619
6039
  ` run id: ${input.runId}`,
5620
6040
  ` get status: deepline runs get ${input.runId} --json`,
5621
- ` tail logs: deepline runs tail ${input.runId} --json`,
6041
+ ` logs: deepline runs logs ${input.runId} --json`,
5622
6042
  ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
5623
6043
  ` result JSON: deepline runs get ${input.runId} --json`
5624
6044
  ];
@@ -5633,7 +6053,7 @@ function writeStartedPlayRun(input) {
5633
6053
  console.log(output);
5634
6054
  }
5635
6055
  function parsePlayRunOptions(args) {
5636
- 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]";
6056
+ 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.";
5637
6057
  let filePath = null;
5638
6058
  let playName = null;
5639
6059
  let input = null;
@@ -5643,8 +6063,8 @@ function parsePlayRunOptions(args) {
5643
6063
  let jsonOutput = watch ? args.includes("--json") : argsWantJson(args);
5644
6064
  const emitLogs = !jsonOutput || args.includes("--logs");
5645
6065
  const force = args.includes("--force");
6066
+ const noOpen = args.includes("--no-open");
5646
6067
  let outPath = null;
5647
- let pollIntervalMs = 500;
5648
6068
  let waitTimeoutMs = null;
5649
6069
  for (let index = 0; index < args.length; index += 1) {
5650
6070
  const arg = args[index];
@@ -5676,15 +6096,16 @@ function parsePlayRunOptions(args) {
5676
6096
  outPath = (0, import_node_path8.resolve)(args[++index]);
5677
6097
  continue;
5678
6098
  }
5679
- if ((arg === "--poll-interval-ms" || arg === "--interval-ms") && args[index + 1]) {
5680
- pollIntervalMs = parsePositiveInteger2(args[++index], arg);
5681
- continue;
6099
+ if (arg === "--poll-interval-ms" || arg === "--interval-ms") {
6100
+ throw new Error(
6101
+ `${arg} was removed. --watch uses the canonical run stream directly.`
6102
+ );
5682
6103
  }
5683
6104
  if ((arg === "--tail-timeout-ms" || arg === "--timeout-ms") && args[index + 1]) {
5684
6105
  waitTimeoutMs = parsePositiveInteger2(args[++index], arg);
5685
6106
  continue;
5686
6107
  }
5687
- if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--logs" || arg === "--force") {
6108
+ if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--logs" || arg === "--force" || arg === "--no-open") {
5688
6109
  if (arg === "--watch") {
5689
6110
  continue;
5690
6111
  }
@@ -5753,10 +6174,10 @@ function parsePlayRunOptions(args) {
5753
6174
  watch,
5754
6175
  emitLogs,
5755
6176
  jsonOutput,
5756
- pollIntervalMs,
5757
6177
  waitTimeoutMs,
5758
6178
  force,
5759
- outPath
6179
+ outPath,
6180
+ noOpen
5760
6181
  };
5761
6182
  }
5762
6183
  function parsePlayCheckOptions(args) {
@@ -5844,11 +6265,24 @@ async function handleFileBackedRun(options) {
5844
6265
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
5845
6266
  const absolutePlayPath = (0, import_node_path8.resolve)(options.target.path);
5846
6267
  progress.phase("compiling play");
5847
- const sourceCode = (0, import_node_fs6.readFileSync)(absolutePlayPath, "utf-8");
6268
+ const sourceCode = traceCliSync(
6269
+ "cli.play_file_read_source",
6270
+ { targetKind: "file" },
6271
+ () => (0, import_node_fs6.readFileSync)(absolutePlayPath, "utf-8")
6272
+ );
6273
+ const runtimeInput = options.input ? { ...options.input } : {};
5848
6274
  let graph;
5849
6275
  try {
5850
- graph = await collectBundledPlayGraph(absolutePlayPath);
5851
- await compileBundledPlayGraphManifests(client, graph);
6276
+ graph = await traceCliSpan(
6277
+ "cli.play_file_bundle_graph",
6278
+ { targetKind: "file" },
6279
+ () => collectBundledPlayGraph(absolutePlayPath)
6280
+ );
6281
+ await traceCliSpan(
6282
+ "cli.play_file_compile_manifests",
6283
+ { targetKind: "file" },
6284
+ () => compileBundledPlayGraphManifests(client, graph)
6285
+ );
5852
6286
  progress.phase("compiled play");
5853
6287
  } catch (error) {
5854
6288
  progress.fail();
@@ -5859,36 +6293,47 @@ async function handleFileBackedRun(options) {
5859
6293
  const playName = bundleResult.playName ?? extractPlayName(sourceCode, absolutePlayPath);
5860
6294
  try {
5861
6295
  progress.phase("publishing imported plays");
5862
- await publishImportedPlayDependencies(client, graph);
6296
+ await traceCliSpan(
6297
+ "cli.play_file_publish_imports",
6298
+ { targetKind: "file" },
6299
+ () => publishImportedPlayDependencies(client, graph)
6300
+ );
5863
6301
  } catch (error) {
5864
6302
  progress.fail();
5865
6303
  console.error(error instanceof Error ? error.message : String(error));
5866
6304
  return 1;
5867
6305
  }
5868
- const runtimeInput = options.input ? { ...options.input } : {};
5869
6306
  const packagedFileUploads = bundleResult.packagedFiles.map(
5870
6307
  (file) => stageFile(file.logicalPath, file.absolutePath)
5871
6308
  );
6309
+ const compilerManifest = requireCompilerManifest(bundleResult);
5872
6310
  const fileInputBindings = fileInputBindingsFromStaticPipeline(
5873
- requireCompilerManifest(bundleResult).staticPipeline
6311
+ compilerManifest.staticPipeline
5874
6312
  );
5875
6313
  applyCsvShortcutInput({
5876
6314
  runtimeInput,
5877
6315
  bindings: fileInputBindings,
5878
6316
  fallbackInputPath: "file"
5879
6317
  });
5880
- const stagedFileInputs = await stageFileInputArgs({
5881
- client,
5882
- runtimeInput,
5883
- bindings: fileInputBindings,
5884
- progress
5885
- });
6318
+ const stagedFileInputs = await traceCliSpan(
6319
+ "cli.play_stage_inputs",
6320
+ {
6321
+ targetKind: "file",
6322
+ bindingCount: fileInputBindings.length
6323
+ },
6324
+ () => stageFileInputArgs({
6325
+ client,
6326
+ runtimeInput,
6327
+ bindings: fileInputBindings,
6328
+ progress
6329
+ })
6330
+ );
5886
6331
  const startRequest = {
5887
6332
  name: playName,
5888
6333
  sourceCode: bundleResult.sourceCode,
5889
6334
  sourceFiles: bundleResult.sourceFiles,
5890
6335
  runtimeArtifact: bundleResult.artifact,
5891
- compilerManifest: requireCompilerManifest(bundleResult),
6336
+ compilerManifest,
5892
6337
  packagedFileUploads,
5893
6338
  ...Object.keys(runtimeInput).length > 0 ? { input: runtimeInput } : {},
5894
6339
  ...stagedFileInputs.inputFile ? { inputFile: stagedFileInputs.inputFile } : {},
@@ -5897,35 +6342,57 @@ async function handleFileBackedRun(options) {
5897
6342
  };
5898
6343
  if (options.watch) {
5899
6344
  progress.phase("starting run");
5900
- const finalStatus = await startAndWaitForPlayCompletionByStream({
5901
- client,
5902
- request: startRequest,
5903
- playName,
5904
- jsonOutput: options.jsonOutput,
5905
- emitLogs: options.emitLogs,
5906
- waitTimeoutMs: options.waitTimeoutMs,
5907
- progress
5908
- });
5909
- const exportedPath = exportPlayStatusRows(finalStatus, options.outPath);
6345
+ const finalStatus = await traceCliSpan(
6346
+ "cli.play_start_watch",
6347
+ { targetKind: "file", playName },
6348
+ () => startAndWaitForPlayCompletionByStream({
6349
+ client,
6350
+ request: startRequest,
6351
+ playName,
6352
+ jsonOutput: options.jsonOutput,
6353
+ emitLogs: options.emitLogs,
6354
+ waitTimeoutMs: options.waitTimeoutMs,
6355
+ noOpen: options.noOpen,
6356
+ progress
6357
+ })
6358
+ );
6359
+ const exportedPath = traceCliSync(
6360
+ "cli.play_export_rows",
6361
+ { targetKind: "file", playName },
6362
+ () => exportPlayStatusRows(finalStatus, options.outPath)
6363
+ );
5910
6364
  if (finalStatus.status === "completed") {
5911
6365
  progress.complete();
5912
6366
  } else {
5913
6367
  progress.fail();
5914
6368
  }
5915
- writePlayResult(finalStatus, options.jsonOutput, { exportedPath });
6369
+ traceCliSync(
6370
+ "cli.play_write_result",
6371
+ { targetKind: "file", playName },
6372
+ () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath })
6373
+ );
5916
6374
  return finalStatus.status === "completed" ? 0 : 1;
5917
6375
  }
5918
6376
  progress.phase("starting run");
5919
- const started = await client.startPlayRun(startRequest);
6377
+ const started = await traceCliSpan(
6378
+ "cli.play_start_unwatched",
6379
+ { targetKind: "file", playName },
6380
+ () => client.startPlayRun(startRequest)
6381
+ );
5920
6382
  const dashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
5921
- progress.phase(`loading play on ${dashboardUrl}`);
6383
+ const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
6384
+ openPlayDashboard({
6385
+ dashboardUrl: resolvedDashboardUrl,
6386
+ jsonOutput: options.jsonOutput,
6387
+ noOpen: options.noOpen
6388
+ });
6389
+ progress.phase(`loading play on ${resolvedDashboardUrl}`);
5922
6390
  progress.complete();
5923
6391
  writeStartedPlayRun({
5924
6392
  runId: started.workflowId,
5925
6393
  playName,
5926
6394
  status: started.status,
5927
- statusUrl: started.statusUrl,
5928
- dashboardUrl: started.dashboardUrl ?? dashboardUrl,
6395
+ dashboardUrl: resolvedDashboardUrl,
5929
6396
  jsonOutput: options.jsonOutput,
5930
6397
  progress
5931
6398
  });
@@ -5951,32 +6418,67 @@ async function handleNamedRun(options) {
5951
6418
  }
5952
6419
  const client = new DeeplineClient();
5953
6420
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
5954
- progress.phase("loading play definition");
5955
- const playDetail = await assertCanonicalNamedPlayReference(client, options.target.name);
5956
- progress.phase("selecting revision");
5957
- const selectedRevisionId = await resolveNamedRunRevisionId({
5958
- client,
5959
- playName: options.target.name,
5960
- revisionId: options.revisionId,
5961
- selector: options.revisionSelector
5962
- });
6421
+ const playName = options.target.name;
5963
6422
  const runtimeInput = options.input ? { ...options.input } : {};
5964
- const fileInputBindings = [
6423
+ const needsPlayDefinition = namedRunNeedsPlayDefinition({
6424
+ runtimeInput,
6425
+ revisionSelector: options.revisionSelector
6426
+ });
6427
+ const playDetail = needsPlayDefinition ? await (async () => {
6428
+ progress.phase("loading play definition");
6429
+ return traceCliSpan(
6430
+ "cli.play_load_definition",
6431
+ { targetKind: "name", playName, skipped: false },
6432
+ () => assertCanonicalNamedPlayReference(client, playName)
6433
+ );
6434
+ })() : (recordCliTrace({
6435
+ phase: "cli.play_load_definition",
6436
+ ms: 0,
6437
+ ok: true,
6438
+ targetKind: "name",
6439
+ playName,
6440
+ skipped: true
6441
+ }), null);
6442
+ progress.phase("selecting revision");
6443
+ const selectedRevisionId = await traceCliSpan(
6444
+ "cli.play_select_revision",
6445
+ {
6446
+ targetKind: "name",
6447
+ playName,
6448
+ selector: options.revisionSelector,
6449
+ hasExplicitRevisionId: Boolean(options.revisionId)
6450
+ },
6451
+ () => resolveNamedRunRevisionId({
6452
+ client,
6453
+ playName,
6454
+ revisionId: options.revisionId,
6455
+ selector: options.revisionSelector
6456
+ })
6457
+ );
6458
+ const fileInputBindings = playDetail ? [
5965
6459
  ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5966
6460
  ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5967
- ];
6461
+ ] : [];
5968
6462
  applyCsvShortcutInput({
5969
6463
  runtimeInput,
5970
6464
  bindings: fileInputBindings
5971
6465
  });
5972
- const stagedFileInputs = await stageFileInputArgs({
5973
- client,
5974
- runtimeInput,
5975
- bindings: fileInputBindings,
5976
- progress
5977
- });
6466
+ const stagedFileInputs = await traceCliSpan(
6467
+ "cli.play_stage_inputs",
6468
+ {
6469
+ targetKind: "name",
6470
+ playName,
6471
+ bindingCount: fileInputBindings.length
6472
+ },
6473
+ () => stageFileInputArgs({
6474
+ client,
6475
+ runtimeInput,
6476
+ bindings: fileInputBindings,
6477
+ progress
6478
+ })
6479
+ );
5978
6480
  const startRequest = {
5979
- name: options.target.name,
6481
+ name: playName,
5980
6482
  ...selectedRevisionId ? { revisionId: selectedRevisionId } : {},
5981
6483
  ...Object.keys(runtimeInput).length > 0 ? { input: runtimeInput } : {},
5982
6484
  ...stagedFileInputs.inputFile ? { inputFile: stagedFileInputs.inputFile } : {},
@@ -5985,38 +6487,60 @@ async function handleNamedRun(options) {
5985
6487
  };
5986
6488
  if (options.watch) {
5987
6489
  progress.phase("starting run");
5988
- const finalStatus = await startAndWaitForPlayCompletionByStream({
5989
- client,
5990
- request: startRequest,
5991
- playName: options.target.name,
5992
- jsonOutput: options.jsonOutput,
5993
- emitLogs: options.emitLogs,
5994
- waitTimeoutMs: options.waitTimeoutMs,
5995
- progress
5996
- });
5997
- const exportedPath = exportPlayStatusRows(finalStatus, options.outPath);
6490
+ const finalStatus = await traceCliSpan(
6491
+ "cli.play_start_watch",
6492
+ { targetKind: "name", playName },
6493
+ () => startAndWaitForPlayCompletionByStream({
6494
+ client,
6495
+ request: startRequest,
6496
+ playName,
6497
+ jsonOutput: options.jsonOutput,
6498
+ emitLogs: options.emitLogs,
6499
+ waitTimeoutMs: options.waitTimeoutMs,
6500
+ noOpen: options.noOpen,
6501
+ progress
6502
+ })
6503
+ );
6504
+ const exportedPath = traceCliSync(
6505
+ "cli.play_export_rows",
6506
+ { targetKind: "name", playName },
6507
+ () => exportPlayStatusRows(finalStatus, options.outPath)
6508
+ );
5998
6509
  if (finalStatus.status === "completed") {
5999
6510
  progress.complete();
6000
6511
  } else {
6001
6512
  progress.fail();
6002
6513
  }
6003
- writePlayResult(finalStatus, options.jsonOutput, { exportedPath });
6514
+ traceCliSync(
6515
+ "cli.play_write_result",
6516
+ { targetKind: "name", playName },
6517
+ () => writePlayResult(finalStatus, options.jsonOutput, { exportedPath })
6518
+ );
6004
6519
  return finalStatus.status === "completed" ? 0 : 1;
6005
6520
  }
6006
6521
  progress.phase("starting run");
6007
- const started = await client.startPlayRun(startRequest);
6522
+ const started = await traceCliSpan(
6523
+ "cli.play_start_unwatched",
6524
+ { targetKind: "name", playName },
6525
+ () => client.startPlayRun(startRequest)
6526
+ );
6008
6527
  const dashboardUrl = buildPlayDashboardUrl(
6009
6528
  client.baseUrl,
6010
- options.target.name
6529
+ playName
6011
6530
  );
6012
- progress.phase(`loading play on ${dashboardUrl}`);
6531
+ const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
6532
+ openPlayDashboard({
6533
+ dashboardUrl: resolvedDashboardUrl,
6534
+ jsonOutput: options.jsonOutput,
6535
+ noOpen: options.noOpen
6536
+ });
6537
+ progress.phase(`loading play on ${resolvedDashboardUrl}`);
6013
6538
  progress.complete();
6014
6539
  writeStartedPlayRun({
6015
6540
  runId: started.workflowId,
6016
- playName: started.name ?? options.target.name,
6541
+ playName: started.name ?? playName,
6017
6542
  status: started.status,
6018
- statusUrl: started.statusUrl,
6019
- dashboardUrl: started.dashboardUrl ?? dashboardUrl,
6543
+ dashboardUrl: resolvedDashboardUrl,
6020
6544
  jsonOutput: options.jsonOutput,
6021
6545
  progress
6022
6546
  });
@@ -6059,7 +6583,7 @@ function parseRunIdPositional(args, usage) {
6059
6583
  }
6060
6584
  continue;
6061
6585
  }
6062
- if ((arg === "--out" || arg === "--cursor" || arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
6586
+ if ((arg === "--out" || arg === "--reason") && args[index + 1]) {
6063
6587
  index += 1;
6064
6588
  continue;
6065
6589
  }
@@ -6144,7 +6668,7 @@ async function handleRunsList(args) {
6144
6668
  return 0;
6145
6669
  }
6146
6670
  async function handleRunTail(args) {
6147
- const usage = "Usage: deepline runs tail <run-id> [--json] [--compact] [--cursor <cursor>]";
6671
+ const usage = "Usage: deepline runs tail <run-id> [--json] [--compact]";
6148
6672
  let runId;
6149
6673
  try {
6150
6674
  runId = parseRunIdPositional(args, usage);
@@ -6152,20 +6676,19 @@ async function handleRunTail(args) {
6152
6676
  console.error(error instanceof Error ? error.message : usage);
6153
6677
  return 1;
6154
6678
  }
6155
- const client = new DeeplineClient();
6156
- let afterLogIndex;
6157
6679
  for (let index = 0; index < args.length; index += 1) {
6158
6680
  const arg = args[index];
6159
- if (arg === "--cursor" && args[index + 1]) {
6160
- const parsed = Number(args[++index]);
6161
- if (Number.isInteger(parsed) && parsed >= 0) {
6162
- afterLogIndex = parsed;
6163
- }
6681
+ if (arg === "--cursor") {
6682
+ console.error("--cursor was removed. deepline runs tail reads the canonical run stream.");
6683
+ return 1;
6684
+ }
6685
+ if (arg.startsWith("--") && arg !== "--json" && arg !== "--compact") {
6686
+ console.error(`${arg} is not supported by deepline runs tail.`);
6687
+ return 1;
6164
6688
  }
6165
6689
  }
6166
- const status = await client.runs.tail(runId, {
6167
- ...afterLogIndex !== void 0 ? { afterLogIndex } : {}
6168
- });
6690
+ const client = new DeeplineClient();
6691
+ const status = await client.runs.tail(runId);
6169
6692
  writePlayResult(status, argsWantJson(args));
6170
6693
  return status.status === "failed" ? 1 : 0;
6171
6694
  }
@@ -6706,8 +7229,9 @@ function registerPlayCommands(program) {
6706
7229
  "after",
6707
7230
  `
6708
7231
  Concepts:
6709
- Plays are durable Deepline cloud workflows. Local .play.ts files are bundled locally,
6710
- then validated and executed in Deepline cloud.
7232
+ Plays are durable cloud workflows.
7233
+ Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
7234
+ ctx.map adds row keys and row progress.
6711
7235
 
6712
7236
  Common commands:
6713
7237
  deepline plays search email --json
@@ -6738,17 +7262,42 @@ Examples:
6738
7262
  "after",
6739
7263
  `
6740
7264
  Notes:
6741
- Local play files are bundled locally, then validated and executed in Deepline cloud.
6742
- Named plays run the stored live cloud revision.
6743
- Unknown --foo and --foo.bar flags are treated as play input args.
6744
- File-like input args accept local paths; the CLI stages those files before submit.
6745
- Run performs server preflight automatically. Use \`deepline plays check <file>\`
6746
- to validate without starting a run.
7265
+ Local files are bundled, preflighted, then run in Deepline cloud.
7266
+ Named plays run the live saved revision.
7267
+ Unknown --foo and --foo.bar flags are treated as play input args.
7268
+ File args accept local paths; the CLI stages files before submit.
7269
+ --watch prints logs, previews, stats, and next commands.
7270
+ The play page opens in your browser as soon as the run starts; use --no-open
7271
+ to only print the URL.
7272
+ --force supersedes active runs; it does not bypass completed reuse.
7273
+
7274
+ Idempotent execution:
7275
+ Stable tool call ids are the reuse key:
7276
+
7277
+ await ctx.tools.execute({ id: 'company_lookup', tool, input });
7278
+
7279
+ For rows, use ctx.map plus a stable row key:
7280
+
7281
+ const rows = await ctx
7282
+ .map('companies_v1', companies)
7283
+ .step('cto', (row, ctx) => ctx.tools.execute({
7284
+ id: 'find_cto',
7285
+ tool: 'apollo_search_people_with_match',
7286
+ input: { q_organization_domains_list: [row.domain], per_page: 1 },
7287
+ }))
7288
+ .run({ key: 'domain' });
7289
+
7290
+ Reuse needs the same play, tool id, map name, row key, and compatible logic.
7291
+ To refresh, change the id/map key or set staleAfterSeconds:
7292
+
7293
+ .run({ key: 'domain', staleAfterSeconds: 86400 })
6747
7294
 
6748
7295
  Examples:
6749
7296
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
6750
7297
  deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}' --watch
6751
7298
  deepline plays run enrich.play.ts --csv leads.csv --watch --out leads-enriched.csv
7299
+ deepline plays run cto-search.play.ts --limit 5 --watch
7300
+ deepline runs get <run-id>
6752
7301
  `
6753
7302
  ).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(
6754
7303
  "--revision-id <id>",
@@ -6759,7 +7308,7 @@ Examples:
6759
7308
  ).option("--watch", "Stream logs until completion").option(
6760
7309
  "--logs",
6761
7310
  "When output is non-interactive, stream play logs to stderr while waiting"
6762
- ).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) => {
7311
+ ).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) => {
6763
7312
  const passthroughArgs = [...command.args];
6764
7313
  const explicitTarget = options.file || options.name;
6765
7314
  const targetIsInputFlag = typeof target === "string" && target.startsWith("--");
@@ -6781,9 +7330,9 @@ Examples:
6781
7330
  ...options.out ? ["--out", options.out] : [],
6782
7331
  ...options.watch ? ["--watch"] : [],
6783
7332
  ...options.logs ? ["--logs"] : [],
6784
- ...options.pollIntervalMs ? ["--poll-interval-ms", options.pollIntervalMs] : [],
6785
7333
  ...options.tailTimeoutMs ? ["--tail-timeout-ms", options.tailTimeoutMs] : [],
6786
7334
  ...options.force ? ["--force"] : [],
7335
+ ...options.open === false ? ["--no-open"] : [],
6787
7336
  ...options.json ? ["--json"] : [],
6788
7337
  ...passthroughArgs
6789
7338
  ]);
@@ -6890,12 +7439,11 @@ Examples:
6890
7439
  ...options.json ? ["--json"] : []
6891
7440
  ]);
6892
7441
  });
6893
- 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) => {
7442
+ 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) => {
6894
7443
  process.exitCode = await handleRunTail([
6895
7444
  runId,
6896
7445
  ...options.json ? ["--json"] : [],
6897
- ...options.compact ? ["--compact"] : [],
6898
- ...options.cursor ? ["--cursor", options.cursor] : []
7446
+ ...options.compact ? ["--compact"] : []
6899
7447
  ]);
6900
7448
  });
6901
7449
  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) => {
@@ -7876,54 +8424,6 @@ async function syncSdkSkillsIfNeeded(baseUrl) {
7876
8424
  writeSdkSkillsStatusLine("SDK skills are up to date.");
7877
8425
  }
7878
8426
 
7879
- // src/cli/trace.ts
7880
- var cliTraceStartedAt = Date.now();
7881
- function isTruthyEnv(value) {
7882
- return value === "1" || value === "true" || value === "yes";
7883
- }
7884
- function isCliTraceEnabled() {
7885
- return isTruthyEnv(process.env.DEEPLINE_CLI_TRACE);
7886
- }
7887
- function recordCliTrace(event) {
7888
- if (!isCliTraceEnabled()) {
7889
- return;
7890
- }
7891
- const now = Date.now();
7892
- const payload = {
7893
- ts: now,
7894
- source: "cli",
7895
- sinceStartMs: now - cliTraceStartedAt,
7896
- ...event
7897
- };
7898
- process.stderr.write(`[cli-trace] ${JSON.stringify(payload)}
7899
- `);
7900
- }
7901
- async function traceCliSpan(phase, fields, run) {
7902
- if (!isCliTraceEnabled()) {
7903
- return run();
7904
- }
7905
- const startedAt = Date.now();
7906
- try {
7907
- const result = await run();
7908
- recordCliTrace({
7909
- phase,
7910
- ms: Date.now() - startedAt,
7911
- ok: true,
7912
- ...fields
7913
- });
7914
- return result;
7915
- } catch (error) {
7916
- recordCliTrace({
7917
- phase,
7918
- ms: Date.now() - startedAt,
7919
- ok: false,
7920
- error: error instanceof Error ? error.message : String(error),
7921
- ...fields
7922
- });
7923
- throw error;
7924
- }
7925
- }
7926
-
7927
8427
  // src/cli/index.ts
7928
8428
  function shouldPrintStartupPhase() {
7929
8429
  if (process.argv.includes("--json")) {
@@ -7934,6 +8434,12 @@ function shouldPrintStartupPhase() {
7934
8434
  const subcommand = args[1];
7935
8435
  return (command === "play" || command === "plays") && subcommand === "run";
7936
8436
  }
8437
+ function shouldDeferSkillsSyncForCommand() {
8438
+ const args = process.argv.slice(2);
8439
+ const command = args[0];
8440
+ const subcommand = args[1];
8441
+ return (command === "play" || command === "plays") && subcommand === "run" && args.includes("--json");
8442
+ }
7937
8443
  async function main() {
7938
8444
  const mainStartedAt = Date.now();
7939
8445
  recordCliTrace({
@@ -7978,11 +8484,13 @@ Output:
7978
8484
  if (printStartupPhase) {
7979
8485
  progress?.phase("checking sdk skills");
7980
8486
  }
7981
- await traceCliSpan(
7982
- "cli.sdk_skills_sync",
7983
- { baseUrl },
7984
- () => syncSdkSkillsIfNeeded(baseUrl)
7985
- );
8487
+ if (!shouldDeferSkillsSyncForCommand()) {
8488
+ await traceCliSpan(
8489
+ "cli.sdk_skills_sync",
8490
+ { baseUrl },
8491
+ () => syncSdkSkillsIfNeeded(baseUrl)
8492
+ );
8493
+ }
7986
8494
  });
7987
8495
  registerAuthCommands(program);
7988
8496
  registerToolsCommands(program);