deepline 0.1.21 → 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.21";
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
@@ -535,7 +535,7 @@ function isRecord(value) {
535
535
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
536
536
  }
537
537
  function normalizePlayStatus(raw) {
538
- 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";
539
539
  const runId = typeof raw.runId === "string" ? raw.runId : typeof raw.workflowId === "string" ? raw.workflowId : "";
540
540
  return {
541
541
  ...raw,
@@ -543,23 +543,6 @@ function normalizePlayStatus(raw) {
543
543
  status
544
544
  };
545
545
  }
546
- function mapLegacyTemporalStatus(status) {
547
- switch (status.trim().toUpperCase()) {
548
- case "PENDING":
549
- return "queued";
550
- case "COMPLETED":
551
- return "completed";
552
- case "FAILED":
553
- return "failed";
554
- case "CANCELLED":
555
- case "TERMINATED":
556
- case "TIMED_OUT":
557
- return "cancelled";
558
- case "RUNNING":
559
- default:
560
- return "running";
561
- }
562
- }
563
546
  function decodeBase64Bytes(value) {
564
547
  const binary = atob(value);
565
548
  const bytes = new Uint8Array(binary.length);
@@ -568,6 +551,79 @@ function decodeBase64Bytes(value) {
568
551
  }
569
552
  return bytes;
570
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;
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
+ };
626
+ }
571
627
  var DeeplineClient = class {
572
628
  http;
573
629
  config;
@@ -677,7 +733,7 @@ var DeeplineClient = class {
677
733
  /**
678
734
  * Search available tools using Deepline's ranked backend search.
679
735
  *
680
- * 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
681
737
  * tool metadata, categories, agent guidance, and input schema fields.
682
738
  */
683
739
  async searchTools(options = {}) {
@@ -770,7 +826,7 @@ var DeeplineClient = class {
770
826
  * `progress.logs`; they are not part of the user output object.
771
827
  *
772
828
  * @param request - Play run configuration (name, code, input, etc.)
773
- * @returns Workflow metadata including the `workflowId` for status polling
829
+ * @returns Run metadata including the public `workflowId`
774
830
  *
775
831
  * @example
776
832
  * ```typescript
@@ -1062,9 +1118,6 @@ var DeeplineClient = class {
1062
1118
  * Internal/advanced primitive. Public callers should usually prefer
1063
1119
  * {@link runPlay}, {@link PlayJob.get}, or `deepline play run --watch`.
1064
1120
  *
1065
- * Poll this method until `status` reaches a terminal state:
1066
- * `'completed'`, `'failed'`, or `'cancelled'`.
1067
- *
1068
1121
  * @param workflowId - Play-run id from {@link startPlayRun}
1069
1122
  * @returns Current status with progress logs and partial results
1070
1123
  *
@@ -1086,35 +1139,11 @@ var DeeplineClient = class {
1086
1139
  );
1087
1140
  return normalizePlayStatus(response);
1088
1141
  }
1089
- /**
1090
- * Get the lightweight tail-polling status for a play execution.
1091
- *
1092
- * This is intentionally smaller than {@link getPlayStatus}: it returns the
1093
- * fields needed for CLI log tailing while the run is in flight, without
1094
- * forcing the API to rebuild final result views on every poll. Call
1095
- * {@link getPlayStatus} once after a terminal state for the full result.
1096
- */
1097
- async getPlayTailStatus(workflowId, options) {
1098
- const params = new URLSearchParams({ mode: "tail" });
1099
- if (typeof options?.afterLogIndex === "number") {
1100
- params.set("afterLogIndex", String(options.afterLogIndex));
1101
- }
1102
- if (typeof options?.waitMs === "number") {
1103
- params.set("waitMs", String(options.waitMs));
1104
- }
1105
- if (options?.terminalOnly) {
1106
- params.set("terminalOnly", "true");
1107
- }
1108
- const response = await this.http.get(
1109
- `/api/v2/plays/run/${encodeURIComponent(workflowId)}?${params.toString()}`
1110
- );
1111
- return normalizePlayStatus(response);
1112
- }
1113
1142
  /**
1114
1143
  * Stream semantic play-run events using the same SSE feed as the dashboard.
1115
1144
  *
1116
- * Consumers should still keep a polling fallback: SSE is the fast live-update
1117
- * 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.
1118
1147
  */
1119
1148
  async *streamPlayRunEvents(workflowId, options) {
1120
1149
  const headers = options?.lastEventId && options.lastEventId.trim() ? { "Last-Event-ID": options.lastEventId.trim() } : void 0;
@@ -1134,7 +1163,7 @@ var DeeplineClient = class {
1134
1163
  *
1135
1164
  * Sends a stop request for the run.
1136
1165
  *
1137
- * @param workflowId - Temporal workflow ID to cancel
1166
+ * @param workflowId - Public Deepline play-run id to cancel
1138
1167
  *
1139
1168
  * @example
1140
1169
  * ```typescript
@@ -1150,7 +1179,7 @@ var DeeplineClient = class {
1150
1179
  /**
1151
1180
  * Stop a running play execution, including open HITL waits.
1152
1181
  *
1153
- * @param workflowId - Temporal workflow ID to stop
1182
+ * @param workflowId - Public Deepline play-run id to stop
1154
1183
  * @param options.reason - Optional audit/debug reason
1155
1184
  */
1156
1185
  async stopPlay(workflowId, options) {
@@ -1222,32 +1251,42 @@ var DeeplineClient = class {
1222
1251
  );
1223
1252
  return response.runs ?? [];
1224
1253
  }
1225
- /**
1226
- * Fetch the lightweight tail status for a run using the public runs resource model.
1227
- *
1228
- * This is the SDK equivalent of:
1229
- *
1230
- * ```bash
1231
- * deepline runs tail <run-id> --json
1232
- * ```
1233
- */
1254
+ /** Read the canonical run stream and return the latest run snapshot. */
1234
1255
  async tailRun(runId, options) {
1235
- 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;
1236
- const params = new URLSearchParams();
1237
- if (Number.isFinite(afterLogIndex)) {
1238
- 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
+ }
1239
1275
  }
1240
- if (typeof options?.waitMs === "number") {
1241
- 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
+ );
1242
1280
  }
1243
- if (options?.terminalOnly) {
1244
- params.set("terminalOnly", "true");
1281
+ if (state.latest) {
1282
+ return state.latest;
1245
1283
  }
1246
- const suffix = params.toString() ? `?${params.toString()}` : "";
1247
- const response = await this.http.get(
1248
- `/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 }
1249
1289
  );
1250
- return normalizePlayStatus(response);
1251
1290
  }
1252
1291
  /**
1253
1292
  * Fetch persisted logs for a run using the public runs resource model.
@@ -1404,11 +1443,11 @@ var DeeplineClient = class {
1404
1443
  // Plays — high-level orchestration
1405
1444
  // ——————————————————————————————————————————————————————————
1406
1445
  /**
1407
- * 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.
1408
1447
  *
1409
1448
  * This is the highest-level play execution method. It submits the play,
1410
- * polls for status updates, and returns a structured result with logs
1411
- * 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`.
1412
1451
  *
1413
1452
  * @param code - Source string fallback; pass the bundled artifact in `options.artifact`
1414
1453
  * @param csvPath - Input CSV path, or `null`
@@ -1424,7 +1463,6 @@ var DeeplineClient = class {
1424
1463
  * const logs = status.progress?.logs ?? [];
1425
1464
  * console.log(`[${status.status}] ${logs.length} log lines`);
1426
1465
  * },
1427
- * pollIntervalMs: 1000,
1428
1466
  * });
1429
1467
  *
1430
1468
  * if (result.success) {
@@ -1455,33 +1493,53 @@ var DeeplineClient = class {
1455
1493
  packagedFiles: options?.packagedFiles,
1456
1494
  force: options?.force
1457
1495
  });
1458
- const pollInterval = options?.pollIntervalMs ?? 500;
1459
1496
  const start = Date.now();
1460
- 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
+ })) {
1461
1517
  if (options?.signal?.aborted) {
1462
1518
  await this.cancelPlay(workflowId);
1463
1519
  return {
1464
1520
  success: false,
1465
1521
  runId: workflowId,
1466
- logs: [],
1522
+ logs: state.logs,
1467
1523
  durationMs: Date.now() - start,
1468
1524
  error: "Cancelled by user"
1469
1525
  };
1470
1526
  }
1471
- const status = await this.getPlayStatus(workflowId);
1527
+ const status = updatePlayLiveStatusState(state, event);
1528
+ if (!status) {
1529
+ continue;
1530
+ }
1472
1531
  options?.onProgress?.(status);
1473
1532
  if (TERMINAL_PLAY_STATUSES.has(status.status)) {
1474
- return {
1475
- success: status.status === "completed",
1476
- runId: status.runId || workflowId,
1477
- result: status.result,
1478
- logs: status.progress?.logs ?? [],
1479
- durationMs: Date.now() - start,
1480
- error: status.progress?.error ?? (status.status !== "completed" ? status.status : void 0)
1481
- };
1533
+ const finalStatus = await this.getPlayStatus(status.runId || workflowId).catch(() => status);
1534
+ return playRunResultFromStatus(finalStatus, start, workflowId);
1482
1535
  }
1483
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
1484
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
+ );
1485
1543
  }
1486
1544
  // ——————————————————————————————————————————————————————————
1487
1545
  // Health
@@ -1555,18 +1613,24 @@ async function enforceSdkCompatibility(baseUrl) {
1555
1613
  }
1556
1614
 
1557
1615
  // src/cli/commands/auth.ts
1558
- 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";
1559
1617
  import { hostname } from "os";
1560
- import { dirname as dirname2 } from "path";
1618
+ import { dirname as dirname3 } from "path";
1561
1619
 
1562
1620
  // src/cli/utils.ts
1563
- 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";
1564
1627
  import { mkdir, writeFile } from "fs/promises";
1565
1628
  import { homedir as homedir2 } from "os";
1566
- import { join as join2, resolve as resolve2 } from "path";
1567
- 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";
1568
1631
  import { parse } from "csv-parse/sync";
1569
1632
  import { stringify } from "csv-stringify/sync";
1633
+ var BROWSER_FOCUS_COOLDOWN_MS = 3e4;
1570
1634
  function getAuthedHttpClient() {
1571
1635
  const config = resolveConfig();
1572
1636
  return { config, http: new HttpClient(config) };
@@ -1578,12 +1642,215 @@ async function writeOutputFile(filename, content) {
1578
1642
  await writeFile(fullPath, content, "utf-8");
1579
1643
  return fullPath;
1580
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
+ }
1581
1837
  function openInBrowser(url) {
1582
1838
  try {
1583
- if (process.platform === "darwin") execSync(`open "${url}"`, { stdio: "ignore" });
1584
- else if (process.platform === "win32")
1585
- execSync(`start "" "${url}"`, { stdio: "ignore" });
1586
- 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" });
1587
1854
  } catch {
1588
1855
  }
1589
1856
  }
@@ -1667,14 +1934,14 @@ function envFilePath(baseUrl) {
1667
1934
  }
1668
1935
  function saveEnvValues(values, baseUrl) {
1669
1936
  const filePath = envFilePath(baseUrl);
1670
- const dir = dirname2(filePath);
1671
- if (!existsSync2(dir)) {
1672
- mkdirSync2(dir, { recursive: true });
1937
+ const dir = dirname3(filePath);
1938
+ if (!existsSync3(dir)) {
1939
+ mkdirSync3(dir, { recursive: true });
1673
1940
  }
1674
- const existing = existsSync2(filePath) ? parseEnvFile(filePath) : {};
1941
+ const existing = existsSync3(filePath) ? parseEnvFile(filePath) : {};
1675
1942
  const merged = { ...existing, ...values };
1676
1943
  const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
1677
- writeFileSync2(filePath, lines.join("\n") + "\n", "utf-8");
1944
+ writeFileSync3(filePath, lines.join("\n") + "\n", "utf-8");
1678
1945
  saveProjectDeeplineEnvValues(baseUrl, values);
1679
1946
  }
1680
1947
  async function httpJson(method, url, apiKey, body) {
@@ -2213,7 +2480,7 @@ function registerBillingCommands(program) {
2213
2480
  }
2214
2481
 
2215
2482
  // src/cli/dataset-stats.ts
2216
- import { writeFileSync as writeFileSync3 } from "fs";
2483
+ import { writeFileSync as writeFileSync4 } from "fs";
2217
2484
  import { resolve as resolve3 } from "path";
2218
2485
  var CSV_PROJECTED_FIELDS_KEY = "__deeplineCsvProjectedFields";
2219
2486
  function csvProjectedFields(row) {
@@ -2545,7 +2812,7 @@ function writeCanonicalRowsCsv(rowsInfo, outPath) {
2545
2812
  columns: rowsInfo.columns
2546
2813
  });
2547
2814
  const resolved = resolve3(outPath);
2548
- writeFileSync3(
2815
+ writeFileSync4(
2549
2816
  resolved,
2550
2817
  csvStringFromRows(sanitized.rows, sanitized.columns),
2551
2818
  "utf-8"
@@ -2838,25 +3105,25 @@ function registerOrgCommands(program) {
2838
3105
  // src/cli/commands/play.ts
2839
3106
  import { createHash as createHash3 } from "crypto";
2840
3107
  import {
2841
- existsSync as existsSync4,
3108
+ existsSync as existsSync5,
2842
3109
  readFileSync as readFileSync3,
2843
3110
  readdirSync,
2844
3111
  realpathSync,
2845
- writeFileSync as writeFileSync4
3112
+ writeFileSync as writeFileSync5
2846
3113
  } from "fs";
2847
- 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";
2848
3115
 
2849
3116
  // src/plays/bundle-play-file.ts
2850
3117
  import { tmpdir as tmpdir2 } from "os";
2851
- 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";
2852
3119
  import { fileURLToPath } from "url";
2853
- import { existsSync as existsSync3 } from "fs";
3120
+ import { existsSync as existsSync4 } from "fs";
2854
3121
 
2855
3122
  // ../shared_libs/plays/bundling/index.ts
2856
3123
  import { createHash } from "crypto";
2857
3124
  import { mkdir as mkdir2, readFile, realpath, stat, writeFile as writeFile2 } from "fs/promises";
2858
3125
  import { tmpdir } from "os";
2859
- 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";
2860
3127
  import { builtinModules, createRequire } from "module";
2861
3128
  import { build } from "esbuild";
2862
3129
 
@@ -2955,7 +3222,7 @@ async function normalizeLocalPath(filePath) {
2955
3222
  function createPlayWorkspace(entryFile) {
2956
3223
  return {
2957
3224
  entryFile,
2958
- rootDir: dirname3(entryFile)
3225
+ rootDir: dirname4(entryFile)
2959
3226
  };
2960
3227
  }
2961
3228
  function isPathInsideDirectory(filePath, directory) {
@@ -3156,7 +3423,7 @@ function workersNamedPlayEntryAliasPlugin(playFilePath, exportName) {
3156
3423
  contents: `export { ${exportName} as default } from ${JSON.stringify(playFilePath)};
3157
3424
  `,
3158
3425
  loader: "ts",
3159
- resolveDir: dirname3(playFilePath)
3426
+ resolveDir: dirname4(playFilePath)
3160
3427
  })
3161
3428
  );
3162
3429
  }
@@ -3320,7 +3587,7 @@ function importedPlayProxyPlugin(importedPlayDependencies) {
3320
3587
  return {
3321
3588
  contents: buildImportedPlayProxyModule(dependency.playName),
3322
3589
  loader: "ts",
3323
- resolveDir: dirname3(args.path)
3590
+ resolveDir: dirname4(args.path)
3324
3591
  };
3325
3592
  });
3326
3593
  }
@@ -3338,7 +3605,7 @@ async function resolveLocalImport(fromFile, specifier) {
3338
3605
  if (specifier.startsWith("file:")) {
3339
3606
  return normalizeLocalPath(new URL(specifier).pathname);
3340
3607
  }
3341
- const base = isAbsolute(specifier) ? resolve4(specifier) : resolve4(dirname3(fromFile), specifier);
3608
+ const base = isAbsolute(specifier) ? resolve4(specifier) : resolve4(dirname4(fromFile), specifier);
3342
3609
  const candidates = [base];
3343
3610
  const explicitExtension = extname(base).toLowerCase();
3344
3611
  if (!explicitExtension) {
@@ -3588,7 +3855,7 @@ async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter
3588
3855
  ...namedExportShim ? {
3589
3856
  stdin: {
3590
3857
  contents: namedExportShim,
3591
- resolveDir: dirname3(entryFile),
3858
+ resolveDir: dirname4(entryFile),
3592
3859
  sourcefile: `${basename(entryFile)}.${exportName}.entry.ts`,
3593
3860
  loader: "ts"
3594
3861
  }
@@ -3844,13 +4111,6 @@ var PLAY_DEDUP_BACKENDS = {
3844
4111
 
3845
4112
  // ../shared_libs/play-runtime/profiles.ts
3846
4113
  var PLAY_EXECUTION_PROFILES = {
3847
- legacy: {
3848
- id: "legacy",
3849
- scheduler: PLAY_SCHEDULER_BACKENDS.temporal,
3850
- runner: PLAY_RUNTIME_BACKENDS.daytona,
3851
- dedup: PLAY_DEDUP_BACKENDS.inMemory,
3852
- label: "Daytona + Temporal (production today)"
3853
- },
3854
4114
  workers_edge: {
3855
4115
  id: "workers_edge",
3856
4116
  scheduler: PLAY_SCHEDULER_BACKENDS.cfWorkflows,
@@ -3885,7 +4145,7 @@ function resolveExecutionProfile(override) {
3885
4145
  // src/plays/local-file-discovery.ts
3886
4146
  import { createHash as createHash2 } from "crypto";
3887
4147
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
3888
- 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";
3889
4149
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
3890
4150
  function sha2562(buffer) {
3891
4151
  return createHash2("sha256").update(buffer).digest("hex");
@@ -4086,7 +4346,7 @@ function isPathInsideDirectory2(filePath, directory) {
4086
4346
  return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute2(relativePath);
4087
4347
  }
4088
4348
  async function resolveLocalImport2(fromFile, specifier) {
4089
- const base = isAbsolute2(specifier) ? resolve5(specifier) : resolve5(dirname4(fromFile), specifier);
4349
+ const base = isAbsolute2(specifier) ? resolve5(specifier) : resolve5(dirname5(fromFile), specifier);
4090
4350
  const candidates = [base];
4091
4351
  const explicitExtension = extname2(base).toLowerCase();
4092
4352
  if (!explicitExtension) {
@@ -4105,7 +4365,7 @@ async function resolveLocalImport2(fromFile, specifier) {
4105
4365
  }
4106
4366
  async function discoverPackagedLocalFiles(entryFile) {
4107
4367
  const absoluteEntryFile = resolve5(entryFile);
4108
- const packagingRoot = dirname4(absoluteEntryFile);
4368
+ const packagingRoot = dirname5(absoluteEntryFile);
4109
4369
  const files = /* @__PURE__ */ new Map();
4110
4370
  const unresolved = [];
4111
4371
  const visitedFiles = /* @__PURE__ */ new Set();
@@ -4142,7 +4402,7 @@ async function discoverPackagedLocalFiles(entryFile) {
4142
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."
4143
4403
  });
4144
4404
  } else {
4145
- const absoluteCsvPath = resolve5(dirname4(absolutePath), resolvedPath);
4405
+ const absoluteCsvPath = resolve5(dirname5(absolutePath), resolvedPath);
4146
4406
  if (isAbsolute2(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
4147
4407
  unresolved.push({
4148
4408
  sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
@@ -4181,14 +4441,14 @@ async function discoverPackagedLocalFiles(entryFile) {
4181
4441
 
4182
4442
  // src/plays/bundle-play-file.ts
4183
4443
  var PLAY_BUNDLE_CACHE_VERSION2 = 26;
4184
- var MODULE_DIR = dirname5(fileURLToPath(import.meta.url));
4444
+ var MODULE_DIR = dirname6(fileURLToPath(import.meta.url));
4185
4445
  var SDK_PACKAGE_ROOT = resolve6(MODULE_DIR, "..", "..");
4186
4446
  var SOURCE_REPO_ROOT = resolve6(SDK_PACKAGE_ROOT, "..");
4187
- var HAS_SOURCE_BUNDLING_SOURCES = existsSync3(
4447
+ var HAS_SOURCE_BUNDLING_SOURCES = existsSync4(
4188
4448
  resolve6(SOURCE_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
4189
4449
  );
4190
4450
  var PACKAGED_REPO_ROOT = resolve6(SDK_PACKAGE_ROOT, "dist", "repo");
4191
- var HAS_PACKAGED_BUNDLING_SOURCES = existsSync3(
4451
+ var HAS_PACKAGED_BUNDLING_SOURCES = existsSync4(
4192
4452
  resolve6(PACKAGED_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
4193
4453
  );
4194
4454
  var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED_BUNDLING_SOURCES ? PACKAGED_REPO_ROOT : resolve6(SDK_PACKAGE_ROOT, "..");
@@ -4227,7 +4487,7 @@ function createSdkPlayBundlingAdapter() {
4227
4487
  sdkSourceRoot: SDK_SOURCE_ROOT,
4228
4488
  sdkPackageJson: SDK_PACKAGE_JSON,
4229
4489
  sdkEntryFile: SDK_ENTRY_FILE,
4230
- 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,
4231
4491
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
4232
4492
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
4233
4493
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
@@ -4513,15 +4773,15 @@ function materializeRemotePlaySource(input) {
4513
4773
  return null;
4514
4774
  }
4515
4775
  const outputPath = input.outPath ?? defaultMaterializedPlayPath(input.playName);
4516
- if (existsSync4(outputPath)) {
4776
+ if (existsSync5(outputPath)) {
4517
4777
  const existingSource = readFileSync3(outputPath, "utf-8");
4518
4778
  if (existingSource === input.sourceCode) {
4519
4779
  return { path: outputPath, status: "unchanged", created: false };
4520
4780
  }
4521
- writeFileSync4(outputPath, input.sourceCode, "utf-8");
4781
+ writeFileSync5(outputPath, input.sourceCode, "utf-8");
4522
4782
  return { path: outputPath, status: "updated", created: false };
4523
4783
  }
4524
- writeFileSync4(outputPath, input.sourceCode, "utf-8");
4784
+ writeFileSync5(outputPath, input.sourceCode, "utf-8");
4525
4785
  return { path: outputPath, status: "created", created: true };
4526
4786
  }
4527
4787
  function formatLoadedPlayMessage(materializedFile) {
@@ -4566,7 +4826,7 @@ function extractPlayName(code, filePath) {
4566
4826
  throw buildMissingDefinePlayError(filePath);
4567
4827
  }
4568
4828
  function isFileTarget(target) {
4569
- return existsSync4(resolve7(target));
4829
+ return existsSync5(resolve7(target));
4570
4830
  }
4571
4831
  function looksLikeFilePath(target) {
4572
4832
  if (target.trim().toLowerCase().startsWith("prebuilt/")) {
@@ -4695,7 +4955,7 @@ function applyCsvShortcutInput(input) {
4695
4955
  function isLocalFilePathValue(value) {
4696
4956
  if (typeof value !== "string" || !value.trim()) return false;
4697
4957
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
4698
- return existsSync4(resolve7(value));
4958
+ return existsSync5(resolve7(value));
4699
4959
  }
4700
4960
  function inputContainsLocalFilePath(value) {
4701
4961
  if (isLocalFilePathValue(value)) {
@@ -4900,7 +5160,7 @@ function formatTimestamp(value) {
4900
5160
  function formatRunLine(run) {
4901
5161
  return `${run.workflowId} ${run.status} ${formatTimestamp(run.startTime)}`;
4902
5162
  }
4903
- function isTransientPlayStatusPollError(error) {
5163
+ function isTransientPlayStreamError(error) {
4904
5164
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
4905
5165
  return error.statusCode >= 500 && error.statusCode < 600;
4906
5166
  }
@@ -4909,12 +5169,6 @@ function isTransientPlayStatusPollError(error) {
4909
5169
  text
4910
5170
  );
4911
5171
  }
4912
- function isTerminalPlayStatusPollError(input) {
4913
- if (input.error instanceof DeeplineError && input.error.statusCode === 404 && input.hasSeenRun) {
4914
- return true;
4915
- }
4916
- return false;
4917
- }
4918
5172
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
4919
5173
  "completed",
4920
5174
  "failed",
@@ -4980,6 +5234,12 @@ function buildPlayDashboardUrl(baseUrl, playName) {
4980
5234
  const encodedPlayName = encodeURIComponent(playName);
4981
5235
  return `${trimmedBase}/dashboard/plays/${encodedPlayName}`;
4982
5236
  }
5237
+ function openPlayDashboard(input) {
5238
+ if (input.jsonOutput || input.noOpen || !input.dashboardUrl) {
5239
+ return;
5240
+ }
5241
+ openInBrowser(input.dashboardUrl);
5242
+ }
4983
5243
  function getDashboardUrlFromLiveEvent(event) {
4984
5244
  const dashboardUrl = getEventPayload(event).dashboardUrl;
4985
5245
  return typeof dashboardUrl === "string" && dashboardUrl.trim() ? dashboardUrl.trim() : null;
@@ -5016,6 +5276,68 @@ function assertPlayWaitNotTimedOut(input) {
5016
5276
  );
5017
5277
  }
5018
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
+ }
5019
5341
  async function startAndWaitForPlayCompletionByStream(input) {
5020
5342
  const startedAt = Date.now();
5021
5343
  const state = {
@@ -5049,6 +5371,11 @@ async function startAndWaitForPlayCompletionByStream(input) {
5049
5371
  const workflowId = lastKnownWorkflowId || "pending";
5050
5372
  if (workflowId !== "pending" && !emittedDashboardUrl) {
5051
5373
  const dashboardUrl = getDashboardUrlFromLiveEvent(event) ?? buildPlayDashboardUrl(input.client.baseUrl, input.playName);
5374
+ openPlayDashboard({
5375
+ dashboardUrl,
5376
+ jsonOutput: input.jsonOutput,
5377
+ noOpen: input.noOpen
5378
+ });
5052
5379
  if (!input.jsonOutput) {
5053
5380
  writeStartedPlayRun({
5054
5381
  runId: workflowId,
@@ -5104,21 +5431,21 @@ async function startAndWaitForPlayCompletionByStream(input) {
5104
5431
  lastPhase
5105
5432
  });
5106
5433
  }
5107
- if (lastKnownWorkflowId && isTransientPlayStatusPollError(error)) {
5434
+ if (lastKnownWorkflowId && isTransientPlayStreamError(error)) {
5108
5435
  if (timeout) {
5109
5436
  clearTimeout(timeout);
5110
5437
  }
5111
5438
  const reason = error instanceof Error ? error.message : String(error);
5112
5439
  if (!input.jsonOutput) {
5113
5440
  process.stderr.write(
5114
- `[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})
5115
5442
  `
5116
5443
  );
5117
5444
  }
5118
5445
  recordCliTrace({
5119
- phase: "cli.play_start_stream_fallback",
5446
+ phase: "cli.play_start_stream_reconnect",
5120
5447
  ms: Date.now() - startedAt,
5121
- ok: false,
5448
+ ok: true,
5122
5449
  playName: input.playName,
5123
5450
  workflowId: lastKnownWorkflowId,
5124
5451
  eventCount,
@@ -5126,10 +5453,9 @@ async function startAndWaitForPlayCompletionByStream(input) {
5126
5453
  lastPhase,
5127
5454
  reason
5128
5455
  });
5129
- return waitForPlayCompletionByPolling({
5456
+ return waitForPlayCompletionByStream({
5130
5457
  client: input.client,
5131
5458
  workflowId: lastKnownWorkflowId,
5132
- pollIntervalMs: 500,
5133
5459
  jsonOutput: input.jsonOutput,
5134
5460
  emitLogs: input.emitLogs,
5135
5461
  waitTimeoutMs: input.waitTimeoutMs,
@@ -5147,13 +5473,13 @@ async function startAndWaitForPlayCompletionByStream(input) {
5147
5473
  if (lastKnownWorkflowId) {
5148
5474
  if (!input.jsonOutput) {
5149
5475
  input.progress.writeLine(
5150
- `[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`
5151
5477
  );
5152
5478
  }
5153
5479
  recordCliTrace({
5154
- phase: "cli.play_start_stream_fallback",
5480
+ phase: "cli.play_start_stream_reconnect",
5155
5481
  ms: Date.now() - startedAt,
5156
- ok: false,
5482
+ ok: true,
5157
5483
  playName: input.playName,
5158
5484
  workflowId: lastKnownWorkflowId,
5159
5485
  eventCount,
@@ -5161,10 +5487,9 @@ async function startAndWaitForPlayCompletionByStream(input) {
5161
5487
  lastPhase,
5162
5488
  reason: "stream ended before terminal event"
5163
5489
  });
5164
- return waitForPlayCompletionByPolling({
5490
+ return waitForPlayCompletionByStream({
5165
5491
  client: input.client,
5166
5492
  workflowId: lastKnownWorkflowId,
5167
- pollIntervalMs: 500,
5168
5493
  jsonOutput: input.jsonOutput,
5169
5494
  emitLogs: input.emitLogs,
5170
5495
  waitTimeoutMs: input.waitTimeoutMs,
@@ -5185,72 +5510,6 @@ async function startAndWaitForPlayCompletionByStream(input) {
5185
5510
  }
5186
5511
  );
5187
5512
  }
5188
- async function waitForPlayCompletionByPolling(input) {
5189
- let lastTransientPollWarningAt = 0;
5190
- let hasSeenRun = false;
5191
- while (true) {
5192
- assertPlayWaitNotTimedOut(input);
5193
- let status;
5194
- try {
5195
- status = await input.client.getPlayTailStatus(input.workflowId, {
5196
- afterLogIndex: input.state.lastLogIndex,
5197
- // Keep the server-side tail wait close to the caller's requested poll
5198
- // cadence. A long wait makes tiny remote runs look slow whenever the
5199
- // terminal update lands just after the held request starts.
5200
- waitMs: Math.max(50, Math.min(input.pollIntervalMs, 1e3))
5201
- });
5202
- } catch (error) {
5203
- if (isTerminalPlayStatusPollError({ error, hasSeenRun })) {
5204
- throw new DeeplineError(
5205
- `Play run ${input.workflowId} no longer exists on the server (404). The run was deleted or the backend lost it.`,
5206
- 404,
5207
- "PLAY_RUN_NOT_FOUND"
5208
- );
5209
- }
5210
- if (!isTransientPlayStatusPollError(error)) {
5211
- throw error;
5212
- }
5213
- const now = Date.now();
5214
- if (now - lastTransientPollWarningAt >= 3e4) {
5215
- const message = error instanceof Error ? error.message : String(error);
5216
- input.progress.writeLine(
5217
- `[play tail] transient status poll failed; retrying: ${message}`
5218
- );
5219
- lastTransientPollWarningAt = now;
5220
- }
5221
- await new Promise(
5222
- (resolvePromise) => setTimeout(resolvePromise, input.pollIntervalMs)
5223
- );
5224
- continue;
5225
- }
5226
- hasSeenRun = true;
5227
- const logs = status.progress?.logs ?? [];
5228
- input.progress.phase(status.status);
5229
- printPlayLogLines({
5230
- lines: logs.slice(input.state.lastLogIndex),
5231
- status,
5232
- jsonOutput: input.jsonOutput,
5233
- emitLogs: input.emitLogs,
5234
- state: input.state,
5235
- progress: input.progress
5236
- });
5237
- if (TERMINAL_PLAY_STATUSES2.has(status.status)) {
5238
- return status.result !== void 0 ? status : await input.client.getPlayStatus(input.workflowId, { billing: false });
5239
- }
5240
- const authoritativeStatus = await input.client.getPlayStatus(
5241
- input.workflowId,
5242
- { billing: false }
5243
- );
5244
- if (TERMINAL_PLAY_STATUSES2.has(authoritativeStatus.status)) {
5245
- return authoritativeStatus;
5246
- }
5247
- if ((status.progress?.logs ?? []).length === input.state.lastLogIndex) {
5248
- await new Promise(
5249
- (resolvePromise) => setTimeout(resolvePromise, input.pollIntervalMs)
5250
- );
5251
- }
5252
- }
5253
- }
5254
5513
  function formatInteger(value) {
5255
5514
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
5256
5515
  }
@@ -5391,7 +5650,6 @@ function buildRunWarnings(status, rowsInfo) {
5391
5650
  function buildRunNextCommands(runId, rowsInfo) {
5392
5651
  const commands = {
5393
5652
  get: `deepline runs get ${runId} --json`,
5394
- tail: `deepline runs tail ${runId} --json`,
5395
5653
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
5396
5654
  logs: `deepline runs logs ${runId} --out run.log --json`
5397
5655
  };
@@ -5507,10 +5765,9 @@ function normalizeErrorsForEnvelope(status, error) {
5507
5765
  }
5508
5766
  function normalizeLogsForEnvelope(status) {
5509
5767
  const logs = Array.isArray(status.progress?.logs) ? status.progress.logs : [];
5510
- const offset = typeof status.progress?.logOffset === "number" && Number.isFinite(status.progress.logOffset) ? Math.max(0, Math.trunc(status.progress.logOffset)) : 0;
5511
- const totalCount = offset + logs.length;
5768
+ const totalCount = logs.length;
5512
5769
  const entries = logs.slice(Math.max(0, logs.length - RUN_LOG_PREVIEW_LIMIT));
5513
- const firstSequence = entries.length === 0 ? null : offset + logs.length - entries.length + 1;
5770
+ const firstSequence = entries.length === 0 ? null : logs.length - entries.length + 1;
5514
5771
  const lastSequence = totalCount === 0 ? null : totalCount;
5515
5772
  return {
5516
5773
  totalCount,
@@ -5756,7 +6013,6 @@ function writeStartedPlayRun(input) {
5756
6013
  workflowId: input.runId,
5757
6014
  name: input.playName,
5758
6015
  status: input.status ?? "started",
5759
- statusUrl: input.statusUrl,
5760
6016
  dashboardUrl: input.dashboardUrl
5761
6017
  };
5762
6018
  if (input.jsonOutput) {
@@ -5768,7 +6024,7 @@ function writeStartedPlayRun(input) {
5768
6024
  `Started ${input.playName}`,
5769
6025
  ` run id: ${input.runId}`,
5770
6026
  ` get status: deepline runs get ${input.runId} --json`,
5771
- ` tail logs: deepline runs tail ${input.runId} --json`,
6027
+ ` logs: deepline runs logs ${input.runId} --json`,
5772
6028
  ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
5773
6029
  ` result JSON: deepline runs get ${input.runId} --json`
5774
6030
  ];
@@ -5783,7 +6039,7 @@ function writeStartedPlayRun(input) {
5783
6039
  console.log(output);
5784
6040
  }
5785
6041
  function parsePlayRunOptions(args) {
5786
- 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.";
5787
6043
  let filePath = null;
5788
6044
  let playName = null;
5789
6045
  let input = null;
@@ -5793,8 +6049,8 @@ function parsePlayRunOptions(args) {
5793
6049
  let jsonOutput = watch ? args.includes("--json") : argsWantJson(args);
5794
6050
  const emitLogs = !jsonOutput || args.includes("--logs");
5795
6051
  const force = args.includes("--force");
6052
+ const noOpen = args.includes("--no-open");
5796
6053
  let outPath = null;
5797
- let pollIntervalMs = 500;
5798
6054
  let waitTimeoutMs = null;
5799
6055
  for (let index = 0; index < args.length; index += 1) {
5800
6056
  const arg = args[index];
@@ -5826,15 +6082,16 @@ function parsePlayRunOptions(args) {
5826
6082
  outPath = resolve7(args[++index]);
5827
6083
  continue;
5828
6084
  }
5829
- if ((arg === "--poll-interval-ms" || arg === "--interval-ms") && args[index + 1]) {
5830
- pollIntervalMs = parsePositiveInteger2(args[++index], arg);
5831
- 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
+ );
5832
6089
  }
5833
6090
  if ((arg === "--tail-timeout-ms" || arg === "--timeout-ms") && args[index + 1]) {
5834
6091
  waitTimeoutMs = parsePositiveInteger2(args[++index], arg);
5835
6092
  continue;
5836
6093
  }
5837
- 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") {
5838
6095
  if (arg === "--watch") {
5839
6096
  continue;
5840
6097
  }
@@ -5903,10 +6160,10 @@ function parsePlayRunOptions(args) {
5903
6160
  watch,
5904
6161
  emitLogs,
5905
6162
  jsonOutput,
5906
- pollIntervalMs,
5907
6163
  waitTimeoutMs,
5908
6164
  force,
5909
- outPath
6165
+ outPath,
6166
+ noOpen
5910
6167
  };
5911
6168
  }
5912
6169
  function parsePlayCheckOptions(args) {
@@ -6081,6 +6338,7 @@ async function handleFileBackedRun(options) {
6081
6338
  jsonOutput: options.jsonOutput,
6082
6339
  emitLogs: options.emitLogs,
6083
6340
  waitTimeoutMs: options.waitTimeoutMs,
6341
+ noOpen: options.noOpen,
6084
6342
  progress
6085
6343
  })
6086
6344
  );
@@ -6108,14 +6366,19 @@ async function handleFileBackedRun(options) {
6108
6366
  () => client.startPlayRun(startRequest)
6109
6367
  );
6110
6368
  const dashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
6111
- 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}`);
6112
6376
  progress.complete();
6113
6377
  writeStartedPlayRun({
6114
6378
  runId: started.workflowId,
6115
6379
  playName,
6116
6380
  status: started.status,
6117
- statusUrl: started.statusUrl,
6118
- dashboardUrl: started.dashboardUrl ?? dashboardUrl,
6381
+ dashboardUrl: resolvedDashboardUrl,
6119
6382
  jsonOutput: options.jsonOutput,
6120
6383
  progress
6121
6384
  });
@@ -6220,6 +6483,7 @@ async function handleNamedRun(options) {
6220
6483
  jsonOutput: options.jsonOutput,
6221
6484
  emitLogs: options.emitLogs,
6222
6485
  waitTimeoutMs: options.waitTimeoutMs,
6486
+ noOpen: options.noOpen,
6223
6487
  progress
6224
6488
  })
6225
6489
  );
@@ -6250,14 +6514,19 @@ async function handleNamedRun(options) {
6250
6514
  client.baseUrl,
6251
6515
  playName
6252
6516
  );
6253
- 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}`);
6254
6524
  progress.complete();
6255
6525
  writeStartedPlayRun({
6256
6526
  runId: started.workflowId,
6257
6527
  playName: started.name ?? playName,
6258
6528
  status: started.status,
6259
- statusUrl: started.statusUrl,
6260
- dashboardUrl: started.dashboardUrl ?? dashboardUrl,
6529
+ dashboardUrl: resolvedDashboardUrl,
6261
6530
  jsonOutput: options.jsonOutput,
6262
6531
  progress
6263
6532
  });
@@ -6271,8 +6540,8 @@ async function handlePlayRun(args) {
6271
6540
  }
6272
6541
  const resolved = resolve7(options.target.path);
6273
6542
  console.error(`File not found: ${resolved}`);
6274
- const dir = dirname6(resolved);
6275
- if (existsSync4(dir)) {
6543
+ const dir = dirname7(resolved);
6544
+ if (existsSync5(dir)) {
6276
6545
  const base = basename3(resolved);
6277
6546
  try {
6278
6547
  const siblings = readdirSync(dir).filter(
@@ -6300,7 +6569,7 @@ function parseRunIdPositional(args, usage) {
6300
6569
  }
6301
6570
  continue;
6302
6571
  }
6303
- 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]) {
6304
6573
  index += 1;
6305
6574
  continue;
6306
6575
  }
@@ -6385,7 +6654,7 @@ async function handleRunsList(args) {
6385
6654
  return 0;
6386
6655
  }
6387
6656
  async function handleRunTail(args) {
6388
- const usage = "Usage: deepline runs tail <run-id> [--json] [--compact] [--cursor <cursor>]";
6657
+ const usage = "Usage: deepline runs tail <run-id> [--json] [--compact]";
6389
6658
  let runId;
6390
6659
  try {
6391
6660
  runId = parseRunIdPositional(args, usage);
@@ -6393,20 +6662,19 @@ async function handleRunTail(args) {
6393
6662
  console.error(error instanceof Error ? error.message : usage);
6394
6663
  return 1;
6395
6664
  }
6396
- const client = new DeeplineClient();
6397
- let afterLogIndex;
6398
6665
  for (let index = 0; index < args.length; index += 1) {
6399
6666
  const arg = args[index];
6400
- if (arg === "--cursor" && args[index + 1]) {
6401
- const parsed = Number(args[++index]);
6402
- if (Number.isInteger(parsed) && parsed >= 0) {
6403
- afterLogIndex = parsed;
6404
- }
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;
6405
6674
  }
6406
6675
  }
6407
- const status = await client.runs.tail(runId, {
6408
- ...afterLogIndex !== void 0 ? { afterLogIndex } : {}
6409
- });
6676
+ const client = new DeeplineClient();
6677
+ const status = await client.runs.tail(runId);
6410
6678
  writePlayResult(status, argsWantJson(args));
6411
6679
  return status.status === "failed" ? 1 : 0;
6412
6680
  }
@@ -6435,7 +6703,7 @@ async function handleRunLogs(args) {
6435
6703
  const status = await client.runs.get(runId);
6436
6704
  const logs = status.progress?.logs ?? [];
6437
6705
  if (outPath) {
6438
- writeFileSync4(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6706
+ writeFileSync5(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6439
6707
  if (argsWantJson(args)) {
6440
6708
  process.stdout.write(
6441
6709
  `${JSON.stringify({
@@ -6947,8 +7215,9 @@ function registerPlayCommands(program) {
6947
7215
  "after",
6948
7216
  `
6949
7217
  Concepts:
6950
- Plays are durable Deepline cloud workflows. Local .play.ts files are bundled locally,
6951
- 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.
6952
7221
 
6953
7222
  Common commands:
6954
7223
  deepline plays search email --json
@@ -6979,17 +7248,42 @@ Examples:
6979
7248
  "after",
6980
7249
  `
6981
7250
  Notes:
6982
- Local play files are bundled locally, then validated and executed in Deepline cloud.
6983
- Named plays run the stored live cloud revision.
6984
- Unknown --foo and --foo.bar flags are treated as play input args.
6985
- File-like input args accept local paths; the CLI stages those files before submit.
6986
- Run performs server preflight automatically. Use \`deepline plays check <file>\`
6987
- 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 })
6988
7280
 
6989
7281
  Examples:
6990
7282
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
6991
7283
  deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}' --watch
6992
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>
6993
7287
  `
6994
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(
6995
7289
  "--revision-id <id>",
@@ -7000,7 +7294,7 @@ Examples:
7000
7294
  ).option("--watch", "Stream logs until completion").option(
7001
7295
  "--logs",
7002
7296
  "When output is non-interactive, stream play logs to stderr while waiting"
7003
- ).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) => {
7004
7298
  const passthroughArgs = [...command.args];
7005
7299
  const explicitTarget = options.file || options.name;
7006
7300
  const targetIsInputFlag = typeof target === "string" && target.startsWith("--");
@@ -7022,9 +7316,9 @@ Examples:
7022
7316
  ...options.out ? ["--out", options.out] : [],
7023
7317
  ...options.watch ? ["--watch"] : [],
7024
7318
  ...options.logs ? ["--logs"] : [],
7025
- ...options.pollIntervalMs ? ["--poll-interval-ms", options.pollIntervalMs] : [],
7026
7319
  ...options.tailTimeoutMs ? ["--tail-timeout-ms", options.tailTimeoutMs] : [],
7027
7320
  ...options.force ? ["--force"] : [],
7321
+ ...options.open === false ? ["--no-open"] : [],
7028
7322
  ...options.json ? ["--json"] : [],
7029
7323
  ...passthroughArgs
7030
7324
  ]);
@@ -7131,12 +7425,11 @@ Examples:
7131
7425
  ...options.json ? ["--json"] : []
7132
7426
  ]);
7133
7427
  });
7134
- 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) => {
7135
7429
  process.exitCode = await handleRunTail([
7136
7430
  runId,
7137
7431
  ...options.json ? ["--json"] : [],
7138
- ...options.compact ? ["--compact"] : [],
7139
- ...options.cursor ? ["--cursor", options.cursor] : []
7432
+ ...options.compact ? ["--compact"] : []
7140
7433
  ]);
7141
7434
  });
7142
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) => {
@@ -7165,12 +7458,12 @@ Examples:
7165
7458
  }
7166
7459
 
7167
7460
  // src/cli/commands/tools.ts
7168
- import { chmodSync, mkdtempSync, writeFileSync as writeFileSync6 } from "fs";
7461
+ import { chmodSync, mkdtempSync, writeFileSync as writeFileSync7 } from "fs";
7169
7462
  import { tmpdir as tmpdir3 } from "os";
7170
7463
  import { join as join8 } from "path";
7171
7464
 
7172
7465
  // src/tool-output.ts
7173
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
7466
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
7174
7467
  import { homedir as homedir3 } from "os";
7175
7468
  import { join as join7 } from "path";
7176
7469
  function isPlainObject(value) {
@@ -7248,13 +7541,13 @@ function tryConvertToList(payload, options) {
7248
7541
  }
7249
7542
  function ensureOutputDir() {
7250
7543
  const outputDir = join7(homedir3(), ".local", "share", "deepline", "data");
7251
- mkdirSync3(outputDir, { recursive: true });
7544
+ mkdirSync4(outputDir, { recursive: true });
7252
7545
  return outputDir;
7253
7546
  }
7254
7547
  function writeJsonOutputFile(payload, stem) {
7255
7548
  const outputDir = ensureOutputDir();
7256
7549
  const outputPath = join7(outputDir, `${stem}_${Date.now()}.json`);
7257
- writeFileSync5(outputPath, JSON.stringify(payload, null, 2), "utf-8");
7550
+ writeFileSync6(outputPath, JSON.stringify(payload, null, 2), "utf-8");
7258
7551
  return outputPath;
7259
7552
  }
7260
7553
  function writeCsvOutputFile(rows, stem) {
@@ -7282,7 +7575,7 @@ function writeCsvOutputFile(rows, stem) {
7282
7575
  for (const row of rows) {
7283
7576
  lines.push(columns.map((column) => escapeCell(row[column])).join(","));
7284
7577
  }
7285
- writeFileSync5(outputPath, `${lines.join("\n")}
7578
+ writeFileSync6(outputPath, `${lines.join("\n")}
7286
7579
  `, "utf-8");
7287
7580
  const previewRows = rows.slice(0, 5);
7288
7581
  const previewColumns = columns.slice(0, 5);
@@ -7819,7 +8112,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
7819
8112
  };
7820
8113
  });
7821
8114
  `;
7822
- writeFileSync6(scriptPath, script, { encoding: "utf-8", mode: 384 });
8115
+ writeFileSync7(scriptPath, script, { encoding: "utf-8", mode: 384 });
7823
8116
  return {
7824
8117
  path: scriptPath,
7825
8118
  projectDir,
@@ -7926,10 +8219,10 @@ async function executeTool(args) {
7926
8219
  }
7927
8220
 
7928
8221
  // src/cli/skills-sync.ts
7929
- import { spawn, spawnSync } from "child_process";
7930
- 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";
7931
8224
  import { homedir as homedir4 } from "os";
7932
- import { dirname as dirname7, join as join9 } from "path";
8225
+ import { dirname as dirname8, join as join9 } from "path";
7933
8226
  var CHECK_TIMEOUT_MS2 = 3e3;
7934
8227
  var SDK_SKILL_NAME = "deepline-sdk";
7935
8228
  var SKILL_AGENTS = ["codex", "claude-code", "cursor"];
@@ -7944,7 +8237,7 @@ function sdkSkillsVersionPath(baseUrl) {
7944
8237
  }
7945
8238
  function readLocalSkillsVersion(baseUrl) {
7946
8239
  const path = sdkSkillsVersionPath(baseUrl);
7947
- if (!existsSync5(path)) return "";
8240
+ if (!existsSync6(path)) return "";
7948
8241
  try {
7949
8242
  return readFileSync4(path, "utf-8").trim();
7950
8243
  } catch {
@@ -7953,8 +8246,8 @@ function readLocalSkillsVersion(baseUrl) {
7953
8246
  }
7954
8247
  function writeLocalSkillsVersion(baseUrl, version) {
7955
8248
  const path = sdkSkillsVersionPath(baseUrl);
7956
- mkdirSync4(dirname7(path), { recursive: true });
7957
- writeFileSync7(path, `${version}
8249
+ mkdirSync5(dirname8(path), { recursive: true });
8250
+ writeFileSync8(path, `${version}
7958
8251
  `, "utf-8");
7959
8252
  }
7960
8253
  async function fetchSkillsUpdate(baseUrl, localVersion) {
@@ -8018,7 +8311,7 @@ function buildBunxSkillsInstallArgs(baseUrl) {
8018
8311
  ];
8019
8312
  }
8020
8313
  function hasCommand(command) {
8021
- const result = spawnSync(command, ["--version"], {
8314
+ const result = spawnSync2(command, ["--version"], {
8022
8315
  stdio: "ignore",
8023
8316
  shell: process.platform === "win32"
8024
8317
  });