deepline 0.1.259 → 0.1.261

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.
@@ -159,7 +159,7 @@ configureProxyFromEnv();
159
159
 
160
160
  // src/cli/index.ts
161
161
  import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile5 } from "fs/promises";
162
- import { join as join15 } from "path";
162
+ import { join as join18 } from "path";
163
163
  import { tmpdir as tmpdir4 } from "os";
164
164
  import { Command as Command4 } from "commander";
165
165
 
@@ -174,7 +174,7 @@ import {
174
174
  writeFileSync
175
175
  } from "fs";
176
176
  import { homedir } from "os";
177
- import { dirname, join, resolve } from "path";
177
+ import { dirname, isAbsolute, join, resolve } from "path";
178
178
 
179
179
  // src/errors.ts
180
180
  var DeeplineError = class extends Error {
@@ -484,6 +484,21 @@ function resolveApiKeyForBaseUrl(baseUrl, explicitApiKey) {
484
484
  cliEnv[API_KEY_ENV]
485
485
  );
486
486
  }
487
+ function resolveProjectApiKeyForBaseUrl(baseUrl, startDir = process.cwd()) {
488
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
489
+ return firstNonEmpty(
490
+ ...loadProjectEnvCandidates(startDir).map(({ env }) => {
491
+ const projectBaseUrl = normalizeBaseUrl(env[HOST_URL_ENV] ?? "");
492
+ return projectBaseUrl === normalizedBaseUrl ? env[API_KEY_ENV] : "";
493
+ })
494
+ );
495
+ }
496
+ function resolveGlobalApiKeyForBaseUrl(baseUrl) {
497
+ return firstNonEmpty(
498
+ process.env[API_KEY_ENV],
499
+ loadCliEnv(normalizeBaseUrl(baseUrl) || baseUrl)[API_KEY_ENV]
500
+ );
501
+ }
487
502
  function getResolvedProjectAuthSource(baseUrl, apiKey, startDir = process.cwd()) {
488
503
  const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
489
504
  const normalizedApiKey = apiKey.trim();
@@ -527,14 +542,79 @@ function mergeProjectEnvFile(filePath, values) {
527
542
  `, "utf-8");
528
543
  }
529
544
  function ensureProjectEnvIsIgnored(dir) {
545
+ ensureProjectPrivatePathsIgnored(dir, [
546
+ PROJECT_DEEPLINE_ENV_FILE,
547
+ ".deepline/"
548
+ ]);
549
+ }
550
+ function ensureProjectPrivatePathsIgnored(dir, entries) {
551
+ const gitDir = findNearestGitCommonDir(dir);
552
+ if (gitDir) {
553
+ const excludePath = join(gitDir, "info", "exclude");
554
+ const existing2 = existsSync(excludePath) ? readFileSync(excludePath, "utf-8") : "";
555
+ const existingEntries2 = new Set(
556
+ existing2.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
557
+ );
558
+ const missing2 = entries.filter(
559
+ (entry) => !existingEntries2.has(entry) && !existingEntries2.has(`/${entry}`)
560
+ );
561
+ if (missing2.length === 0) return;
562
+ mkdirSync(dirname(excludePath), { recursive: true });
563
+ const prefix2 = existing2 && !existing2.endsWith("\n") ? "\n" : "";
564
+ writeFileSync(
565
+ excludePath,
566
+ `${existing2}${prefix2}${missing2.join("\n")}
567
+ `,
568
+ "utf-8"
569
+ );
570
+ return;
571
+ }
530
572
  const gitignorePath = join(dir, ".gitignore");
531
- const entry = PROJECT_DEEPLINE_ENV_FILE;
532
573
  const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : "";
533
- const alreadyIgnored = existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === entry || line === `/${entry}`);
534
- if (alreadyIgnored) return;
574
+ const existingEntries = new Set(
575
+ existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
576
+ );
577
+ const missing = entries.filter(
578
+ (entry) => !existingEntries.has(entry) && !existingEntries.has(`/${entry}`)
579
+ );
580
+ if (missing.length === 0) return;
535
581
  const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
536
- writeFileSync(gitignorePath, `${existing}${prefix}${entry}
537
- `, "utf-8");
582
+ writeFileSync(
583
+ gitignorePath,
584
+ `${existing}${prefix}${missing.join("\n")}
585
+ `,
586
+ "utf-8"
587
+ );
588
+ }
589
+ function findNearestGitCommonDir(startDir) {
590
+ let current = resolve(startDir);
591
+ while (true) {
592
+ const candidate = join(current, ".git");
593
+ if (existsSync(candidate)) {
594
+ try {
595
+ const stat2 = statSync(candidate);
596
+ if (stat2.isDirectory()) return candidate;
597
+ if (stat2.isFile()) {
598
+ const match = readFileSync(candidate, "utf8").match(
599
+ /^gitdir:\s*(.+)$/m
600
+ );
601
+ const rawGitDir = match?.[1]?.trim();
602
+ if (rawGitDir) {
603
+ const gitDir = isAbsolute(rawGitDir) ? rawGitDir : resolve(dirname(candidate), rawGitDir);
604
+ const commonDirPath = join(gitDir, "commondir");
605
+ if (!existsSync(commonDirPath)) return gitDir;
606
+ const rawCommonDir = readFileSync(commonDirPath, "utf8").trim();
607
+ return rawCommonDir ? isAbsolute(rawCommonDir) ? rawCommonDir : resolve(gitDir, rawCommonDir) : gitDir;
608
+ }
609
+ }
610
+ } catch {
611
+ return null;
612
+ }
613
+ }
614
+ const parent = dirname(current);
615
+ if (parent === current) return null;
616
+ current = parent;
617
+ }
538
618
  }
539
619
  function saveProjectDeeplineEnvValues(values, startDir = process.cwd()) {
540
620
  const target = resolveProjectPinTarget(startDir);
@@ -621,7 +701,7 @@ var SDK_RELEASE = {
621
701
  // Deepline-native radars. Older clients must update before discovering,
622
702
  // checking, or deploying an unlaunched monitor integration.
623
703
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
624
- version: "0.1.259",
704
+ version: "0.1.261",
625
705
  apiContract: "2026-07-native-monitor-launch-hard-cutover",
626
706
  supportPolicy: {
627
707
  minimumSupported: "0.1.53",
@@ -1306,7 +1386,7 @@ function decodeSseFrame(frame) {
1306
1386
  return parsed;
1307
1387
  }
1308
1388
  function sleep(ms) {
1309
- return new Promise((resolve14) => setTimeout(resolve14, ms));
1389
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
1310
1390
  }
1311
1391
  function withCoworkNetworkHint(message) {
1312
1392
  if (!isCoworkLikeSandbox2() || message.includes(COWORK_NETWORK_HINT)) {
@@ -2278,14 +2358,14 @@ async function* observeRunEvents(options) {
2278
2358
  try {
2279
2359
  for (; ; ) {
2280
2360
  if (queue.length === 0) {
2281
- const waitForItem = new Promise((resolve14) => {
2282
- wake = resolve14;
2361
+ const waitForItem = new Promise((resolve15) => {
2362
+ wake = resolve15;
2283
2363
  });
2284
2364
  if (!sawFirstSnapshot) {
2285
2365
  const timedOut = await Promise.race([
2286
2366
  waitForItem.then(() => false),
2287
2367
  new Promise(
2288
- (resolve14) => setTimeout(() => resolve14(true), OBSERVE_BOOTSTRAP_TIMEOUT_MS)
2368
+ (resolve15) => setTimeout(() => resolve15(true), OBSERVE_BOOTSTRAP_TIMEOUT_MS)
2289
2369
  )
2290
2370
  ]);
2291
2371
  if (timedOut && queue.length === 0) {
@@ -2503,7 +2583,7 @@ function parseEnvTestPolicyOverrides() {
2503
2583
  return normalizeTestPolicyOverrides(parsed, "DEEPLINE_TEST_POLICY_OVERRIDES");
2504
2584
  }
2505
2585
  function sleep2(ms) {
2506
- return new Promise((resolve14) => setTimeout(resolve14, ms));
2586
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
2507
2587
  }
2508
2588
  function isTransientCompileManifestError(error) {
2509
2589
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
@@ -5669,37 +5749,93 @@ import {
5669
5749
  writeFileSync as writeFileSync4
5670
5750
  } from "fs";
5671
5751
  import { hostname } from "os";
5672
- import { dirname as dirname4 } from "path";
5752
+ import { dirname as dirname4, join as join5 } from "path";
5673
5753
  var EXIT_OK = 0;
5674
5754
  var EXIT_AUTH = 3;
5675
5755
  var EXIT_SERVER = 5;
5676
5756
  function envFilePath(baseUrl) {
5677
5757
  return hostEnvFilePath(baseUrl);
5678
5758
  }
5679
- function pendingClaimTokenPath(baseUrl) {
5759
+ function normalizeAuthScope(value) {
5760
+ if (!value || value === "global") return "global";
5761
+ if (value === "folder") return "folder";
5762
+ throw new Error("--auth-scope must be one of: folder, global");
5763
+ }
5764
+ function parseAuthScope(args) {
5765
+ const index = args.indexOf("--auth-scope");
5766
+ return normalizeAuthScope(index >= 0 ? args[index + 1] : void 0);
5767
+ }
5768
+ function pendingClaimPath(baseUrl, scope) {
5769
+ if (scope === "folder") {
5770
+ const target = resolveProjectPinTarget();
5771
+ if (!target.ok) {
5772
+ throw new Error(
5773
+ `Cowork project folder is ambiguous. Candidate folders: ${target.candidates.join(
5774
+ ", "
5775
+ )}. Set CLAUDE_PROJECT_DIR or cd into the intended project folder.`
5776
+ );
5777
+ }
5778
+ return join5(target.dir, ".deepline", "setup", "pending-auth.json");
5779
+ }
5780
+ return `${hostConfigDirPath(baseUrl)}/pending-auth.json`;
5781
+ }
5782
+ function legacyPendingClaimTokenPath(baseUrl) {
5680
5783
  return `${hostConfigDirPath(baseUrl)}/pending-claim-token`;
5681
5784
  }
5682
- function savePendingClaimToken(baseUrl, claimToken) {
5683
- const filePath = pendingClaimTokenPath(baseUrl);
5785
+ function savePendingClaim(baseUrl, claim) {
5786
+ const filePath = pendingClaimPath(baseUrl, claim.scope);
5684
5787
  const dir = dirname4(filePath);
5685
5788
  if (!existsSync5(dir)) {
5686
5789
  mkdirSync4(dir, { recursive: true });
5687
5790
  }
5688
- writeFileSync4(filePath, `${claimToken}
5689
- `, "utf-8");
5791
+ writeFileSync4(filePath, `${JSON.stringify(claim, null, 2)}
5792
+ `, {
5793
+ encoding: "utf-8",
5794
+ mode: 384
5795
+ });
5690
5796
  }
5691
- function readPendingClaimToken(baseUrl) {
5692
- const filePath = pendingClaimTokenPath(baseUrl);
5693
- if (!existsSync5(filePath)) return "";
5797
+ function readPendingAuthClaim(baseUrl, scope) {
5798
+ let filePath;
5694
5799
  try {
5695
- return readFileSync5(filePath, "utf-8").trim();
5800
+ filePath = pendingClaimPath(baseUrl, scope);
5696
5801
  } catch {
5697
- return "";
5802
+ return null;
5803
+ }
5804
+ if (!existsSync5(filePath)) {
5805
+ if (scope !== "global") return null;
5806
+ try {
5807
+ const claimToken = readFileSync5(
5808
+ legacyPendingClaimTokenPath(baseUrl),
5809
+ "utf-8"
5810
+ ).trim();
5811
+ return claimToken ? {
5812
+ claimToken,
5813
+ claimUrl: `${baseUrl}/api/v2/auth/cli/claim/${encodeURIComponent(claimToken)}`,
5814
+ scope
5815
+ } : null;
5816
+ } catch {
5817
+ return null;
5818
+ }
5819
+ }
5820
+ try {
5821
+ const parsed = JSON.parse(readFileSync5(filePath, "utf-8"));
5822
+ const claimToken = typeof parsed.claimToken === "string" ? parsed.claimToken.trim() : "";
5823
+ if (!claimToken) return null;
5824
+ return {
5825
+ claimToken,
5826
+ claimUrl: typeof parsed.claimUrl === "string" ? parsed.claimUrl.trim() : "",
5827
+ scope
5828
+ };
5829
+ } catch {
5830
+ return null;
5698
5831
  }
5699
5832
  }
5700
- function clearPendingClaimToken(baseUrl) {
5833
+ function clearPendingClaim(baseUrl, scope) {
5701
5834
  try {
5702
- rmSync2(pendingClaimTokenPath(baseUrl), { force: true });
5835
+ rmSync2(pendingClaimPath(baseUrl, scope), { force: true });
5836
+ if (scope === "global") {
5837
+ rmSync2(legacyPendingClaimTokenPath(baseUrl), { force: true });
5838
+ }
5703
5839
  } catch {
5704
5840
  }
5705
5841
  }
@@ -5724,12 +5860,16 @@ function shouldWaitForRegisterClaim(mode) {
5724
5860
  if (mode === "no") return false;
5725
5861
  return detectAgentRuntime() !== "claude_cowork";
5726
5862
  }
5727
- function saveEnvValues(values, baseUrl) {
5863
+ function saveEnvValues(values, baseUrl, scope) {
5728
5864
  const filtered = {
5729
5865
  ...values[HOST_URL_ENV] ? { [HOST_URL_ENV]: values[HOST_URL_ENV] } : {},
5730
5866
  ...values[API_KEY_ENV] ? { [API_KEY_ENV]: values[API_KEY_ENV] } : {}
5731
5867
  };
5732
- saveHostEnvValues(baseUrl, filtered);
5868
+ if (scope === "folder") {
5869
+ saveProjectDeeplineEnvValues(filtered);
5870
+ } else {
5871
+ saveHostEnvValues(baseUrl, filtered);
5872
+ }
5733
5873
  }
5734
5874
  async function httpJson(method, url, apiKey, body) {
5735
5875
  const headers = {
@@ -5792,7 +5932,7 @@ function buildCandidateUrls2(url) {
5792
5932
  }
5793
5933
  }
5794
5934
  function sleep4(ms) {
5795
- return new Promise((resolve14) => setTimeout(resolve14, ms));
5935
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
5796
5936
  }
5797
5937
  function printDeeplineLogo() {
5798
5938
  if (process.stdout.isTTY && (process.stdout.columns ?? 80) >= 70) {
@@ -5853,8 +5993,13 @@ async function handleRegister(args) {
5853
5993
  let orgName = "";
5854
5994
  let agentName = "";
5855
5995
  let waitMode;
5996
+ let authScope;
5856
5997
  try {
5857
5998
  waitMode = parseRegisterWaitMode(args);
5999
+ authScope = parseAuthScope(args);
6000
+ if (authScope === "folder") {
6001
+ pendingClaimPath(baseUrl, authScope);
6002
+ }
5858
6003
  } catch (error) {
5859
6004
  console.error(error instanceof Error ? error.message : String(error));
5860
6005
  return 2;
@@ -5887,18 +6032,22 @@ async function handleRegister(args) {
5887
6032
  const claimUrl = String(data.claim_url || "");
5888
6033
  const claimToken = String(data.claim_token || "");
5889
6034
  if (claimToken) {
5890
- savePendingClaimToken(baseUrl, claimToken);
6035
+ savePendingClaim(baseUrl, { claimToken, claimUrl, scope: authScope });
5891
6036
  saveEnvValues(
5892
6037
  {
5893
6038
  [HOST_URL_ENV]: baseUrl
5894
6039
  },
5895
- baseUrl
6040
+ baseUrl,
6041
+ authScope
5896
6042
  );
5897
6043
  }
5898
6044
  if (claimUrl) {
5899
- console.log(" Opening approval page in your browser.");
5900
- console.log(` If it didn't open, cmd+click: ${claimUrl}`);
5901
- openInBrowser(claimUrl);
6045
+ const shouldOpen = waitMode !== "no" && detectAgentRuntime() !== "claude_cowork";
6046
+ console.log(
6047
+ shouldOpen ? " Opening approval page in your browser." : " Open this approval page in your browser:"
6048
+ );
6049
+ console.log(` ${claimUrl}`);
6050
+ if (shouldOpen) openInBrowser(claimUrl);
5902
6051
  }
5903
6052
  if (data.cli_message) {
5904
6053
  console.log(String(data.cli_message));
@@ -5916,7 +6065,7 @@ async function handleRegister(args) {
5916
6065
  { claim_token: claimToken, reveal: true }
5917
6066
  );
5918
6067
  if (s === 401 || s === 403) {
5919
- clearPendingClaimToken(baseUrl);
6068
+ clearPendingClaim(baseUrl, authScope);
5920
6069
  console.log("Status: unauthorized");
5921
6070
  return EXIT_AUTH;
5922
6071
  }
@@ -5937,15 +6086,16 @@ async function handleRegister(args) {
5937
6086
  [HOST_URL_ENV]: baseUrl,
5938
6087
  [API_KEY_ENV]: apiKey
5939
6088
  },
5940
- baseUrl
6089
+ baseUrl,
6090
+ authScope
5941
6091
  );
5942
- clearPendingClaimToken(baseUrl);
6092
+ clearPendingClaim(baseUrl, authScope);
5943
6093
  await printClaimSuccessBanner(baseUrl, apiKey, statusData);
5944
6094
  return EXIT_OK;
5945
6095
  }
5946
6096
  }
5947
6097
  if (state === "expired") {
5948
- clearPendingClaimToken(baseUrl);
6098
+ clearPendingClaim(baseUrl, authScope);
5949
6099
  console.log(
5950
6100
  "That approval link expired. Please run: deepline auth register"
5951
6101
  );
@@ -5957,6 +6107,13 @@ async function handleRegister(args) {
5957
6107
  async function handleWait(args) {
5958
6108
  const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
5959
6109
  let timeoutSeconds = 300;
6110
+ let authScope;
6111
+ try {
6112
+ authScope = parseAuthScope(args);
6113
+ } catch (error) {
6114
+ console.error(error instanceof Error ? error.message : String(error));
6115
+ return 2;
6116
+ }
5960
6117
  for (let i = 0; i < args.length; i++) {
5961
6118
  if (args[i] === "--timeout" && args[i + 1]) {
5962
6119
  const parsed = Number.parseInt(args[++i], 10);
@@ -5965,9 +6122,11 @@ async function handleWait(args) {
5965
6122
  }
5966
6123
  }
5967
6124
  }
5968
- const claimToken = readPendingClaimToken(baseUrl);
5969
- if (!claimToken) {
5970
- if (resolveApiKeyForBaseUrl(baseUrl)) {
6125
+ const pendingClaim = readPendingAuthClaim(baseUrl, authScope);
6126
+ const claimToken = pendingClaim?.claimToken ?? "";
6127
+ if (!pendingClaim) {
6128
+ const scopedApiKey = authScope === "folder" ? resolveProjectApiKeyForBaseUrl(baseUrl) : resolveGlobalApiKeyForBaseUrl(baseUrl);
6129
+ if (scopedApiKey) {
5971
6130
  console.log("Already connected.");
5972
6131
  return EXIT_OK;
5973
6132
  }
@@ -5983,7 +6142,7 @@ async function handleWait(args) {
5983
6142
  { claim_token: claimToken, reveal: true }
5984
6143
  );
5985
6144
  if (status === 401 || status === 403) {
5986
- clearPendingClaimToken(baseUrl);
6145
+ clearPendingClaim(baseUrl, authScope);
5987
6146
  console.error("Claim is invalid. Run: deepline auth register");
5988
6147
  return EXIT_AUTH;
5989
6148
  }
@@ -6004,15 +6163,16 @@ async function handleWait(args) {
6004
6163
  [HOST_URL_ENV]: baseUrl,
6005
6164
  [API_KEY_ENV]: apiKey
6006
6165
  },
6007
- baseUrl
6166
+ baseUrl,
6167
+ authScope
6008
6168
  );
6009
- clearPendingClaimToken(baseUrl);
6169
+ clearPendingClaim(baseUrl, authScope);
6010
6170
  await printClaimSuccessBanner(baseUrl, apiKey, data);
6011
6171
  return EXIT_OK;
6012
6172
  }
6013
6173
  }
6014
6174
  if (state === "expired") {
6015
- clearPendingClaimToken(baseUrl);
6175
+ clearPendingClaim(baseUrl, authScope);
6016
6176
  console.error("That approval link expired. Run: deepline auth register");
6017
6177
  return EXIT_AUTH;
6018
6178
  }
@@ -6027,6 +6187,16 @@ async function handleStatus(args) {
6027
6187
  const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
6028
6188
  const reveal = args.includes("--reveal");
6029
6189
  const jsonOutput = argsWantJson(args);
6190
+ const hasExplicitAuthScope = args.includes("--auth-scope");
6191
+ let authScope = null;
6192
+ if (hasExplicitAuthScope) {
6193
+ try {
6194
+ authScope = parseAuthScope(args);
6195
+ } catch (error) {
6196
+ console.error(error instanceof Error ? error.message : String(error));
6197
+ return 2;
6198
+ }
6199
+ }
6030
6200
  let hostStatusPayload = null;
6031
6201
  const hostLines = [];
6032
6202
  try {
@@ -6053,14 +6223,16 @@ async function handleStatus(args) {
6053
6223
  };
6054
6224
  hostLines.push(`Host: ${baseUrl} (unreachable)`);
6055
6225
  }
6056
- const apiKey = resolveApiKeyForBaseUrl(baseUrl);
6226
+ const apiKey = authScope === "folder" ? resolveProjectApiKeyForBaseUrl(baseUrl) : authScope === "global" ? resolveGlobalApiKeyForBaseUrl(baseUrl) : resolveApiKeyForBaseUrl(baseUrl);
6057
6227
  if (!apiKey) {
6058
- if (readPendingClaimToken(baseUrl)) {
6228
+ const pendingClaim = authScope ? readPendingAuthClaim(baseUrl, authScope) : readPendingAuthClaim(baseUrl, "folder") ?? readPendingAuthClaim(baseUrl, "global");
6229
+ if (pendingClaim) {
6059
6230
  printCommandEnvelope(
6060
6231
  {
6061
6232
  ...hostStatusPayload ?? { host: baseUrl },
6062
6233
  status: "pending",
6063
6234
  connected: false,
6235
+ authorization_url: pendingClaim.claimUrl || null,
6064
6236
  next: "deepline auth wait",
6065
6237
  render: {
6066
6238
  sections: [
@@ -6142,7 +6314,8 @@ async function handleStatus(args) {
6142
6314
  console.error(`Auth status error (status ${status}).`);
6143
6315
  return EXIT_SERVER;
6144
6316
  }
6145
- clearPendingClaimToken(baseUrl);
6317
+ const resolvedAuthScope = authScope ?? (getResolvedProjectAuthSource(baseUrl, apiKey) ? "folder" : "global");
6318
+ clearPendingClaim(baseUrl, resolvedAuthScope);
6146
6319
  const payload = {
6147
6320
  ...hostStatusPayload ?? { host: baseUrl },
6148
6321
  status: data.status || "(unknown)",
@@ -6167,9 +6340,15 @@ async function handleStatus(args) {
6167
6340
  [HOST_URL_ENV]: baseUrl,
6168
6341
  [API_KEY_ENV]: apiKeyResp
6169
6342
  },
6170
- baseUrl
6343
+ baseUrl,
6344
+ resolvedAuthScope
6171
6345
  );
6172
- savedApiKeyPath = envFilePath(baseUrl);
6346
+ if (resolvedAuthScope === "folder") {
6347
+ const target = resolveProjectPinTarget();
6348
+ savedApiKeyPath = target.ok ? join5(target.dir, ".env.deepline") : null;
6349
+ } else {
6350
+ savedApiKeyPath = envFilePath(baseUrl);
6351
+ }
6173
6352
  }
6174
6353
  }
6175
6354
  printCommandEnvelope(
@@ -6234,12 +6413,19 @@ Examples:
6234
6413
  deepline auth register
6235
6414
  deepline auth register --org-name Acme --agent-name local-cli
6236
6415
  deepline auth register --wait no
6416
+ deepline auth register --auth-scope folder --wait no
6237
6417
  `
6238
- ).option("--org-name <name>", "Workspace name to prefill").option("--agent-name <name>", "Agent name to register").option("--wait <mode>", "Wait mode: auto, yes, or no", "auto").option("--no-wait", "Alias for --wait no").action(async (options) => {
6418
+ ).option("--org-name <name>", "Workspace name to prefill").option("--agent-name <name>", "Agent name to register").option("--wait <mode>", "Wait mode: auto, yes, or no", "auto").option("--no-wait", "Alias for --wait no").option(
6419
+ "--auth-scope <scope>",
6420
+ "Credential scope: global or folder",
6421
+ "global"
6422
+ ).action(async (options) => {
6239
6423
  process.exitCode = await handleRegister([
6240
6424
  ...options.orgName ? ["--org-name", options.orgName] : [],
6241
6425
  ...options.agentName ? ["--agent-name", options.agentName] : [],
6242
- ...options.noWait || options.wait === false ? ["--wait", "no"] : ["--wait", String(options.wait ?? "auto")]
6426
+ ...options.noWait || options.wait === false ? ["--wait", "no"] : ["--wait", String(options.wait ?? "auto")],
6427
+ "--auth-scope",
6428
+ String(options.authScope ?? "global")
6243
6429
  ]);
6244
6430
  });
6245
6431
  auth.command("wait").description("Wait for a pending browser approval and save the API key.").addHelpText(
@@ -6252,10 +6438,17 @@ Notes:
6252
6438
  Examples:
6253
6439
  deepline auth wait
6254
6440
  deepline auth wait --timeout 120
6441
+ deepline auth wait --auth-scope folder --timeout 120
6255
6442
  `
6256
- ).option("--timeout <seconds>", "Maximum seconds to wait", "300").action(async (options) => {
6443
+ ).option("--timeout <seconds>", "Maximum seconds to wait", "300").option(
6444
+ "--auth-scope <scope>",
6445
+ "Credential scope: global or folder",
6446
+ "global"
6447
+ ).action(async (options) => {
6257
6448
  process.exitCode = await handleWait([
6258
- ...options.timeout ? ["--timeout", options.timeout] : []
6449
+ ...options.timeout ? ["--timeout", options.timeout] : [],
6450
+ "--auth-scope",
6451
+ String(options.authScope ?? "global")
6259
6452
  ]);
6260
6453
  });
6261
6454
  auth.command("status").description("Show the current CLI auth and workspace status.").addHelpText(
@@ -6268,14 +6461,16 @@ Notes:
6268
6461
 
6269
6462
  Examples:
6270
6463
  deepline auth status
6464
+ deepline auth status --auth-scope folder
6271
6465
  deepline auth status --json
6272
6466
  `
6273
6467
  ).option(
6274
6468
  "--reveal",
6275
6469
  "Persist the revealed API key back to the host auth file"
6276
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
6470
+ ).option("--auth-scope <scope>", "Read only folder or global auth").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
6277
6471
  process.exitCode = await handleStatus([
6278
6472
  ...options.reveal ? ["--reveal"] : [],
6473
+ ...options.authScope ? ["--auth-scope", String(options.authScope)] : [],
6279
6474
  ...options.json ? ["--json"] : []
6280
6475
  ]);
6281
6476
  });
@@ -7486,7 +7681,7 @@ import {
7486
7681
  writeFileSync as writeFileSync6
7487
7682
  } from "fs";
7488
7683
  import { homedir as homedir6 } from "os";
7489
- import { join as join5, resolve as resolve5 } from "path";
7684
+ import { join as join6, resolve as resolve5 } from "path";
7490
7685
 
7491
7686
  // src/cli/dataset-stats.ts
7492
7687
  import { writeFileSync as writeFileSync5 } from "fs";
@@ -8252,13 +8447,13 @@ async function handleCsvShow(options) {
8252
8447
  );
8253
8448
  }
8254
8449
  function csvRenderStatePath() {
8255
- return join5(homedir6(), ".local", "deepline", "runtime", "csv-render.json");
8450
+ return join6(homedir6(), ".local", "deepline", "runtime", "csv-render.json");
8256
8451
  }
8257
8452
  function csvRenderLogPath() {
8258
- return join5(homedir6(), ".local", "deepline", "runtime", "csv-render.log");
8453
+ return join6(homedir6(), ".local", "deepline", "runtime", "csv-render.log");
8259
8454
  }
8260
8455
  function ensureCsvRenderStateDir() {
8261
- mkdirSync5(join5(homedir6(), ".local", "deepline", "runtime"), {
8456
+ mkdirSync5(join6(homedir6(), ".local", "deepline", "runtime"), {
8262
8457
  recursive: true
8263
8458
  });
8264
8459
  }
@@ -9037,7 +9232,7 @@ import {
9037
9232
  writeFile as writeFile3
9038
9233
  } from "fs/promises";
9039
9234
  import { homedir as homedir7, tmpdir as tmpdir2 } from "os";
9040
- import { basename as basename2, dirname as dirname7, extname, join as join7, resolve as resolve9 } from "path";
9235
+ import { basename as basename2, dirname as dirname7, extname, join as join8, resolve as resolve9 } from "path";
9041
9236
  import { Option } from "commander";
9042
9237
 
9043
9238
  // src/cli/commands/play.ts
@@ -9050,7 +9245,7 @@ import {
9050
9245
  statSync as statSync3,
9051
9246
  writeFileSync as writeFileSync9
9052
9247
  } from "fs";
9053
- import { basename, dirname as dirname6, join as join6, resolve as resolve8 } from "path";
9248
+ import { basename, dirname as dirname6, join as join7, resolve as resolve8 } from "path";
9054
9249
  import { parse as parseCsvSync2 } from "csv-parse/sync";
9055
9250
 
9056
9251
  // src/cli/commands/plays/bootstrap.ts
@@ -9061,7 +9256,7 @@ import {
9061
9256
  statSync as statSync2,
9062
9257
  writeFileSync as writeFileSync8
9063
9258
  } from "fs";
9064
- import { isAbsolute, relative, resolve as resolve7 } from "path";
9259
+ import { isAbsolute as isAbsolute2, relative, resolve as resolve7 } from "path";
9065
9260
  import { parse as parseCsvSync } from "csv-parse/sync";
9066
9261
 
9067
9262
  // ../shared_libs/plays/bootstrap-routes.ts
@@ -9709,7 +9904,7 @@ function packagedCsvPathForPlay(csvPath) {
9709
9904
  const playDir = process.cwd();
9710
9905
  const absoluteCsvPath = resolve7(csvPath);
9711
9906
  const relativePath = relative(playDir, absoluteCsvPath);
9712
- if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath)) {
9907
+ if (relativePath === "" || relativePath.startsWith("..") || isAbsolute2(relativePath)) {
9713
9908
  throw new PlayBootstrapUsageError(
9714
9909
  `--from csv:${csvPath} must point to a file inside the directory where you run plays bootstrap. Run bootstrap from the intended play directory and write the play with --out there.`
9715
9910
  );
@@ -11331,7 +11526,7 @@ function traceCliSync(phase, fields, run) {
11331
11526
  }
11332
11527
  }
11333
11528
  function sleep5(ms) {
11334
- return new Promise((resolve14) => setTimeout(resolve14, ms));
11529
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
11335
11530
  }
11336
11531
  function parseReferencedPlayTarget2(target) {
11337
11532
  const trimmed = target.trim();
@@ -15985,7 +16180,7 @@ async function handlePlayRun(args, hooks) {
15985
16180
  if (siblings.length > 0) {
15986
16181
  console.error(`Did you mean one of these?`);
15987
16182
  for (const s of siblings.slice(0, 5)) {
15988
- console.error(` ${join6(dir, s)}`);
16183
+ console.error(` ${join7(dir, s)}`);
15989
16184
  }
15990
16185
  }
15991
16186
  } catch {
@@ -19646,7 +19841,7 @@ function emitEnrichDebug(message) {
19646
19841
  );
19647
19842
  }
19648
19843
  function sleep6(ms) {
19649
- return new Promise((resolve14) => setTimeout(resolve14, ms));
19844
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
19650
19845
  }
19651
19846
  function enrichExportBackingRowsWaitMs() {
19652
19847
  const raw = process.env.DEEPLINE_ENRICH_EXPORT_BACKING_ROWS_WAIT_MS?.trim();
@@ -19678,7 +19873,7 @@ function expandAtFilePath(rawPath) {
19678
19873
  return homedir7();
19679
19874
  }
19680
19875
  if (expanded.startsWith("~/") || expanded.startsWith("~\\")) {
19681
- return join7(homedir7(), expanded.slice(2));
19876
+ return join8(homedir7(), expanded.slice(2));
19682
19877
  }
19683
19878
  return expanded;
19684
19879
  }
@@ -22048,7 +22243,7 @@ function sidecarEnrichRowsExportPath(outputPath) {
22048
22243
  const resolved = resolve9(outputPath);
22049
22244
  const ext = extname(resolved) || ".csv";
22050
22245
  const stem = basename2(resolved, ext);
22051
- return join7(dirname7(resolved), `${stem}.deepline-enrich-rows${ext}`);
22246
+ return join8(dirname7(resolved), `${stem}.deepline-enrich-rows${ext}`);
22052
22247
  }
22053
22248
  function collectDatasetFollowUpCommands(value, state) {
22054
22249
  if (state.depth > 12 || !value || typeof value !== "object" || state.commands.length >= 8) {
@@ -22129,10 +22324,10 @@ async function persistEnrichFailureReport(input2) {
22129
22324
  if (input2.jobs.length === 0 && input2.issues.length === 0) {
22130
22325
  return null;
22131
22326
  }
22132
- const stateDir = join7(homedir7(), ".local", "deepline", "runtime", "state");
22327
+ const stateDir = join8(homedir7(), ".local", "deepline", "runtime", "state");
22133
22328
  const reportPrefix = input2.jobs.length > 0 ? "run-block-failures" : "enrich-issues";
22134
22329
  await mkdir3(stateDir, { recursive: true });
22135
- const reportPath = join7(
22330
+ const reportPath = join8(
22136
22331
  stateDir,
22137
22332
  `${reportPrefix}-${Math.floor(Date.now() / 1e3)}-${process.pid}.json`
22138
22333
  );
@@ -23036,9 +23231,9 @@ function registerEnrichCommand(program) {
23036
23231
  sdkEnrichTelemetryCompleted = true;
23037
23232
  await completeSdkEnrichTelemetry(sdkEnrichTelemetry, input2);
23038
23233
  };
23039
- const tempDir = await mkdtemp(join7(tmpdir2(), "deepline-enrich-play-"));
23234
+ const tempDir = await mkdtemp(join8(tmpdir2(), "deepline-enrich-play-"));
23040
23235
  await emitSdkEnrichTelemetry(sdkEnrichTelemetry, "enrich_started");
23041
- const tempPlay = join7(tempDir, "deepline-enrich.play.ts");
23236
+ const tempPlay = join8(tempDir, "deepline-enrich.play.ts");
23042
23237
  let inPlaceTempDir = null;
23043
23238
  let inPlaceTempOutputPath = null;
23044
23239
  const inPlaceFinalOutputPath = options.inPlace ? resolve9(inputCsv) : null;
@@ -23066,12 +23261,12 @@ function registerEnrichCommand(program) {
23066
23261
  await rm(inPlaceTempDir, { recursive: true, force: true });
23067
23262
  }
23068
23263
  inPlaceTempDir = await mkdtemp(
23069
- join7(
23264
+ join8(
23070
23265
  dirname7(inPlaceCommitOutputPath ?? resolve9(inputCsv)),
23071
23266
  ".deepline-enrich-in-place-"
23072
23267
  )
23073
23268
  );
23074
- inPlaceTempOutputPath = join7(inPlaceTempDir, "output.csv");
23269
+ inPlaceTempOutputPath = join8(inPlaceTempDir, "output.csv");
23075
23270
  await copyFile(resolve9(inputCsv), inPlaceTempOutputPath);
23076
23271
  outputPath = inPlaceTempOutputPath;
23077
23272
  };
@@ -23352,7 +23547,7 @@ import {
23352
23547
  writeFileSync as writeFileSync10
23353
23548
  } from "fs";
23354
23549
  import { homedir as homedir8, platform } from "os";
23355
- import { basename as basename3, dirname as dirname8, join as join8, resolve as resolve10 } from "path";
23550
+ import { basename as basename3, dirname as dirname8, join as join9, resolve as resolve10 } from "path";
23356
23551
  import { gzipSync } from "zlib";
23357
23552
  import { randomUUID as randomUUID3 } from "crypto";
23358
23553
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -23378,10 +23573,10 @@ function detectShellContext() {
23378
23573
  };
23379
23574
  }
23380
23575
  function claudeProjectsRoot() {
23381
- return join8(homeDir(), ".claude", "projects");
23576
+ return join9(homeDir(), ".claude", "projects");
23382
23577
  }
23383
23578
  function codexSessionsRoot() {
23384
- return join8(homeDir(), ".codex", "sessions");
23579
+ return join9(homeDir(), ".codex", "sessions");
23385
23580
  }
23386
23581
  function listClaudeSessionFiles() {
23387
23582
  const root = claudeProjectsRoot();
@@ -23389,10 +23584,10 @@ function listClaudeSessionFiles() {
23389
23584
  const projectDirs = readDirectoryNames(root);
23390
23585
  const files = [];
23391
23586
  for (const projectDir of projectDirs) {
23392
- const fullProjectDir = join8(root, projectDir);
23587
+ const fullProjectDir = join9(root, projectDir);
23393
23588
  for (const fileName of readDirectoryNames(fullProjectDir)) {
23394
23589
  if (fileName.endsWith(".jsonl")) {
23395
- const filePath = join8(fullProjectDir, fileName);
23590
+ const filePath = join9(fullProjectDir, fileName);
23396
23591
  const sessionId = sessionIdFromClaudeFilePath(filePath);
23397
23592
  const stat2 = statIfReadable(filePath);
23398
23593
  if (sessionId && stat2) {
@@ -23449,7 +23644,7 @@ function listJsonlFilesRecursive(root, maxDepth) {
23449
23644
  return;
23450
23645
  }
23451
23646
  for (const entry of entries) {
23452
- const fullPath = join8(dir, entry.name);
23647
+ const fullPath = join9(dir, entry.name);
23453
23648
  if (entry.isDirectory()) {
23454
23649
  visit(fullPath, depth + 1);
23455
23650
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -23864,8 +24059,8 @@ function loadViewerAssets() {
23864
24059
  const cliEntry = process.argv[1]?.trim() ? resolve10(process.argv[1]) : null;
23865
24060
  const candidateRoots2 = [
23866
24061
  ...cliEntry ? [
23867
- join8(dirname8(dirname8(cliEntry)), "viewer"),
23868
- join8(
24062
+ join9(dirname8(dirname8(cliEntry)), "viewer"),
24063
+ join9(
23869
24064
  dirname8(dirname8(dirname8(cliEntry))),
23870
24065
  "src",
23871
24066
  "lib",
@@ -23873,12 +24068,12 @@ function loadViewerAssets() {
23873
24068
  "viewer"
23874
24069
  )
23875
24070
  ] : [],
23876
- join8(process.cwd(), "src", "lib", "cli", "viewer")
24071
+ join9(process.cwd(), "src", "lib", "cli", "viewer")
23877
24072
  ];
23878
24073
  for (const root of candidateRoots2) {
23879
24074
  try {
23880
- const cssPath = join8(root, "viewer.css");
23881
- const jsPath = join8(root, "viewer.js");
24075
+ const cssPath = join9(root, "viewer.css");
24076
+ const jsPath = join9(root, "viewer.js");
23882
24077
  if (!existsSync8(cssPath) || !existsSync8(jsPath)) continue;
23883
24078
  return {
23884
24079
  css: readFileSync8(cssPath, "utf8"),
@@ -23908,9 +24103,9 @@ async function handleSessionsRender(options) {
23908
24103
  });
23909
24104
  let outputPath = options.output ? resolve10(options.output) : "";
23910
24105
  if (!outputPath) {
23911
- const outputDir = join8(process.cwd(), "deepline", "data");
24106
+ const outputDir = join9(process.cwd(), "deepline", "data");
23912
24107
  mkdirSync6(outputDir, { recursive: true });
23913
- outputPath = join8(
24108
+ outputPath = join9(
23914
24109
  outputDir,
23915
24110
  targets.length > 1 ? "session-viewer.html" : `session-${targets[0]?.sessionId}.html`
23916
24111
  );
@@ -25080,7 +25275,7 @@ Examples:
25080
25275
  async function fetchOrganizations(http2, apiKey) {
25081
25276
  return http2.post("/api/v2/auth/cli/organizations", { api_key: apiKey });
25082
25277
  }
25083
- function normalizeAuthScope(value) {
25278
+ function normalizeAuthScope2(value) {
25084
25279
  if (!value) return "auto";
25085
25280
  if (value === "auto" || value === "folder" || value === "global") {
25086
25281
  return value;
@@ -25283,7 +25478,7 @@ async function handleOrgStatus(options) {
25283
25478
  );
25284
25479
  }
25285
25480
  async function handleOrgSwitch(selection, options) {
25286
- const authScope = normalizeAuthScope(options.authScope);
25481
+ const authScope = normalizeAuthScope2(options.authScope);
25287
25482
  const config = resolveConfig();
25288
25483
  const http2 = new HttpClient(config);
25289
25484
  const payload = await fetchOrganizations(http2, config.apiKey);
@@ -25660,17 +25855,17 @@ function hasClaudeBinary() {
25660
25855
  }
25661
25856
  }
25662
25857
  function launchClaude(prompt) {
25663
- return new Promise((resolve14) => {
25858
+ return new Promise((resolve15) => {
25664
25859
  const child = spawn2("claude", [prompt], {
25665
25860
  stdio: "inherit",
25666
25861
  shell: process.platform === "win32"
25667
25862
  });
25668
- child.on("error", () => resolve14(EXIT_SERVER3));
25669
- child.on("close", (status) => resolve14(status ?? EXIT_OK2));
25863
+ child.on("error", () => resolve15(EXIT_SERVER3));
25864
+ child.on("close", (status) => resolve15(status ?? EXIT_OK2));
25670
25865
  });
25671
25866
  }
25672
25867
  function readBody(req) {
25673
- return new Promise((resolve14, reject) => {
25868
+ return new Promise((resolve15, reject) => {
25674
25869
  let raw = "";
25675
25870
  req.setEncoding("utf8");
25676
25871
  req.on("data", (chunk) => {
@@ -25680,7 +25875,7 @@ function readBody(req) {
25680
25875
  req.destroy();
25681
25876
  }
25682
25877
  });
25683
- req.on("end", () => resolve14(raw));
25878
+ req.on("end", () => resolve15(raw));
25684
25879
  req.on("error", reject);
25685
25880
  });
25686
25881
  }
@@ -25735,7 +25930,7 @@ function startCallbackServer(input2) {
25735
25930
  writeJson(res, 400, { error: "Invalid request body." });
25736
25931
  });
25737
25932
  });
25738
- return new Promise((resolve14, reject) => {
25933
+ return new Promise((resolve15, reject) => {
25739
25934
  server.once("error", reject);
25740
25935
  server.listen(0, "127.0.0.1", () => {
25741
25936
  const address = server.address();
@@ -25743,7 +25938,7 @@ function startCallbackServer(input2) {
25743
25938
  reject(new Error("Failed to bind quickstart callback server."));
25744
25939
  return;
25745
25940
  }
25746
- resolve14({ server, port: address.port });
25941
+ resolve15({ server, port: address.port });
25747
25942
  });
25748
25943
  });
25749
25944
  }
@@ -25769,8 +25964,8 @@ async function handleQuickstart(options) {
25769
25964
  }
25770
25965
  const state = randomBytes(32).toString("hex");
25771
25966
  let resolveSelection;
25772
- const selectionPromise = new Promise((resolve14) => {
25773
- resolveSelection = resolve14;
25967
+ const selectionPromise = new Promise((resolve15) => {
25968
+ resolveSelection = resolve15;
25774
25969
  });
25775
25970
  let callback;
25776
25971
  try {
@@ -25909,7 +26104,7 @@ async function readHiddenLine(prompt, streams = {}) {
25909
26104
  }
25910
26105
  let value = "";
25911
26106
  inputStream.resume();
25912
- return await new Promise((resolve14, reject) => {
26107
+ return await new Promise((resolve15, reject) => {
25913
26108
  let settled = false;
25914
26109
  const cleanup = () => {
25915
26110
  inputStream.off("data", onData);
@@ -25927,7 +26122,7 @@ async function readHiddenLine(prompt, streams = {}) {
25927
26122
  settled = true;
25928
26123
  outputStream.write("\n");
25929
26124
  cleanup();
25930
- resolve14(line);
26125
+ resolve15(line);
25931
26126
  };
25932
26127
  const fail = (error) => {
25933
26128
  if (settled) return;
@@ -26100,7 +26295,7 @@ Examples:
26100
26295
  // src/cli/commands/switch.ts
26101
26296
  import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "fs";
26102
26297
  import { homedir as homedir9 } from "os";
26103
- import { dirname as dirname9, join as join9 } from "path";
26298
+ import { dirname as dirname9, join as join10 } from "path";
26104
26299
  function hostSlugFromBaseUrl(baseUrl) {
26105
26300
  try {
26106
26301
  const url = new URL(baseUrl);
@@ -26121,7 +26316,7 @@ function resolveConfigScope() {
26121
26316
  }
26122
26317
  function activeFamilyPath() {
26123
26318
  const home = process.env.HOME || process.env.USERPROFILE || homedir9();
26124
- return join9(
26319
+ return join10(
26125
26320
  home,
26126
26321
  ".local",
26127
26322
  "deepline",
@@ -26266,7 +26461,7 @@ import {
26266
26461
  writeFileSync as writeFileSync13
26267
26462
  } from "fs";
26268
26463
  import { tmpdir as tmpdir3 } from "os";
26269
- import { join as join11, resolve as resolve11 } from "path";
26464
+ import { join as join12, resolve as resolve11 } from "path";
26270
26465
 
26271
26466
  // src/tool-output.ts
26272
26467
  import {
@@ -26277,7 +26472,7 @@ import {
26277
26472
  writeSync
26278
26473
  } from "fs";
26279
26474
  import { homedir as homedir10 } from "os";
26280
- import { dirname as dirname10, join as join10 } from "path";
26475
+ import { dirname as dirname10, join as join11 } from "path";
26281
26476
  function isPlainObject(value) {
26282
26477
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
26283
26478
  }
@@ -26404,18 +26599,18 @@ function projectRowOutput(conversion) {
26404
26599
  };
26405
26600
  }
26406
26601
  function ensureOutputDir() {
26407
- const outputDir = join10(homedir10(), ".local", "share", "deepline", "data");
26602
+ const outputDir = join11(homedir10(), ".local", "share", "deepline", "data");
26408
26603
  mkdirSync8(outputDir, { recursive: true });
26409
26604
  return outputDir;
26410
26605
  }
26411
26606
  function writeJsonOutputFile(payload, stem) {
26412
26607
  const outputDir = ensureOutputDir();
26413
- const outputPath = join10(outputDir, `${stem}_${Date.now()}.json`);
26608
+ const outputPath = join11(outputDir, `${stem}_${Date.now()}.json`);
26414
26609
  writeFileSync12(outputPath, JSON.stringify(payload, null, 2), "utf-8");
26415
26610
  return outputPath;
26416
26611
  }
26417
26612
  function writeCsvOutputFile(rows, stem, options) {
26418
- const outputPath = options?.outPath ? options.outPath : join10(ensureOutputDir(), `${stem}_${Date.now()}.csv`);
26613
+ const outputPath = options?.outPath ? options.outPath : join11(ensureOutputDir(), `${stem}_${Date.now()}.csv`);
26419
26614
  mkdirSync8(dirname10(outputPath), { recursive: true });
26420
26615
  const columns = columnsForRows(rows);
26421
26616
  const escapeCell = (value) => {
@@ -27812,9 +28007,9 @@ function starterScriptJson(script) {
27812
28007
  function seedToolListScript(input2) {
27813
28008
  const stem = safeFileStem(input2.toolId);
27814
28009
  const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
27815
- const scriptDir = mkdtempSync(join11(tmpdir3(), "deepline-workflow-seed-"));
28010
+ const scriptDir = mkdtempSync(join12(tmpdir3(), "deepline-workflow-seed-"));
27816
28011
  chmodSync(scriptDir, 448);
27817
- const scriptPath = join11(scriptDir, fileName);
28012
+ const scriptPath = join12(scriptDir, fileName);
27818
28013
  const projectDir = `deepline/projects/${stem}-workflow`;
27819
28014
  const playName = `${stem}-workflow`;
27820
28015
  const sampleRows = input2.rows.length > 0 ? `${JSON.stringify(input2.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
@@ -28200,7 +28395,7 @@ async function executeTool(args) {
28200
28395
 
28201
28396
  // src/cli/commands/workflow.ts
28202
28397
  import { mkdir as mkdir4, readFile as readFile2, writeFile as writeFile4 } from "fs/promises";
28203
- import { dirname as dirname11, join as join12, resolve as resolve12 } from "path";
28398
+ import { dirname as dirname11, join as join13, resolve as resolve12 } from "path";
28204
28399
 
28205
28400
  // src/cli/workflow-to-play.ts
28206
28401
  import { createHash as createHash3 } from "crypto";
@@ -28489,7 +28684,7 @@ async function transformOne(api, workflowId, outDir, publish) {
28489
28684
  revision.config,
28490
28685
  { workflowName: workflow.name, version: revision.version }
28491
28686
  );
28492
- const file = join12(resolve12(outDir), `${compiled.playName}.play.ts`);
28687
+ const file = join13(resolve12(outDir), `${compiled.playName}.play.ts`);
28493
28688
  await mkdir4(dirname11(file), { recursive: true });
28494
28689
  await writeFile4(file, compiled.sourceCode, "utf8");
28495
28690
  let published = false;
@@ -28748,22 +28943,17 @@ import {
28748
28943
  readFileSync as readFileSync13,
28749
28944
  renameSync,
28750
28945
  rmSync as rmSync4,
28751
- unlinkSync as unlinkSync2,
28946
+ unlinkSync,
28752
28947
  writeFileSync as writeFileSync15
28753
28948
  } from "fs";
28754
- import { homedir as homedir11 } from "os";
28755
- import { dirname as dirname13, isAbsolute as isAbsolute2, join as join14, relative as relative2, resolve as resolve13 } from "path";
28949
+ import { homedir as homedir12 } from "os";
28950
+ import { dirname as dirname13, isAbsolute as isAbsolute3, join as join15, relative as relative2, resolve as resolve13 } from "path";
28756
28951
 
28757
- // src/cli/skills-sync.ts
28758
- import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
28759
- import {
28760
- existsSync as existsSync11,
28761
- mkdirSync as mkdirSync9,
28762
- readFileSync as readFileSync12,
28763
- unlinkSync,
28764
- writeFileSync as writeFileSync14
28765
- } from "fs";
28766
- import { dirname as dirname12, join as join13 } from "path";
28952
+ // src/cli/commands/skills.ts
28953
+ import { spawn as spawn3 } from "child_process";
28954
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync14 } from "fs";
28955
+ import { homedir as homedir11 } from "os";
28956
+ import { dirname as dirname12, join as join14 } from "path";
28767
28957
 
28768
28958
  // ../shared_libs/cli/install-commands.json
28769
28959
  var install_commands_default = {
@@ -28779,7 +28969,7 @@ var install_commands_default = {
28779
28969
  npx_binary: "npx",
28780
28970
  npx_add_args_template: [
28781
28971
  "--yes",
28782
- "skills",
28972
+ "skills@latest",
28783
28973
  "add",
28784
28974
  "{skills_index_url}",
28785
28975
  "--agent",
@@ -28869,311 +29059,380 @@ function sdkNpmGlobalInstallCommand() {
28869
29059
  return INSTALL_COMMANDS.cli.sdk_npm_global;
28870
29060
  }
28871
29061
 
28872
- // src/cli/skills-sync.ts
28873
- var CHECK_TIMEOUT_MS2 = 3e3;
28874
- var attemptedSync = false;
28875
- function shouldSkipSkillsSync() {
28876
- if (detectAgentRuntime() === "claude_cowork") {
28877
- return true;
28878
- }
28879
- const value = process.env.DEEPLINE_SKIP_SKILLS_SYNC?.trim().toLowerCase();
28880
- return value === "1" || value === "true" || value === "yes" || value === "on";
29062
+ // src/cli/commands/skills.ts
29063
+ var RUNTIME_TO_SKILLS_AGENT = {
29064
+ antigravity: "antigravity",
29065
+ claude_code: "claude-code",
29066
+ claude_cowork: "claude-code",
29067
+ cline: "cline",
29068
+ codex: "codex",
29069
+ cursor: "cursor",
29070
+ gemini: "gemini-cli",
29071
+ windsurf: "windsurf"
29072
+ };
29073
+ var AGENT_MARKERS = [
29074
+ { agent: "codex", paths: [".codex"] },
29075
+ { agent: "claude-code", paths: [".claude"] },
29076
+ { agent: "cursor", paths: [".cursor"] },
29077
+ { agent: "gemini-cli", paths: [".gemini", ".gemini-cli"] },
29078
+ { agent: "antigravity", paths: [".antigravity"] }
29079
+ ];
29080
+ var WORKSPACE_SKILL_ROOTS_BY_AGENT = {
29081
+ antigravity: [".agents"],
29082
+ "claude-code": [".claude"],
29083
+ cline: [".agents"],
29084
+ codex: [".agents"],
29085
+ cursor: [".agents"],
29086
+ "gemini-cli": [".agents"],
29087
+ windsurf: [".windsurf"],
29088
+ "*": [".agents", ".claude", ".windsurf"]
29089
+ };
29090
+ function workspaceSkillRootsForAgents(agents) {
29091
+ return [
29092
+ ...new Set(
29093
+ agents.flatMap((agent) => WORKSPACE_SKILL_ROOTS_BY_AGENT[agent] ?? [])
29094
+ )
29095
+ ].sort((a, b) => a.localeCompare(b));
28881
29096
  }
28882
- function activePluginSkillsDir() {
28883
- const pluginMode = process.env.DEEPLINE_PLUGIN_MODE?.trim().toLowerCase();
28884
- if (pluginMode !== "true" && pluginMode !== "1" && pluginMode !== "yes" && pluginMode !== "on") {
28885
- return "";
28886
- }
28887
- const dir = process.env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim() ?? "";
28888
- return dir && existsSync11(dir) ? dir : "";
29097
+ function normalizeScope(value) {
29098
+ if (value === "global") return "global";
29099
+ if (value === "local") return "local";
29100
+ if (!value) return inferActiveSkillsScope();
29101
+ throw new Error("--scope must be one of: global, local");
28889
29102
  }
28890
- function readPluginSkillsVersion() {
28891
- const dir = activePluginSkillsDir();
28892
- if (!dir) return "";
28893
- try {
28894
- return readFileSync12(join13(dir, ".version"), "utf-8").trim();
28895
- } catch {
28896
- return "";
29103
+ function inferActiveSkillsScope(entrypoint = process.argv[1] ?? "") {
29104
+ return /[\\/]\.deepline[\\/]runtime[\\/]/.test(entrypoint) ? "local" : "global";
29105
+ }
29106
+ function resolveLocalRoot() {
29107
+ const target = resolveProjectPinTarget();
29108
+ if (!target.ok) {
29109
+ throw new Error(
29110
+ `Cowork project folder is ambiguous. Candidate folders: ${target.candidates.join(
29111
+ ", "
29112
+ )}. Set CLAUDE_PROJECT_DIR or cd into the intended project folder.`
29113
+ );
28897
29114
  }
29115
+ return target.dir;
28898
29116
  }
28899
- function sdkSkillsVersionPath(baseUrl) {
28900
- return join13(sdkCliStateDirPath(baseUrl), "skills-version");
29117
+ function detectSkillsAgents(input2) {
29118
+ const runtime = input2.runtime ?? detectAgentRuntime();
29119
+ const knownAgent = RUNTIME_TO_SKILLS_AGENT[runtime];
29120
+ if (knownAgent) return [knownAgent];
29121
+ const roots = [
29122
+ ...input2.scope === "local" && input2.root ? [input2.root] : [],
29123
+ input2.homeDir ?? homedir11()
29124
+ ];
29125
+ const detected = AGENT_MARKERS.filter(
29126
+ (marker) => roots.some(
29127
+ (root) => marker.paths.some((path) => existsSync11(join14(root, path)))
29128
+ )
29129
+ ).map((marker) => marker.agent);
29130
+ return detected.length > 0 ? detected : ["*"];
28901
29131
  }
28902
- function legacySdkSkillsVersionPath(baseUrl) {
28903
- return join13(dirname12(sdkCliStateDirPath(baseUrl)), "sdk-skills", ".version");
29132
+ async function fetchSkillCatalog(baseUrl) {
29133
+ const response = await fetch(skillsIndexUrl(baseUrl));
29134
+ if (!response.ok) {
29135
+ throw new Error(
29136
+ `Skill catalog request failed (status ${response.status}).`
29137
+ );
29138
+ }
29139
+ const index = await response.json();
29140
+ const skillNames = (index.skills ?? []).filter((skill) => skill.install_surface === "v1").map((skill) => skill.name).filter((name) => typeof name === "string" && Boolean(name)).sort((a, b) => a.localeCompare(b));
29141
+ if (skillNames.length === 0) {
29142
+ throw new Error(
29143
+ "The Deepline skill catalog contains no installable skills."
29144
+ );
29145
+ }
29146
+ return {
29147
+ skillNames,
29148
+ version: typeof index.version === "string" && index.version.trim() ? index.version.trim() : "unversioned"
29149
+ };
28904
29150
  }
28905
- function unavailableSkillsNoticePath(baseUrl) {
28906
- return join13(sdkCliStateDirPath(baseUrl), "skills-sync-unavailable-version");
29151
+ function skillsStatePathForScope(baseUrl, scope, root) {
29152
+ return scope === "local" && root ? join14(root, ".deepline", "setup", "skills.json") : join14(sdkCliStateDirPath(baseUrl), "skills-install.json");
28907
29153
  }
28908
- function readSdkSkillsLocalVersion(baseUrl) {
28909
- const pluginVersion = readPluginSkillsVersion();
28910
- if (pluginVersion) return pluginVersion;
28911
- const path = existsSync11(sdkSkillsVersionPath(baseUrl)) ? sdkSkillsVersionPath(baseUrl) : legacySdkSkillsVersionPath(baseUrl);
28912
- if (!existsSync11(path)) return "";
28913
- try {
28914
- return readFileSync12(path, "utf-8").trim();
28915
- } catch {
28916
- return "";
28917
- }
29154
+ function buildSkillsPlan(input2) {
29155
+ const scopeArgs = input2.scope === "global" ? ["--global"] : [];
29156
+ const allManagedNames = [
29157
+ .../* @__PURE__ */ new Set([...input2.skillNames, ...LEGACY_SKILL_NAMES_TO_REMOVE])
29158
+ ].sort((a, b) => a.localeCompare(b));
29159
+ return {
29160
+ scope: input2.scope,
29161
+ root: input2.root,
29162
+ agents: input2.agents,
29163
+ skillNames: input2.skillNames,
29164
+ version: input2.version,
29165
+ remove: {
29166
+ command: "npm",
29167
+ args: [
29168
+ "exec",
29169
+ "--yes",
29170
+ "--package=skills@latest",
29171
+ "--",
29172
+ "skills",
29173
+ "remove",
29174
+ ...scopeArgs,
29175
+ "--agent",
29176
+ ...input2.agents,
29177
+ "-y",
29178
+ ...allManagedNames
29179
+ ]
29180
+ },
29181
+ install: {
29182
+ command: "npm",
29183
+ args: [
29184
+ "exec",
29185
+ "--yes",
29186
+ "--package=skills@latest",
29187
+ "--",
29188
+ "skills",
29189
+ "add",
29190
+ skillsIndexUrl(input2.baseUrl),
29191
+ "--agent",
29192
+ ...input2.agents,
29193
+ ...scopeArgs,
29194
+ "--yes",
29195
+ ...input2.skillNames.flatMap((name) => ["--skill", name]),
29196
+ "--full-depth"
29197
+ ]
29198
+ },
29199
+ statePath: skillsStatePathForScope(input2.baseUrl, input2.scope, input2.root)
29200
+ };
28918
29201
  }
28919
- function writeLocalSkillsVersion(baseUrl, version) {
28920
- const path = sdkSkillsVersionPath(baseUrl);
28921
- mkdirSync9(dirname12(path), { recursive: true });
28922
- writeFileSync14(path, `${version}
28923
- `, "utf-8");
29202
+ function sortedStrings(value) {
29203
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
29204
+ return null;
29205
+ }
29206
+ return [...value].sort((a, b) => a.localeCompare(b));
28924
29207
  }
28925
- function writeUnavailableSkillsNotice(baseUrl, remoteVersion, skillNames) {
28926
- const path = unavailableSkillsNoticePath(baseUrl);
28927
- try {
28928
- if (existsSync11(path) && readFileSync12(path, "utf-8").trim() === remoteVersion) {
28929
- return;
28930
- }
28931
- mkdirSync9(dirname12(path), { recursive: true });
28932
- writeFileSync14(path, `${remoteVersion}
28933
- `, "utf-8");
28934
- } catch {
29208
+ function isSkillsPlanCurrent(plan, state) {
29209
+ if (!state || state.scope !== plan.scope || state.skillsVersion !== plan.version) {
29210
+ return false;
28935
29211
  }
28936
- const manualCommand = `npx ${buildSkillsInstallArgs(baseUrl, skillNames).join(" ")}`;
28937
- writeSdkSkillsStatusLine(
28938
- `Deepline agent skills are out of date, but neither \`bunx\` nor \`npx\` is available. Install Node.js/npm or Bun, then run:
28939
- ${manualCommand}`
28940
- );
29212
+ const installedAgents = sortedStrings(state.agents);
29213
+ const installedSkillNames = sortedStrings(state.skillNames);
29214
+ if (!installedAgents || !installedSkillNames) return false;
29215
+ return installedAgents.join("\0") === [...plan.agents].sort((a, b) => a.localeCompare(b)).join("\0") && installedSkillNames.join("\0") === [...plan.skillNames].sort((a, b) => a.localeCompare(b)).join("\0");
28941
29216
  }
28942
- function clearUnavailableSkillsNotice(baseUrl) {
29217
+ function readSkillsInstallState(path) {
28943
29218
  try {
28944
- unlinkSync(unavailableSkillsNoticePath(baseUrl));
29219
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
29220
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
28945
29221
  } catch {
29222
+ return null;
28946
29223
  }
28947
29224
  }
28948
- function sortedUniqueSkillNames(names) {
28949
- return [...new Set(names.map((name) => name.trim()).filter(Boolean))].sort(
28950
- (a, b) => a.localeCompare(b)
28951
- );
29225
+ function runProcess(command, args, cwd) {
29226
+ return new Promise((resolve15, reject) => {
29227
+ const child = spawn3(command, args, {
29228
+ cwd,
29229
+ env: process.env,
29230
+ stdio: ["ignore", "ignore", "pipe"],
29231
+ shell: process.platform === "win32"
29232
+ });
29233
+ let stderr = "";
29234
+ child.stderr.on("data", (chunk) => {
29235
+ stderr += chunk.toString("utf8");
29236
+ process.stderr.write(chunk);
29237
+ });
29238
+ child.once("error", reject);
29239
+ child.once("close", (code) => {
29240
+ if (code && stderr.trim()) {
29241
+ process.stderr.write(`skills@latest exited ${code}.
29242
+ `);
29243
+ }
29244
+ resolve15(code ?? 1);
29245
+ });
29246
+ });
28952
29247
  }
28953
- async function fetchV1SkillNames(baseUrl) {
28954
- const controller = new AbortController();
28955
- const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
29248
+ async function runSkillsCommand(options, dependencies = {}) {
29249
+ let scope;
29250
+ let root;
28956
29251
  try {
28957
- const response = await fetch(
28958
- new URL("/.well-known/skills/index.json", baseUrl),
28959
- { signal: controller.signal }
28960
- );
28961
- if (!response.ok) return [];
28962
- const data = await response.json().catch(() => null);
28963
- const names = (data?.skills ?? []).filter((skill) => skill.install_surface === "v1").map((skill) => skill.name).filter(
28964
- (name) => typeof name === "string" && name.length > 0
29252
+ scope = normalizeScope(options.scope);
29253
+ root = scope === "local" ? resolveLocalRoot() : null;
29254
+ } catch (error) {
29255
+ printCommandEnvelope(
29256
+ {
29257
+ ok: false,
29258
+ status: "failed",
29259
+ code: "INVALID_SKILLS_SCOPE",
29260
+ exitCode: 2,
29261
+ message: error instanceof Error ? error.message : String(error)
29262
+ },
29263
+ { json: options.json }
28965
29264
  );
28966
- return sortedUniqueSkillNames(names);
28967
- } catch {
28968
- return [];
28969
- } finally {
28970
- clearTimeout(timeout);
29265
+ return 2;
28971
29266
  }
28972
- }
28973
- function buildSdkSkillNames(v1SkillNames) {
28974
- return sortedUniqueSkillNames(v1SkillNames);
28975
- }
28976
- async function fetchSkillsUpdate(baseUrl, localVersion) {
28977
- const controller = new AbortController();
28978
- const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
29267
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
29268
+ let catalog;
28979
29269
  try {
28980
- const response = await fetch(new URL("/api/v2/cli/update-check", baseUrl), {
28981
- method: "POST",
28982
- headers: { "Content-Type": "application/json" },
28983
- body: JSON.stringify({
28984
- skills: {
28985
- version: localVersion
28986
- }
28987
- }),
28988
- signal: controller.signal
28989
- });
28990
- if (!response.ok) return null;
28991
- const data = await response.json().catch(() => null);
28992
- const skills = data?.skills;
28993
- if (!skills) return null;
28994
- return {
28995
- needsUpdate: skills.needs_update === true,
28996
- remoteVersion: typeof skills.remote?.version === "string" ? skills.remote.version.trim() : ""
28997
- };
28998
- } catch {
28999
- return null;
29000
- } finally {
29001
- clearTimeout(timeout);
29270
+ catalog = options.dryRun ? { skillNames: [...DEFAULT_SDK_SKILL_NAMES], version: "latest" } : await (dependencies.fetchCatalog ?? fetchSkillCatalog)(baseUrl);
29271
+ } catch (error) {
29272
+ printCommandEnvelope(
29273
+ {
29274
+ ok: false,
29275
+ status: "failed",
29276
+ code: "SKILLS_CATALOG_UNAVAILABLE",
29277
+ exitCode: 5,
29278
+ message: error instanceof Error ? error.message : String(error),
29279
+ next: "Retry: deepline skills"
29280
+ },
29281
+ { json: options.json }
29282
+ );
29283
+ return 5;
29002
29284
  }
29003
- }
29004
- function buildSkillsInstallArgs(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
29005
- return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames));
29006
- }
29007
- function buildBunxSkillsInstallArgs(baseUrl, skillNames) {
29008
- return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames), {
29009
- firstArg: "--bun"
29010
- });
29011
- }
29012
- function hasCommand(command) {
29013
- const result = spawnSync2(command, ["--version"], {
29014
- stdio: "ignore",
29015
- shell: process.platform === "win32"
29016
- });
29017
- return result.status === 0;
29018
- }
29019
- function shellQuote5(arg) {
29020
- return `'${arg.replace(/'/g, `'\\''`)}'`;
29021
- }
29022
- function resolveSkillsInstallCommands(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
29023
- const commands = [];
29024
- if (hasCommand("bunx")) {
29025
- const bunxArgs = buildBunxSkillsInstallArgs(baseUrl, skillNames);
29026
- commands.push({
29027
- command: "bunx",
29028
- args: bunxArgs,
29029
- manualCommand: `bunx ${bunxArgs.map(shellQuote5).join(" ")}`
29030
- });
29285
+ const agents = options.agent ? [options.agent] : detectSkillsAgents({ scope, root });
29286
+ const plan = buildSkillsPlan({ baseUrl, scope, root, agents, ...catalog });
29287
+ if (options.dryRun) {
29288
+ printCommandEnvelope(
29289
+ { ok: true, status: "planned", dryRun: true, ...plan },
29290
+ { json: options.json }
29291
+ );
29292
+ return 0;
29031
29293
  }
29032
- if (hasCommand("npx")) {
29033
- const npxArgs = buildSkillsInstallArgs(baseUrl, skillNames);
29034
- commands.push({
29035
- command: "npx",
29036
- args: npxArgs,
29037
- manualCommand: `npx ${npxArgs.map(shellQuote5).join(" ")}`
29038
- });
29294
+ if (isSkillsPlanCurrent(plan, readSkillsInstallState(plan.statePath))) {
29295
+ printCommandEnvelope(
29296
+ {
29297
+ ok: true,
29298
+ status: "current",
29299
+ complete: true,
29300
+ changed: false,
29301
+ skipped: true,
29302
+ skipReason: "skills_version_current",
29303
+ scope,
29304
+ agents,
29305
+ skillsVersion: plan.version,
29306
+ skillCount: plan.skillNames.length,
29307
+ statePath: plan.statePath,
29308
+ render: {
29309
+ sections: [
29310
+ {
29311
+ title: "skills",
29312
+ lines: [
29313
+ `Scope: ${scope}`,
29314
+ `Agents: ${agents.join(", ")}`,
29315
+ `Current: ${plan.skillNames.length}`
29316
+ ]
29317
+ }
29318
+ ]
29319
+ }
29320
+ },
29321
+ { json: options.json }
29322
+ );
29323
+ return 0;
29039
29324
  }
29040
- return commands;
29041
- }
29042
- function runOneSkillsInstall(install) {
29043
- return new Promise((resolve14) => {
29044
- const child = spawn3(install.command, install.args, {
29045
- stdio: ["ignore", "ignore", "pipe"],
29046
- env: process.env
29047
- });
29048
- let stderr = "";
29049
- child.stderr.on("data", (chunk) => {
29050
- stderr += chunk.toString("utf-8");
29051
- });
29052
- child.on("error", (error) => {
29053
- resolve14({
29054
- ok: false,
29055
- detail: `failed to start ${install.command}: ${error.message}`,
29056
- manualCommand: install.manualCommand
29057
- });
29058
- });
29059
- child.on("close", (code) => {
29060
- if (code === 0) {
29061
- resolve14({ ok: true, detail: "", manualCommand: install.manualCommand });
29062
- return;
29063
- }
29064
- const detail = stderr.trim();
29065
- resolve14({
29066
- ok: false,
29067
- detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
29068
- manualCommand: install.manualCommand
29069
- });
29070
- });
29071
- });
29072
- }
29073
- async function runSkillsInstall(installs) {
29074
- const failures = [];
29075
- for (const install of installs) {
29076
- const result = await runOneSkillsInstall(install);
29077
- if (result.ok) return true;
29078
- failures.push(result);
29325
+ if (scope === "local" && root) {
29326
+ const managedNames = [
29327
+ .../* @__PURE__ */ new Set([...plan.skillNames, ...LEGACY_SKILL_NAMES_TO_REMOVE])
29328
+ ];
29329
+ const agentRoots = workspaceSkillRootsForAgents(plan.agents);
29330
+ ensureProjectPrivatePathsIgnored(root, [
29331
+ ".deepline/",
29332
+ ...agentRoots.flatMap(
29333
+ (agentRoot) => managedNames.map((name) => `${agentRoot}/skills/${name}/`)
29334
+ )
29335
+ ]);
29079
29336
  }
29080
- const details = failures.map((failure) => failure.detail).filter(Boolean).join("\n");
29081
- const manualCommand = failures.at(-1)?.manualCommand;
29082
29337
  process.stderr.write(
29083
- `SDK skills sync failed${details ? `:
29084
- ${details}` : ""}
29085
- ` + (manualCommand ? `Run manually: ${manualCommand}
29086
- ` : "")
29338
+ `Replacing Deepline skills for ${agents.join(", ")} (${scope})...
29339
+ `
29087
29340
  );
29088
- return false;
29089
- }
29090
- function runLegacySkillsCleanup() {
29091
- const candidates = hasCommand("bunx") ? [
29092
- {
29093
- command: "bunx",
29094
- args: [
29095
- "--bun",
29096
- "skills",
29097
- "remove",
29098
- "--global",
29099
- "-y",
29100
- ...LEGACY_SKILL_NAMES_TO_REMOVE
29101
- ]
29102
- },
29103
- {
29104
- command: "npx",
29105
- args: [
29106
- "--yes",
29107
- "skills",
29108
- "remove",
29109
- "--global",
29110
- "-y",
29111
- ...LEGACY_SKILL_NAMES_TO_REMOVE
29112
- ]
29341
+ try {
29342
+ const execute = dependencies.runProcess ?? runProcess;
29343
+ const removeCode = await execute(
29344
+ plan.remove.command,
29345
+ plan.remove.args,
29346
+ root ?? void 0
29347
+ );
29348
+ if (removeCode !== 0) {
29349
+ throw new Error("Could not remove the existing Deepline skills.");
29113
29350
  }
29114
- ] : [
29115
- {
29116
- command: "npx",
29117
- args: [
29118
- "--yes",
29119
- "skills",
29120
- "remove",
29121
- "--global",
29122
- "-y",
29123
- ...LEGACY_SKILL_NAMES_TO_REMOVE
29124
- ]
29351
+ const installCode = await execute(
29352
+ plan.install.command,
29353
+ plan.install.args,
29354
+ root ?? void 0
29355
+ );
29356
+ if (installCode !== 0) {
29357
+ throw new Error("Could not install the current Deepline skills.");
29125
29358
  }
29126
- ];
29127
- for (const candidate of candidates) {
29128
- const result = spawnSync2(candidate.command, candidate.args, {
29129
- stdio: "ignore",
29130
- env: process.env,
29131
- shell: process.platform === "win32"
29132
- });
29133
- if (result.status === 0) return;
29134
- }
29135
- }
29136
- function writeSdkSkillsStatusLine(line) {
29137
- const progress = getActiveCliProgress();
29138
- if (progress) {
29139
- progress.writeLine(line);
29140
- return;
29141
- }
29142
- process.stderr.write(`${line}
29143
- `);
29144
- }
29145
- async function syncSdkSkillsIfNeeded(baseUrl, options = {}) {
29146
- if (attemptedSync || shouldSkipSkillsSync()) return;
29147
- attemptedSync = true;
29148
- const usingPluginSkills = Boolean(activePluginSkillsDir());
29149
- if (usingPluginSkills) {
29150
- return;
29151
- }
29152
- const localVersion = readSdkSkillsLocalVersion(baseUrl);
29153
- const update = options.update === void 0 ? await fetchSkillsUpdate(baseUrl, localVersion) : options.update ? {
29154
- needsUpdate: options.update.needs_update,
29155
- remoteVersion: options.update.remote.version
29156
- } : null;
29157
- if (!update?.needsUpdate || !update.remoteVersion) {
29158
- return;
29359
+ } catch (error) {
29360
+ printCommandEnvelope(
29361
+ {
29362
+ ok: false,
29363
+ status: "failed",
29364
+ code: "SKILLS_INSTALL_FAILED",
29365
+ exitCode: 5,
29366
+ scope,
29367
+ agents,
29368
+ message: error instanceof Error ? error.message : String(error),
29369
+ next: `deepline skills --scope ${scope} --json`
29370
+ },
29371
+ { json: options.json }
29372
+ );
29373
+ return 5;
29159
29374
  }
29160
- const remoteSkillNames = await fetchV1SkillNames(baseUrl);
29161
- const skillNames = buildSdkSkillNames(
29162
- remoteSkillNames.length > 0 ? remoteSkillNames : DEFAULT_SDK_SKILL_NAMES
29375
+ mkdirSync9(dirname12(plan.statePath), { recursive: true });
29376
+ writeFileSync14(
29377
+ plan.statePath,
29378
+ `${JSON.stringify(
29379
+ {
29380
+ schemaVersion: 1,
29381
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
29382
+ cliVersion: SDK_VERSION,
29383
+ scope,
29384
+ agents,
29385
+ skillsVersion: plan.version,
29386
+ skillNames: plan.skillNames
29387
+ },
29388
+ null,
29389
+ 2
29390
+ )}
29391
+ `,
29392
+ "utf8"
29163
29393
  );
29164
- if (skillNames.length === 0) return;
29165
- const installs = resolveSkillsInstallCommands(baseUrl, skillNames);
29166
- if (installs.length === 0) {
29167
- writeUnavailableSkillsNotice(baseUrl, update.remoteVersion, skillNames);
29168
- return;
29169
- }
29170
- writeSdkSkillsStatusLine("Deepline skills changed; syncing agent skills...");
29171
- const installed = await runSkillsInstall(installs);
29172
- if (!installed) return;
29173
- runLegacySkillsCleanup();
29174
- writeLocalSkillsVersion(baseUrl, update.remoteVersion);
29175
- clearUnavailableSkillsNotice(baseUrl);
29176
- writeSdkSkillsStatusLine("Deepline agent skills are up to date.");
29394
+ printCommandEnvelope(
29395
+ {
29396
+ ok: true,
29397
+ status: "complete",
29398
+ scope,
29399
+ agents,
29400
+ skillsVersion: plan.version,
29401
+ skillCount: plan.skillNames.length,
29402
+ statePath: plan.statePath,
29403
+ render: {
29404
+ sections: [
29405
+ {
29406
+ title: "skills",
29407
+ lines: [
29408
+ `Scope: ${scope}`,
29409
+ `Agents: ${agents.join(", ")}`,
29410
+ `Installed: ${plan.skillNames.length}`
29411
+ ]
29412
+ }
29413
+ ]
29414
+ }
29415
+ },
29416
+ { json: options.json }
29417
+ );
29418
+ return 0;
29419
+ }
29420
+ function registerSkillsCommand(program) {
29421
+ program.command("skills").description("Replace Deepline agent skills with the current release.").option("--scope <scope>", "Install scope: global or local").option("--agent <agent>", "Override the detected agent target").option("--dry-run", "Print the install plan without changing files").option("--json", "Emit one JSON result envelope").addHelpText(
29422
+ "after",
29423
+ `
29424
+ Notes:
29425
+ This command removes and reinstalls only Deepline-managed skill names using
29426
+ skills@latest. Local scope writes into the resolved persistent project.
29427
+
29428
+ Examples:
29429
+ deepline skills --json
29430
+ deepline skills --scope local --json
29431
+ deepline skills --agent codex --dry-run --json
29432
+ `
29433
+ ).action(async (options) => {
29434
+ process.exitCode = await runSkillsCommand(options);
29435
+ });
29177
29436
  }
29178
29437
 
29179
29438
  // src/cli/commands/update.ts
@@ -29205,14 +29464,14 @@ function posixShellQuote(value) {
29205
29464
  function windowsCmdQuote(value) {
29206
29465
  return `"${value.replace(/"/g, '""')}"`;
29207
29466
  }
29208
- function shellQuote6(value) {
29467
+ function shellQuote5(value) {
29209
29468
  if (process.platform === "win32") {
29210
29469
  return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : windowsCmdQuote(value);
29211
29470
  }
29212
29471
  return posixShellQuote(value);
29213
29472
  }
29214
29473
  function buildSourceUpdateCommand(sourceRoot) {
29215
- const quotedRoot = shellQuote6(sourceRoot);
29474
+ const quotedRoot = shellQuote5(sourceRoot);
29216
29475
  const cdCommand = process.platform === "win32" ? `cd /d ${quotedRoot}` : `cd ${quotedRoot}`;
29217
29476
  return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
29218
29477
  }
@@ -29224,14 +29483,14 @@ function buildSidecarProjectConfigCommand(versionDir, nodeBin) {
29224
29483
  "fs.mkdirSync(dir,{recursive:true});",
29225
29484
  `fs.writeFileSync(path.join(dir,'package.json'),${JSON.stringify(NPM_SDK_SIDECAR_PACKAGE_JSON)});`
29226
29485
  ].join("");
29227
- return `${shellQuote6(nodeBin)} -e ${shellQuote6(script)} ${shellQuote6(versionDir)}`;
29486
+ return `${shellQuote5(nodeBin)} -e ${shellQuote5(script)} ${shellQuote5(versionDir)}`;
29228
29487
  }
29229
29488
  function sidecarStateDir(input2) {
29230
29489
  const scope = input2.env.DEEPLINE_CONFIG_SCOPE?.trim();
29231
29490
  if (!scope || scope.includes("/") || scope.includes("\\")) {
29232
29491
  return null;
29233
29492
  }
29234
- return join14(input2.homeDir, ".local", "deepline", scope, "sdk-cli");
29493
+ return join15(input2.homeDir, ".local", "deepline", scope, "sdk-cli");
29235
29494
  }
29236
29495
  function sidecarRegistryUrl(hostUrl) {
29237
29496
  let url;
@@ -29270,15 +29529,15 @@ function resolvePythonSidecarUpdatePlan(options) {
29270
29529
  resolve13(stateDir),
29271
29530
  resolve13(options.entrypoint)
29272
29531
  );
29273
- if (!relativeEntrypoint || relativeEntrypoint.startsWith("..") || isAbsolute2(relativeEntrypoint)) {
29532
+ if (!relativeEntrypoint || relativeEntrypoint.startsWith("..") || isAbsolute3(relativeEntrypoint)) {
29274
29533
  return null;
29275
29534
  }
29276
- const installMethod = readOptionalText(join14(stateDir, ".install-method"));
29535
+ const installMethod = readOptionalText(join15(stateDir, ".install-method"));
29277
29536
  if (installMethod !== "python-sidecar") return null;
29278
29537
  const scope = options.env.DEEPLINE_CONFIG_SCOPE?.trim() || "";
29279
29538
  const hostUrl = options.env.DEEPLINE_HOST_URL?.trim() || "";
29280
- const nodeBin = readOptionalText(join14(stateDir, ".node-bin")) || process.execPath;
29281
- const sidecarPath = readOptionalText(join14(stateDir, ".command-path")) || join14(
29539
+ const nodeBin = readOptionalText(join15(stateDir, ".node-bin")) || process.execPath;
29540
+ const sidecarPath = readOptionalText(join15(stateDir, ".command-path")) || join15(
29282
29541
  stateDir,
29283
29542
  "bin",
29284
29543
  process.platform === "win32" ? "deepline-sdk.cmd" : "deepline-sdk"
@@ -29286,8 +29545,8 @@ function resolvePythonSidecarUpdatePlan(options) {
29286
29545
  const packageSpec = options.packageSpec || "deepline@latest";
29287
29546
  const npmCommand = "npm";
29288
29547
  const registryUrl = sidecarRegistryUrl(hostUrl);
29289
- const versionDir = join14(stateDir, "versions", "<version>");
29290
- const manualCommand = `${buildSidecarProjectConfigCommand(versionDir, nodeBin)} && ${npmCommand} install --prefix ${shellQuote6(versionDir)} --registry ${shellQuote6(registryUrl)} ${NPM_SDK_INSTALL_COMMON_FLAGS.map(shellQuote6).join(" ")} ${shellQuote6(packageSpec)}`;
29548
+ const versionDir = join15(stateDir, "versions", "<version>");
29549
+ const manualCommand = `${buildSidecarProjectConfigCommand(versionDir, nodeBin)} && ${npmCommand} install --prefix ${shellQuote5(versionDir)} --registry ${shellQuote5(registryUrl)} ${NPM_SDK_INSTALL_COMMON_FLAGS.map(shellQuote5).join(" ")} ${shellQuote5(packageSpec)}`;
29291
29550
  return {
29292
29551
  kind: "python-sidecar",
29293
29552
  stateDir,
@@ -29304,7 +29563,7 @@ function resolvePythonSidecarUpdatePlan(options) {
29304
29563
  function findRepoBackedSdkRoot(startPath) {
29305
29564
  let current = resolve13(startPath);
29306
29565
  while (true) {
29307
- if (existsSync12(join14(current, "sdk", "package.json")) && existsSync12(join14(current, "sdk", "bin", "deepline-dev.ts"))) {
29566
+ if (existsSync12(join15(current, "sdk", "package.json")) && existsSync12(join15(current, "sdk", "bin", "deepline-dev.ts"))) {
29308
29567
  return current;
29309
29568
  }
29310
29569
  const parent = dirname13(current);
@@ -29335,7 +29594,7 @@ function inferNpmGlobalPrefixFromEntrypoint(entrypoint) {
29335
29594
  }
29336
29595
  function resolveUpdatePlan(options = {}) {
29337
29596
  const env = options.env ?? process.env;
29338
- const homeDir2 = options.homeDir ?? homedir11();
29597
+ const homeDir2 = options.homeDir ?? homedir12();
29339
29598
  const entrypoint = options.entrypoint ?? (process.argv[1] ? resolve13(process.argv[1]) : "");
29340
29599
  const sourceRoot = entrypoint ? findRepoBackedSdkRoot(dirname13(entrypoint)) : null;
29341
29600
  if (sourceRoot) {
@@ -29371,17 +29630,17 @@ function resolveUpdatePlan(options = {}) {
29371
29630
  fallbackRegistryUrl: publicNpmFallbackRegistryUrl(
29372
29631
  env.DEEPLINE_HOST_URL?.trim() || autoDetectBaseUrl()
29373
29632
  ),
29374
- manualCommand: `${command} ${args.map(shellQuote6).join(" ")}`
29633
+ manualCommand: `${command} ${args.map(shellQuote5).join(" ")}`
29375
29634
  };
29376
29635
  }
29377
29636
  var AUTO_UPDATE_FAILURE_FILE = ".auto-update-failure.json";
29378
29637
  function autoUpdateFailurePath(plan) {
29379
29638
  if (plan.kind === "source") return null;
29380
29639
  if (plan.kind === "python-sidecar") {
29381
- return join14(plan.stateDir, AUTO_UPDATE_FAILURE_FILE);
29640
+ return join15(plan.stateDir, AUTO_UPDATE_FAILURE_FILE);
29382
29641
  }
29383
- return join14(
29384
- homedir11(),
29642
+ return join15(
29643
+ homedir12(),
29385
29644
  ".local",
29386
29645
  "deepline",
29387
29646
  "sdk-cli",
@@ -29429,7 +29688,7 @@ function clearAutoUpdateFailure(plan) {
29429
29688
  const path = autoUpdateFailurePath(plan);
29430
29689
  if (!path) return;
29431
29690
  try {
29432
- unlinkSync2(path);
29691
+ unlinkSync(path);
29433
29692
  } catch {
29434
29693
  }
29435
29694
  }
@@ -29465,7 +29724,7 @@ function safeVersionSegment(value) {
29465
29724
  return /^[0-9A-Za-z._-]+$/.test(normalized) ? normalized : "";
29466
29725
  }
29467
29726
  function entryPathInVersionDir(versionDir) {
29468
- return join14(
29727
+ return join15(
29469
29728
  versionDir,
29470
29729
  "node_modules",
29471
29730
  "deepline",
@@ -29475,7 +29734,7 @@ function entryPathInVersionDir(versionDir) {
29475
29734
  );
29476
29735
  }
29477
29736
  function installedPackageVersion(versionDir) {
29478
- const packageJsonPath = join14(
29737
+ const packageJsonPath = join15(
29479
29738
  versionDir,
29480
29739
  "node_modules",
29481
29740
  "deepline",
@@ -29576,23 +29835,23 @@ function writeSidecarLauncher(input2) {
29576
29835
  input2.path,
29577
29836
  [
29578
29837
  "#!/usr/bin/env sh",
29579
- `export DEEPLINE_HOST_URL=${shellQuote6(input2.hostUrl)}`,
29580
- `export DEEPLINE_CONFIG_SCOPE=${shellQuote6(input2.scope)}`,
29581
- `exec ${shellQuote6(input2.nodeBin)} ${shellQuote6(input2.entryPath)} "$@"`,
29838
+ `export DEEPLINE_HOST_URL=${shellQuote5(input2.hostUrl)}`,
29839
+ `export DEEPLINE_CONFIG_SCOPE=${shellQuote5(input2.scope)}`,
29840
+ `exec ${shellQuote5(input2.nodeBin)} ${shellQuote5(input2.entryPath)} "$@"`,
29582
29841
  ""
29583
29842
  ].join("\n"),
29584
29843
  { encoding: "utf8", mode: 493 }
29585
29844
  );
29586
29845
  }
29587
29846
  async function runPythonSidecarUpdatePlan(plan) {
29588
- const versionsDir = join14(plan.stateDir, "versions");
29589
- const tempDir = join14(
29847
+ const versionsDir = join15(plan.stateDir, "versions");
29848
+ const tempDir = join15(
29590
29849
  versionsDir,
29591
29850
  `.tmp-sdk-update-${process.pid}-${Date.now()}`
29592
29851
  );
29593
29852
  rmSync4(tempDir, { recursive: true, force: true });
29594
29853
  mkdirSync10(tempDir, { recursive: true });
29595
- writeFileSync15(join14(tempDir, "package.json"), NPM_SDK_SIDECAR_PACKAGE_JSON);
29854
+ writeFileSync15(join15(tempDir, "package.json"), NPM_SDK_SIDECAR_PACKAGE_JSON);
29596
29855
  const env = {
29597
29856
  ...process.env,
29598
29857
  PATH: `${dirname13(plan.nodeBin)}${process.platform === "win32" ? ";" : ":"}${process.env.PATH ?? ""}`
@@ -29623,7 +29882,7 @@ async function runPythonSidecarUpdatePlan(plan) {
29623
29882
  rmSync4(tempDir, { recursive: true, force: true });
29624
29883
  return 1;
29625
29884
  }
29626
- const finalDir = join14(versionsDir, installedVersion);
29885
+ const finalDir = join15(versionsDir, installedVersion);
29627
29886
  const finalEntryPath = entryPathInVersionDir(finalDir);
29628
29887
  if (existsSync12(finalEntryPath)) {
29629
29888
  rmSync4(tempDir, { recursive: true, force: true });
@@ -29655,27 +29914,27 @@ async function runPythonSidecarUpdatePlan(plan) {
29655
29914
  entryPath: finalEntryPath
29656
29915
  });
29657
29916
  writeFileSync15(
29658
- join14(plan.stateDir, ".version"),
29917
+ join15(plan.stateDir, ".version"),
29659
29918
  `${installedVersion}
29660
29919
  `,
29661
29920
  "utf8"
29662
29921
  );
29663
29922
  writeFileSync15(
29664
- join14(plan.stateDir, ".install-method"),
29923
+ join15(plan.stateDir, ".install-method"),
29665
29924
  "python-sidecar\n",
29666
29925
  "utf8"
29667
29926
  );
29668
29927
  writeFileSync15(
29669
- join14(plan.stateDir, ".command-path"),
29928
+ join15(plan.stateDir, ".command-path"),
29670
29929
  `${plan.sidecarPath}
29671
29930
  `,
29672
29931
  "utf8"
29673
29932
  );
29674
- writeFileSync15(join14(plan.stateDir, ".runner"), "node\n", "utf8");
29675
- writeFileSync15(join14(plan.stateDir, ".node-bin"), `${plan.nodeBin}
29933
+ writeFileSync15(join15(plan.stateDir, ".runner"), "node\n", "utf8");
29934
+ writeFileSync15(join15(plan.stateDir, ".node-bin"), `${plan.nodeBin}
29676
29935
  `, "utf8");
29677
29936
  writeFileSync15(
29678
- join14(plan.stateDir, ".entry-path"),
29937
+ join15(plan.stateDir, ".entry-path"),
29679
29938
  `${finalEntryPath}
29680
29939
  `,
29681
29940
  "utf8"
@@ -29708,7 +29967,20 @@ async function runUpdateCommand(options, dependencies = {}) {
29708
29967
  const detectBaseUrl = dependencies.detectBaseUrl ?? autoDetectBaseUrl;
29709
29968
  const resolvePlan = dependencies.resolvePlan ?? resolveUpdatePlan;
29710
29969
  const runPlan = dependencies.runPlan ?? runUpdatePlan;
29711
- const syncSkills = dependencies.syncSkillsIfNeeded ?? syncSdkSkillsIfNeeded;
29970
+ const syncSkills = dependencies.syncSkillsIfNeeded ?? (async () => {
29971
+ const originalWrite = process.stdout.write.bind(process.stdout);
29972
+ process.stdout.write = (() => true);
29973
+ try {
29974
+ const exitCode = await runSkillsCommand({ json: true });
29975
+ if (exitCode !== 0) {
29976
+ throw new Error(
29977
+ `Deepline skills update failed with exit ${exitCode}.`
29978
+ );
29979
+ }
29980
+ } finally {
29981
+ process.stdout.write = originalWrite;
29982
+ }
29983
+ });
29712
29984
  const stderr = dependencies.stderr ?? process.stderr;
29713
29985
  const plan = resolvePlan();
29714
29986
  const render = {
@@ -29776,210 +30048,1409 @@ Examples:
29776
30048
  });
29777
30049
  }
29778
30050
 
29779
- // ../shared_libs/cli/command-compatibility.json
29780
- var command_compatibility_default = {
29781
- enrich: {
29782
- family: "python",
29783
- label: "a legacy Python CLI enrichment command",
29784
- sdk_alternative: "Use `deepline plays ...` for durable workflows or `deepline tools execute ...` for one tool call."
29785
- },
29786
- session: {
29787
- family: "python",
29788
- label: "a legacy Python CLI session/playground command",
29789
- sdk_alternative: "Use `deepline sessions send ...` or `deepline sessions render ...` for transcript workflows."
29790
- },
29791
- workflows: {
29792
- family: "python",
29793
- label: "a legacy Python CLI workflow command",
29794
- sdk_alternative: "Use `deepline plays ...` in the SDK CLI."
29795
- },
29796
- events: {
29797
- family: "python",
29798
- label: "a legacy Python CLI event command"
29799
- },
29800
- plays: {
29801
- family: "sdk",
29802
- label: "an SDK CLI play command",
29803
- python_alternative: "Use `deepline workflows ...` only for legacy workflows."
29804
- },
29805
- runs: {
29806
- family: "sdk",
29807
- label: "an SDK CLI run inspection command"
29808
- },
29809
- sessions: {
29810
- family: "sdk",
29811
- label: "an SDK CLI session transcript command"
29812
- },
29813
- health: {
29814
- family: "sdk",
29815
- label: "an SDK CLI health command"
30051
+ // src/cli/commands/setup.ts
30052
+ import { spawnSync as spawnSync2 } from "child_process";
30053
+ import {
30054
+ existsSync as existsSync13,
30055
+ lstatSync,
30056
+ mkdirSync as mkdirSync11,
30057
+ readFileSync as readFileSync14,
30058
+ realpathSync as realpathSync4,
30059
+ rmSync as rmSync5,
30060
+ writeFileSync as writeFileSync16
30061
+ } from "fs";
30062
+ import { homedir as homedir13 } from "os";
30063
+ import { dirname as dirname14, join as join16, resolve as resolve14 } from "path";
30064
+ var SETUP_PHASE_NAMES = [
30065
+ "cli",
30066
+ "cleanup",
30067
+ "skills",
30068
+ "auth",
30069
+ "verify"
30070
+ ];
30071
+ function initialSetupPhases() {
30072
+ return {
30073
+ cli: { status: "pending" },
30074
+ cleanup: { status: "pending" },
30075
+ skills: { status: "pending" },
30076
+ auth: { status: "pending" },
30077
+ verify: { status: "pending" }
30078
+ };
30079
+ }
30080
+ function isSetupPhaseStatus(value) {
30081
+ return value === "pending" || value === "in_progress" || value === "complete" || value === "waiting" || value === "failed";
30082
+ }
30083
+ function parseSetupPhases(value) {
30084
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
30085
+ const source = value;
30086
+ const phases = initialSetupPhases();
30087
+ for (const name of SETUP_PHASE_NAMES) {
30088
+ const phase = source[name];
30089
+ if (!phase || typeof phase !== "object" || Array.isArray(phase)) {
30090
+ return null;
30091
+ }
30092
+ const record = phase;
30093
+ if (!isSetupPhaseStatus(record.status)) return null;
30094
+ phases[name] = {
30095
+ status: record.status,
30096
+ ...typeof record.outcome === "string" ? { outcome: record.outcome } : {},
30097
+ ...typeof record.code === "string" ? { code: record.code } : {}
30098
+ };
29816
30099
  }
29817
- };
29818
-
29819
- // src/cli/command-compatibility.ts
29820
- var COMMAND_COMPATIBILITY = command_compatibility_default;
29821
- function cliFamilyLabel(family) {
29822
- return family === "sdk" ? "SDK CLI" : "legacy Python CLI";
30100
+ return phases;
29823
30101
  }
29824
- function commandCompatibilityHint(currentFamily, commandName, baseUrl) {
29825
- const compatibility = COMMAND_COMPATIBILITY[commandName];
29826
- if (!compatibility || compatibility.family === currentFamily) {
29827
- return null;
30102
+ function phasesFromLegacyStatus(status) {
30103
+ const phases = initialSetupPhases();
30104
+ if (status === "skills_installed" || status === "authorization_pending" || status === "complete") {
30105
+ phases.cli = { status: "complete" };
30106
+ phases.cleanup = { status: "complete" };
30107
+ phases.skills = { status: "complete" };
29828
30108
  }
29829
- const expectedFamily = compatibility.family;
29830
- const currentLabel = cliFamilyLabel(currentFamily);
29831
- const expectedLabel = cliFamilyLabel(expectedFamily);
29832
- const lines = [
29833
- "",
29834
- "Command compatibility:",
29835
- ` \`deepline ${commandName}\` is ${compatibility.label}.`,
29836
- ` Current binary: ${currentLabel}. Required binary: ${expectedLabel}.`,
29837
- " If this came from an agent skill, the installed skill likely targets the other Deepline CLI."
29838
- ];
29839
- if (currentFamily === "sdk") {
29840
- lines.push(
29841
- "",
29842
- " To stay on the SDK CLI, refresh the Deepline agent skills:",
29843
- ` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
29844
- " To use the legacy Python CLI instead:",
29845
- ` ${legacyPythonInstallCommand(baseUrl)}`,
29846
- " `deepline update` updates this SDK CLI, but it will not switch CLI families."
29847
- );
29848
- if (compatibility.sdk_alternative) {
29849
- lines.push(` SDK alternative: ${compatibility.sdk_alternative}`);
29850
- }
29851
- } else {
29852
- lines.push(
29853
- "",
29854
- " To use SDK commands, install the SDK CLI and refresh Deepline agent skills:",
29855
- ` ${sdkNpmGlobalInstallCommand()}`,
29856
- ` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
29857
- " `deepline update` updates this Python CLI and its skills, but it will not switch CLI families."
30109
+ if (status === "authorization_pending") {
30110
+ phases.auth = { status: "waiting", outcome: "authorization_pending" };
30111
+ } else if (status === "complete") {
30112
+ phases.auth = { status: "complete" };
30113
+ phases.verify = { status: "complete" };
30114
+ }
30115
+ return phases;
30116
+ }
30117
+ function readSetupState(input2) {
30118
+ try {
30119
+ const parsed = JSON.parse(
30120
+ readFileSync14(
30121
+ setupStatePath(input2.baseUrl, input2.scope, input2.root),
30122
+ "utf8"
30123
+ )
29858
30124
  );
29859
- if (compatibility.python_alternative) {
29860
- lines.push(` Python alternative: ${compatibility.python_alternative}`);
30125
+ if (parsed.host !== input2.baseUrl || parsed.scope !== input2.scope || parsed.root !== input2.root || typeof parsed.status !== "string") {
30126
+ return null;
29861
30127
  }
30128
+ return {
30129
+ status: parsed.status,
30130
+ phases: parseSetupPhases(parsed.phases) ?? phasesFromLegacyStatus(parsed.status)
30131
+ };
30132
+ } catch {
30133
+ return null;
29862
30134
  }
29863
- return lines.join("\n");
29864
30135
  }
29865
- function unknownCommandNameFromMessage(message) {
29866
- const match = message.match(/unknown command ['"]([^'"]+)['"]/i);
29867
- const command = match?.[1]?.trim();
29868
- return command ? command : null;
30136
+ function selectSetupProgress(previousState) {
30137
+ const resumed = Boolean(previousState && previousState.status !== "complete");
30138
+ return {
30139
+ resumed,
30140
+ phases: resumed ? previousState.phases : initialSetupPhases()
30141
+ };
29869
30142
  }
29870
-
29871
- // src/cli/self-update.ts
29872
- import { spawn as spawn5 } from "child_process";
29873
- function envTruthy(name) {
29874
- const value = process.env[name]?.trim().toLowerCase();
29875
- return value === "1" || value === "true" || value === "yes";
30143
+ function normalizeScope2(value) {
30144
+ if (!value || value === "global") return "global";
30145
+ if (value === "local") return "local";
30146
+ throw new Error("--scope must be one of: global, local");
29876
30147
  }
29877
- function isCi() {
29878
- return envTruthy("CI") || envTruthy("GITHUB_ACTIONS");
30148
+ function resolveScopeRoot(scope) {
30149
+ if (scope === "global") return null;
30150
+ const target = resolveProjectPinTarget();
30151
+ if (!target.ok) {
30152
+ throw new Error(
30153
+ `Cowork project folder is ambiguous. Candidate folders: ${target.candidates.join(
30154
+ ", "
30155
+ )}. Set CLAUDE_PROJECT_DIR or cd into the intended project folder.`
30156
+ );
30157
+ }
30158
+ return target.dir;
29879
30159
  }
29880
- function shouldSkipSelfUpdate() {
29881
- return envTruthy("DEEPLINE_SKIP_SELF_UPDATE") || envTruthy("DEEPLINE_NO_AUTO_UPDATE") || envTruthy("DEEPLINE_SKIP_SDK_AUTO_UPDATE") || envTruthy("DEEPLINE_DISABLE_AUTO_UPDATE") || isCi();
30160
+ function authScopeForSetup(scope) {
30161
+ return scope === "local" ? "folder" : "global";
29882
30162
  }
29883
- function parseSemver(version) {
29884
- const trimmed = version?.trim();
29885
- if (!trimmed) return null;
29886
- const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(
29887
- trimmed
29888
- );
29889
- if (!match) return null;
29890
- return {
29891
- major: Number(match[1]),
29892
- minor: Number(match[2]),
29893
- patch: Number(match[3]),
29894
- prerelease: match[4] ?? ""
29895
- };
30163
+ function setupStatePath(baseUrl, scope, root) {
30164
+ return scope === "local" && root ? join16(root, ".deepline", "setup", "state.json") : join16(sdkCliStateDirPath(baseUrl), "setup.json");
29896
30165
  }
29897
- function compareSemver(left, right) {
29898
- const a = parseSemver(left);
29899
- const b = parseSemver(right);
29900
- if (!a || !b) {
29901
- return left.localeCompare(right);
30166
+ async function captureStdout2(run) {
30167
+ let stdout = "";
30168
+ const originalWrite = process.stdout.write.bind(process.stdout);
30169
+ process.stdout.write = ((chunk) => {
30170
+ stdout += typeof chunk === "string" ? chunk : String(chunk);
30171
+ return true;
30172
+ });
30173
+ try {
30174
+ return { exitCode: await run(), stdout, error: null };
30175
+ } catch (error) {
30176
+ return { exitCode: 4, stdout, error };
30177
+ } finally {
30178
+ process.stdout.write = originalWrite;
29902
30179
  }
29903
- for (const key of ["major", "minor", "patch"]) {
29904
- if (a[key] !== b[key]) return a[key] > b[key] ? 1 : -1;
30180
+ }
30181
+ function parseCapturedJson(stdout) {
30182
+ try {
30183
+ return JSON.parse(stdout.trim());
30184
+ } catch {
30185
+ return null;
29905
30186
  }
29906
- if (a.prerelease === b.prerelease) return 0;
29907
- if (!a.prerelease) return 1;
29908
- if (!b.prerelease) return -1;
29909
- return a.prerelease.localeCompare(b.prerelease);
29910
30187
  }
29911
- function isDowngradeAutoUpdateResponse(response) {
29912
- const target = response?.latest?.trim();
29913
- const current = response?.current?.trim() || SDK_VERSION;
29914
- if (!target) return false;
29915
- return compareSemver(target, current) < 0;
30188
+ function asRecord2(value) {
30189
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
29916
30190
  }
29917
- function relaunchCurrentCommand(plan) {
29918
- return new Promise((resolve14) => {
29919
- const command = plan.kind === "python-sidecar" ? plan.sidecarPath : process.execPath;
29920
- const args = plan.kind === "python-sidecar" ? process.argv.slice(2) : process.argv.slice(1);
29921
- const child = spawn5(command, args, {
29922
- stdio: "inherit",
29923
- shell: process.platform === "win32",
29924
- env: {
29925
- ...process.env,
29926
- DEEPLINE_NO_AUTO_UPDATE: "1"
29927
- }
29928
- });
29929
- child.on("error", (error) => {
29930
- process.stderr.write(
29931
- `Deepline SDK/CLI updated, but relaunch failed: ${error.message}
29932
- `
29933
- );
29934
- resolve14(1);
29935
- });
29936
- child.on("close", (code) => resolve14(code ?? 1));
29937
- });
30191
+ function printCapturedAuthorizationUrl(stdout) {
30192
+ const urls = stdout.match(/https:\/\/[^\s]+/g) ?? [];
30193
+ for (const url of new Set(
30194
+ urls.map((value) => value.replace(/[).,]+$/, ""))
30195
+ )) {
30196
+ process.stderr.write(`Authorize Deepline: ${url}
30197
+ `);
30198
+ }
29938
30199
  }
29939
- async function maybeAutoUpdateAndRelaunch(response) {
29940
- const autoUpdate = response?.auto_update;
29941
- if (!response || !autoUpdate?.should_auto_update || shouldSkipSelfUpdate()) {
29942
- return false;
30200
+ function safeRead(path) {
30201
+ try {
30202
+ return readFileSync14(path, "utf8");
30203
+ } catch {
30204
+ return "";
29943
30205
  }
29944
- if (isDowngradeAutoUpdateResponse(response)) {
29945
- const target = response.latest;
29946
- const current = response.current?.trim() || SDK_VERSION;
29947
- process.stderr.write(
29948
- `Deepline SDK/CLI auto-update refused: server advertised older ${target} than current ${current}. Continuing without mutating the CLI.
29949
- `
29950
- );
30206
+ }
30207
+ function isNpmManagedDeeplinePath(path) {
30208
+ try {
30209
+ return realpathSync4(path).includes(`${join16("node_modules", "deepline")}`);
30210
+ } catch {
29951
30211
  return false;
29952
30212
  }
29953
- const packageSpec = response.latest ? `deepline@${response.latest}` : void 0;
29954
- const plan = resolveUpdatePlan({ packageSpec });
29955
- if (plan.kind === "source") {
29956
- return false;
30213
+ }
30214
+ function removeKnownLegacyPaths(baseUrl) {
30215
+ const home = homedir13();
30216
+ const hostDir = join16(home, ".local", "deepline", baseUrlSlug(baseUrl));
30217
+ const installerCommandPath = safeRead(
30218
+ join16(hostDir, "sdk", ".command-path")
30219
+ ).trim();
30220
+ const candidates = [
30221
+ join16(home, ".local", "bin", "deepline-real"),
30222
+ join16(hostDir, "bin", "deepline"),
30223
+ join16(hostDir, "bin", "deepline-real"),
30224
+ join16(hostDir, "cli", ".install-method"),
30225
+ join16(hostDir, "cli", ".version"),
30226
+ join16(hostDir, "sdk", ".install-method"),
30227
+ join16(hostDir, "sdk", ".command-path"),
30228
+ ...installerCommandPath ? [
30229
+ installerCommandPath,
30230
+ join16(dirname14(installerCommandPath), "deepline-sdk")
30231
+ ] : []
30232
+ ];
30233
+ const removed = [];
30234
+ for (const path of candidates) {
30235
+ if (!existsSync13(path)) continue;
30236
+ if (path === installerCommandPath && isNpmManagedDeeplinePath(path)) {
30237
+ continue;
30238
+ }
30239
+ rmSync5(path, { force: true });
30240
+ removed.push(path);
29957
30241
  }
29958
- const label = autoUpdate.reason === "rollback_forced" ? "has a server rollback pending and needs the latest rollback-aware CLI" : autoUpdate.reason === "deprecated" ? "is deprecated and will update automatically" : autoUpdate.required ? "requires an update" : "is more than the supported auto-update lag behind";
29959
- process.stderr.write(
29960
- `Deepline SDK/CLI ${label}; running ${plan.manualCommand}
29961
- `
30242
+ return removed;
30243
+ }
30244
+ function resolvePathCommands(command) {
30245
+ const lookup = spawnSync2(
30246
+ process.platform === "win32" ? "where" : "which",
30247
+ process.platform === "win32" ? [command] : ["-a", command],
30248
+ { encoding: "utf8", shell: process.platform === "win32" }
29962
30249
  );
29963
- const updateResult = await runAutomaticUpdatePlan(plan);
29964
- if (updateResult.status === "skipped_previous_failure") {
29965
- return false;
30250
+ return [
30251
+ ...new Set(
30252
+ String(lookup.stdout ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((path) => resolve14(path))
30253
+ )
30254
+ ];
30255
+ }
30256
+ function resolvePathCommand(command) {
30257
+ return resolvePathCommands(command)[0] ?? null;
30258
+ }
30259
+ function resolvePersistentGlobalCommand() {
30260
+ const prefix = spawnSync2("npm", ["prefix", "-g"], { encoding: "utf8" });
30261
+ if (prefix.status !== 0) return null;
30262
+ const root = String(prefix.stdout ?? "").trim();
30263
+ if (!root) return null;
30264
+ const candidates = process.platform === "win32" ? [join16(root, "deepline.cmd"), join16(root, "deepline")] : [join16(root, "bin", "deepline")];
30265
+ return candidates.find((candidate) => existsSync13(candidate)) ?? null;
30266
+ }
30267
+ function inspectGlobalCliAvailability(input2) {
30268
+ const persistentPath = input2 && "persistentGlobalCli" in input2 ? input2.persistentGlobalCli ?? null : resolvePersistentGlobalCommand();
30269
+ const pathClis = input2?.pathClis ?? resolvePathCommands("deepline");
30270
+ const path = persistentPath ? pathClis.find(
30271
+ (candidate) => pathsResolveToSameFile(candidate, persistentPath)
30272
+ ) ?? null : null;
30273
+ return { ok: Boolean(path), persistentPath, path };
30274
+ }
30275
+ function pathsResolveToSameFile(left, right) {
30276
+ try {
30277
+ return realpathSync4(left) === realpathSync4(right);
30278
+ } catch {
30279
+ return resolve14(left) === resolve14(right);
29966
30280
  }
29967
- if (updateResult.exitCode !== 0) {
29968
- if (autoUpdate.required) {
29969
- throw new Error(
29970
- `Automatic Deepline SDK/CLI update failed with exit code ${updateResult.exitCode}. ${response.message}`
29971
- );
30281
+ }
30282
+ function isKnownDeeplineCommand(path) {
30283
+ const entrypoint = process.argv[1] ? resolve14(process.argv[1]) : "";
30284
+ let resolvedPath = path;
30285
+ try {
30286
+ resolvedPath = realpathSync4(path);
30287
+ } catch {
30288
+ }
30289
+ if (entrypoint && resolvedPath === entrypoint) return true;
30290
+ if (resolvedPath.includes(`${join16("node_modules", "deepline")}`)) return true;
30291
+ const content = safeRead(path);
30292
+ return content.includes("node_modules/deepline") || content.includes("node_modules\\deepline") || content.includes("DEEPLINE_CONFIG_SCOPE") || content.includes("deepline-real");
30293
+ }
30294
+ function inspectPathConflict() {
30295
+ const commandPath = resolvePathCommand("deepline");
30296
+ if (!commandPath || isKnownDeeplineCommand(commandPath)) return null;
30297
+ try {
30298
+ if (lstatSync(commandPath).isSymbolicLink()) {
30299
+ const target = realpathSync4(commandPath);
30300
+ if (target.includes(`${join16("node_modules", "deepline")}`)) return null;
29972
30301
  }
29973
- process.stderr.write(
29974
- `Deepline SDK/CLI auto-update failed with exit code ${updateResult.exitCode}; continuing with ${response.current ?? "current version"}.
29975
- `
29976
- );
29977
- return false;
30302
+ } catch {
29978
30303
  }
29979
- process.stderr.write("Deepline SDK/CLI updated; rerunning command.\n");
29980
- const exitCode = await relaunchCurrentCommand(plan);
29981
- process.exit(exitCode);
29982
- return true;
30304
+ return commandPath;
30305
+ }
30306
+ function writeSetupState(input2) {
30307
+ const path = setupStatePath(input2.baseUrl, input2.scope, input2.root);
30308
+ mkdirSync11(dirname14(path), { recursive: true });
30309
+ writeFileSync16(
30310
+ path,
30311
+ `${JSON.stringify(
30312
+ {
30313
+ schemaVersion: 2,
30314
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
30315
+ cliVersion: SDK_VERSION,
30316
+ host: input2.baseUrl,
30317
+ scope: input2.scope,
30318
+ root: input2.root,
30319
+ status: input2.status,
30320
+ phases: input2.phases
30321
+ },
30322
+ null,
30323
+ 2
30324
+ )}
30325
+ `,
30326
+ "utf8"
30327
+ );
30328
+ return path;
30329
+ }
30330
+ function persistSetupProgress(input2) {
30331
+ return writeSetupState({
30332
+ ...input2,
30333
+ status: input2.status ?? "in_progress"
30334
+ });
30335
+ }
30336
+ function completeSetupPhase(phases, phase, outcome) {
30337
+ phases[phase] = {
30338
+ status: "complete",
30339
+ ...outcome ? { outcome } : {}
30340
+ };
30341
+ }
30342
+ function beginSetupPhase(phases, phase) {
30343
+ phases[phase] = { status: "in_progress" };
30344
+ }
30345
+ function failSetupPhase(phases, phase, code) {
30346
+ phases[phase] = { status: "failed", code };
30347
+ }
30348
+ function rollbackCommand(scope, root) {
30349
+ const prefix = scope === "local" && root ? ` --prefix ${JSON.stringify(join16(root, ".deepline", "runtime"))}` : "";
30350
+ return `npm install -g${prefix} --no-audit --no-fund --include=optional --allow-scripts=esbuild deepline@${SDK_VERSION}`;
30351
+ }
30352
+ function setupQuickstartCommand(baseUrl) {
30353
+ return baseUrl === "https://code.deepline.com" ? "deepline quickstart" : `DEEPLINE_HOST_URL=${JSON.stringify(baseUrl)} deepline quickstart`;
30354
+ }
30355
+ function setupResumeCommand(baseUrl, scope) {
30356
+ const hostPrefix = baseUrl === "https://code.deepline.com" ? "" : `DEEPLINE_HOST_URL=${JSON.stringify(baseUrl)} `;
30357
+ return `${hostPrefix}deepline setup --scope ${scope} --json`;
30358
+ }
30359
+ function setupRetry(input2) {
30360
+ return {
30361
+ phase: input2.phase,
30362
+ command: setupResumeCommand(input2.baseUrl, input2.scope),
30363
+ automatic: true
30364
+ };
30365
+ }
30366
+ function reportSetupPhaseFailure(input2) {
30367
+ failSetupPhase(input2.phases, input2.phase, input2.code);
30368
+ const statePath = persistSetupProgress({
30369
+ baseUrl: input2.baseUrl,
30370
+ scope: input2.scope,
30371
+ root: input2.root,
30372
+ phases: input2.phases,
30373
+ status: "failed"
30374
+ });
30375
+ const retry = setupRetry(input2);
30376
+ printCommandEnvelope(
30377
+ {
30378
+ ok: false,
30379
+ status: "failed",
30380
+ code: input2.code,
30381
+ exitCode: input2.exitCode,
30382
+ scope: input2.scope,
30383
+ message: input2.message,
30384
+ phases: input2.phases,
30385
+ failedPhase: input2.phase,
30386
+ retry,
30387
+ statePath,
30388
+ next: retry.command,
30389
+ ...input2.extra ?? {}
30390
+ },
30391
+ { json: input2.json }
30392
+ );
30393
+ return input2.exitCode;
30394
+ }
30395
+ async function readAuthStatus(authScope) {
30396
+ try {
30397
+ const captured = await captureStdout2(
30398
+ () => handleStatus([
30399
+ "--json",
30400
+ ...authScope ? ["--auth-scope", authScope] : []
30401
+ ])
30402
+ );
30403
+ return {
30404
+ exitCode: captured.exitCode,
30405
+ payload: parseCapturedJson(captured.stdout),
30406
+ error: captured.error
30407
+ };
30408
+ } catch (error) {
30409
+ return { exitCode: 4, payload: null, error };
30410
+ }
30411
+ }
30412
+ function reportSetupAuthStatusFailure(input2) {
30413
+ if (!input2.auth.error) return null;
30414
+ return reportSetupPhaseFailure({
30415
+ baseUrl: input2.baseUrl,
30416
+ scope: input2.scope,
30417
+ root: input2.root,
30418
+ phases: input2.phases,
30419
+ phase: "auth",
30420
+ code: "AUTH_STATUS_FAILED",
30421
+ exitCode: 4,
30422
+ message: "Deepline could not verify authorization with the configured host.",
30423
+ json: input2.json,
30424
+ extra: { authScope: input2.authScope }
30425
+ });
30426
+ }
30427
+ function buildDoctorAssessment(input2) {
30428
+ const skillsStatePath = skillsStatePathForScope(
30429
+ input2.baseUrl,
30430
+ input2.scope,
30431
+ input2.root
30432
+ );
30433
+ const skillsState = parseCapturedJson(safeRead(skillsStatePath));
30434
+ const apiKey = input2.scope === "local" ? resolveProjectApiKeyForBaseUrl(input2.baseUrl) : resolveGlobalApiKeyForBaseUrl(input2.baseUrl);
30435
+ const projectAuth = input2.scope === "local" && apiKey ? getResolvedProjectAuthSource(input2.baseUrl, apiKey) : null;
30436
+ const connected = input2.authStatus.payload?.connected === true;
30437
+ const authScopeOk = input2.scope === "local" ? Boolean(projectAuth) : Boolean(apiKey && !projectAuth);
30438
+ const skillsOk = skillsState?.scope === input2.scope && typeof skillsState.skillsVersion === "string" && Array.isArray(skillsState.agents) && skillsState.agents.length > 0;
30439
+ const runningCliPath = process.argv[1] ? resolve14(process.argv[1]) : null;
30440
+ const globalCli = input2.scope === "global" ? inspectGlobalCliAvailability() : null;
30441
+ const pathGlobalCli = globalCli?.path ?? null;
30442
+ const cliPath = input2.scope === "global" ? pathGlobalCli : runningCliPath;
30443
+ const cliScopeOk = input2.scope === "global" ? Boolean(pathGlobalCli) : Boolean(
30444
+ input2.root && runningCliPath?.includes(join16(input2.root, ".deepline", "runtime"))
30445
+ );
30446
+ const checks = {
30447
+ cli: {
30448
+ ok: Boolean(cliPath) && cliScopeOk,
30449
+ version: SDK_VERSION,
30450
+ path: cliPath,
30451
+ scope: input2.scope
30452
+ },
30453
+ skills: {
30454
+ ok: skillsOk,
30455
+ statePath: skillsStatePath,
30456
+ version: skillsState?.skillsVersion ?? null,
30457
+ agents: skillsState?.agents ?? []
30458
+ },
30459
+ auth: {
30460
+ ok: connected && authScopeOk,
30461
+ scope: projectAuth ? "local" : apiKey ? "global" : null,
30462
+ status: input2.authStatus.payload?.status ?? "not_connected"
30463
+ },
30464
+ api: {
30465
+ ok: connected && input2.authStatus.exitCode === 0,
30466
+ host: input2.baseUrl,
30467
+ workspace: input2.authStatus.payload?.workspace ?? null,
30468
+ providerSpend: false
30469
+ }
30470
+ };
30471
+ return {
30472
+ ok: Object.values(checks).every((check) => check.ok),
30473
+ checks
30474
+ };
30475
+ }
30476
+ async function runDoctorCommand(options) {
30477
+ let scope;
30478
+ let root;
30479
+ try {
30480
+ scope = normalizeScope2(options.scope);
30481
+ root = resolveScopeRoot(scope);
30482
+ } catch (error) {
30483
+ printCommandEnvelope(
30484
+ {
30485
+ ok: false,
30486
+ status: "failed",
30487
+ code: "INVALID_SETUP_SCOPE",
30488
+ exitCode: 2,
30489
+ message: error instanceof Error ? error.message : String(error)
30490
+ },
30491
+ { json: options.json }
30492
+ );
30493
+ return 2;
30494
+ }
30495
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30496
+ const authScope = authScopeForSetup(scope);
30497
+ const authStatus = await readAuthStatus(authScope);
30498
+ const { ok, checks } = buildDoctorAssessment({
30499
+ baseUrl,
30500
+ scope,
30501
+ root,
30502
+ authStatus
30503
+ });
30504
+ const quickstart = setupQuickstartCommand(baseUrl);
30505
+ printCommandEnvelope(
30506
+ {
30507
+ ok,
30508
+ status: ok ? "complete" : "failed",
30509
+ code: ok ? "DOCTOR_OK" : "DOCTOR_FAILED",
30510
+ exitCode: ok ? 0 : 7,
30511
+ checks,
30512
+ next: ok ? quickstart : "Review failed checks, then rerun: deepline doctor --json",
30513
+ render: {
30514
+ sections: [
30515
+ {
30516
+ title: "doctor",
30517
+ lines: Object.entries(checks).map(
30518
+ ([name, check]) => `${check.ok ? "pass" : "fail"} ${name}`
30519
+ )
30520
+ }
30521
+ ]
30522
+ }
30523
+ },
30524
+ { json: options.json }
30525
+ );
30526
+ return ok ? 0 : 7;
30527
+ }
30528
+ function pendingResult(input2) {
30529
+ input2.phases.auth = {
30530
+ status: "waiting",
30531
+ outcome: "authorization_pending"
30532
+ };
30533
+ const statePath = writeSetupState({
30534
+ baseUrl: input2.baseUrl,
30535
+ scope: input2.scope,
30536
+ root: input2.root,
30537
+ status: "authorization_pending",
30538
+ phases: input2.phases
30539
+ });
30540
+ if (input2.authorizationUrl) {
30541
+ process.stderr.write(`Authorize Deepline: ${input2.authorizationUrl}
30542
+ `);
30543
+ }
30544
+ printCommandEnvelope(
30545
+ {
30546
+ ok: true,
30547
+ status: "authorization_pending",
30548
+ complete: false,
30549
+ scope: input2.scope,
30550
+ authorizationUrl: input2.authorizationUrl || null,
30551
+ statePath,
30552
+ phases: input2.phases,
30553
+ currentPhase: "auth",
30554
+ resumed: input2.resumed,
30555
+ retry: setupRetry({
30556
+ baseUrl: input2.baseUrl,
30557
+ scope: input2.scope,
30558
+ phase: "auth"
30559
+ }),
30560
+ next: `Approve the link, then run: ${setupResumeCommand(input2.baseUrl, input2.scope)}`,
30561
+ render: {
30562
+ sections: [
30563
+ {
30564
+ title: "setup",
30565
+ lines: [
30566
+ "Authorization is waiting for browser approval.",
30567
+ ...input2.authorizationUrl ? [input2.authorizationUrl] : []
30568
+ ]
30569
+ }
30570
+ ]
30571
+ }
30572
+ },
30573
+ { json: input2.json }
30574
+ );
30575
+ return 0;
30576
+ }
30577
+ async function runSetupCommand(options) {
30578
+ let scope;
30579
+ let root;
30580
+ try {
30581
+ scope = normalizeScope2(options.scope);
30582
+ root = resolveScopeRoot(scope);
30583
+ } catch (error) {
30584
+ printCommandEnvelope(
30585
+ {
30586
+ ok: false,
30587
+ status: "failed",
30588
+ code: "INVALID_SETUP_SCOPE",
30589
+ exitCode: 2,
30590
+ message: error instanceof Error ? error.message : String(error),
30591
+ phases: {
30592
+ ...initialSetupPhases(),
30593
+ cli: { status: "failed", code: "INVALID_SETUP_SCOPE" }
30594
+ },
30595
+ failedPhase: "cli"
30596
+ },
30597
+ { json: options.json }
30598
+ );
30599
+ return 2;
30600
+ }
30601
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30602
+ const previousState = readSetupState({ baseUrl, scope, root });
30603
+ const { resumed, phases } = selectSetupProgress(previousState);
30604
+ if (phases.cli.status !== "complete") {
30605
+ beginSetupPhase(phases, "cli");
30606
+ persistSetupProgress({ baseUrl, scope, root, phases });
30607
+ if (scope === "global") {
30608
+ const globalCli = inspectGlobalCliAvailability();
30609
+ if (!globalCli.ok) {
30610
+ const installedButUnreachable = Boolean(globalCli.persistentPath);
30611
+ const code = installedButUnreachable ? "GLOBAL_CLI_NOT_ON_PATH" : "GLOBAL_CLI_NOT_INSTALLED";
30612
+ return reportSetupPhaseFailure({
30613
+ baseUrl,
30614
+ scope,
30615
+ root,
30616
+ phases,
30617
+ phase: "cli",
30618
+ code,
30619
+ exitCode: 7,
30620
+ message: installedButUnreachable ? `The global Deepline executable is not reachable on PATH: ${globalCli.persistentPath}` : "No persistent global Deepline executable was found.",
30621
+ json: options.json,
30622
+ extra: {
30623
+ persistentPath: globalCli.persistentPath,
30624
+ fallback: "https://code.deepline.com/INSTALL.md"
30625
+ }
30626
+ });
30627
+ }
30628
+ }
30629
+ const conflict = inspectPathConflict();
30630
+ if (conflict) {
30631
+ return reportSetupPhaseFailure({
30632
+ baseUrl,
30633
+ scope,
30634
+ root,
30635
+ phases,
30636
+ phase: "cli",
30637
+ code: "PATH_CONFLICT",
30638
+ exitCode: 7,
30639
+ message: `An unknown executable named deepline is first on PATH: ${conflict}`,
30640
+ json: options.json,
30641
+ extra: { path: conflict }
30642
+ });
30643
+ }
30644
+ completeSetupPhase(phases, "cli", "available");
30645
+ persistSetupProgress({ baseUrl, scope, root, phases });
30646
+ }
30647
+ if (phases.cleanup.status !== "complete") {
30648
+ beginSetupPhase(phases, "cleanup");
30649
+ persistSetupProgress({ baseUrl, scope, root, phases });
30650
+ try {
30651
+ const removedLegacyPaths = removeKnownLegacyPaths(baseUrl);
30652
+ if (removedLegacyPaths.length > 0) {
30653
+ process.stderr.write(
30654
+ `Removed ${removedLegacyPaths.length} known legacy Deepline path(s).
30655
+ `
30656
+ );
30657
+ }
30658
+ completeSetupPhase(
30659
+ phases,
30660
+ "cleanup",
30661
+ removedLegacyPaths.length > 0 ? "removed_legacy_paths" : "clean"
30662
+ );
30663
+ persistSetupProgress({ baseUrl, scope, root, phases });
30664
+ } catch (error) {
30665
+ return reportSetupPhaseFailure({
30666
+ baseUrl,
30667
+ scope,
30668
+ root,
30669
+ phases,
30670
+ phase: "cleanup",
30671
+ code: "LEGACY_CLEANUP_FAILED",
30672
+ exitCode: 5,
30673
+ message: error instanceof Error ? error.message : String(error),
30674
+ json: options.json
30675
+ });
30676
+ }
30677
+ }
30678
+ let skillsPayload = null;
30679
+ if (phases.skills.status !== "complete") {
30680
+ beginSetupPhase(phases, "skills");
30681
+ persistSetupProgress({ baseUrl, scope, root, phases });
30682
+ process.stderr.write(`Installing Deepline skills (${scope})...
30683
+ `);
30684
+ const skills = await captureStdout2(
30685
+ () => runSkillsCommand({ scope, json: true })
30686
+ );
30687
+ skillsPayload = parseCapturedJson(skills.stdout);
30688
+ if (skills.exitCode !== 0) {
30689
+ return reportSetupPhaseFailure({
30690
+ baseUrl,
30691
+ scope,
30692
+ root,
30693
+ phases,
30694
+ phase: "skills",
30695
+ code: "SKILLS_INSTALL_FAILED",
30696
+ exitCode: skills.exitCode,
30697
+ message: typeof skillsPayload?.message === "string" ? skillsPayload.message : "Deepline skills could not be installed.",
30698
+ json: options.json,
30699
+ extra: { detail: skillsPayload }
30700
+ });
30701
+ }
30702
+ completeSetupPhase(
30703
+ phases,
30704
+ "skills",
30705
+ skillsPayload?.status === "current" ? "current" : "installed"
30706
+ );
30707
+ writeSetupState({
30708
+ baseUrl,
30709
+ scope,
30710
+ root,
30711
+ status: "skills_installed",
30712
+ phases
30713
+ });
30714
+ } else {
30715
+ skillsPayload = parseCapturedJson(
30716
+ safeRead(skillsStatePathForScope(baseUrl, scope, root))
30717
+ );
30718
+ }
30719
+ const authScope = authScopeForSetup(scope);
30720
+ beginSetupPhase(phases, "auth");
30721
+ persistSetupProgress({ baseUrl, scope, root, phases });
30722
+ let auth = await readAuthStatus(authScope);
30723
+ const initialAuthFailure = reportSetupAuthStatusFailure({
30724
+ auth,
30725
+ baseUrl,
30726
+ authScope,
30727
+ scope,
30728
+ root,
30729
+ phases,
30730
+ json: options.json
30731
+ });
30732
+ if (initialAuthFailure !== null) return initialAuthFailure;
30733
+ if (auth.payload?.connected !== true) {
30734
+ const pending = readPendingAuthClaim(baseUrl, authScope);
30735
+ if (pending) {
30736
+ process.stderr.write("Checking pending Deepline authorization...\n");
30737
+ const waited = await captureStdout2(
30738
+ () => handleWait(["--timeout", "1", "--auth-scope", authScope])
30739
+ );
30740
+ printCapturedAuthorizationUrl(waited.stdout);
30741
+ auth = await readAuthStatus(authScope);
30742
+ const resumedAuthFailure = reportSetupAuthStatusFailure({
30743
+ auth,
30744
+ baseUrl,
30745
+ authScope,
30746
+ scope,
30747
+ root,
30748
+ phases,
30749
+ json: options.json
30750
+ });
30751
+ if (resumedAuthFailure !== null) return resumedAuthFailure;
30752
+ if (auth.payload?.connected !== true) {
30753
+ const stillPending = readPendingAuthClaim(baseUrl, authScope);
30754
+ if (stillPending) {
30755
+ return pendingResult({
30756
+ baseUrl,
30757
+ scope,
30758
+ root,
30759
+ phases,
30760
+ authorizationUrl: stillPending.claimUrl,
30761
+ resumed,
30762
+ json: options.json
30763
+ });
30764
+ }
30765
+ }
30766
+ }
30767
+ }
30768
+ if (auth.payload?.connected !== true) {
30769
+ process.stderr.write("Starting Deepline browser authorization...\n");
30770
+ const agentRuntime = detectAgentRuntime();
30771
+ const agentLed = agentRuntime !== "unknown";
30772
+ const waitMode = options.json || agentLed || !process.stdin.isTTY ? "no" : "auto";
30773
+ const registered = await captureStdout2(
30774
+ () => handleRegister(["--wait", waitMode, "--auth-scope", authScope])
30775
+ );
30776
+ printCapturedAuthorizationUrl(registered.stdout);
30777
+ if (registered.exitCode !== 0) {
30778
+ return reportSetupPhaseFailure({
30779
+ baseUrl,
30780
+ scope,
30781
+ root,
30782
+ phases,
30783
+ phase: "auth",
30784
+ code: "AUTH_REGISTER_FAILED",
30785
+ exitCode: registered.exitCode,
30786
+ message: "Deepline browser authorization could not be started.",
30787
+ json: options.json,
30788
+ extra: {
30789
+ detail: parseCapturedJson(registered.stdout),
30790
+ authScope
30791
+ }
30792
+ });
30793
+ }
30794
+ auth = await readAuthStatus(authScope);
30795
+ const registeredAuthFailure = reportSetupAuthStatusFailure({
30796
+ auth,
30797
+ baseUrl,
30798
+ authScope,
30799
+ scope,
30800
+ root,
30801
+ phases,
30802
+ json: options.json
30803
+ });
30804
+ if (registeredAuthFailure !== null) return registeredAuthFailure;
30805
+ if (auth.payload?.connected !== true) {
30806
+ const pending = readPendingAuthClaim(baseUrl, authScope);
30807
+ return pendingResult({
30808
+ baseUrl,
30809
+ scope,
30810
+ root,
30811
+ phases,
30812
+ authorizationUrl: pending?.claimUrl ?? "",
30813
+ resumed,
30814
+ json: options.json
30815
+ });
30816
+ }
30817
+ }
30818
+ completeSetupPhase(phases, "auth", "connected");
30819
+ beginSetupPhase(phases, "verify");
30820
+ persistSetupProgress({ baseUrl, scope, root, phases });
30821
+ const assessment = buildDoctorAssessment({
30822
+ baseUrl,
30823
+ scope,
30824
+ root,
30825
+ authStatus: auth
30826
+ });
30827
+ const quickstart = setupQuickstartCommand(baseUrl);
30828
+ const doctorPayload = {
30829
+ ok: assessment.ok,
30830
+ status: assessment.ok ? "complete" : "failed",
30831
+ code: assessment.ok ? "DOCTOR_OK" : "DOCTOR_FAILED",
30832
+ exitCode: assessment.ok ? 0 : 7,
30833
+ checks: assessment.checks,
30834
+ next: assessment.ok ? quickstart : `deepline doctor --scope ${scope} --json`
30835
+ };
30836
+ if (!assessment.ok) {
30837
+ return reportSetupPhaseFailure({
30838
+ baseUrl,
30839
+ scope,
30840
+ root,
30841
+ phases,
30842
+ phase: "verify",
30843
+ code: "DOCTOR_FAILED",
30844
+ exitCode: 7,
30845
+ message: "Deepline setup verification found one or more failed checks.",
30846
+ json: options.json,
30847
+ extra: {
30848
+ doctor: doctorPayload,
30849
+ diagnostic: {
30850
+ command: `deepline doctor --scope ${scope} --json`
30851
+ }
30852
+ }
30853
+ });
30854
+ }
30855
+ completeSetupPhase(phases, "verify", "verified");
30856
+ const statePath = writeSetupState({
30857
+ baseUrl,
30858
+ scope,
30859
+ root,
30860
+ status: "complete",
30861
+ phases
30862
+ });
30863
+ const doctorChecks = asRecord2(doctorPayload?.checks);
30864
+ const apiCheck = asRecord2(doctorChecks?.api);
30865
+ printCommandEnvelope(
30866
+ {
30867
+ ok: true,
30868
+ status: "complete",
30869
+ complete: true,
30870
+ scope,
30871
+ cliVersion: SDK_VERSION,
30872
+ agents: skillsPayload?.agents ?? [],
30873
+ workspace: apiCheck?.workspace ?? null,
30874
+ rollbackCommand: rollbackCommand(scope, root),
30875
+ statePath,
30876
+ doctor: doctorPayload,
30877
+ phases,
30878
+ currentPhase: null,
30879
+ failedPhase: null,
30880
+ resumed,
30881
+ next: quickstart,
30882
+ render: {
30883
+ sections: [
30884
+ {
30885
+ title: "setup",
30886
+ lines: [
30887
+ "Deepline is installed and connected.",
30888
+ `Next: ${quickstart}`
30889
+ ]
30890
+ }
30891
+ ]
30892
+ }
30893
+ },
30894
+ { json: options.json }
30895
+ );
30896
+ return 0;
30897
+ }
30898
+ function registerSetupCommands(program) {
30899
+ program.command("setup").description("Install skills, authenticate, and verify Deepline.").option("--scope <scope>", "Setup scope: global or local", "global").option("--json", "Emit one final JSON result envelope").addHelpText(
30900
+ "after",
30901
+ `
30902
+ Notes:
30903
+ Setup is idempotent. Bare setup resumes the first incomplete or failed phase.
30904
+ It installs skills before auth and does not run quickstart.
30905
+ JSON output includes phase status and an exact retry command.
30906
+
30907
+ Examples:
30908
+ deepline setup --json
30909
+ deepline setup --scope local --json
30910
+ `
30911
+ ).action(async (options) => {
30912
+ process.exitCode = await runSetupCommand(options);
30913
+ });
30914
+ program.command("doctor").description("Verify CLI, skills, auth, workspace, and API connectivity.").option(
30915
+ "--scope <scope>",
30916
+ "Expected setup scope: global or local",
30917
+ "global"
30918
+ ).option("--json", "Emit one JSON result envelope").addHelpText(
30919
+ "after",
30920
+ `
30921
+ Notes:
30922
+ Doctor is read-only and makes no paid provider calls. It reports repairs but
30923
+ does not apply them automatically.
30924
+
30925
+ Examples:
30926
+ deepline doctor --json
30927
+ deepline doctor --scope local --json
30928
+ `
30929
+ ).action(async (options) => {
30930
+ process.exitCode = await runDoctorCommand(options);
30931
+ });
30932
+ }
30933
+
30934
+ // ../shared_libs/cli/command-compatibility.json
30935
+ var command_compatibility_default = {
30936
+ enrich: {
30937
+ family: "python",
30938
+ label: "a legacy Python CLI enrichment command",
30939
+ sdk_alternative: "Use `deepline plays ...` for durable workflows or `deepline tools execute ...` for one tool call."
30940
+ },
30941
+ session: {
30942
+ family: "python",
30943
+ label: "a legacy Python CLI session/playground command",
30944
+ sdk_alternative: "Use `deepline sessions send ...` or `deepline sessions render ...` for transcript workflows."
30945
+ },
30946
+ workflows: {
30947
+ family: "python",
30948
+ label: "a legacy Python CLI workflow command",
30949
+ sdk_alternative: "Use `deepline plays ...` in the SDK CLI."
30950
+ },
30951
+ events: {
30952
+ family: "python",
30953
+ label: "a legacy Python CLI event command"
30954
+ },
30955
+ plays: {
30956
+ family: "sdk",
30957
+ label: "an SDK CLI play command",
30958
+ python_alternative: "Use `deepline workflows ...` only for legacy workflows."
30959
+ },
30960
+ runs: {
30961
+ family: "sdk",
30962
+ label: "an SDK CLI run inspection command"
30963
+ },
30964
+ sessions: {
30965
+ family: "sdk",
30966
+ label: "an SDK CLI session transcript command"
30967
+ },
30968
+ health: {
30969
+ family: "sdk",
30970
+ label: "an SDK CLI health command"
30971
+ }
30972
+ };
30973
+
30974
+ // src/cli/command-compatibility.ts
30975
+ var COMMAND_COMPATIBILITY = command_compatibility_default;
30976
+ function cliFamilyLabel(family) {
30977
+ return family === "sdk" ? "SDK CLI" : "legacy Python CLI";
30978
+ }
30979
+ function commandCompatibilityHint(currentFamily, commandName, baseUrl) {
30980
+ const compatibility = COMMAND_COMPATIBILITY[commandName];
30981
+ if (!compatibility || compatibility.family === currentFamily) {
30982
+ return null;
30983
+ }
30984
+ const expectedFamily = compatibility.family;
30985
+ const currentLabel = cliFamilyLabel(currentFamily);
30986
+ const expectedLabel = cliFamilyLabel(expectedFamily);
30987
+ const lines = [
30988
+ "",
30989
+ "Command compatibility:",
30990
+ ` \`deepline ${commandName}\` is ${compatibility.label}.`,
30991
+ ` Current binary: ${currentLabel}. Required binary: ${expectedLabel}.`,
30992
+ " If this came from an agent skill, the installed skill likely targets the other Deepline CLI."
30993
+ ];
30994
+ if (currentFamily === "sdk") {
30995
+ lines.push(
30996
+ "",
30997
+ " To stay on the SDK CLI, refresh the Deepline agent skills:",
30998
+ ` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
30999
+ " To use the legacy Python CLI instead:",
31000
+ ` ${legacyPythonInstallCommand(baseUrl)}`,
31001
+ " `deepline update` updates this SDK CLI, but it will not switch CLI families."
31002
+ );
31003
+ if (compatibility.sdk_alternative) {
31004
+ lines.push(` SDK alternative: ${compatibility.sdk_alternative}`);
31005
+ }
31006
+ } else {
31007
+ lines.push(
31008
+ "",
31009
+ " To use SDK commands, install the SDK CLI and refresh Deepline agent skills:",
31010
+ ` ${sdkNpmGlobalInstallCommand()}`,
31011
+ ` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
31012
+ " `deepline update` updates this Python CLI and its skills, but it will not switch CLI families."
31013
+ );
31014
+ if (compatibility.python_alternative) {
31015
+ lines.push(` Python alternative: ${compatibility.python_alternative}`);
31016
+ }
31017
+ }
31018
+ return lines.join("\n");
31019
+ }
31020
+ function unknownCommandNameFromMessage(message) {
31021
+ const match = message.match(/unknown command ['"]([^'"]+)['"]/i);
31022
+ const command = match?.[1]?.trim();
31023
+ return command ? command : null;
31024
+ }
31025
+
31026
+ // src/cli/self-update.ts
31027
+ import { spawn as spawn5 } from "child_process";
31028
+ function envTruthy(name) {
31029
+ const value = process.env[name]?.trim().toLowerCase();
31030
+ return value === "1" || value === "true" || value === "yes";
31031
+ }
31032
+ function isCi() {
31033
+ return envTruthy("CI") || envTruthy("GITHUB_ACTIONS");
31034
+ }
31035
+ function shouldSkipSelfUpdate() {
31036
+ return envTruthy("DEEPLINE_SKIP_SELF_UPDATE") || envTruthy("DEEPLINE_NO_AUTO_UPDATE") || envTruthy("DEEPLINE_SKIP_SDK_AUTO_UPDATE") || envTruthy("DEEPLINE_DISABLE_AUTO_UPDATE") || isCi();
31037
+ }
31038
+ function parseSemver(version) {
31039
+ const trimmed = version?.trim();
31040
+ if (!trimmed) return null;
31041
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(
31042
+ trimmed
31043
+ );
31044
+ if (!match) return null;
31045
+ return {
31046
+ major: Number(match[1]),
31047
+ minor: Number(match[2]),
31048
+ patch: Number(match[3]),
31049
+ prerelease: match[4] ?? ""
31050
+ };
31051
+ }
31052
+ function compareSemver(left, right) {
31053
+ const a = parseSemver(left);
31054
+ const b = parseSemver(right);
31055
+ if (!a || !b) {
31056
+ return left.localeCompare(right);
31057
+ }
31058
+ for (const key of ["major", "minor", "patch"]) {
31059
+ if (a[key] !== b[key]) return a[key] > b[key] ? 1 : -1;
31060
+ }
31061
+ if (a.prerelease === b.prerelease) return 0;
31062
+ if (!a.prerelease) return 1;
31063
+ if (!b.prerelease) return -1;
31064
+ return a.prerelease.localeCompare(b.prerelease);
31065
+ }
31066
+ function isDowngradeAutoUpdateResponse(response) {
31067
+ const target = response?.latest?.trim();
31068
+ const current = response?.current?.trim() || SDK_VERSION;
31069
+ if (!target) return false;
31070
+ return compareSemver(target, current) < 0;
31071
+ }
31072
+ function relaunchCurrentCommand(plan) {
31073
+ return new Promise((resolve15) => {
31074
+ const command = plan.kind === "python-sidecar" ? plan.sidecarPath : process.execPath;
31075
+ const args = plan.kind === "python-sidecar" ? process.argv.slice(2) : process.argv.slice(1);
31076
+ const child = spawn5(command, args, {
31077
+ stdio: "inherit",
31078
+ shell: process.platform === "win32",
31079
+ env: {
31080
+ ...process.env,
31081
+ DEEPLINE_NO_AUTO_UPDATE: "1"
31082
+ }
31083
+ });
31084
+ child.on("error", (error) => {
31085
+ process.stderr.write(
31086
+ `Deepline SDK/CLI updated, but relaunch failed: ${error.message}
31087
+ `
31088
+ );
31089
+ resolve15(1);
31090
+ });
31091
+ child.on("close", (code) => resolve15(code ?? 1));
31092
+ });
31093
+ }
31094
+ async function maybeAutoUpdateAndRelaunch(response) {
31095
+ const autoUpdate = response?.auto_update;
31096
+ if (!response || !autoUpdate?.should_auto_update || shouldSkipSelfUpdate()) {
31097
+ return false;
31098
+ }
31099
+ if (isDowngradeAutoUpdateResponse(response)) {
31100
+ const target = response.latest;
31101
+ const current = response.current?.trim() || SDK_VERSION;
31102
+ process.stderr.write(
31103
+ `Deepline SDK/CLI auto-update refused: server advertised older ${target} than current ${current}. Continuing without mutating the CLI.
31104
+ `
31105
+ );
31106
+ return false;
31107
+ }
31108
+ const packageSpec = response.latest ? `deepline@${response.latest}` : void 0;
31109
+ const plan = resolveUpdatePlan({ packageSpec });
31110
+ if (plan.kind === "source") {
31111
+ return false;
31112
+ }
31113
+ const label = autoUpdate.reason === "rollback_forced" ? "has a server rollback pending and needs the latest rollback-aware CLI" : autoUpdate.reason === "deprecated" ? "is deprecated and will update automatically" : autoUpdate.required ? "requires an update" : "is more than the supported auto-update lag behind";
31114
+ process.stderr.write(
31115
+ `Deepline SDK/CLI ${label}; running ${plan.manualCommand}
31116
+ `
31117
+ );
31118
+ const updateResult = await runAutomaticUpdatePlan(plan);
31119
+ if (updateResult.status === "skipped_previous_failure") {
31120
+ return false;
31121
+ }
31122
+ if (updateResult.exitCode !== 0) {
31123
+ if (autoUpdate.required) {
31124
+ throw new Error(
31125
+ `Automatic Deepline SDK/CLI update failed with exit code ${updateResult.exitCode}. ${response.message}`
31126
+ );
31127
+ }
31128
+ process.stderr.write(
31129
+ `Deepline SDK/CLI auto-update failed with exit code ${updateResult.exitCode}; continuing with ${response.current ?? "current version"}.
31130
+ `
31131
+ );
31132
+ return false;
31133
+ }
31134
+ process.stderr.write("Deepline SDK/CLI updated; rerunning command.\n");
31135
+ const exitCode = await relaunchCurrentCommand(plan);
31136
+ process.exit(exitCode);
31137
+ return true;
31138
+ }
31139
+
31140
+ // src/cli/skills-sync.ts
31141
+ import { spawn as spawn6, spawnSync as spawnSync3 } from "child_process";
31142
+ import {
31143
+ existsSync as existsSync14,
31144
+ mkdirSync as mkdirSync12,
31145
+ readFileSync as readFileSync15,
31146
+ unlinkSync as unlinkSync2,
31147
+ writeFileSync as writeFileSync17
31148
+ } from "fs";
31149
+ import { dirname as dirname15, join as join17 } from "path";
31150
+ var CHECK_TIMEOUT_MS2 = 3e3;
31151
+ var attemptedSync = false;
31152
+ function shouldSkipSkillsSync() {
31153
+ if (detectAgentRuntime() === "claude_cowork") {
31154
+ return true;
31155
+ }
31156
+ const value = process.env.DEEPLINE_SKIP_SKILLS_SYNC?.trim().toLowerCase();
31157
+ return value === "1" || value === "true" || value === "yes" || value === "on";
31158
+ }
31159
+ function activePluginSkillsDir() {
31160
+ const pluginMode = process.env.DEEPLINE_PLUGIN_MODE?.trim().toLowerCase();
31161
+ if (pluginMode !== "true" && pluginMode !== "1" && pluginMode !== "yes" && pluginMode !== "on") {
31162
+ return "";
31163
+ }
31164
+ const dir = process.env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim() ?? "";
31165
+ return dir && existsSync14(dir) ? dir : "";
31166
+ }
31167
+ function readPluginSkillsVersion() {
31168
+ const dir = activePluginSkillsDir();
31169
+ if (!dir) return "";
31170
+ try {
31171
+ return readFileSync15(join17(dir, ".version"), "utf-8").trim();
31172
+ } catch {
31173
+ return "";
31174
+ }
31175
+ }
31176
+ function sdkSkillsVersionPath(baseUrl) {
31177
+ return join17(sdkCliStateDirPath(baseUrl), "skills-version");
31178
+ }
31179
+ function legacySdkSkillsVersionPath(baseUrl) {
31180
+ return join17(dirname15(sdkCliStateDirPath(baseUrl)), "sdk-skills", ".version");
31181
+ }
31182
+ function unavailableSkillsNoticePath(baseUrl) {
31183
+ return join17(sdkCliStateDirPath(baseUrl), "skills-sync-unavailable-version");
31184
+ }
31185
+ function readSdkSkillsLocalVersion(baseUrl) {
31186
+ const pluginVersion = readPluginSkillsVersion();
31187
+ if (pluginVersion) return pluginVersion;
31188
+ const path = existsSync14(sdkSkillsVersionPath(baseUrl)) ? sdkSkillsVersionPath(baseUrl) : legacySdkSkillsVersionPath(baseUrl);
31189
+ if (!existsSync14(path)) return "";
31190
+ try {
31191
+ return readFileSync15(path, "utf-8").trim();
31192
+ } catch {
31193
+ return "";
31194
+ }
31195
+ }
31196
+ function writeLocalSkillsVersion(baseUrl, version) {
31197
+ const path = sdkSkillsVersionPath(baseUrl);
31198
+ mkdirSync12(dirname15(path), { recursive: true });
31199
+ writeFileSync17(path, `${version}
31200
+ `, "utf-8");
31201
+ }
31202
+ function writeUnavailableSkillsNotice(baseUrl, remoteVersion, skillNames) {
31203
+ const path = unavailableSkillsNoticePath(baseUrl);
31204
+ try {
31205
+ if (existsSync14(path) && readFileSync15(path, "utf-8").trim() === remoteVersion) {
31206
+ return;
31207
+ }
31208
+ mkdirSync12(dirname15(path), { recursive: true });
31209
+ writeFileSync17(path, `${remoteVersion}
31210
+ `, "utf-8");
31211
+ } catch {
31212
+ }
31213
+ const manualCommand = `npx ${buildSkillsInstallArgs(baseUrl, skillNames).join(" ")}`;
31214
+ writeSdkSkillsStatusLine(
31215
+ `Deepline agent skills are out of date, but neither \`bunx\` nor \`npx\` is available. Install Node.js/npm or Bun, then run:
31216
+ ${manualCommand}`
31217
+ );
31218
+ }
31219
+ function clearUnavailableSkillsNotice(baseUrl) {
31220
+ try {
31221
+ unlinkSync2(unavailableSkillsNoticePath(baseUrl));
31222
+ } catch {
31223
+ }
31224
+ }
31225
+ function sortedUniqueSkillNames(names) {
31226
+ return [...new Set(names.map((name) => name.trim()).filter(Boolean))].sort(
31227
+ (a, b) => a.localeCompare(b)
31228
+ );
31229
+ }
31230
+ async function fetchV1SkillNames(baseUrl) {
31231
+ const controller = new AbortController();
31232
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
31233
+ try {
31234
+ const response = await fetch(
31235
+ new URL("/.well-known/skills/index.json", baseUrl),
31236
+ { signal: controller.signal }
31237
+ );
31238
+ if (!response.ok) return [];
31239
+ const data = await response.json().catch(() => null);
31240
+ const names = (data?.skills ?? []).filter((skill) => skill.install_surface === "v1").map((skill) => skill.name).filter(
31241
+ (name) => typeof name === "string" && name.length > 0
31242
+ );
31243
+ return sortedUniqueSkillNames(names);
31244
+ } catch {
31245
+ return [];
31246
+ } finally {
31247
+ clearTimeout(timeout);
31248
+ }
31249
+ }
31250
+ function buildSdkSkillNames(v1SkillNames) {
31251
+ return sortedUniqueSkillNames(v1SkillNames);
31252
+ }
31253
+ async function fetchSkillsUpdate(baseUrl, localVersion) {
31254
+ const controller = new AbortController();
31255
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
31256
+ try {
31257
+ const response = await fetch(new URL("/api/v2/cli/update-check", baseUrl), {
31258
+ method: "POST",
31259
+ headers: { "Content-Type": "application/json" },
31260
+ body: JSON.stringify({
31261
+ skills: {
31262
+ version: localVersion
31263
+ }
31264
+ }),
31265
+ signal: controller.signal
31266
+ });
31267
+ if (!response.ok) return null;
31268
+ const data = await response.json().catch(() => null);
31269
+ const skills = data?.skills;
31270
+ if (!skills) return null;
31271
+ return {
31272
+ needsUpdate: skills.needs_update === true,
31273
+ remoteVersion: typeof skills.remote?.version === "string" ? skills.remote.version.trim() : ""
31274
+ };
31275
+ } catch {
31276
+ return null;
31277
+ } finally {
31278
+ clearTimeout(timeout);
31279
+ }
31280
+ }
31281
+ function buildSkillsInstallArgs(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
31282
+ return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames));
31283
+ }
31284
+ function buildBunxSkillsInstallArgs(baseUrl, skillNames) {
31285
+ return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames), {
31286
+ firstArg: "--bun"
31287
+ });
31288
+ }
31289
+ function hasCommand(command) {
31290
+ const result = spawnSync3(command, ["--version"], {
31291
+ stdio: "ignore",
31292
+ shell: process.platform === "win32"
31293
+ });
31294
+ return result.status === 0;
31295
+ }
31296
+ function shellQuote6(arg) {
31297
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
31298
+ }
31299
+ function resolveSkillsInstallCommands(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
31300
+ const commands = [];
31301
+ if (hasCommand("bunx")) {
31302
+ const bunxArgs = buildBunxSkillsInstallArgs(baseUrl, skillNames);
31303
+ commands.push({
31304
+ command: "bunx",
31305
+ args: bunxArgs,
31306
+ manualCommand: `bunx ${bunxArgs.map(shellQuote6).join(" ")}`
31307
+ });
31308
+ }
31309
+ if (hasCommand("npx")) {
31310
+ const npxArgs = buildSkillsInstallArgs(baseUrl, skillNames);
31311
+ commands.push({
31312
+ command: "npx",
31313
+ args: npxArgs,
31314
+ manualCommand: `npx ${npxArgs.map(shellQuote6).join(" ")}`
31315
+ });
31316
+ }
31317
+ return commands;
31318
+ }
31319
+ function runOneSkillsInstall(install) {
31320
+ return new Promise((resolve15) => {
31321
+ const child = spawn6(install.command, install.args, {
31322
+ stdio: ["ignore", "ignore", "pipe"],
31323
+ env: process.env
31324
+ });
31325
+ let stderr = "";
31326
+ child.stderr.on("data", (chunk) => {
31327
+ stderr += chunk.toString("utf-8");
31328
+ });
31329
+ child.on("error", (error) => {
31330
+ resolve15({
31331
+ ok: false,
31332
+ detail: `failed to start ${install.command}: ${error.message}`,
31333
+ manualCommand: install.manualCommand
31334
+ });
31335
+ });
31336
+ child.on("close", (code) => {
31337
+ if (code === 0) {
31338
+ resolve15({ ok: true, detail: "", manualCommand: install.manualCommand });
31339
+ return;
31340
+ }
31341
+ const detail = stderr.trim();
31342
+ resolve15({
31343
+ ok: false,
31344
+ detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
31345
+ manualCommand: install.manualCommand
31346
+ });
31347
+ });
31348
+ });
31349
+ }
31350
+ async function runSkillsInstall(installs) {
31351
+ const failures = [];
31352
+ for (const install of installs) {
31353
+ const result = await runOneSkillsInstall(install);
31354
+ if (result.ok) return true;
31355
+ failures.push(result);
31356
+ }
31357
+ const details = failures.map((failure) => failure.detail).filter(Boolean).join("\n");
31358
+ const manualCommand = failures.at(-1)?.manualCommand;
31359
+ process.stderr.write(
31360
+ `SDK skills sync failed${details ? `:
31361
+ ${details}` : ""}
31362
+ ` + (manualCommand ? `Run manually: ${manualCommand}
31363
+ ` : "")
31364
+ );
31365
+ return false;
31366
+ }
31367
+ function runLegacySkillsCleanup() {
31368
+ const candidates = hasCommand("bunx") ? [
31369
+ {
31370
+ command: "bunx",
31371
+ args: [
31372
+ "--bun",
31373
+ "skills",
31374
+ "remove",
31375
+ "--global",
31376
+ "-y",
31377
+ ...LEGACY_SKILL_NAMES_TO_REMOVE
31378
+ ]
31379
+ },
31380
+ {
31381
+ command: "npx",
31382
+ args: [
31383
+ "--yes",
31384
+ "skills",
31385
+ "remove",
31386
+ "--global",
31387
+ "-y",
31388
+ ...LEGACY_SKILL_NAMES_TO_REMOVE
31389
+ ]
31390
+ }
31391
+ ] : [
31392
+ {
31393
+ command: "npx",
31394
+ args: [
31395
+ "--yes",
31396
+ "skills",
31397
+ "remove",
31398
+ "--global",
31399
+ "-y",
31400
+ ...LEGACY_SKILL_NAMES_TO_REMOVE
31401
+ ]
31402
+ }
31403
+ ];
31404
+ for (const candidate of candidates) {
31405
+ const result = spawnSync3(candidate.command, candidate.args, {
31406
+ stdio: "ignore",
31407
+ env: process.env,
31408
+ shell: process.platform === "win32"
31409
+ });
31410
+ if (result.status === 0) return;
31411
+ }
31412
+ }
31413
+ function writeSdkSkillsStatusLine(line) {
31414
+ const progress = getActiveCliProgress();
31415
+ if (progress) {
31416
+ progress.writeLine(line);
31417
+ return;
31418
+ }
31419
+ process.stderr.write(`${line}
31420
+ `);
31421
+ }
31422
+ async function syncSdkSkillsIfNeeded(baseUrl, options = {}) {
31423
+ if (attemptedSync || shouldSkipSkillsSync()) return;
31424
+ attemptedSync = true;
31425
+ const usingPluginSkills = Boolean(activePluginSkillsDir());
31426
+ if (usingPluginSkills) {
31427
+ return;
31428
+ }
31429
+ const localVersion = readSdkSkillsLocalVersion(baseUrl);
31430
+ const update = options.update === void 0 ? await fetchSkillsUpdate(baseUrl, localVersion) : options.update ? {
31431
+ needsUpdate: options.update.needs_update,
31432
+ remoteVersion: options.update.remote.version
31433
+ } : null;
31434
+ if (!update?.needsUpdate || !update.remoteVersion) {
31435
+ return;
31436
+ }
31437
+ const remoteSkillNames = await fetchV1SkillNames(baseUrl);
31438
+ const skillNames = buildSdkSkillNames(
31439
+ remoteSkillNames.length > 0 ? remoteSkillNames : DEFAULT_SDK_SKILL_NAMES
31440
+ );
31441
+ if (skillNames.length === 0) return;
31442
+ const installs = resolveSkillsInstallCommands(baseUrl, skillNames);
31443
+ if (installs.length === 0) {
31444
+ writeUnavailableSkillsNotice(baseUrl, update.remoteVersion, skillNames);
31445
+ return;
31446
+ }
31447
+ writeSdkSkillsStatusLine("Deepline skills changed; syncing agent skills...");
31448
+ const installed = await runSkillsInstall(installs);
31449
+ if (!installed) return;
31450
+ runLegacySkillsCleanup();
31451
+ writeLocalSkillsVersion(baseUrl, update.remoteVersion);
31452
+ clearUnavailableSkillsNotice(baseUrl);
31453
+ writeSdkSkillsStatusLine("Deepline agent skills are up to date.");
29983
31454
  }
29984
31455
 
29985
31456
  // src/cli/failure-reporting.ts
@@ -30283,8 +31754,8 @@ function topLevelCommandKnown(program, commandName) {
30283
31754
  );
30284
31755
  }
30285
31756
  async function runPlayRunnerHealthCheck() {
30286
- const dir = await mkdtemp2(join15(tmpdir4(), "deepline-health-play-"));
30287
- const file = join15(dir, "health-check.play.ts");
31757
+ const dir = await mkdtemp2(join18(tmpdir4(), "deepline-health-play-"));
31758
+ const file = join18(dir, "health-check.play.ts");
30288
31759
  try {
30289
31760
  await writeFile5(
30290
31761
  file,
@@ -30502,7 +31973,7 @@ Exit codes:
30502
31973
  `
30503
31974
  );
30504
31975
  program.hook("preAction", async (_thisCommand, actionCommand) => {
30505
- if (actionCommand.name() === "version" || actionCommand.name() === "update" || actionCommand.name() === "switch" || isLegacyNoopInvocation()) {
31976
+ if (actionCommand.name() === "version" || actionCommand.name() === "update" || actionCommand.name() === "switch" || actionCommand.name() === "setup" || actionCommand.name() === "skills" || actionCommand.name() === "doctor" || isLegacyNoopInvocation()) {
30506
31977
  return;
30507
31978
  }
30508
31979
  if (printStartupPhase) {
@@ -30574,6 +32045,8 @@ Exit codes:
30574
32045
  registerFeedbackCommands(program);
30575
32046
  registerLegacyNoopCommands(program);
30576
32047
  registerUpdateCommand(program);
32048
+ registerSkillsCommand(program);
32049
+ registerSetupCommands(program);
30577
32050
  registerQuickstartCommands(program);
30578
32051
  registerSwitchCommands(program);
30579
32052
  program.command("preflight").description("Run compact health, auth, and Deepline billing checks.").option("--json", "Force JSON output.").addHelpText(