deepline 0.1.32 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@
4
4
  import { Command as Command2 } from "commander";
5
5
 
6
6
  // src/config.ts
7
- import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
8
  import { homedir } from "os";
9
9
  import { dirname, join, resolve } from "path";
10
10
 
@@ -44,19 +44,12 @@ var ConfigError = class extends DeeplineError {
44
44
  };
45
45
 
46
46
  // src/config.ts
47
+ var HOST_URL_ENV = "DEEPLINE_HOST_URL";
48
+ var API_KEY_ENV = "DEEPLINE_API_KEY";
47
49
  var PROD_URL = "https://code.deepline.com";
48
50
  var DEFAULT_TIMEOUT = 6e4;
49
51
  var DEFAULT_MAX_RETRIES = 3;
50
- var ACTIVE_DEEPLINE_ENV_FILE = ".env.deepline";
51
- function isProdBaseUrl(baseUrl) {
52
- return baseUrl.trim().replace(/\/$/, "") === PROD_URL;
53
- }
54
- function profileNameForBaseUrl(baseUrl) {
55
- return isProdBaseUrl(baseUrl) ? "prod" : "dev";
56
- }
57
- function projectEnvStartDir() {
58
- return process.env.DEEPLINE_PROJECT_ENV_DIR?.trim() || process.cwd();
59
- }
52
+ var PROJECT_DEEPLINE_ENV_FILE = ".env.deepline";
60
53
  function baseUrlSlug(baseUrl) {
61
54
  let url;
62
55
  try {
@@ -65,7 +58,7 @@ function baseUrlSlug(baseUrl) {
65
58
  return "unknown";
66
59
  }
67
60
  const host = url.hostname || "unknown";
68
- const port = url.port ? parseInt(url.port, 10) : null;
61
+ const port = url.port ? Number.parseInt(url.port, 10) : null;
69
62
  let slug = host.replace(/[^a-zA-Z0-9]/g, "-");
70
63
  if (port && port !== 80 && port !== 443) {
71
64
  slug = `${slug}-${port}`;
@@ -92,79 +85,52 @@ function parseEnvFile(filePath) {
92
85
  }
93
86
  return env;
94
87
  }
95
- function findNearestEnvFile(names, startDir = process.cwd()) {
88
+ function findNearestEnvFile(name, startDir = process.cwd()) {
96
89
  let current = resolve(startDir);
97
90
  while (true) {
98
- for (const name of names) {
99
- const filePath = join(current, name);
100
- if (existsSync(filePath)) return filePath;
101
- }
91
+ const filePath = join(current, name);
92
+ if (existsSync(filePath)) return filePath;
102
93
  const parent = dirname(current);
103
94
  if (parent === current) return null;
104
95
  current = parent;
105
96
  }
106
97
  }
107
- function findNearestEnv(names, startDir = process.cwd()) {
108
- const filePath = findNearestEnvFile(names, startDir);
98
+ function loadProjectDeeplineEnv(startDir = process.cwd()) {
99
+ const filePath = findNearestEnvFile(PROJECT_DEEPLINE_ENV_FILE, startDir);
109
100
  return filePath ? parseEnvFile(filePath) : {};
110
101
  }
111
- function findNearestWorktreeEnv(startDir = process.cwd()) {
112
- return findNearestEnv([".env.worktree"], startDir);
113
- }
114
- function resolveProfileEnvFileNames() {
115
- const explicitProfile = process.env.DEEPLINE_ENV_PROFILE?.trim() || process.env.DEEPLINE_PROFILE?.trim() || "";
116
- const names = [];
117
- if (explicitProfile) names.push(`.env.deepline.${explicitProfile}`);
118
- const nodeEnv = process.env.NODE_ENV?.trim();
119
- if (nodeEnv === "production") names.push(".env.deepline.prod");
120
- else if (nodeEnv === "staging") names.push(".env.deepline.staging");
121
- names.push(ACTIVE_DEEPLINE_ENV_FILE);
122
- return names;
123
- }
124
- function resolveProjectAppEnvFileNames() {
125
- const nodeEnv = process.env.NODE_ENV?.trim();
126
- const names = [];
127
- if (nodeEnv === "production") names.push(".env.prod");
128
- if (nodeEnv === "staging") names.push(".env.staging");
129
- names.push(".env.local", ".env");
130
- return names;
131
- }
132
- function resolveBaseUrlFromEnvValues(env) {
133
- return env.DEEPLINE_ORIGIN_URL?.trim() || env.DEEPLINE_API_BASE_URL?.trim() || "";
134
- }
135
- function loadProjectDeeplineEnv() {
136
- return findNearestEnv(resolveProfileEnvFileNames(), projectEnvStartDir());
137
- }
138
- function loadProjectAppEnv() {
139
- return findNearestEnv(resolveProjectAppEnvFileNames(), projectEnvStartDir());
140
- }
141
- function normalizeWorktreeBaseUrl(baseUrl, worktreeEnv = findNearestWorktreeEnv()) {
142
- const trimmed = baseUrl.trim().replace(/\/$/, "");
143
- if (!trimmed) return trimmed;
102
+ function normalizeBaseUrl(baseUrl) {
103
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
104
+ if (!trimmed) return "";
144
105
  try {
145
106
  const parsed = new URL(trimmed);
146
- if (parsed.hostname.endsWith(".localhost") && parsed.port === "1355") {
147
- const port = worktreeEnv.WORKTREE_APP_PORT || worktreeEnv.PORT;
148
- if (port) return `${parsed.protocol}//localhost:${port}`;
107
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
108
+ return "";
149
109
  }
110
+ return parsed.toString().replace(/\/+$/, "");
150
111
  } catch {
112
+ return "";
151
113
  }
152
- return trimmed;
153
114
  }
154
- function resolveWorktreeBaseUrl() {
155
- const worktreeEnv = findNearestWorktreeEnv();
156
- const declared = worktreeEnv.DEEPLINE_API_BASE_URL || worktreeEnv.WORKTREE_PUBLIC_APP_URL || worktreeEnv.APP_URL || "";
157
- if (declared) return normalizeWorktreeBaseUrl(declared, worktreeEnv);
158
- const port = worktreeEnv.WORKTREE_APP_PORT || worktreeEnv.PORT || "";
159
- return port ? `http://localhost:${port}` : "";
115
+ function firstNonEmpty(...values) {
116
+ for (const value of values) {
117
+ const trimmed = value?.trim();
118
+ if (trimmed) return trimmed;
119
+ }
120
+ return "";
160
121
  }
161
- function sdkCliEnvFilePath(baseUrl) {
122
+ function sdkCliConfigDir(baseUrl) {
162
123
  const home = process.env.HOME?.trim() || homedir();
163
- return join(home, ".local", "deepline", baseUrlSlug(baseUrl || PROD_URL), ".env");
124
+ return join(home, ".local", "deepline", baseUrlSlug(baseUrl || PROD_URL));
125
+ }
126
+ function sdkCliEnvFilePath(baseUrl) {
127
+ return join(sdkCliConfigDir(baseUrl), ".env");
164
128
  }
165
129
  function loadCliEnv(baseUrl = PROD_URL) {
166
- const envPath = sdkCliEnvFilePath(baseUrl);
167
- return parseEnvFile(envPath);
130
+ return parseEnvFile(sdkCliEnvFilePath(baseUrl));
131
+ }
132
+ function hostConfigDirPath(baseUrl) {
133
+ return sdkCliConfigDir(baseUrl);
168
134
  }
169
135
  function hostEnvFilePath(baseUrl) {
170
136
  return sdkCliEnvFilePath(baseUrl);
@@ -175,9 +141,10 @@ function saveHostEnvValues(baseUrl, values) {
175
141
  if (!existsSync(dir)) {
176
142
  mkdirSync(dir, { recursive: true });
177
143
  }
178
- const existing = existsSync(filePath) ? parseEnvFile(filePath) : {};
144
+ const existing = parseEnvFile(filePath);
179
145
  const merged = { ...existing, ...values };
180
- const lines = Object.entries(merged).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${value}`);
146
+ const allowedKeys = /* @__PURE__ */ new Set([HOST_URL_ENV, API_KEY_ENV]);
147
+ const lines = Object.entries(merged).filter(([key, value]) => allowedKeys.has(key) && value !== "").map(([key, value]) => `${key}=${value}`);
181
148
  writeFileSync(filePath, `${lines.join("\n")}
182
149
  `, "utf-8");
183
150
  }
@@ -185,31 +152,36 @@ function loadGlobalCliEnv() {
185
152
  return loadCliEnv(PROD_URL);
186
153
  }
187
154
  function autoDetectBaseUrl() {
188
- const envOrigin = process.env.DEEPLINE_ORIGIN_URL?.trim();
189
- if (envOrigin) return normalizeWorktreeBaseUrl(envOrigin);
190
- const envBase = process.env.DEEPLINE_API_BASE_URL?.trim();
191
- if (envBase) return normalizeWorktreeBaseUrl(envBase);
192
- const projectDeeplineBaseUrl = resolveBaseUrlFromEnvValues(loadProjectDeeplineEnv());
193
- if (projectDeeplineBaseUrl) return normalizeWorktreeBaseUrl(projectDeeplineBaseUrl);
194
- const projectAppBaseUrl = resolveBaseUrlFromEnvValues(loadProjectAppEnv());
195
- if (projectAppBaseUrl) return normalizeWorktreeBaseUrl(projectAppBaseUrl);
196
- const worktreeBaseUrl = resolveWorktreeBaseUrl();
197
- if (worktreeBaseUrl) return worktreeBaseUrl;
155
+ const projectEnv = loadProjectDeeplineEnv();
198
156
  const globalEnv = loadGlobalCliEnv();
199
- const globalOrigin = globalEnv.DEEPLINE_ORIGIN_URL?.trim();
200
- if (globalOrigin) return normalizeWorktreeBaseUrl(globalOrigin);
201
- return PROD_URL;
157
+ return normalizeBaseUrl(process.env[HOST_URL_ENV] ?? "") || normalizeBaseUrl(projectEnv[HOST_URL_ENV] ?? "") || normalizeBaseUrl(globalEnv[HOST_URL_ENV] ?? "") || PROD_URL;
158
+ }
159
+ function resolveApiKeyForBaseUrl(baseUrl, explicitApiKey) {
160
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
161
+ const projectEnv = loadProjectDeeplineEnv();
162
+ const cliEnv = loadCliEnv(normalizedBaseUrl || baseUrl);
163
+ const projectBaseUrl = normalizeBaseUrl(projectEnv[HOST_URL_ENV] ?? "");
164
+ const projectKeyApplies = projectBaseUrl === normalizedBaseUrl;
165
+ return firstNonEmpty(
166
+ explicitApiKey,
167
+ process.env[API_KEY_ENV],
168
+ projectKeyApplies ? projectEnv[API_KEY_ENV] : "",
169
+ cliEnv[API_KEY_ENV]
170
+ );
202
171
  }
203
172
  function resolveConfig(options) {
204
- const requestedBaseUrl = options?.baseUrl?.trim() || autoDetectBaseUrl();
205
- const baseUrl = normalizeWorktreeBaseUrl(requestedBaseUrl);
206
- const cliEnv = loadCliEnv(baseUrl);
207
- const projectDeeplineEnv = loadProjectDeeplineEnv();
208
- const projectAppEnv = loadProjectAppEnv();
209
- const apiKey = options?.apiKey?.trim() || process.env.DEEPLINE_API_KEY?.trim() || projectDeeplineEnv.DEEPLINE_API_KEY || projectAppEnv.DEEPLINE_API_KEY || cliEnv.DEEPLINE_API_KEY || "";
173
+ const baseUrl = normalizeBaseUrl(
174
+ options?.baseUrl?.trim() || autoDetectBaseUrl()
175
+ );
176
+ if (!baseUrl) {
177
+ throw new ConfigError(
178
+ `Invalid ${HOST_URL_ENV}. Expected an http(s) URL such as https://code.deepline.com.`
179
+ );
180
+ }
181
+ const apiKey = resolveApiKeyForBaseUrl(baseUrl, options?.apiKey);
210
182
  if (!apiKey) {
211
183
  throw new ConfigError(
212
- `No API key found. Set DEEPLINE_API_KEY env var, pass apiKey option, or run: deepline auth register`
184
+ `No API key found. Set ${API_KEY_ENV}, add it to .env.deepline, or run: deepline auth register`
213
185
  );
214
186
  }
215
187
  return {
@@ -219,32 +191,10 @@ function resolveConfig(options) {
219
191
  maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES
220
192
  };
221
193
  }
222
- function mergeEnvFile(filePath, values) {
223
- const existing = existsSync(filePath) ? parseEnvFile(filePath) : {};
224
- const merged = { ...existing, ...values };
225
- const dir = dirname(filePath);
226
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
227
- const lines = Object.entries(merged).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${value}`);
228
- writeFileSync(filePath, `${lines.join("\n")}
229
- `, "utf-8");
230
- }
231
- function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStartDir()) {
232
- const root = resolve(startDir);
233
- const profile = profileNameForBaseUrl(baseUrl);
234
- const files = [
235
- join(root, ACTIVE_DEEPLINE_ENV_FILE),
236
- join(root, `.env.deepline.${profile}`)
237
- ];
238
- if (profile === "dev") files.push(join(root, ".env"));
239
- for (const filePath of files) {
240
- mergeEnvFile(filePath, values);
241
- }
242
- return files;
243
- }
244
194
 
245
195
  // src/version.ts
246
- var SDK_VERSION = "0.1.32";
247
- var SDK_API_CONTRACT = "2026-05-generic-play-input-flags";
196
+ var SDK_VERSION = "0.1.33";
197
+ var SDK_API_CONTRACT = "2026-05-host-env-generic-play-input-flags";
248
198
 
249
199
  // ../shared_libs/play-runtime/coordinator-headers.ts
250
200
  var COORDINATOR_INTERNAL_TOKEN_HEADER = "x-deepline-internal-token";
@@ -1654,7 +1604,7 @@ async function enforceSdkCompatibility(baseUrl) {
1654
1604
  }
1655
1605
 
1656
1606
  // src/cli/commands/auth.ts
1657
- import { writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
1607
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
1658
1608
  import { hostname } from "os";
1659
1609
  import { dirname as dirname3 } from "path";
1660
1610
 
@@ -1668,10 +1618,11 @@ import {
1668
1618
  import { mkdir, writeFile } from "fs/promises";
1669
1619
  import { homedir as homedir2 } from "os";
1670
1620
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
1671
- import { execFileSync, spawnSync } from "child_process";
1621
+ import * as childProcess from "child_process";
1672
1622
  import { parse } from "csv-parse/sync";
1673
1623
  import { stringify } from "csv-stringify/sync";
1674
1624
  var BROWSER_FOCUS_COOLDOWN_MS = 3e4;
1625
+ var defaultBrowserCommandRunner = childProcess;
1675
1626
  function getAuthedHttpClient() {
1676
1627
  const config = resolveConfig();
1677
1628
  return { config, http: new HttpClient(config) };
@@ -1740,9 +1691,9 @@ function browserAppNameFromBundleId(bundleId) {
1740
1691
  };
1741
1692
  return names[bundleId.toLowerCase()] ?? "";
1742
1693
  }
1743
- function readDefaultMacBrowserBundleId() {
1694
+ function readDefaultMacBrowserBundleId(runner = defaultBrowserCommandRunner) {
1744
1695
  try {
1745
- const output = execFileSync(
1696
+ const output = runner.execFileSync(
1746
1697
  "/usr/bin/defaults",
1747
1698
  [
1748
1699
  "read",
@@ -1778,8 +1729,8 @@ function browserStrategyForBundleId(bundleId) {
1778
1729
  }
1779
1730
  return normalized === "com.apple.safari" ? "safari" : "fallback";
1780
1731
  }
1781
- function runAppleScript(script, args) {
1782
- const result = spawnSync("osascript", ["-", ...args], {
1732
+ function runAppleScript(script, args, runner = defaultBrowserCommandRunner) {
1733
+ const result = runner.spawnSync("osascript", ["-", ...args], {
1783
1734
  input: script,
1784
1735
  encoding: "utf-8",
1785
1736
  stdio: ["pipe", "ignore", "ignore"],
@@ -1787,7 +1738,7 @@ function runAppleScript(script, args) {
1787
1738
  });
1788
1739
  return result.status === 0;
1789
1740
  }
1790
- function retargetChromiumMacos(appName, targetUrl, allowFocus) {
1741
+ function retargetChromiumMacos(appName, targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
1791
1742
  const host = extractUrlHost(targetUrl);
1792
1743
  if (!host) return false;
1793
1744
  const escapedAppName = appName.replace(/"/g, '\\"');
@@ -1819,9 +1770,9 @@ ${newTabBlock} end if
1819
1770
  end tell
1820
1771
  end run
1821
1772
  `;
1822
- return runAppleScript(script, [targetUrl, host]);
1773
+ return runAppleScript(script, [targetUrl, host], runner);
1823
1774
  }
1824
- function retargetSafariMacos(appName, targetUrl, allowFocus) {
1775
+ function retargetSafariMacos(appName, targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
1825
1776
  const host = extractUrlHost(targetUrl);
1826
1777
  if (!host) return false;
1827
1778
  const escapedAppName = appName.replace(/"/g, '\\"');
@@ -1853,30 +1804,38 @@ ${newTabBlock} end if
1853
1804
  end tell
1854
1805
  end run
1855
1806
  `;
1856
- return runAppleScript(script, [targetUrl, host]);
1807
+ return runAppleScript(script, [targetUrl, host], runner);
1857
1808
  }
1858
- function openUrlMacos(targetUrl, allowFocus) {
1859
- const defaultBundleId = readDefaultMacBrowserBundleId();
1809
+ function openUrlMacos(targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
1810
+ const defaultBundleId = readDefaultMacBrowserBundleId(runner);
1860
1811
  const appName = defaultBundleId ? browserAppNameFromBundleId(defaultBundleId) : "";
1861
1812
  const strategy = browserStrategyForBundleId(defaultBundleId);
1862
- if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus)) {
1813
+ if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus, runner)) {
1863
1814
  return true;
1864
1815
  }
1865
- if (appName && strategy === "safari" && retargetSafariMacos(appName, targetUrl, allowFocus)) {
1816
+ if (appName && strategy === "safari" && retargetSafariMacos(appName, targetUrl, allowFocus, runner)) {
1866
1817
  return true;
1867
1818
  }
1868
- if (!allowFocus) {
1869
- return false;
1870
- }
1871
1819
  try {
1872
- execFileSync("open", [targetUrl], { stdio: "ignore" });
1820
+ runner.execFileSync(
1821
+ "open",
1822
+ [...allowFocus ? [] : ["-g"], targetUrl],
1823
+ { stdio: "ignore" }
1824
+ );
1873
1825
  return true;
1874
1826
  } catch {
1875
1827
  return false;
1876
1828
  }
1877
1829
  }
1830
+ function browserOpeningDisabled() {
1831
+ const value = String(
1832
+ process.env.DEEPLINE_NO_BROWSER ?? process.env.PLAYGROUND_HEADLESS ?? ""
1833
+ ).trim().toLowerCase();
1834
+ return value === "1" || value === "true" || value === "yes" || value === "on";
1835
+ }
1878
1836
  function openInBrowser(url) {
1879
1837
  try {
1838
+ if (browserOpeningDisabled()) return;
1880
1839
  const targetUrl = String(url || "").trim();
1881
1840
  if (!targetUrl) return;
1882
1841
  const allowFocus = claimBrowserFocus();
@@ -1886,12 +1845,12 @@ function openInBrowser(url) {
1886
1845
  }
1887
1846
  if (!allowFocus) return;
1888
1847
  if (process.platform === "win32") {
1889
- execFileSync("cmd.exe", ["/c", "start", "", targetUrl], {
1848
+ childProcess.execFileSync("cmd.exe", ["/c", "start", "", targetUrl], {
1890
1849
  stdio: "ignore"
1891
1850
  });
1892
1851
  return;
1893
1852
  }
1894
- execFileSync("xdg-open", [targetUrl], { stdio: "ignore" });
1853
+ childProcess.execFileSync("xdg-open", [targetUrl], { stdio: "ignore" });
1895
1854
  } catch {
1896
1855
  }
1897
1856
  }
@@ -2005,17 +1964,39 @@ var EXIT_SERVER = 2;
2005
1964
  function envFilePath(baseUrl) {
2006
1965
  return hostEnvFilePath(baseUrl);
2007
1966
  }
2008
- function saveEnvValues(values, baseUrl) {
2009
- const filePath = envFilePath(baseUrl);
1967
+ function pendingClaimTokenPath(baseUrl) {
1968
+ return `${hostConfigDirPath(baseUrl)}/pending-claim-token`;
1969
+ }
1970
+ function savePendingClaimToken(baseUrl, claimToken) {
1971
+ const filePath = pendingClaimTokenPath(baseUrl);
2010
1972
  const dir = dirname3(filePath);
2011
1973
  if (!existsSync3(dir)) {
2012
1974
  mkdirSync3(dir, { recursive: true });
2013
1975
  }
2014
- const existing = existsSync3(filePath) ? parseEnvFile(filePath) : {};
2015
- const merged = { ...existing, ...values };
2016
- const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
2017
- writeFileSync3(filePath, lines.join("\n") + "\n", "utf-8");
2018
- saveProjectDeeplineEnvValues(baseUrl, values);
1976
+ writeFileSync3(filePath, `${claimToken}
1977
+ `, "utf-8");
1978
+ }
1979
+ function readPendingClaimToken(baseUrl) {
1980
+ const filePath = pendingClaimTokenPath(baseUrl);
1981
+ if (!existsSync3(filePath)) return "";
1982
+ try {
1983
+ return readFileSync3(filePath, "utf-8").trim();
1984
+ } catch {
1985
+ return "";
1986
+ }
1987
+ }
1988
+ function clearPendingClaimToken(baseUrl) {
1989
+ try {
1990
+ rmSync(pendingClaimTokenPath(baseUrl), { force: true });
1991
+ } catch {
1992
+ }
1993
+ }
1994
+ function saveEnvValues(values, baseUrl) {
1995
+ const filtered = {
1996
+ ...values[HOST_URL_ENV] ? { [HOST_URL_ENV]: values[HOST_URL_ENV] } : {},
1997
+ ...values[API_KEY_ENV] ? { [API_KEY_ENV]: values[API_KEY_ENV] } : {}
1998
+ };
1999
+ saveHostEnvValues(baseUrl, filtered);
2019
2000
  }
2020
2001
  async function httpJson(method, url, apiKey, body) {
2021
2002
  const headers = { "Content-Type": "application/json" };
@@ -2121,9 +2102,9 @@ async function handleRegister(args) {
2121
2102
  const claimUrl = String(data.claim_url || "");
2122
2103
  const claimToken = String(data.claim_token || "");
2123
2104
  if (claimToken) {
2105
+ savePendingClaimToken(baseUrl, claimToken);
2124
2106
  saveEnvValues({
2125
- DEEPLINE_ORIGIN_URL: baseUrl,
2126
- DEEPLINE_CLAIM_TOKEN: claimToken
2107
+ [HOST_URL_ENV]: baseUrl
2127
2108
  }, baseUrl);
2128
2109
  }
2129
2110
  if (claimUrl) {
@@ -2147,6 +2128,7 @@ async function handleRegister(args) {
2147
2128
  { claim_token: claimToken, reveal: true }
2148
2129
  );
2149
2130
  if (s === 401 || s === 403) {
2131
+ clearPendingClaimToken(baseUrl);
2150
2132
  console.log("Status: unauthorized");
2151
2133
  return EXIT_AUTH;
2152
2134
  }
@@ -2163,15 +2145,16 @@ async function handleRegister(args) {
2163
2145
  const apiKey = String(statusData.api_key || "");
2164
2146
  if (apiKey) {
2165
2147
  saveEnvValues({
2166
- DEEPLINE_ORIGIN_URL: baseUrl,
2167
- DEEPLINE_API_KEY: apiKey,
2168
- DEEPLINE_CLAIM_TOKEN: ""
2148
+ [HOST_URL_ENV]: baseUrl,
2149
+ [API_KEY_ENV]: apiKey
2169
2150
  }, baseUrl);
2151
+ clearPendingClaimToken(baseUrl);
2170
2152
  printClaimSuccessBanner(statusData);
2171
2153
  return EXIT_OK;
2172
2154
  }
2173
2155
  }
2174
2156
  if (state === "expired") {
2157
+ clearPendingClaimToken(baseUrl);
2175
2158
  console.log("That approval link expired. Please run: deepline auth register");
2176
2159
  return EXIT_AUTH;
2177
2160
  }
@@ -2189,13 +2172,12 @@ async function handleWait(args) {
2189
2172
  }
2190
2173
  }
2191
2174
  }
2192
- const env = loadCliEnv(baseUrl);
2193
- if (env.DEEPLINE_API_KEY?.trim()) {
2194
- console.log("Already connected.");
2195
- return EXIT_OK;
2196
- }
2197
- const claimToken = env.DEEPLINE_CLAIM_TOKEN?.trim() || "";
2175
+ const claimToken = readPendingClaimToken(baseUrl);
2198
2176
  if (!claimToken) {
2177
+ if (resolveApiKeyForBaseUrl(baseUrl)) {
2178
+ console.log("Already connected.");
2179
+ return EXIT_OK;
2180
+ }
2199
2181
  console.error("No pending approval. Run: deepline auth register --no-wait");
2200
2182
  return EXIT_AUTH;
2201
2183
  }
@@ -2208,6 +2190,7 @@ async function handleWait(args) {
2208
2190
  { claim_token: claimToken, reveal: true }
2209
2191
  );
2210
2192
  if (status === 401 || status === 403) {
2193
+ clearPendingClaimToken(baseUrl);
2211
2194
  console.error("Claim is invalid. Run: deepline auth register");
2212
2195
  return EXIT_AUTH;
2213
2196
  }
@@ -2224,15 +2207,16 @@ async function handleWait(args) {
2224
2207
  const apiKey = String(data.api_key || "");
2225
2208
  if (apiKey) {
2226
2209
  saveEnvValues({
2227
- DEEPLINE_ORIGIN_URL: baseUrl,
2228
- DEEPLINE_API_KEY: apiKey,
2229
- DEEPLINE_CLAIM_TOKEN: ""
2210
+ [HOST_URL_ENV]: baseUrl,
2211
+ [API_KEY_ENV]: apiKey
2230
2212
  }, baseUrl);
2213
+ clearPendingClaimToken(baseUrl);
2231
2214
  printClaimSuccessBanner(data);
2232
2215
  return EXIT_OK;
2233
2216
  }
2234
2217
  }
2235
2218
  if (state === "expired") {
2219
+ clearPendingClaimToken(baseUrl);
2236
2220
  console.error("That approval link expired. Run: deepline auth register");
2237
2221
  return EXIT_AUTH;
2238
2222
  }
@@ -2267,10 +2251,9 @@ async function handleStatus(args) {
2267
2251
  };
2268
2252
  hostLines.push(`Host: ${baseUrl} (unreachable)`);
2269
2253
  }
2270
- const env = loadCliEnv(baseUrl);
2271
- const apiKey = process.env.DEEPLINE_API_KEY?.trim() || env.DEEPLINE_API_KEY || "";
2254
+ const apiKey = resolveApiKeyForBaseUrl(baseUrl);
2272
2255
  if (!apiKey) {
2273
- if (env.DEEPLINE_CLAIM_TOKEN?.trim()) {
2256
+ if (readPendingClaimToken(baseUrl)) {
2274
2257
  printCommandEnvelope({
2275
2258
  ...hostStatusPayload ?? { host: baseUrl },
2276
2259
  status: "pending",
@@ -2316,6 +2299,7 @@ async function handleStatus(args) {
2316
2299
  console.error(`Auth status error (status ${status}).`);
2317
2300
  return EXIT_SERVER;
2318
2301
  }
2302
+ clearPendingClaimToken(baseUrl);
2319
2303
  const payload = {
2320
2304
  ...hostStatusPayload ?? { host: baseUrl },
2321
2305
  status: data.status || "(unknown)",
@@ -2336,9 +2320,8 @@ async function handleStatus(args) {
2336
2320
  const apiKeyResp = String(data.api_key || apiKey);
2337
2321
  if (apiKeyResp) {
2338
2322
  saveEnvValues({
2339
- DEEPLINE_ORIGIN_URL: baseUrl,
2340
- DEEPLINE_API_KEY: apiKeyResp,
2341
- DEEPLINE_CLAIM_TOKEN: ""
2323
+ [HOST_URL_ENV]: baseUrl,
2324
+ [API_KEY_ENV]: apiKeyResp
2342
2325
  }, baseUrl);
2343
2326
  savedApiKeyPath = envFilePath(baseUrl);
2344
2327
  }
@@ -3675,7 +3658,7 @@ Examples:
3675
3658
  import { createHash as createHash3 } from "crypto";
3676
3659
  import {
3677
3660
  existsSync as existsSync6,
3678
- readFileSync as readFileSync4,
3661
+ readFileSync as readFileSync5,
3679
3662
  readdirSync,
3680
3663
  realpathSync,
3681
3664
  writeFileSync as writeFileSync5
@@ -3690,7 +3673,7 @@ import { existsSync as existsSync5 } from "fs";
3690
3673
 
3691
3674
  // ../shared_libs/plays/bundling/index.ts
3692
3675
  import { createHash } from "crypto";
3693
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
3676
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
3694
3677
  import { mkdir as mkdir3, readFile, realpath, stat, writeFile as writeFile3 } from "fs/promises";
3695
3678
  import { tmpdir } from "os";
3696
3679
  import { basename, dirname as dirname5, extname, isAbsolute, join as join3, resolve as resolve5 } from "path";
@@ -3944,7 +3927,7 @@ function extractDefinedPlayName(sourceCode) {
3944
3927
  }
3945
3928
  function readPackageVersionFromPackageJson(packageJsonPath, packageName) {
3946
3929
  try {
3947
- const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
3930
+ const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
3948
3931
  if (packageJson.name === packageName && typeof packageJson.version === "string") {
3949
3932
  return packageJson.version;
3950
3933
  }
@@ -4239,7 +4222,7 @@ function resolvePackageImport(specifier, fromFile, adapter) {
4239
4222
  const packageName = getPackageName(specifier);
4240
4223
  if (packageName === "deepline" && existsSync4(adapter.sdkPackageJson)) {
4241
4224
  const packageJson = JSON.parse(
4242
- readFileSync3(adapter.sdkPackageJson, "utf-8")
4225
+ readFileSync4(adapter.sdkPackageJson, "utf-8")
4243
4226
  );
4244
4227
  return {
4245
4228
  name: "deepline",
@@ -5370,7 +5353,7 @@ function materializeRemotePlaySource(input) {
5370
5353
  }
5371
5354
  const outputPath = input.outPath ?? defaultMaterializedPlayPath(input.playName);
5372
5355
  if (existsSync6(outputPath)) {
5373
- const existingSource = readFileSync4(outputPath, "utf-8");
5356
+ const existingSource = readFileSync5(outputPath, "utf-8");
5374
5357
  if (existingSource === input.sourceCode) {
5375
5358
  return { path: outputPath, status: "unchanged", created: false };
5376
5359
  }
@@ -5441,7 +5424,7 @@ function parsePositiveInteger2(value, flagName) {
5441
5424
  return parsed;
5442
5425
  }
5443
5426
  function parseJsonInput(raw) {
5444
- const source = raw.startsWith("@") ? readFileSync4(resolve8(raw.slice(1)), "utf-8") : raw;
5427
+ const source = raw.startsWith("@") ? readFileSync5(resolve8(raw.slice(1)), "utf-8") : raw;
5445
5428
  const parsed = JSON.parse(source);
5446
5429
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
5447
5430
  throw new Error("--input must be a JSON object.");
@@ -5593,7 +5576,7 @@ async function stageFileInputArgs(input) {
5593
5576
  };
5594
5577
  }
5595
5578
  function stageFile(logicalPath, absolutePath) {
5596
- const buffer = readFileSync4(absolutePath);
5579
+ const buffer = readFileSync5(absolutePath);
5597
5580
  return {
5598
5581
  logicalPath,
5599
5582
  contentBase64: buffer.toString("base64"),
@@ -5830,10 +5813,6 @@ function openPlayDashboard(input) {
5830
5813
  }
5831
5814
  openInBrowser(input.dashboardUrl);
5832
5815
  }
5833
- function getDashboardUrlFromLiveEvent(event) {
5834
- const dashboardUrl = getEventPayload(event).dashboardUrl;
5835
- return typeof dashboardUrl === "string" && dashboardUrl.trim() ? dashboardUrl.trim() : null;
5836
- }
5837
5816
  function printPlayLogLines(input) {
5838
5817
  for (const line of input.lines) {
5839
5818
  if (input.emitLogs) {
@@ -5902,7 +5881,7 @@ async function waitForPlayCompletionByStream(input) {
5902
5881
  billing: false
5903
5882
  });
5904
5883
  if (TERMINAL_PLAY_STATUSES2.has(finalStatus.status)) {
5905
- return finalStatus;
5884
+ return input.dashboardUrl ? { ...finalStatus, dashboardUrl: input.dashboardUrl } : finalStatus;
5906
5885
  }
5907
5886
  }
5908
5887
  }
@@ -5930,6 +5909,10 @@ async function waitForPlayCompletionByStream(input) {
5930
5909
  }
5931
5910
  async function startAndWaitForPlayCompletionByStream(input) {
5932
5911
  const startedAt = Date.now();
5912
+ const dashboardUrl = buildPlayDashboardUrl(
5913
+ input.client.baseUrl,
5914
+ input.playName
5915
+ );
5933
5916
  const state = {
5934
5917
  lastLogIndex: 0,
5935
5918
  emittedRunnerStarted: false
@@ -5960,7 +5943,6 @@ async function startAndWaitForPlayCompletionByStream(input) {
5960
5943
  }
5961
5944
  const workflowId = lastKnownWorkflowId || "pending";
5962
5945
  if (workflowId !== "pending" && !emittedDashboardUrl) {
5963
- const dashboardUrl = getDashboardUrlFromLiveEvent(event) ?? buildPlayDashboardUrl(input.client.baseUrl, input.playName);
5964
5946
  openPlayDashboard({
5965
5947
  dashboardUrl,
5966
5948
  jsonOutput: input.jsonOutput,
@@ -6009,7 +5991,7 @@ async function startAndWaitForPlayCompletionByStream(input) {
6009
5991
  firstRunIdMs,
6010
5992
  lastPhase
6011
5993
  });
6012
- return finalStatus;
5994
+ return { ...finalStatus, dashboardUrl };
6013
5995
  }
6014
5996
  }
6015
5997
  } catch (error) {
@@ -6046,6 +6028,7 @@ async function startAndWaitForPlayCompletionByStream(input) {
6046
6028
  return waitForPlayCompletionByStream({
6047
6029
  client: input.client,
6048
6030
  workflowId: lastKnownWorkflowId,
6031
+ dashboardUrl,
6049
6032
  jsonOutput: input.jsonOutput,
6050
6033
  emitLogs: input.emitLogs,
6051
6034
  waitTimeoutMs: input.waitTimeoutMs,
@@ -6080,6 +6063,7 @@ async function startAndWaitForPlayCompletionByStream(input) {
6080
6063
  return waitForPlayCompletionByStream({
6081
6064
  client: input.client,
6082
6065
  workflowId: lastKnownWorkflowId,
6066
+ dashboardUrl,
6083
6067
  jsonOutput: input.jsonOutput,
6084
6068
  emitLogs: input.emitLogs,
6085
6069
  waitTimeoutMs: input.waitTimeoutMs,
@@ -6250,12 +6234,16 @@ function buildRunWarnings(status, rowsInfo) {
6250
6234
  }
6251
6235
  return [];
6252
6236
  }
6253
- function buildRunNextCommands(runId) {
6254
- return {
6237
+ function buildRunNextCommands(runId, dashboardUrl) {
6238
+ const commands = {
6255
6239
  get: `deepline runs get ${runId} --json`,
6256
6240
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
6257
6241
  logs: `deepline runs logs ${runId} --out run.log --json`
6258
6242
  };
6243
+ if (dashboardUrl) {
6244
+ commands.open = `Open ${dashboardUrl} to see results.`;
6245
+ }
6246
+ return commands;
6259
6247
  }
6260
6248
  var RUN_LOG_PREVIEW_LIMIT = 20;
6261
6249
  function getRecordField(value, key) {
@@ -6391,6 +6379,7 @@ function compactPlayStatus(status) {
6391
6379
  apiVersion: status.apiVersion ?? 1,
6392
6380
  ...typeof status.name === "string" ? { name: status.name } : {},
6393
6381
  ...typeof status.playName === "string" ? { playName: status.playName } : {},
6382
+ ...status.dashboardUrl ? { dashboardUrl: status.dashboardUrl } : {},
6394
6383
  status: status.status,
6395
6384
  run: normalizeRunStatusForEnvelope(status),
6396
6385
  progress: normalizeProgressForEnvelope(status, rowsInfo),
@@ -6403,7 +6392,7 @@ function compactPlayStatus(status) {
6403
6392
  ...status.resultView ? { resultView: status.resultView } : {},
6404
6393
  ...datasetStats ? { dataset_stats: datasetStats } : {},
6405
6394
  ...billing ? { billing } : {},
6406
- next: buildRunNextCommands(status.runId)
6395
+ next: buildRunNextCommands(status.runId, status.dashboardUrl)
6407
6396
  };
6408
6397
  }
6409
6398
  function enrichPlayStatusWithDatasetStats(status) {
@@ -6616,7 +6605,7 @@ async function exportPlayStatusRows(client, status, outPath, options = {}) {
6616
6605
  return null;
6617
6606
  }
6618
6607
  const availableRows = collectSerializedDatasetRowsInfos(status);
6619
- let rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
6608
+ const rowsInfo = options.datasetPath ? availableRows.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
6620
6609
  if (!rowsInfo && options.datasetPath) {
6621
6610
  const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
6622
6611
  throw new DeeplineError(
@@ -6930,7 +6919,7 @@ async function handlePlayCheck(args) {
6930
6919
  return 1;
6931
6920
  }
6932
6921
  const absolutePlayPath = resolve8(options.target);
6933
- const sourceCode = readFileSync4(absolutePlayPath, "utf-8");
6922
+ const sourceCode = readFileSync5(absolutePlayPath, "utf-8");
6934
6923
  let graph;
6935
6924
  try {
6936
6925
  graph = await collectBundledPlayGraph(absolutePlayPath);
@@ -6998,7 +6987,7 @@ async function handleFileBackedRun(options) {
6998
6987
  const sourceCode = traceCliSync(
6999
6988
  "cli.play_file_read_source",
7000
6989
  { targetKind: "file" },
7001
- () => readFileSync4(absolutePlayPath, "utf-8")
6990
+ () => readFileSync5(absolutePlayPath, "utf-8")
7002
6991
  );
7003
6992
  const runtimeInput = options.input ? { ...options.input } : {};
7004
6993
  let graph;
@@ -7099,8 +7088,7 @@ async function handleFileBackedRun(options) {
7099
7088
  { targetKind: "file", playName },
7100
7089
  () => client.startPlayRun(startRequest)
7101
7090
  );
7102
- const dashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7103
- const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
7091
+ const resolvedDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7104
7092
  openPlayDashboard({
7105
7093
  dashboardUrl: resolvedDashboardUrl,
7106
7094
  jsonOutput: options.jsonOutput,
@@ -7235,11 +7223,7 @@ async function handleNamedRun(options) {
7235
7223
  { targetKind: "name", playName },
7236
7224
  () => client.startPlayRun(startRequest)
7237
7225
  );
7238
- const dashboardUrl = buildPlayDashboardUrl(
7239
- client.baseUrl,
7240
- playName
7241
- );
7242
- const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
7226
+ const resolvedDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
7243
7227
  openPlayDashboard({
7244
7228
  dashboardUrl: resolvedDashboardUrl,
7245
7229
  jsonOutput: options.jsonOutput,
@@ -7530,7 +7514,7 @@ async function handlePlayGet(args) {
7530
7514
  outPath = resolve8(args[++index]);
7531
7515
  }
7532
7516
  }
7533
- const playName = isFileTarget(target) ? extractPlayName(readFileSync4(resolve8(target), "utf-8"), resolve8(target)) : parseReferencedPlayTarget(target).playName;
7517
+ const playName = isFileTarget(target) ? extractPlayName(readFileSync5(resolve8(target), "utf-8"), resolve8(target)) : parseReferencedPlayTarget(target).playName;
7534
7518
  const detail = isFileTarget(target) ? await client.getPlay(playName) : await assertCanonicalNamedPlayReference(client, target);
7535
7519
  const resolvedSource = detail.play.workingRevision?.sourceCode ?? detail.play.liveRevision?.sourceCode ?? detail.play.currentRevision?.sourceCode ?? detail.play.sourceCode ?? "";
7536
7520
  const materializedFile = outPath ? materializeRemotePlaySource({
@@ -9363,8 +9347,8 @@ Examples:
9363
9347
  }
9364
9348
 
9365
9349
  // src/cli/skills-sync.ts
9366
- import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
9367
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync8 } from "fs";
9350
+ import { spawn as spawn2, spawnSync } from "child_process";
9351
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync8 } from "fs";
9368
9352
  import { homedir as homedir4 } from "os";
9369
9353
  import { dirname as dirname10, join as join10 } from "path";
9370
9354
  var CHECK_TIMEOUT_MS2 = 3e3;
@@ -9383,7 +9367,7 @@ function readLocalSkillsVersion(baseUrl) {
9383
9367
  const path = sdkSkillsVersionPath(baseUrl);
9384
9368
  if (!existsSync8(path)) return "";
9385
9369
  try {
9386
- return readFileSync5(path, "utf-8").trim();
9370
+ return readFileSync6(path, "utf-8").trim();
9387
9371
  } catch {
9388
9372
  return "";
9389
9373
  }
@@ -9455,7 +9439,7 @@ function buildBunxSkillsInstallArgs(baseUrl) {
9455
9439
  ];
9456
9440
  }
9457
9441
  function hasCommand(command) {
9458
- const result = spawnSync2(command, ["--version"], {
9442
+ const result = spawnSync(command, ["--version"], {
9459
9443
  stdio: "ignore",
9460
9444
  shell: process.platform === "win32"
9461
9445
  });