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.
package/dist/cli/index.js CHANGED
@@ -182,8 +182,8 @@ configureProxyFromEnv();
182
182
 
183
183
  // src/cli/index.ts
184
184
  var import_promises6 = require("fs/promises");
185
- var import_node_path20 = require("path");
186
- var import_node_os15 = require("os");
185
+ var import_node_path22 = require("path");
186
+ var import_node_os17 = require("os");
187
187
  var import_commander4 = require("commander");
188
188
 
189
189
  // src/config.ts
@@ -499,6 +499,21 @@ function resolveApiKeyForBaseUrl(baseUrl, explicitApiKey) {
499
499
  cliEnv[API_KEY_ENV]
500
500
  );
501
501
  }
502
+ function resolveProjectApiKeyForBaseUrl(baseUrl, startDir = process.cwd()) {
503
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
504
+ return firstNonEmpty(
505
+ ...loadProjectEnvCandidates(startDir).map(({ env }) => {
506
+ const projectBaseUrl = normalizeBaseUrl(env[HOST_URL_ENV] ?? "");
507
+ return projectBaseUrl === normalizedBaseUrl ? env[API_KEY_ENV] : "";
508
+ })
509
+ );
510
+ }
511
+ function resolveGlobalApiKeyForBaseUrl(baseUrl) {
512
+ return firstNonEmpty(
513
+ process.env[API_KEY_ENV],
514
+ loadCliEnv(normalizeBaseUrl(baseUrl) || baseUrl)[API_KEY_ENV]
515
+ );
516
+ }
502
517
  function getResolvedProjectAuthSource(baseUrl, apiKey, startDir = process.cwd()) {
503
518
  const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
504
519
  const normalizedApiKey = apiKey.trim();
@@ -542,14 +557,79 @@ function mergeProjectEnvFile(filePath, values) {
542
557
  `, "utf-8");
543
558
  }
544
559
  function ensureProjectEnvIsIgnored(dir) {
560
+ ensureProjectPrivatePathsIgnored(dir, [
561
+ PROJECT_DEEPLINE_ENV_FILE,
562
+ ".deepline/"
563
+ ]);
564
+ }
565
+ function ensureProjectPrivatePathsIgnored(dir, entries) {
566
+ const gitDir = findNearestGitCommonDir(dir);
567
+ if (gitDir) {
568
+ const excludePath = (0, import_node_path.join)(gitDir, "info", "exclude");
569
+ const existing2 = (0, import_node_fs.existsSync)(excludePath) ? (0, import_node_fs.readFileSync)(excludePath, "utf-8") : "";
570
+ const existingEntries2 = new Set(
571
+ existing2.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
572
+ );
573
+ const missing2 = entries.filter(
574
+ (entry) => !existingEntries2.has(entry) && !existingEntries2.has(`/${entry}`)
575
+ );
576
+ if (missing2.length === 0) return;
577
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(excludePath), { recursive: true });
578
+ const prefix2 = existing2 && !existing2.endsWith("\n") ? "\n" : "";
579
+ (0, import_node_fs.writeFileSync)(
580
+ excludePath,
581
+ `${existing2}${prefix2}${missing2.join("\n")}
582
+ `,
583
+ "utf-8"
584
+ );
585
+ return;
586
+ }
545
587
  const gitignorePath = (0, import_node_path.join)(dir, ".gitignore");
546
- const entry = PROJECT_DEEPLINE_ENV_FILE;
547
588
  const existing = (0, import_node_fs.existsSync)(gitignorePath) ? (0, import_node_fs.readFileSync)(gitignorePath, "utf-8") : "";
548
- const alreadyIgnored = existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === entry || line === `/${entry}`);
549
- if (alreadyIgnored) return;
589
+ const existingEntries = new Set(
590
+ existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
591
+ );
592
+ const missing = entries.filter(
593
+ (entry) => !existingEntries.has(entry) && !existingEntries.has(`/${entry}`)
594
+ );
595
+ if (missing.length === 0) return;
550
596
  const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
551
- (0, import_node_fs.writeFileSync)(gitignorePath, `${existing}${prefix}${entry}
552
- `, "utf-8");
597
+ (0, import_node_fs.writeFileSync)(
598
+ gitignorePath,
599
+ `${existing}${prefix}${missing.join("\n")}
600
+ `,
601
+ "utf-8"
602
+ );
603
+ }
604
+ function findNearestGitCommonDir(startDir) {
605
+ let current = (0, import_node_path.resolve)(startDir);
606
+ while (true) {
607
+ const candidate = (0, import_node_path.join)(current, ".git");
608
+ if ((0, import_node_fs.existsSync)(candidate)) {
609
+ try {
610
+ const stat2 = (0, import_node_fs.statSync)(candidate);
611
+ if (stat2.isDirectory()) return candidate;
612
+ if (stat2.isFile()) {
613
+ const match = (0, import_node_fs.readFileSync)(candidate, "utf8").match(
614
+ /^gitdir:\s*(.+)$/m
615
+ );
616
+ const rawGitDir = match?.[1]?.trim();
617
+ if (rawGitDir) {
618
+ const gitDir = (0, import_node_path.isAbsolute)(rawGitDir) ? rawGitDir : (0, import_node_path.resolve)((0, import_node_path.dirname)(candidate), rawGitDir);
619
+ const commonDirPath = (0, import_node_path.join)(gitDir, "commondir");
620
+ if (!(0, import_node_fs.existsSync)(commonDirPath)) return gitDir;
621
+ const rawCommonDir = (0, import_node_fs.readFileSync)(commonDirPath, "utf8").trim();
622
+ return rawCommonDir ? (0, import_node_path.isAbsolute)(rawCommonDir) ? rawCommonDir : (0, import_node_path.resolve)(gitDir, rawCommonDir) : gitDir;
623
+ }
624
+ }
625
+ } catch {
626
+ return null;
627
+ }
628
+ }
629
+ const parent = (0, import_node_path.dirname)(current);
630
+ if (parent === current) return null;
631
+ current = parent;
632
+ }
553
633
  }
554
634
  function saveProjectDeeplineEnvValues(values, startDir = process.cwd()) {
555
635
  const target = resolveProjectPinTarget(startDir);
@@ -636,7 +716,7 @@ var SDK_RELEASE = {
636
716
  // Deepline-native radars. Older clients must update before discovering,
637
717
  // checking, or deploying an unlaunched monitor integration.
638
718
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
639
- version: "0.1.259",
719
+ version: "0.1.261",
640
720
  apiContract: "2026-07-native-monitor-launch-hard-cutover",
641
721
  supportPolicy: {
642
722
  minimumSupported: "0.1.53",
@@ -1321,7 +1401,7 @@ function decodeSseFrame(frame) {
1321
1401
  return parsed;
1322
1402
  }
1323
1403
  function sleep(ms) {
1324
- return new Promise((resolve14) => setTimeout(resolve14, ms));
1404
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
1325
1405
  }
1326
1406
  function withCoworkNetworkHint(message) {
1327
1407
  if (!isCoworkLikeSandbox2() || message.includes(COWORK_NETWORK_HINT)) {
@@ -2293,14 +2373,14 @@ async function* observeRunEvents(options) {
2293
2373
  try {
2294
2374
  for (; ; ) {
2295
2375
  if (queue.length === 0) {
2296
- const waitForItem = new Promise((resolve14) => {
2297
- wake = resolve14;
2376
+ const waitForItem = new Promise((resolve15) => {
2377
+ wake = resolve15;
2298
2378
  });
2299
2379
  if (!sawFirstSnapshot) {
2300
2380
  const timedOut = await Promise.race([
2301
2381
  waitForItem.then(() => false),
2302
2382
  new Promise(
2303
- (resolve14) => setTimeout(() => resolve14(true), OBSERVE_BOOTSTRAP_TIMEOUT_MS)
2383
+ (resolve15) => setTimeout(() => resolve15(true), OBSERVE_BOOTSTRAP_TIMEOUT_MS)
2304
2384
  )
2305
2385
  ]);
2306
2386
  if (timedOut && queue.length === 0) {
@@ -2518,7 +2598,7 @@ function parseEnvTestPolicyOverrides() {
2518
2598
  return normalizeTestPolicyOverrides(parsed, "DEEPLINE_TEST_POLICY_OVERRIDES");
2519
2599
  }
2520
2600
  function sleep2(ms) {
2521
- return new Promise((resolve14) => setTimeout(resolve14, ms));
2601
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
2522
2602
  }
2523
2603
  function isTransientCompileManifestError(error) {
2524
2604
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
@@ -5679,30 +5759,86 @@ var EXIT_SERVER = 5;
5679
5759
  function envFilePath(baseUrl) {
5680
5760
  return hostEnvFilePath(baseUrl);
5681
5761
  }
5682
- function pendingClaimTokenPath(baseUrl) {
5762
+ function normalizeAuthScope(value) {
5763
+ if (!value || value === "global") return "global";
5764
+ if (value === "folder") return "folder";
5765
+ throw new Error("--auth-scope must be one of: folder, global");
5766
+ }
5767
+ function parseAuthScope(args) {
5768
+ const index = args.indexOf("--auth-scope");
5769
+ return normalizeAuthScope(index >= 0 ? args[index + 1] : void 0);
5770
+ }
5771
+ function pendingClaimPath(baseUrl, scope) {
5772
+ if (scope === "folder") {
5773
+ const target = resolveProjectPinTarget();
5774
+ if (!target.ok) {
5775
+ throw new Error(
5776
+ `Cowork project folder is ambiguous. Candidate folders: ${target.candidates.join(
5777
+ ", "
5778
+ )}. Set CLAUDE_PROJECT_DIR or cd into the intended project folder.`
5779
+ );
5780
+ }
5781
+ return (0, import_node_path5.join)(target.dir, ".deepline", "setup", "pending-auth.json");
5782
+ }
5783
+ return `${hostConfigDirPath(baseUrl)}/pending-auth.json`;
5784
+ }
5785
+ function legacyPendingClaimTokenPath(baseUrl) {
5683
5786
  return `${hostConfigDirPath(baseUrl)}/pending-claim-token`;
5684
5787
  }
5685
- function savePendingClaimToken(baseUrl, claimToken) {
5686
- const filePath = pendingClaimTokenPath(baseUrl);
5788
+ function savePendingClaim(baseUrl, claim) {
5789
+ const filePath = pendingClaimPath(baseUrl, claim.scope);
5687
5790
  const dir = (0, import_node_path5.dirname)(filePath);
5688
5791
  if (!(0, import_node_fs5.existsSync)(dir)) {
5689
5792
  (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
5690
5793
  }
5691
- (0, import_node_fs5.writeFileSync)(filePath, `${claimToken}
5692
- `, "utf-8");
5794
+ (0, import_node_fs5.writeFileSync)(filePath, `${JSON.stringify(claim, null, 2)}
5795
+ `, {
5796
+ encoding: "utf-8",
5797
+ mode: 384
5798
+ });
5693
5799
  }
5694
- function readPendingClaimToken(baseUrl) {
5695
- const filePath = pendingClaimTokenPath(baseUrl);
5696
- if (!(0, import_node_fs5.existsSync)(filePath)) return "";
5800
+ function readPendingAuthClaim(baseUrl, scope) {
5801
+ let filePath;
5697
5802
  try {
5698
- return (0, import_node_fs5.readFileSync)(filePath, "utf-8").trim();
5803
+ filePath = pendingClaimPath(baseUrl, scope);
5699
5804
  } catch {
5700
- return "";
5805
+ return null;
5806
+ }
5807
+ if (!(0, import_node_fs5.existsSync)(filePath)) {
5808
+ if (scope !== "global") return null;
5809
+ try {
5810
+ const claimToken = (0, import_node_fs5.readFileSync)(
5811
+ legacyPendingClaimTokenPath(baseUrl),
5812
+ "utf-8"
5813
+ ).trim();
5814
+ return claimToken ? {
5815
+ claimToken,
5816
+ claimUrl: `${baseUrl}/api/v2/auth/cli/claim/${encodeURIComponent(claimToken)}`,
5817
+ scope
5818
+ } : null;
5819
+ } catch {
5820
+ return null;
5821
+ }
5822
+ }
5823
+ try {
5824
+ const parsed = JSON.parse((0, import_node_fs5.readFileSync)(filePath, "utf-8"));
5825
+ const claimToken = typeof parsed.claimToken === "string" ? parsed.claimToken.trim() : "";
5826
+ if (!claimToken) return null;
5827
+ return {
5828
+ claimToken,
5829
+ claimUrl: typeof parsed.claimUrl === "string" ? parsed.claimUrl.trim() : "",
5830
+ scope
5831
+ };
5832
+ } catch {
5833
+ return null;
5701
5834
  }
5702
5835
  }
5703
- function clearPendingClaimToken(baseUrl) {
5836
+ function clearPendingClaim(baseUrl, scope) {
5704
5837
  try {
5705
- (0, import_node_fs5.rmSync)(pendingClaimTokenPath(baseUrl), { force: true });
5838
+ (0, import_node_fs5.rmSync)(pendingClaimPath(baseUrl, scope), { force: true });
5839
+ if (scope === "global") {
5840
+ (0, import_node_fs5.rmSync)(legacyPendingClaimTokenPath(baseUrl), { force: true });
5841
+ }
5706
5842
  } catch {
5707
5843
  }
5708
5844
  }
@@ -5727,12 +5863,16 @@ function shouldWaitForRegisterClaim(mode) {
5727
5863
  if (mode === "no") return false;
5728
5864
  return detectAgentRuntime() !== "claude_cowork";
5729
5865
  }
5730
- function saveEnvValues(values, baseUrl) {
5866
+ function saveEnvValues(values, baseUrl, scope) {
5731
5867
  const filtered = {
5732
5868
  ...values[HOST_URL_ENV] ? { [HOST_URL_ENV]: values[HOST_URL_ENV] } : {},
5733
5869
  ...values[API_KEY_ENV] ? { [API_KEY_ENV]: values[API_KEY_ENV] } : {}
5734
5870
  };
5735
- saveHostEnvValues(baseUrl, filtered);
5871
+ if (scope === "folder") {
5872
+ saveProjectDeeplineEnvValues(filtered);
5873
+ } else {
5874
+ saveHostEnvValues(baseUrl, filtered);
5875
+ }
5736
5876
  }
5737
5877
  async function httpJson(method, url, apiKey, body) {
5738
5878
  const headers = {
@@ -5795,7 +5935,7 @@ function buildCandidateUrls2(url) {
5795
5935
  }
5796
5936
  }
5797
5937
  function sleep4(ms) {
5798
- return new Promise((resolve14) => setTimeout(resolve14, ms));
5938
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
5799
5939
  }
5800
5940
  function printDeeplineLogo() {
5801
5941
  if (process.stdout.isTTY && (process.stdout.columns ?? 80) >= 70) {
@@ -5856,8 +5996,13 @@ async function handleRegister(args) {
5856
5996
  let orgName = "";
5857
5997
  let agentName = "";
5858
5998
  let waitMode;
5999
+ let authScope;
5859
6000
  try {
5860
6001
  waitMode = parseRegisterWaitMode(args);
6002
+ authScope = parseAuthScope(args);
6003
+ if (authScope === "folder") {
6004
+ pendingClaimPath(baseUrl, authScope);
6005
+ }
5861
6006
  } catch (error) {
5862
6007
  console.error(error instanceof Error ? error.message : String(error));
5863
6008
  return 2;
@@ -5890,18 +6035,22 @@ async function handleRegister(args) {
5890
6035
  const claimUrl = String(data.claim_url || "");
5891
6036
  const claimToken = String(data.claim_token || "");
5892
6037
  if (claimToken) {
5893
- savePendingClaimToken(baseUrl, claimToken);
6038
+ savePendingClaim(baseUrl, { claimToken, claimUrl, scope: authScope });
5894
6039
  saveEnvValues(
5895
6040
  {
5896
6041
  [HOST_URL_ENV]: baseUrl
5897
6042
  },
5898
- baseUrl
6043
+ baseUrl,
6044
+ authScope
5899
6045
  );
5900
6046
  }
5901
6047
  if (claimUrl) {
5902
- console.log(" Opening approval page in your browser.");
5903
- console.log(` If it didn't open, cmd+click: ${claimUrl}`);
5904
- openInBrowser(claimUrl);
6048
+ const shouldOpen = waitMode !== "no" && detectAgentRuntime() !== "claude_cowork";
6049
+ console.log(
6050
+ shouldOpen ? " Opening approval page in your browser." : " Open this approval page in your browser:"
6051
+ );
6052
+ console.log(` ${claimUrl}`);
6053
+ if (shouldOpen) openInBrowser(claimUrl);
5905
6054
  }
5906
6055
  if (data.cli_message) {
5907
6056
  console.log(String(data.cli_message));
@@ -5919,7 +6068,7 @@ async function handleRegister(args) {
5919
6068
  { claim_token: claimToken, reveal: true }
5920
6069
  );
5921
6070
  if (s === 401 || s === 403) {
5922
- clearPendingClaimToken(baseUrl);
6071
+ clearPendingClaim(baseUrl, authScope);
5923
6072
  console.log("Status: unauthorized");
5924
6073
  return EXIT_AUTH;
5925
6074
  }
@@ -5940,15 +6089,16 @@ async function handleRegister(args) {
5940
6089
  [HOST_URL_ENV]: baseUrl,
5941
6090
  [API_KEY_ENV]: apiKey
5942
6091
  },
5943
- baseUrl
6092
+ baseUrl,
6093
+ authScope
5944
6094
  );
5945
- clearPendingClaimToken(baseUrl);
6095
+ clearPendingClaim(baseUrl, authScope);
5946
6096
  await printClaimSuccessBanner(baseUrl, apiKey, statusData);
5947
6097
  return EXIT_OK;
5948
6098
  }
5949
6099
  }
5950
6100
  if (state === "expired") {
5951
- clearPendingClaimToken(baseUrl);
6101
+ clearPendingClaim(baseUrl, authScope);
5952
6102
  console.log(
5953
6103
  "That approval link expired. Please run: deepline auth register"
5954
6104
  );
@@ -5960,6 +6110,13 @@ async function handleRegister(args) {
5960
6110
  async function handleWait(args) {
5961
6111
  const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
5962
6112
  let timeoutSeconds = 300;
6113
+ let authScope;
6114
+ try {
6115
+ authScope = parseAuthScope(args);
6116
+ } catch (error) {
6117
+ console.error(error instanceof Error ? error.message : String(error));
6118
+ return 2;
6119
+ }
5963
6120
  for (let i = 0; i < args.length; i++) {
5964
6121
  if (args[i] === "--timeout" && args[i + 1]) {
5965
6122
  const parsed = Number.parseInt(args[++i], 10);
@@ -5968,9 +6125,11 @@ async function handleWait(args) {
5968
6125
  }
5969
6126
  }
5970
6127
  }
5971
- const claimToken = readPendingClaimToken(baseUrl);
5972
- if (!claimToken) {
5973
- if (resolveApiKeyForBaseUrl(baseUrl)) {
6128
+ const pendingClaim = readPendingAuthClaim(baseUrl, authScope);
6129
+ const claimToken = pendingClaim?.claimToken ?? "";
6130
+ if (!pendingClaim) {
6131
+ const scopedApiKey = authScope === "folder" ? resolveProjectApiKeyForBaseUrl(baseUrl) : resolveGlobalApiKeyForBaseUrl(baseUrl);
6132
+ if (scopedApiKey) {
5974
6133
  console.log("Already connected.");
5975
6134
  return EXIT_OK;
5976
6135
  }
@@ -5986,7 +6145,7 @@ async function handleWait(args) {
5986
6145
  { claim_token: claimToken, reveal: true }
5987
6146
  );
5988
6147
  if (status === 401 || status === 403) {
5989
- clearPendingClaimToken(baseUrl);
6148
+ clearPendingClaim(baseUrl, authScope);
5990
6149
  console.error("Claim is invalid. Run: deepline auth register");
5991
6150
  return EXIT_AUTH;
5992
6151
  }
@@ -6007,15 +6166,16 @@ async function handleWait(args) {
6007
6166
  [HOST_URL_ENV]: baseUrl,
6008
6167
  [API_KEY_ENV]: apiKey
6009
6168
  },
6010
- baseUrl
6169
+ baseUrl,
6170
+ authScope
6011
6171
  );
6012
- clearPendingClaimToken(baseUrl);
6172
+ clearPendingClaim(baseUrl, authScope);
6013
6173
  await printClaimSuccessBanner(baseUrl, apiKey, data);
6014
6174
  return EXIT_OK;
6015
6175
  }
6016
6176
  }
6017
6177
  if (state === "expired") {
6018
- clearPendingClaimToken(baseUrl);
6178
+ clearPendingClaim(baseUrl, authScope);
6019
6179
  console.error("That approval link expired. Run: deepline auth register");
6020
6180
  return EXIT_AUTH;
6021
6181
  }
@@ -6030,6 +6190,16 @@ async function handleStatus(args) {
6030
6190
  const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
6031
6191
  const reveal = args.includes("--reveal");
6032
6192
  const jsonOutput = argsWantJson(args);
6193
+ const hasExplicitAuthScope = args.includes("--auth-scope");
6194
+ let authScope = null;
6195
+ if (hasExplicitAuthScope) {
6196
+ try {
6197
+ authScope = parseAuthScope(args);
6198
+ } catch (error) {
6199
+ console.error(error instanceof Error ? error.message : String(error));
6200
+ return 2;
6201
+ }
6202
+ }
6033
6203
  let hostStatusPayload = null;
6034
6204
  const hostLines = [];
6035
6205
  try {
@@ -6056,14 +6226,16 @@ async function handleStatus(args) {
6056
6226
  };
6057
6227
  hostLines.push(`Host: ${baseUrl} (unreachable)`);
6058
6228
  }
6059
- const apiKey = resolveApiKeyForBaseUrl(baseUrl);
6229
+ const apiKey = authScope === "folder" ? resolveProjectApiKeyForBaseUrl(baseUrl) : authScope === "global" ? resolveGlobalApiKeyForBaseUrl(baseUrl) : resolveApiKeyForBaseUrl(baseUrl);
6060
6230
  if (!apiKey) {
6061
- if (readPendingClaimToken(baseUrl)) {
6231
+ const pendingClaim = authScope ? readPendingAuthClaim(baseUrl, authScope) : readPendingAuthClaim(baseUrl, "folder") ?? readPendingAuthClaim(baseUrl, "global");
6232
+ if (pendingClaim) {
6062
6233
  printCommandEnvelope(
6063
6234
  {
6064
6235
  ...hostStatusPayload ?? { host: baseUrl },
6065
6236
  status: "pending",
6066
6237
  connected: false,
6238
+ authorization_url: pendingClaim.claimUrl || null,
6067
6239
  next: "deepline auth wait",
6068
6240
  render: {
6069
6241
  sections: [
@@ -6145,7 +6317,8 @@ async function handleStatus(args) {
6145
6317
  console.error(`Auth status error (status ${status}).`);
6146
6318
  return EXIT_SERVER;
6147
6319
  }
6148
- clearPendingClaimToken(baseUrl);
6320
+ const resolvedAuthScope = authScope ?? (getResolvedProjectAuthSource(baseUrl, apiKey) ? "folder" : "global");
6321
+ clearPendingClaim(baseUrl, resolvedAuthScope);
6149
6322
  const payload = {
6150
6323
  ...hostStatusPayload ?? { host: baseUrl },
6151
6324
  status: data.status || "(unknown)",
@@ -6170,9 +6343,15 @@ async function handleStatus(args) {
6170
6343
  [HOST_URL_ENV]: baseUrl,
6171
6344
  [API_KEY_ENV]: apiKeyResp
6172
6345
  },
6173
- baseUrl
6346
+ baseUrl,
6347
+ resolvedAuthScope
6174
6348
  );
6175
- savedApiKeyPath = envFilePath(baseUrl);
6349
+ if (resolvedAuthScope === "folder") {
6350
+ const target = resolveProjectPinTarget();
6351
+ savedApiKeyPath = target.ok ? (0, import_node_path5.join)(target.dir, ".env.deepline") : null;
6352
+ } else {
6353
+ savedApiKeyPath = envFilePath(baseUrl);
6354
+ }
6176
6355
  }
6177
6356
  }
6178
6357
  printCommandEnvelope(
@@ -6237,12 +6416,19 @@ Examples:
6237
6416
  deepline auth register
6238
6417
  deepline auth register --org-name Acme --agent-name local-cli
6239
6418
  deepline auth register --wait no
6419
+ deepline auth register --auth-scope folder --wait no
6240
6420
  `
6241
- ).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) => {
6421
+ ).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(
6422
+ "--auth-scope <scope>",
6423
+ "Credential scope: global or folder",
6424
+ "global"
6425
+ ).action(async (options) => {
6242
6426
  process.exitCode = await handleRegister([
6243
6427
  ...options.orgName ? ["--org-name", options.orgName] : [],
6244
6428
  ...options.agentName ? ["--agent-name", options.agentName] : [],
6245
- ...options.noWait || options.wait === false ? ["--wait", "no"] : ["--wait", String(options.wait ?? "auto")]
6429
+ ...options.noWait || options.wait === false ? ["--wait", "no"] : ["--wait", String(options.wait ?? "auto")],
6430
+ "--auth-scope",
6431
+ String(options.authScope ?? "global")
6246
6432
  ]);
6247
6433
  });
6248
6434
  auth.command("wait").description("Wait for a pending browser approval and save the API key.").addHelpText(
@@ -6255,10 +6441,17 @@ Notes:
6255
6441
  Examples:
6256
6442
  deepline auth wait
6257
6443
  deepline auth wait --timeout 120
6444
+ deepline auth wait --auth-scope folder --timeout 120
6258
6445
  `
6259
- ).option("--timeout <seconds>", "Maximum seconds to wait", "300").action(async (options) => {
6446
+ ).option("--timeout <seconds>", "Maximum seconds to wait", "300").option(
6447
+ "--auth-scope <scope>",
6448
+ "Credential scope: global or folder",
6449
+ "global"
6450
+ ).action(async (options) => {
6260
6451
  process.exitCode = await handleWait([
6261
- ...options.timeout ? ["--timeout", options.timeout] : []
6452
+ ...options.timeout ? ["--timeout", options.timeout] : [],
6453
+ "--auth-scope",
6454
+ String(options.authScope ?? "global")
6262
6455
  ]);
6263
6456
  });
6264
6457
  auth.command("status").description("Show the current CLI auth and workspace status.").addHelpText(
@@ -6271,14 +6464,16 @@ Notes:
6271
6464
 
6272
6465
  Examples:
6273
6466
  deepline auth status
6467
+ deepline auth status --auth-scope folder
6274
6468
  deepline auth status --json
6275
6469
  `
6276
6470
  ).option(
6277
6471
  "--reveal",
6278
6472
  "Persist the revealed API key back to the host auth file"
6279
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
6473
+ ).option("--auth-scope <scope>", "Read only folder or global auth").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
6280
6474
  process.exitCode = await handleStatus([
6281
6475
  ...options.reveal ? ["--reveal"] : [],
6476
+ ...options.authScope ? ["--auth-scope", String(options.authScope)] : [],
6282
6477
  ...options.json ? ["--json"] : []
6283
6478
  ]);
6284
6479
  });
@@ -11302,7 +11497,7 @@ function traceCliSync(phase, fields, run) {
11302
11497
  }
11303
11498
  }
11304
11499
  function sleep5(ms) {
11305
- return new Promise((resolve14) => setTimeout(resolve14, ms));
11500
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
11306
11501
  }
11307
11502
  function parseReferencedPlayTarget2(target) {
11308
11503
  const trimmed = target.trim();
@@ -19617,7 +19812,7 @@ function emitEnrichDebug(message) {
19617
19812
  );
19618
19813
  }
19619
19814
  function sleep6(ms) {
19620
- return new Promise((resolve14) => setTimeout(resolve14, ms));
19815
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
19621
19816
  }
19622
19817
  function enrichExportBackingRowsWaitMs() {
19623
19818
  const raw = process.env.DEEPLINE_ENRICH_EXPORT_BACKING_ROWS_WAIT_MS?.trim();
@@ -25044,7 +25239,7 @@ Examples:
25044
25239
  async function fetchOrganizations(http2, apiKey) {
25045
25240
  return http2.post("/api/v2/auth/cli/organizations", { api_key: apiKey });
25046
25241
  }
25047
- function normalizeAuthScope(value) {
25242
+ function normalizeAuthScope2(value) {
25048
25243
  if (!value) return "auto";
25049
25244
  if (value === "auto" || value === "folder" || value === "global") {
25050
25245
  return value;
@@ -25247,7 +25442,7 @@ async function handleOrgStatus(options) {
25247
25442
  );
25248
25443
  }
25249
25444
  async function handleOrgSwitch(selection, options) {
25250
- const authScope = normalizeAuthScope(options.authScope);
25445
+ const authScope = normalizeAuthScope2(options.authScope);
25251
25446
  const config = resolveConfig();
25252
25447
  const http2 = new HttpClient(config);
25253
25448
  const payload = await fetchOrganizations(http2, config.apiKey);
@@ -25624,17 +25819,17 @@ function hasClaudeBinary() {
25624
25819
  }
25625
25820
  }
25626
25821
  function launchClaude(prompt) {
25627
- return new Promise((resolve14) => {
25822
+ return new Promise((resolve15) => {
25628
25823
  const child = (0, import_node_child_process2.spawn)("claude", [prompt], {
25629
25824
  stdio: "inherit",
25630
25825
  shell: process.platform === "win32"
25631
25826
  });
25632
- child.on("error", () => resolve14(EXIT_SERVER3));
25633
- child.on("close", (status) => resolve14(status ?? EXIT_OK2));
25827
+ child.on("error", () => resolve15(EXIT_SERVER3));
25828
+ child.on("close", (status) => resolve15(status ?? EXIT_OK2));
25634
25829
  });
25635
25830
  }
25636
25831
  function readBody(req) {
25637
- return new Promise((resolve14, reject) => {
25832
+ return new Promise((resolve15, reject) => {
25638
25833
  let raw = "";
25639
25834
  req.setEncoding("utf8");
25640
25835
  req.on("data", (chunk) => {
@@ -25644,7 +25839,7 @@ function readBody(req) {
25644
25839
  req.destroy();
25645
25840
  }
25646
25841
  });
25647
- req.on("end", () => resolve14(raw));
25842
+ req.on("end", () => resolve15(raw));
25648
25843
  req.on("error", reject);
25649
25844
  });
25650
25845
  }
@@ -25699,7 +25894,7 @@ function startCallbackServer(input2) {
25699
25894
  writeJson(res, 400, { error: "Invalid request body." });
25700
25895
  });
25701
25896
  });
25702
- return new Promise((resolve14, reject) => {
25897
+ return new Promise((resolve15, reject) => {
25703
25898
  server.once("error", reject);
25704
25899
  server.listen(0, "127.0.0.1", () => {
25705
25900
  const address = server.address();
@@ -25707,7 +25902,7 @@ function startCallbackServer(input2) {
25707
25902
  reject(new Error("Failed to bind quickstart callback server."));
25708
25903
  return;
25709
25904
  }
25710
- resolve14({ server, port: address.port });
25905
+ resolve15({ server, port: address.port });
25711
25906
  });
25712
25907
  });
25713
25908
  }
@@ -25733,8 +25928,8 @@ async function handleQuickstart(options) {
25733
25928
  }
25734
25929
  const state = (0, import_node_crypto6.randomBytes)(32).toString("hex");
25735
25930
  let resolveSelection;
25736
- const selectionPromise = new Promise((resolve14) => {
25737
- resolveSelection = resolve14;
25931
+ const selectionPromise = new Promise((resolve15) => {
25932
+ resolveSelection = resolve15;
25738
25933
  });
25739
25934
  let callback;
25740
25935
  try {
@@ -25873,7 +26068,7 @@ async function readHiddenLine(prompt, streams = {}) {
25873
26068
  }
25874
26069
  let value = "";
25875
26070
  inputStream.resume();
25876
- return await new Promise((resolve14, reject) => {
26071
+ return await new Promise((resolve15, reject) => {
25877
26072
  let settled = false;
25878
26073
  const cleanup = () => {
25879
26074
  inputStream.off("data", onData);
@@ -25891,7 +26086,7 @@ async function readHiddenLine(prompt, streams = {}) {
25891
26086
  settled = true;
25892
26087
  outputStream.write("\n");
25893
26088
  cleanup();
25894
- resolve14(line);
26089
+ resolve15(line);
25895
26090
  };
25896
26091
  const fail = (error) => {
25897
26092
  if (settled) return;
@@ -28694,12 +28889,13 @@ Notes:
28694
28889
  // src/cli/commands/update.ts
28695
28890
  var import_node_child_process4 = require("child_process");
28696
28891
  var import_node_fs17 = require("fs");
28697
- var import_node_os13 = require("os");
28892
+ var import_node_os14 = require("os");
28698
28893
  var import_node_path19 = require("path");
28699
28894
 
28700
- // src/cli/skills-sync.ts
28895
+ // src/cli/commands/skills.ts
28701
28896
  var import_node_child_process3 = require("child_process");
28702
28897
  var import_node_fs16 = require("fs");
28898
+ var import_node_os13 = require("os");
28703
28899
  var import_node_path18 = require("path");
28704
28900
 
28705
28901
  // ../shared_libs/cli/install-commands.json
@@ -28716,7 +28912,7 @@ var install_commands_default = {
28716
28912
  npx_binary: "npx",
28717
28913
  npx_add_args_template: [
28718
28914
  "--yes",
28719
- "skills",
28915
+ "skills@latest",
28720
28916
  "add",
28721
28917
  "{skills_index_url}",
28722
28918
  "--agent",
@@ -28806,311 +29002,380 @@ function sdkNpmGlobalInstallCommand() {
28806
29002
  return INSTALL_COMMANDS.cli.sdk_npm_global;
28807
29003
  }
28808
29004
 
28809
- // src/cli/skills-sync.ts
28810
- var CHECK_TIMEOUT_MS2 = 3e3;
28811
- var attemptedSync = false;
28812
- function shouldSkipSkillsSync() {
28813
- if (detectAgentRuntime() === "claude_cowork") {
28814
- return true;
28815
- }
28816
- const value = process.env.DEEPLINE_SKIP_SKILLS_SYNC?.trim().toLowerCase();
28817
- return value === "1" || value === "true" || value === "yes" || value === "on";
29005
+ // src/cli/commands/skills.ts
29006
+ var RUNTIME_TO_SKILLS_AGENT = {
29007
+ antigravity: "antigravity",
29008
+ claude_code: "claude-code",
29009
+ claude_cowork: "claude-code",
29010
+ cline: "cline",
29011
+ codex: "codex",
29012
+ cursor: "cursor",
29013
+ gemini: "gemini-cli",
29014
+ windsurf: "windsurf"
29015
+ };
29016
+ var AGENT_MARKERS = [
29017
+ { agent: "codex", paths: [".codex"] },
29018
+ { agent: "claude-code", paths: [".claude"] },
29019
+ { agent: "cursor", paths: [".cursor"] },
29020
+ { agent: "gemini-cli", paths: [".gemini", ".gemini-cli"] },
29021
+ { agent: "antigravity", paths: [".antigravity"] }
29022
+ ];
29023
+ var WORKSPACE_SKILL_ROOTS_BY_AGENT = {
29024
+ antigravity: [".agents"],
29025
+ "claude-code": [".claude"],
29026
+ cline: [".agents"],
29027
+ codex: [".agents"],
29028
+ cursor: [".agents"],
29029
+ "gemini-cli": [".agents"],
29030
+ windsurf: [".windsurf"],
29031
+ "*": [".agents", ".claude", ".windsurf"]
29032
+ };
29033
+ function workspaceSkillRootsForAgents(agents) {
29034
+ return [
29035
+ ...new Set(
29036
+ agents.flatMap((agent) => WORKSPACE_SKILL_ROOTS_BY_AGENT[agent] ?? [])
29037
+ )
29038
+ ].sort((a, b) => a.localeCompare(b));
28818
29039
  }
28819
- function activePluginSkillsDir() {
28820
- const pluginMode = process.env.DEEPLINE_PLUGIN_MODE?.trim().toLowerCase();
28821
- if (pluginMode !== "true" && pluginMode !== "1" && pluginMode !== "yes" && pluginMode !== "on") {
28822
- return "";
28823
- }
28824
- const dir = process.env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim() ?? "";
28825
- return dir && (0, import_node_fs16.existsSync)(dir) ? dir : "";
29040
+ function normalizeScope(value) {
29041
+ if (value === "global") return "global";
29042
+ if (value === "local") return "local";
29043
+ if (!value) return inferActiveSkillsScope();
29044
+ throw new Error("--scope must be one of: global, local");
28826
29045
  }
28827
- function readPluginSkillsVersion() {
28828
- const dir = activePluginSkillsDir();
28829
- if (!dir) return "";
28830
- try {
28831
- return (0, import_node_fs16.readFileSync)((0, import_node_path18.join)(dir, ".version"), "utf-8").trim();
28832
- } catch {
28833
- return "";
29046
+ function inferActiveSkillsScope(entrypoint = process.argv[1] ?? "") {
29047
+ return /[\\/]\.deepline[\\/]runtime[\\/]/.test(entrypoint) ? "local" : "global";
29048
+ }
29049
+ function resolveLocalRoot() {
29050
+ const target = resolveProjectPinTarget();
29051
+ if (!target.ok) {
29052
+ throw new Error(
29053
+ `Cowork project folder is ambiguous. Candidate folders: ${target.candidates.join(
29054
+ ", "
29055
+ )}. Set CLAUDE_PROJECT_DIR or cd into the intended project folder.`
29056
+ );
28834
29057
  }
29058
+ return target.dir;
28835
29059
  }
28836
- function sdkSkillsVersionPath(baseUrl) {
28837
- return (0, import_node_path18.join)(sdkCliStateDirPath(baseUrl), "skills-version");
29060
+ function detectSkillsAgents(input2) {
29061
+ const runtime = input2.runtime ?? detectAgentRuntime();
29062
+ const knownAgent = RUNTIME_TO_SKILLS_AGENT[runtime];
29063
+ if (knownAgent) return [knownAgent];
29064
+ const roots = [
29065
+ ...input2.scope === "local" && input2.root ? [input2.root] : [],
29066
+ input2.homeDir ?? (0, import_node_os13.homedir)()
29067
+ ];
29068
+ const detected = AGENT_MARKERS.filter(
29069
+ (marker) => roots.some(
29070
+ (root) => marker.paths.some((path) => (0, import_node_fs16.existsSync)((0, import_node_path18.join)(root, path)))
29071
+ )
29072
+ ).map((marker) => marker.agent);
29073
+ return detected.length > 0 ? detected : ["*"];
28838
29074
  }
28839
- function legacySdkSkillsVersionPath(baseUrl) {
28840
- return (0, import_node_path18.join)((0, import_node_path18.dirname)(sdkCliStateDirPath(baseUrl)), "sdk-skills", ".version");
29075
+ async function fetchSkillCatalog(baseUrl) {
29076
+ const response = await fetch(skillsIndexUrl(baseUrl));
29077
+ if (!response.ok) {
29078
+ throw new Error(
29079
+ `Skill catalog request failed (status ${response.status}).`
29080
+ );
29081
+ }
29082
+ const index = await response.json();
29083
+ 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));
29084
+ if (skillNames.length === 0) {
29085
+ throw new Error(
29086
+ "The Deepline skill catalog contains no installable skills."
29087
+ );
29088
+ }
29089
+ return {
29090
+ skillNames,
29091
+ version: typeof index.version === "string" && index.version.trim() ? index.version.trim() : "unversioned"
29092
+ };
28841
29093
  }
28842
- function unavailableSkillsNoticePath(baseUrl) {
28843
- return (0, import_node_path18.join)(sdkCliStateDirPath(baseUrl), "skills-sync-unavailable-version");
29094
+ function skillsStatePathForScope(baseUrl, scope, root) {
29095
+ return scope === "local" && root ? (0, import_node_path18.join)(root, ".deepline", "setup", "skills.json") : (0, import_node_path18.join)(sdkCliStateDirPath(baseUrl), "skills-install.json");
28844
29096
  }
28845
- function readSdkSkillsLocalVersion(baseUrl) {
28846
- const pluginVersion = readPluginSkillsVersion();
28847
- if (pluginVersion) return pluginVersion;
28848
- const path = (0, import_node_fs16.existsSync)(sdkSkillsVersionPath(baseUrl)) ? sdkSkillsVersionPath(baseUrl) : legacySdkSkillsVersionPath(baseUrl);
28849
- if (!(0, import_node_fs16.existsSync)(path)) return "";
28850
- try {
28851
- return (0, import_node_fs16.readFileSync)(path, "utf-8").trim();
28852
- } catch {
28853
- return "";
28854
- }
29097
+ function buildSkillsPlan(input2) {
29098
+ const scopeArgs = input2.scope === "global" ? ["--global"] : [];
29099
+ const allManagedNames = [
29100
+ .../* @__PURE__ */ new Set([...input2.skillNames, ...LEGACY_SKILL_NAMES_TO_REMOVE])
29101
+ ].sort((a, b) => a.localeCompare(b));
29102
+ return {
29103
+ scope: input2.scope,
29104
+ root: input2.root,
29105
+ agents: input2.agents,
29106
+ skillNames: input2.skillNames,
29107
+ version: input2.version,
29108
+ remove: {
29109
+ command: "npm",
29110
+ args: [
29111
+ "exec",
29112
+ "--yes",
29113
+ "--package=skills@latest",
29114
+ "--",
29115
+ "skills",
29116
+ "remove",
29117
+ ...scopeArgs,
29118
+ "--agent",
29119
+ ...input2.agents,
29120
+ "-y",
29121
+ ...allManagedNames
29122
+ ]
29123
+ },
29124
+ install: {
29125
+ command: "npm",
29126
+ args: [
29127
+ "exec",
29128
+ "--yes",
29129
+ "--package=skills@latest",
29130
+ "--",
29131
+ "skills",
29132
+ "add",
29133
+ skillsIndexUrl(input2.baseUrl),
29134
+ "--agent",
29135
+ ...input2.agents,
29136
+ ...scopeArgs,
29137
+ "--yes",
29138
+ ...input2.skillNames.flatMap((name) => ["--skill", name]),
29139
+ "--full-depth"
29140
+ ]
29141
+ },
29142
+ statePath: skillsStatePathForScope(input2.baseUrl, input2.scope, input2.root)
29143
+ };
28855
29144
  }
28856
- function writeLocalSkillsVersion(baseUrl, version) {
28857
- const path = sdkSkillsVersionPath(baseUrl);
28858
- (0, import_node_fs16.mkdirSync)((0, import_node_path18.dirname)(path), { recursive: true });
28859
- (0, import_node_fs16.writeFileSync)(path, `${version}
28860
- `, "utf-8");
29145
+ function sortedStrings(value) {
29146
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
29147
+ return null;
29148
+ }
29149
+ return [...value].sort((a, b) => a.localeCompare(b));
28861
29150
  }
28862
- function writeUnavailableSkillsNotice(baseUrl, remoteVersion, skillNames) {
28863
- const path = unavailableSkillsNoticePath(baseUrl);
28864
- try {
28865
- if ((0, import_node_fs16.existsSync)(path) && (0, import_node_fs16.readFileSync)(path, "utf-8").trim() === remoteVersion) {
28866
- return;
28867
- }
28868
- (0, import_node_fs16.mkdirSync)((0, import_node_path18.dirname)(path), { recursive: true });
28869
- (0, import_node_fs16.writeFileSync)(path, `${remoteVersion}
28870
- `, "utf-8");
28871
- } catch {
29151
+ function isSkillsPlanCurrent(plan, state) {
29152
+ if (!state || state.scope !== plan.scope || state.skillsVersion !== plan.version) {
29153
+ return false;
28872
29154
  }
28873
- const manualCommand = `npx ${buildSkillsInstallArgs(baseUrl, skillNames).join(" ")}`;
28874
- writeSdkSkillsStatusLine(
28875
- `Deepline agent skills are out of date, but neither \`bunx\` nor \`npx\` is available. Install Node.js/npm or Bun, then run:
28876
- ${manualCommand}`
28877
- );
29155
+ const installedAgents = sortedStrings(state.agents);
29156
+ const installedSkillNames = sortedStrings(state.skillNames);
29157
+ if (!installedAgents || !installedSkillNames) return false;
29158
+ 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");
28878
29159
  }
28879
- function clearUnavailableSkillsNotice(baseUrl) {
29160
+ function readSkillsInstallState(path) {
28880
29161
  try {
28881
- (0, import_node_fs16.unlinkSync)(unavailableSkillsNoticePath(baseUrl));
29162
+ const parsed = JSON.parse((0, import_node_fs16.readFileSync)(path, "utf8"));
29163
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
28882
29164
  } catch {
29165
+ return null;
28883
29166
  }
28884
29167
  }
28885
- function sortedUniqueSkillNames(names) {
28886
- return [...new Set(names.map((name) => name.trim()).filter(Boolean))].sort(
28887
- (a, b) => a.localeCompare(b)
28888
- );
29168
+ function runProcess(command, args, cwd) {
29169
+ return new Promise((resolve15, reject) => {
29170
+ const child = (0, import_node_child_process3.spawn)(command, args, {
29171
+ cwd,
29172
+ env: process.env,
29173
+ stdio: ["ignore", "ignore", "pipe"],
29174
+ shell: process.platform === "win32"
29175
+ });
29176
+ let stderr = "";
29177
+ child.stderr.on("data", (chunk) => {
29178
+ stderr += chunk.toString("utf8");
29179
+ process.stderr.write(chunk);
29180
+ });
29181
+ child.once("error", reject);
29182
+ child.once("close", (code) => {
29183
+ if (code && stderr.trim()) {
29184
+ process.stderr.write(`skills@latest exited ${code}.
29185
+ `);
29186
+ }
29187
+ resolve15(code ?? 1);
29188
+ });
29189
+ });
28889
29190
  }
28890
- async function fetchV1SkillNames(baseUrl) {
28891
- const controller = new AbortController();
28892
- const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
29191
+ async function runSkillsCommand(options, dependencies = {}) {
29192
+ let scope;
29193
+ let root;
28893
29194
  try {
28894
- const response = await fetch(
28895
- new URL("/.well-known/skills/index.json", baseUrl),
28896
- { signal: controller.signal }
28897
- );
28898
- if (!response.ok) return [];
28899
- const data = await response.json().catch(() => null);
28900
- const names = (data?.skills ?? []).filter((skill) => skill.install_surface === "v1").map((skill) => skill.name).filter(
28901
- (name) => typeof name === "string" && name.length > 0
29195
+ scope = normalizeScope(options.scope);
29196
+ root = scope === "local" ? resolveLocalRoot() : null;
29197
+ } catch (error) {
29198
+ printCommandEnvelope(
29199
+ {
29200
+ ok: false,
29201
+ status: "failed",
29202
+ code: "INVALID_SKILLS_SCOPE",
29203
+ exitCode: 2,
29204
+ message: error instanceof Error ? error.message : String(error)
29205
+ },
29206
+ { json: options.json }
28902
29207
  );
28903
- return sortedUniqueSkillNames(names);
28904
- } catch {
28905
- return [];
28906
- } finally {
28907
- clearTimeout(timeout);
29208
+ return 2;
28908
29209
  }
28909
- }
28910
- function buildSdkSkillNames(v1SkillNames) {
28911
- return sortedUniqueSkillNames(v1SkillNames);
28912
- }
28913
- async function fetchSkillsUpdate(baseUrl, localVersion) {
28914
- const controller = new AbortController();
28915
- const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
29210
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
29211
+ let catalog;
28916
29212
  try {
28917
- const response = await fetch(new URL("/api/v2/cli/update-check", baseUrl), {
28918
- method: "POST",
28919
- headers: { "Content-Type": "application/json" },
28920
- body: JSON.stringify({
28921
- skills: {
28922
- version: localVersion
28923
- }
28924
- }),
28925
- signal: controller.signal
28926
- });
28927
- if (!response.ok) return null;
28928
- const data = await response.json().catch(() => null);
28929
- const skills = data?.skills;
28930
- if (!skills) return null;
28931
- return {
28932
- needsUpdate: skills.needs_update === true,
28933
- remoteVersion: typeof skills.remote?.version === "string" ? skills.remote.version.trim() : ""
28934
- };
28935
- } catch {
28936
- return null;
28937
- } finally {
28938
- clearTimeout(timeout);
29213
+ catalog = options.dryRun ? { skillNames: [...DEFAULT_SDK_SKILL_NAMES], version: "latest" } : await (dependencies.fetchCatalog ?? fetchSkillCatalog)(baseUrl);
29214
+ } catch (error) {
29215
+ printCommandEnvelope(
29216
+ {
29217
+ ok: false,
29218
+ status: "failed",
29219
+ code: "SKILLS_CATALOG_UNAVAILABLE",
29220
+ exitCode: 5,
29221
+ message: error instanceof Error ? error.message : String(error),
29222
+ next: "Retry: deepline skills"
29223
+ },
29224
+ { json: options.json }
29225
+ );
29226
+ return 5;
28939
29227
  }
28940
- }
28941
- function buildSkillsInstallArgs(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
28942
- return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames));
28943
- }
28944
- function buildBunxSkillsInstallArgs(baseUrl, skillNames) {
28945
- return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames), {
28946
- firstArg: "--bun"
28947
- });
28948
- }
28949
- function hasCommand(command) {
28950
- const result = (0, import_node_child_process3.spawnSync)(command, ["--version"], {
28951
- stdio: "ignore",
28952
- shell: process.platform === "win32"
28953
- });
28954
- return result.status === 0;
28955
- }
28956
- function shellQuote5(arg) {
28957
- return `'${arg.replace(/'/g, `'\\''`)}'`;
28958
- }
28959
- function resolveSkillsInstallCommands(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
28960
- const commands = [];
28961
- if (hasCommand("bunx")) {
28962
- const bunxArgs = buildBunxSkillsInstallArgs(baseUrl, skillNames);
28963
- commands.push({
28964
- command: "bunx",
28965
- args: bunxArgs,
28966
- manualCommand: `bunx ${bunxArgs.map(shellQuote5).join(" ")}`
28967
- });
29228
+ const agents = options.agent ? [options.agent] : detectSkillsAgents({ scope, root });
29229
+ const plan = buildSkillsPlan({ baseUrl, scope, root, agents, ...catalog });
29230
+ if (options.dryRun) {
29231
+ printCommandEnvelope(
29232
+ { ok: true, status: "planned", dryRun: true, ...plan },
29233
+ { json: options.json }
29234
+ );
29235
+ return 0;
28968
29236
  }
28969
- if (hasCommand("npx")) {
28970
- const npxArgs = buildSkillsInstallArgs(baseUrl, skillNames);
28971
- commands.push({
28972
- command: "npx",
28973
- args: npxArgs,
28974
- manualCommand: `npx ${npxArgs.map(shellQuote5).join(" ")}`
28975
- });
29237
+ if (isSkillsPlanCurrent(plan, readSkillsInstallState(plan.statePath))) {
29238
+ printCommandEnvelope(
29239
+ {
29240
+ ok: true,
29241
+ status: "current",
29242
+ complete: true,
29243
+ changed: false,
29244
+ skipped: true,
29245
+ skipReason: "skills_version_current",
29246
+ scope,
29247
+ agents,
29248
+ skillsVersion: plan.version,
29249
+ skillCount: plan.skillNames.length,
29250
+ statePath: plan.statePath,
29251
+ render: {
29252
+ sections: [
29253
+ {
29254
+ title: "skills",
29255
+ lines: [
29256
+ `Scope: ${scope}`,
29257
+ `Agents: ${agents.join(", ")}`,
29258
+ `Current: ${plan.skillNames.length}`
29259
+ ]
29260
+ }
29261
+ ]
29262
+ }
29263
+ },
29264
+ { json: options.json }
29265
+ );
29266
+ return 0;
28976
29267
  }
28977
- return commands;
28978
- }
28979
- function runOneSkillsInstall(install) {
28980
- return new Promise((resolve14) => {
28981
- const child = (0, import_node_child_process3.spawn)(install.command, install.args, {
28982
- stdio: ["ignore", "ignore", "pipe"],
28983
- env: process.env
28984
- });
28985
- let stderr = "";
28986
- child.stderr.on("data", (chunk) => {
28987
- stderr += chunk.toString("utf-8");
28988
- });
28989
- child.on("error", (error) => {
28990
- resolve14({
28991
- ok: false,
28992
- detail: `failed to start ${install.command}: ${error.message}`,
28993
- manualCommand: install.manualCommand
28994
- });
28995
- });
28996
- child.on("close", (code) => {
28997
- if (code === 0) {
28998
- resolve14({ ok: true, detail: "", manualCommand: install.manualCommand });
28999
- return;
29000
- }
29001
- const detail = stderr.trim();
29002
- resolve14({
29003
- ok: false,
29004
- detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
29005
- manualCommand: install.manualCommand
29006
- });
29007
- });
29008
- });
29009
- }
29010
- async function runSkillsInstall(installs) {
29011
- const failures = [];
29012
- for (const install of installs) {
29013
- const result = await runOneSkillsInstall(install);
29014
- if (result.ok) return true;
29015
- failures.push(result);
29268
+ if (scope === "local" && root) {
29269
+ const managedNames = [
29270
+ .../* @__PURE__ */ new Set([...plan.skillNames, ...LEGACY_SKILL_NAMES_TO_REMOVE])
29271
+ ];
29272
+ const agentRoots = workspaceSkillRootsForAgents(plan.agents);
29273
+ ensureProjectPrivatePathsIgnored(root, [
29274
+ ".deepline/",
29275
+ ...agentRoots.flatMap(
29276
+ (agentRoot) => managedNames.map((name) => `${agentRoot}/skills/${name}/`)
29277
+ )
29278
+ ]);
29016
29279
  }
29017
- const details = failures.map((failure) => failure.detail).filter(Boolean).join("\n");
29018
- const manualCommand = failures.at(-1)?.manualCommand;
29019
29280
  process.stderr.write(
29020
- `SDK skills sync failed${details ? `:
29021
- ${details}` : ""}
29022
- ` + (manualCommand ? `Run manually: ${manualCommand}
29023
- ` : "")
29281
+ `Replacing Deepline skills for ${agents.join(", ")} (${scope})...
29282
+ `
29024
29283
  );
29025
- return false;
29026
- }
29027
- function runLegacySkillsCleanup() {
29028
- const candidates = hasCommand("bunx") ? [
29029
- {
29030
- command: "bunx",
29031
- args: [
29032
- "--bun",
29033
- "skills",
29034
- "remove",
29035
- "--global",
29036
- "-y",
29037
- ...LEGACY_SKILL_NAMES_TO_REMOVE
29038
- ]
29039
- },
29040
- {
29041
- command: "npx",
29042
- args: [
29043
- "--yes",
29044
- "skills",
29045
- "remove",
29046
- "--global",
29047
- "-y",
29048
- ...LEGACY_SKILL_NAMES_TO_REMOVE
29049
- ]
29284
+ try {
29285
+ const execute = dependencies.runProcess ?? runProcess;
29286
+ const removeCode = await execute(
29287
+ plan.remove.command,
29288
+ plan.remove.args,
29289
+ root ?? void 0
29290
+ );
29291
+ if (removeCode !== 0) {
29292
+ throw new Error("Could not remove the existing Deepline skills.");
29050
29293
  }
29051
- ] : [
29052
- {
29053
- command: "npx",
29054
- args: [
29055
- "--yes",
29056
- "skills",
29057
- "remove",
29058
- "--global",
29059
- "-y",
29060
- ...LEGACY_SKILL_NAMES_TO_REMOVE
29061
- ]
29294
+ const installCode = await execute(
29295
+ plan.install.command,
29296
+ plan.install.args,
29297
+ root ?? void 0
29298
+ );
29299
+ if (installCode !== 0) {
29300
+ throw new Error("Could not install the current Deepline skills.");
29062
29301
  }
29063
- ];
29064
- for (const candidate of candidates) {
29065
- const result = (0, import_node_child_process3.spawnSync)(candidate.command, candidate.args, {
29066
- stdio: "ignore",
29067
- env: process.env,
29068
- shell: process.platform === "win32"
29069
- });
29070
- if (result.status === 0) return;
29071
- }
29072
- }
29073
- function writeSdkSkillsStatusLine(line) {
29074
- const progress = getActiveCliProgress();
29075
- if (progress) {
29076
- progress.writeLine(line);
29077
- return;
29078
- }
29079
- process.stderr.write(`${line}
29080
- `);
29081
- }
29082
- async function syncSdkSkillsIfNeeded(baseUrl, options = {}) {
29083
- if (attemptedSync || shouldSkipSkillsSync()) return;
29084
- attemptedSync = true;
29085
- const usingPluginSkills = Boolean(activePluginSkillsDir());
29086
- if (usingPluginSkills) {
29087
- return;
29088
- }
29089
- const localVersion = readSdkSkillsLocalVersion(baseUrl);
29090
- const update = options.update === void 0 ? await fetchSkillsUpdate(baseUrl, localVersion) : options.update ? {
29091
- needsUpdate: options.update.needs_update,
29092
- remoteVersion: options.update.remote.version
29093
- } : null;
29094
- if (!update?.needsUpdate || !update.remoteVersion) {
29095
- return;
29302
+ } catch (error) {
29303
+ printCommandEnvelope(
29304
+ {
29305
+ ok: false,
29306
+ status: "failed",
29307
+ code: "SKILLS_INSTALL_FAILED",
29308
+ exitCode: 5,
29309
+ scope,
29310
+ agents,
29311
+ message: error instanceof Error ? error.message : String(error),
29312
+ next: `deepline skills --scope ${scope} --json`
29313
+ },
29314
+ { json: options.json }
29315
+ );
29316
+ return 5;
29096
29317
  }
29097
- const remoteSkillNames = await fetchV1SkillNames(baseUrl);
29098
- const skillNames = buildSdkSkillNames(
29099
- remoteSkillNames.length > 0 ? remoteSkillNames : DEFAULT_SDK_SKILL_NAMES
29318
+ (0, import_node_fs16.mkdirSync)((0, import_node_path18.dirname)(plan.statePath), { recursive: true });
29319
+ (0, import_node_fs16.writeFileSync)(
29320
+ plan.statePath,
29321
+ `${JSON.stringify(
29322
+ {
29323
+ schemaVersion: 1,
29324
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
29325
+ cliVersion: SDK_VERSION,
29326
+ scope,
29327
+ agents,
29328
+ skillsVersion: plan.version,
29329
+ skillNames: plan.skillNames
29330
+ },
29331
+ null,
29332
+ 2
29333
+ )}
29334
+ `,
29335
+ "utf8"
29100
29336
  );
29101
- if (skillNames.length === 0) return;
29102
- const installs = resolveSkillsInstallCommands(baseUrl, skillNames);
29103
- if (installs.length === 0) {
29104
- writeUnavailableSkillsNotice(baseUrl, update.remoteVersion, skillNames);
29105
- return;
29106
- }
29107
- writeSdkSkillsStatusLine("Deepline skills changed; syncing agent skills...");
29108
- const installed = await runSkillsInstall(installs);
29109
- if (!installed) return;
29110
- runLegacySkillsCleanup();
29111
- writeLocalSkillsVersion(baseUrl, update.remoteVersion);
29112
- clearUnavailableSkillsNotice(baseUrl);
29113
- writeSdkSkillsStatusLine("Deepline agent skills are up to date.");
29337
+ printCommandEnvelope(
29338
+ {
29339
+ ok: true,
29340
+ status: "complete",
29341
+ scope,
29342
+ agents,
29343
+ skillsVersion: plan.version,
29344
+ skillCount: plan.skillNames.length,
29345
+ statePath: plan.statePath,
29346
+ render: {
29347
+ sections: [
29348
+ {
29349
+ title: "skills",
29350
+ lines: [
29351
+ `Scope: ${scope}`,
29352
+ `Agents: ${agents.join(", ")}`,
29353
+ `Installed: ${plan.skillNames.length}`
29354
+ ]
29355
+ }
29356
+ ]
29357
+ }
29358
+ },
29359
+ { json: options.json }
29360
+ );
29361
+ return 0;
29362
+ }
29363
+ function registerSkillsCommand(program) {
29364
+ 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(
29365
+ "after",
29366
+ `
29367
+ Notes:
29368
+ This command removes and reinstalls only Deepline-managed skill names using
29369
+ skills@latest. Local scope writes into the resolved persistent project.
29370
+
29371
+ Examples:
29372
+ deepline skills --json
29373
+ deepline skills --scope local --json
29374
+ deepline skills --agent codex --dry-run --json
29375
+ `
29376
+ ).action(async (options) => {
29377
+ process.exitCode = await runSkillsCommand(options);
29378
+ });
29114
29379
  }
29115
29380
 
29116
29381
  // src/cli/commands/update.ts
@@ -29142,14 +29407,14 @@ function posixShellQuote(value) {
29142
29407
  function windowsCmdQuote(value) {
29143
29408
  return `"${value.replace(/"/g, '""')}"`;
29144
29409
  }
29145
- function shellQuote6(value) {
29410
+ function shellQuote5(value) {
29146
29411
  if (process.platform === "win32") {
29147
29412
  return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : windowsCmdQuote(value);
29148
29413
  }
29149
29414
  return posixShellQuote(value);
29150
29415
  }
29151
29416
  function buildSourceUpdateCommand(sourceRoot) {
29152
- const quotedRoot = shellQuote6(sourceRoot);
29417
+ const quotedRoot = shellQuote5(sourceRoot);
29153
29418
  const cdCommand = process.platform === "win32" ? `cd /d ${quotedRoot}` : `cd ${quotedRoot}`;
29154
29419
  return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
29155
29420
  }
@@ -29161,7 +29426,7 @@ function buildSidecarProjectConfigCommand(versionDir, nodeBin) {
29161
29426
  "fs.mkdirSync(dir,{recursive:true});",
29162
29427
  `fs.writeFileSync(path.join(dir,'package.json'),${JSON.stringify(NPM_SDK_SIDECAR_PACKAGE_JSON)});`
29163
29428
  ].join("");
29164
- return `${shellQuote6(nodeBin)} -e ${shellQuote6(script)} ${shellQuote6(versionDir)}`;
29429
+ return `${shellQuote5(nodeBin)} -e ${shellQuote5(script)} ${shellQuote5(versionDir)}`;
29165
29430
  }
29166
29431
  function sidecarStateDir(input2) {
29167
29432
  const scope = input2.env.DEEPLINE_CONFIG_SCOPE?.trim();
@@ -29224,7 +29489,7 @@ function resolvePythonSidecarUpdatePlan(options) {
29224
29489
  const npmCommand = "npm";
29225
29490
  const registryUrl = sidecarRegistryUrl(hostUrl);
29226
29491
  const versionDir = (0, import_node_path19.join)(stateDir, "versions", "<version>");
29227
- const manualCommand = `${buildSidecarProjectConfigCommand(versionDir, nodeBin)} && ${npmCommand} install --prefix ${shellQuote6(versionDir)} --registry ${shellQuote6(registryUrl)} ${NPM_SDK_INSTALL_COMMON_FLAGS.map(shellQuote6).join(" ")} ${shellQuote6(packageSpec)}`;
29492
+ const manualCommand = `${buildSidecarProjectConfigCommand(versionDir, nodeBin)} && ${npmCommand} install --prefix ${shellQuote5(versionDir)} --registry ${shellQuote5(registryUrl)} ${NPM_SDK_INSTALL_COMMON_FLAGS.map(shellQuote5).join(" ")} ${shellQuote5(packageSpec)}`;
29228
29493
  return {
29229
29494
  kind: "python-sidecar",
29230
29495
  stateDir,
@@ -29272,7 +29537,7 @@ function inferNpmGlobalPrefixFromEntrypoint(entrypoint) {
29272
29537
  }
29273
29538
  function resolveUpdatePlan(options = {}) {
29274
29539
  const env = options.env ?? process.env;
29275
- const homeDir2 = options.homeDir ?? (0, import_node_os13.homedir)();
29540
+ const homeDir2 = options.homeDir ?? (0, import_node_os14.homedir)();
29276
29541
  const entrypoint = options.entrypoint ?? (process.argv[1] ? (0, import_node_path19.resolve)(process.argv[1]) : "");
29277
29542
  const sourceRoot = entrypoint ? findRepoBackedSdkRoot((0, import_node_path19.dirname)(entrypoint)) : null;
29278
29543
  if (sourceRoot) {
@@ -29308,7 +29573,7 @@ function resolveUpdatePlan(options = {}) {
29308
29573
  fallbackRegistryUrl: publicNpmFallbackRegistryUrl(
29309
29574
  env.DEEPLINE_HOST_URL?.trim() || autoDetectBaseUrl()
29310
29575
  ),
29311
- manualCommand: `${command} ${args.map(shellQuote6).join(" ")}`
29576
+ manualCommand: `${command} ${args.map(shellQuote5).join(" ")}`
29312
29577
  };
29313
29578
  }
29314
29579
  var AUTO_UPDATE_FAILURE_FILE = ".auto-update-failure.json";
@@ -29318,7 +29583,7 @@ function autoUpdateFailurePath(plan) {
29318
29583
  return (0, import_node_path19.join)(plan.stateDir, AUTO_UPDATE_FAILURE_FILE);
29319
29584
  }
29320
29585
  return (0, import_node_path19.join)(
29321
- (0, import_node_os13.homedir)(),
29586
+ (0, import_node_os14.homedir)(),
29322
29587
  ".local",
29323
29588
  "deepline",
29324
29589
  "sdk-cli",
@@ -29513,9 +29778,9 @@ function writeSidecarLauncher(input2) {
29513
29778
  input2.path,
29514
29779
  [
29515
29780
  "#!/usr/bin/env sh",
29516
- `export DEEPLINE_HOST_URL=${shellQuote6(input2.hostUrl)}`,
29517
- `export DEEPLINE_CONFIG_SCOPE=${shellQuote6(input2.scope)}`,
29518
- `exec ${shellQuote6(input2.nodeBin)} ${shellQuote6(input2.entryPath)} "$@"`,
29781
+ `export DEEPLINE_HOST_URL=${shellQuote5(input2.hostUrl)}`,
29782
+ `export DEEPLINE_CONFIG_SCOPE=${shellQuote5(input2.scope)}`,
29783
+ `exec ${shellQuote5(input2.nodeBin)} ${shellQuote5(input2.entryPath)} "$@"`,
29519
29784
  ""
29520
29785
  ].join("\n"),
29521
29786
  { encoding: "utf8", mode: 493 }
@@ -29645,7 +29910,20 @@ async function runUpdateCommand(options, dependencies = {}) {
29645
29910
  const detectBaseUrl = dependencies.detectBaseUrl ?? autoDetectBaseUrl;
29646
29911
  const resolvePlan = dependencies.resolvePlan ?? resolveUpdatePlan;
29647
29912
  const runPlan = dependencies.runPlan ?? runUpdatePlan;
29648
- const syncSkills = dependencies.syncSkillsIfNeeded ?? syncSdkSkillsIfNeeded;
29913
+ const syncSkills = dependencies.syncSkillsIfNeeded ?? (async () => {
29914
+ const originalWrite = process.stdout.write.bind(process.stdout);
29915
+ process.stdout.write = (() => true);
29916
+ try {
29917
+ const exitCode = await runSkillsCommand({ json: true });
29918
+ if (exitCode !== 0) {
29919
+ throw new Error(
29920
+ `Deepline skills update failed with exit ${exitCode}.`
29921
+ );
29922
+ }
29923
+ } finally {
29924
+ process.stdout.write = originalWrite;
29925
+ }
29926
+ });
29649
29927
  const stderr = dependencies.stderr ?? process.stderr;
29650
29928
  const plan = resolvePlan();
29651
29929
  const render = {
@@ -29713,214 +29991,1399 @@ Examples:
29713
29991
  });
29714
29992
  }
29715
29993
 
29716
- // ../shared_libs/cli/command-compatibility.json
29717
- var command_compatibility_default = {
29718
- enrich: {
29719
- family: "python",
29720
- label: "a legacy Python CLI enrichment command",
29721
- sdk_alternative: "Use `deepline plays ...` for durable workflows or `deepline tools execute ...` for one tool call."
29722
- },
29723
- session: {
29724
- family: "python",
29725
- label: "a legacy Python CLI session/playground command",
29726
- sdk_alternative: "Use `deepline sessions send ...` or `deepline sessions render ...` for transcript workflows."
29727
- },
29728
- workflows: {
29729
- family: "python",
29730
- label: "a legacy Python CLI workflow command",
29731
- sdk_alternative: "Use `deepline plays ...` in the SDK CLI."
29732
- },
29733
- events: {
29734
- family: "python",
29735
- label: "a legacy Python CLI event command"
29736
- },
29737
- plays: {
29738
- family: "sdk",
29739
- label: "an SDK CLI play command",
29740
- python_alternative: "Use `deepline workflows ...` only for legacy workflows."
29741
- },
29742
- runs: {
29743
- family: "sdk",
29744
- label: "an SDK CLI run inspection command"
29745
- },
29746
- sessions: {
29747
- family: "sdk",
29748
- label: "an SDK CLI session transcript command"
29749
- },
29750
- health: {
29751
- family: "sdk",
29752
- label: "an SDK CLI health command"
29994
+ // src/cli/commands/setup.ts
29995
+ var import_node_child_process5 = require("child_process");
29996
+ var import_node_fs18 = require("fs");
29997
+ var import_node_os15 = require("os");
29998
+ var import_node_path20 = require("path");
29999
+ var SETUP_PHASE_NAMES = [
30000
+ "cli",
30001
+ "cleanup",
30002
+ "skills",
30003
+ "auth",
30004
+ "verify"
30005
+ ];
30006
+ function initialSetupPhases() {
30007
+ return {
30008
+ cli: { status: "pending" },
30009
+ cleanup: { status: "pending" },
30010
+ skills: { status: "pending" },
30011
+ auth: { status: "pending" },
30012
+ verify: { status: "pending" }
30013
+ };
30014
+ }
30015
+ function isSetupPhaseStatus(value) {
30016
+ return value === "pending" || value === "in_progress" || value === "complete" || value === "waiting" || value === "failed";
30017
+ }
30018
+ function parseSetupPhases(value) {
30019
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
30020
+ const source = value;
30021
+ const phases = initialSetupPhases();
30022
+ for (const name of SETUP_PHASE_NAMES) {
30023
+ const phase = source[name];
30024
+ if (!phase || typeof phase !== "object" || Array.isArray(phase)) {
30025
+ return null;
30026
+ }
30027
+ const record = phase;
30028
+ if (!isSetupPhaseStatus(record.status)) return null;
30029
+ phases[name] = {
30030
+ status: record.status,
30031
+ ...typeof record.outcome === "string" ? { outcome: record.outcome } : {},
30032
+ ...typeof record.code === "string" ? { code: record.code } : {}
30033
+ };
29753
30034
  }
29754
- };
29755
-
29756
- // src/cli/command-compatibility.ts
29757
- var COMMAND_COMPATIBILITY = command_compatibility_default;
29758
- function cliFamilyLabel(family) {
29759
- return family === "sdk" ? "SDK CLI" : "legacy Python CLI";
30035
+ return phases;
29760
30036
  }
29761
- function commandCompatibilityHint(currentFamily, commandName, baseUrl) {
29762
- const compatibility = COMMAND_COMPATIBILITY[commandName];
29763
- if (!compatibility || compatibility.family === currentFamily) {
29764
- return null;
30037
+ function phasesFromLegacyStatus(status) {
30038
+ const phases = initialSetupPhases();
30039
+ if (status === "skills_installed" || status === "authorization_pending" || status === "complete") {
30040
+ phases.cli = { status: "complete" };
30041
+ phases.cleanup = { status: "complete" };
30042
+ phases.skills = { status: "complete" };
29765
30043
  }
29766
- const expectedFamily = compatibility.family;
29767
- const currentLabel = cliFamilyLabel(currentFamily);
29768
- const expectedLabel = cliFamilyLabel(expectedFamily);
29769
- const lines = [
29770
- "",
29771
- "Command compatibility:",
29772
- ` \`deepline ${commandName}\` is ${compatibility.label}.`,
29773
- ` Current binary: ${currentLabel}. Required binary: ${expectedLabel}.`,
29774
- " If this came from an agent skill, the installed skill likely targets the other Deepline CLI."
29775
- ];
29776
- if (currentFamily === "sdk") {
29777
- lines.push(
29778
- "",
29779
- " To stay on the SDK CLI, refresh the Deepline agent skills:",
29780
- ` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
29781
- " To use the legacy Python CLI instead:",
29782
- ` ${legacyPythonInstallCommand(baseUrl)}`,
29783
- " `deepline update` updates this SDK CLI, but it will not switch CLI families."
29784
- );
29785
- if (compatibility.sdk_alternative) {
29786
- lines.push(` SDK alternative: ${compatibility.sdk_alternative}`);
29787
- }
29788
- } else {
29789
- lines.push(
29790
- "",
29791
- " To use SDK commands, install the SDK CLI and refresh Deepline agent skills:",
29792
- ` ${sdkNpmGlobalInstallCommand()}`,
29793
- ` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
29794
- " `deepline update` updates this Python CLI and its skills, but it will not switch CLI families."
30044
+ if (status === "authorization_pending") {
30045
+ phases.auth = { status: "waiting", outcome: "authorization_pending" };
30046
+ } else if (status === "complete") {
30047
+ phases.auth = { status: "complete" };
30048
+ phases.verify = { status: "complete" };
30049
+ }
30050
+ return phases;
30051
+ }
30052
+ function readSetupState(input2) {
30053
+ try {
30054
+ const parsed = JSON.parse(
30055
+ (0, import_node_fs18.readFileSync)(
30056
+ setupStatePath(input2.baseUrl, input2.scope, input2.root),
30057
+ "utf8"
30058
+ )
29795
30059
  );
29796
- if (compatibility.python_alternative) {
29797
- lines.push(` Python alternative: ${compatibility.python_alternative}`);
30060
+ if (parsed.host !== input2.baseUrl || parsed.scope !== input2.scope || parsed.root !== input2.root || typeof parsed.status !== "string") {
30061
+ return null;
29798
30062
  }
30063
+ return {
30064
+ status: parsed.status,
30065
+ phases: parseSetupPhases(parsed.phases) ?? phasesFromLegacyStatus(parsed.status)
30066
+ };
30067
+ } catch {
30068
+ return null;
29799
30069
  }
29800
- return lines.join("\n");
29801
30070
  }
29802
- function unknownCommandNameFromMessage(message) {
29803
- const match = message.match(/unknown command ['"]([^'"]+)['"]/i);
29804
- const command = match?.[1]?.trim();
29805
- return command ? command : null;
30071
+ function selectSetupProgress(previousState) {
30072
+ const resumed = Boolean(previousState && previousState.status !== "complete");
30073
+ return {
30074
+ resumed,
30075
+ phases: resumed ? previousState.phases : initialSetupPhases()
30076
+ };
29806
30077
  }
29807
-
29808
- // src/cli/self-update.ts
29809
- var import_node_child_process5 = require("child_process");
29810
- function envTruthy(name) {
29811
- const value = process.env[name]?.trim().toLowerCase();
29812
- return value === "1" || value === "true" || value === "yes";
30078
+ function normalizeScope2(value) {
30079
+ if (!value || value === "global") return "global";
30080
+ if (value === "local") return "local";
30081
+ throw new Error("--scope must be one of: global, local");
29813
30082
  }
29814
- function isCi() {
29815
- return envTruthy("CI") || envTruthy("GITHUB_ACTIONS");
30083
+ function resolveScopeRoot(scope) {
30084
+ if (scope === "global") return null;
30085
+ const target = resolveProjectPinTarget();
30086
+ if (!target.ok) {
30087
+ throw new Error(
30088
+ `Cowork project folder is ambiguous. Candidate folders: ${target.candidates.join(
30089
+ ", "
30090
+ )}. Set CLAUDE_PROJECT_DIR or cd into the intended project folder.`
30091
+ );
30092
+ }
30093
+ return target.dir;
29816
30094
  }
29817
- function shouldSkipSelfUpdate() {
29818
- return envTruthy("DEEPLINE_SKIP_SELF_UPDATE") || envTruthy("DEEPLINE_NO_AUTO_UPDATE") || envTruthy("DEEPLINE_SKIP_SDK_AUTO_UPDATE") || envTruthy("DEEPLINE_DISABLE_AUTO_UPDATE") || isCi();
30095
+ function authScopeForSetup(scope) {
30096
+ return scope === "local" ? "folder" : "global";
29819
30097
  }
29820
- function parseSemver(version) {
29821
- const trimmed = version?.trim();
29822
- if (!trimmed) return null;
29823
- const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(
29824
- trimmed
29825
- );
29826
- if (!match) return null;
29827
- return {
29828
- major: Number(match[1]),
29829
- minor: Number(match[2]),
29830
- patch: Number(match[3]),
29831
- prerelease: match[4] ?? ""
29832
- };
30098
+ function setupStatePath(baseUrl, scope, root) {
30099
+ return scope === "local" && root ? (0, import_node_path20.join)(root, ".deepline", "setup", "state.json") : (0, import_node_path20.join)(sdkCliStateDirPath(baseUrl), "setup.json");
29833
30100
  }
29834
- function compareSemver(left, right) {
29835
- const a = parseSemver(left);
29836
- const b = parseSemver(right);
29837
- if (!a || !b) {
29838
- return left.localeCompare(right);
30101
+ async function captureStdout2(run) {
30102
+ let stdout = "";
30103
+ const originalWrite = process.stdout.write.bind(process.stdout);
30104
+ process.stdout.write = ((chunk) => {
30105
+ stdout += typeof chunk === "string" ? chunk : String(chunk);
30106
+ return true;
30107
+ });
30108
+ try {
30109
+ return { exitCode: await run(), stdout, error: null };
30110
+ } catch (error) {
30111
+ return { exitCode: 4, stdout, error };
30112
+ } finally {
30113
+ process.stdout.write = originalWrite;
29839
30114
  }
29840
- for (const key of ["major", "minor", "patch"]) {
29841
- if (a[key] !== b[key]) return a[key] > b[key] ? 1 : -1;
30115
+ }
30116
+ function parseCapturedJson(stdout) {
30117
+ try {
30118
+ return JSON.parse(stdout.trim());
30119
+ } catch {
30120
+ return null;
29842
30121
  }
29843
- if (a.prerelease === b.prerelease) return 0;
29844
- if (!a.prerelease) return 1;
29845
- if (!b.prerelease) return -1;
29846
- return a.prerelease.localeCompare(b.prerelease);
29847
30122
  }
29848
- function isDowngradeAutoUpdateResponse(response) {
29849
- const target = response?.latest?.trim();
29850
- const current = response?.current?.trim() || SDK_VERSION;
29851
- if (!target) return false;
29852
- return compareSemver(target, current) < 0;
30123
+ function asRecord2(value) {
30124
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
29853
30125
  }
29854
- function relaunchCurrentCommand(plan) {
29855
- return new Promise((resolve14) => {
29856
- const command = plan.kind === "python-sidecar" ? plan.sidecarPath : process.execPath;
29857
- const args = plan.kind === "python-sidecar" ? process.argv.slice(2) : process.argv.slice(1);
29858
- const child = (0, import_node_child_process5.spawn)(command, args, {
29859
- stdio: "inherit",
29860
- shell: process.platform === "win32",
29861
- env: {
29862
- ...process.env,
29863
- DEEPLINE_NO_AUTO_UPDATE: "1"
29864
- }
29865
- });
29866
- child.on("error", (error) => {
29867
- process.stderr.write(
29868
- `Deepline SDK/CLI updated, but relaunch failed: ${error.message}
29869
- `
29870
- );
29871
- resolve14(1);
29872
- });
29873
- child.on("close", (code) => resolve14(code ?? 1));
29874
- });
30126
+ function printCapturedAuthorizationUrl(stdout) {
30127
+ const urls = stdout.match(/https:\/\/[^\s]+/g) ?? [];
30128
+ for (const url of new Set(
30129
+ urls.map((value) => value.replace(/[).,]+$/, ""))
30130
+ )) {
30131
+ process.stderr.write(`Authorize Deepline: ${url}
30132
+ `);
30133
+ }
29875
30134
  }
29876
- async function maybeAutoUpdateAndRelaunch(response) {
29877
- const autoUpdate = response?.auto_update;
29878
- if (!response || !autoUpdate?.should_auto_update || shouldSkipSelfUpdate()) {
29879
- return false;
30135
+ function safeRead(path) {
30136
+ try {
30137
+ return (0, import_node_fs18.readFileSync)(path, "utf8");
30138
+ } catch {
30139
+ return "";
29880
30140
  }
29881
- if (isDowngradeAutoUpdateResponse(response)) {
29882
- const target = response.latest;
29883
- const current = response.current?.trim() || SDK_VERSION;
29884
- process.stderr.write(
29885
- `Deepline SDK/CLI auto-update refused: server advertised older ${target} than current ${current}. Continuing without mutating the CLI.
29886
- `
29887
- );
30141
+ }
30142
+ function isNpmManagedDeeplinePath(path) {
30143
+ try {
30144
+ return (0, import_node_fs18.realpathSync)(path).includes(`${(0, import_node_path20.join)("node_modules", "deepline")}`);
30145
+ } catch {
29888
30146
  return false;
29889
30147
  }
29890
- const packageSpec = response.latest ? `deepline@${response.latest}` : void 0;
29891
- const plan = resolveUpdatePlan({ packageSpec });
29892
- if (plan.kind === "source") {
29893
- return false;
30148
+ }
30149
+ function removeKnownLegacyPaths(baseUrl) {
30150
+ const home = (0, import_node_os15.homedir)();
30151
+ const hostDir = (0, import_node_path20.join)(home, ".local", "deepline", baseUrlSlug(baseUrl));
30152
+ const installerCommandPath = safeRead(
30153
+ (0, import_node_path20.join)(hostDir, "sdk", ".command-path")
30154
+ ).trim();
30155
+ const candidates = [
30156
+ (0, import_node_path20.join)(home, ".local", "bin", "deepline-real"),
30157
+ (0, import_node_path20.join)(hostDir, "bin", "deepline"),
30158
+ (0, import_node_path20.join)(hostDir, "bin", "deepline-real"),
30159
+ (0, import_node_path20.join)(hostDir, "cli", ".install-method"),
30160
+ (0, import_node_path20.join)(hostDir, "cli", ".version"),
30161
+ (0, import_node_path20.join)(hostDir, "sdk", ".install-method"),
30162
+ (0, import_node_path20.join)(hostDir, "sdk", ".command-path"),
30163
+ ...installerCommandPath ? [
30164
+ installerCommandPath,
30165
+ (0, import_node_path20.join)((0, import_node_path20.dirname)(installerCommandPath), "deepline-sdk")
30166
+ ] : []
30167
+ ];
30168
+ const removed = [];
30169
+ for (const path of candidates) {
30170
+ if (!(0, import_node_fs18.existsSync)(path)) continue;
30171
+ if (path === installerCommandPath && isNpmManagedDeeplinePath(path)) {
30172
+ continue;
30173
+ }
30174
+ (0, import_node_fs18.rmSync)(path, { force: true });
30175
+ removed.push(path);
29894
30176
  }
29895
- 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";
29896
- process.stderr.write(
29897
- `Deepline SDK/CLI ${label}; running ${plan.manualCommand}
29898
- `
30177
+ return removed;
30178
+ }
30179
+ function resolvePathCommands(command) {
30180
+ const lookup = (0, import_node_child_process5.spawnSync)(
30181
+ process.platform === "win32" ? "where" : "which",
30182
+ process.platform === "win32" ? [command] : ["-a", command],
30183
+ { encoding: "utf8", shell: process.platform === "win32" }
29899
30184
  );
29900
- const updateResult = await runAutomaticUpdatePlan(plan);
29901
- if (updateResult.status === "skipped_previous_failure") {
29902
- return false;
30185
+ return [
30186
+ ...new Set(
30187
+ String(lookup.stdout ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((path) => (0, import_node_path20.resolve)(path))
30188
+ )
30189
+ ];
30190
+ }
30191
+ function resolvePathCommand(command) {
30192
+ return resolvePathCommands(command)[0] ?? null;
30193
+ }
30194
+ function resolvePersistentGlobalCommand() {
30195
+ const prefix = (0, import_node_child_process5.spawnSync)("npm", ["prefix", "-g"], { encoding: "utf8" });
30196
+ if (prefix.status !== 0) return null;
30197
+ const root = String(prefix.stdout ?? "").trim();
30198
+ if (!root) return null;
30199
+ const candidates = process.platform === "win32" ? [(0, import_node_path20.join)(root, "deepline.cmd"), (0, import_node_path20.join)(root, "deepline")] : [(0, import_node_path20.join)(root, "bin", "deepline")];
30200
+ return candidates.find((candidate) => (0, import_node_fs18.existsSync)(candidate)) ?? null;
30201
+ }
30202
+ function inspectGlobalCliAvailability(input2) {
30203
+ const persistentPath = input2 && "persistentGlobalCli" in input2 ? input2.persistentGlobalCli ?? null : resolvePersistentGlobalCommand();
30204
+ const pathClis = input2?.pathClis ?? resolvePathCommands("deepline");
30205
+ const path = persistentPath ? pathClis.find(
30206
+ (candidate) => pathsResolveToSameFile(candidate, persistentPath)
30207
+ ) ?? null : null;
30208
+ return { ok: Boolean(path), persistentPath, path };
30209
+ }
30210
+ function pathsResolveToSameFile(left, right) {
30211
+ try {
30212
+ return (0, import_node_fs18.realpathSync)(left) === (0, import_node_fs18.realpathSync)(right);
30213
+ } catch {
30214
+ return (0, import_node_path20.resolve)(left) === (0, import_node_path20.resolve)(right);
29903
30215
  }
29904
- if (updateResult.exitCode !== 0) {
29905
- if (autoUpdate.required) {
29906
- throw new Error(
29907
- `Automatic Deepline SDK/CLI update failed with exit code ${updateResult.exitCode}. ${response.message}`
29908
- );
30216
+ }
30217
+ function isKnownDeeplineCommand(path) {
30218
+ const entrypoint = process.argv[1] ? (0, import_node_path20.resolve)(process.argv[1]) : "";
30219
+ let resolvedPath = path;
30220
+ try {
30221
+ resolvedPath = (0, import_node_fs18.realpathSync)(path);
30222
+ } catch {
30223
+ }
30224
+ if (entrypoint && resolvedPath === entrypoint) return true;
30225
+ if (resolvedPath.includes(`${(0, import_node_path20.join)("node_modules", "deepline")}`)) return true;
30226
+ const content = safeRead(path);
30227
+ return content.includes("node_modules/deepline") || content.includes("node_modules\\deepline") || content.includes("DEEPLINE_CONFIG_SCOPE") || content.includes("deepline-real");
30228
+ }
30229
+ function inspectPathConflict() {
30230
+ const commandPath = resolvePathCommand("deepline");
30231
+ if (!commandPath || isKnownDeeplineCommand(commandPath)) return null;
30232
+ try {
30233
+ if ((0, import_node_fs18.lstatSync)(commandPath).isSymbolicLink()) {
30234
+ const target = (0, import_node_fs18.realpathSync)(commandPath);
30235
+ if (target.includes(`${(0, import_node_path20.join)("node_modules", "deepline")}`)) return null;
29909
30236
  }
29910
- process.stderr.write(
29911
- `Deepline SDK/CLI auto-update failed with exit code ${updateResult.exitCode}; continuing with ${response.current ?? "current version"}.
29912
- `
29913
- );
29914
- return false;
30237
+ } catch {
29915
30238
  }
29916
- process.stderr.write("Deepline SDK/CLI updated; rerunning command.\n");
29917
- const exitCode = await relaunchCurrentCommand(plan);
29918
- process.exit(exitCode);
29919
- return true;
30239
+ return commandPath;
29920
30240
  }
29921
-
29922
- // src/cli/failure-reporting.ts
29923
- var import_node_os14 = require("os");
30241
+ function writeSetupState(input2) {
30242
+ const path = setupStatePath(input2.baseUrl, input2.scope, input2.root);
30243
+ (0, import_node_fs18.mkdirSync)((0, import_node_path20.dirname)(path), { recursive: true });
30244
+ (0, import_node_fs18.writeFileSync)(
30245
+ path,
30246
+ `${JSON.stringify(
30247
+ {
30248
+ schemaVersion: 2,
30249
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
30250
+ cliVersion: SDK_VERSION,
30251
+ host: input2.baseUrl,
30252
+ scope: input2.scope,
30253
+ root: input2.root,
30254
+ status: input2.status,
30255
+ phases: input2.phases
30256
+ },
30257
+ null,
30258
+ 2
30259
+ )}
30260
+ `,
30261
+ "utf8"
30262
+ );
30263
+ return path;
30264
+ }
30265
+ function persistSetupProgress(input2) {
30266
+ return writeSetupState({
30267
+ ...input2,
30268
+ status: input2.status ?? "in_progress"
30269
+ });
30270
+ }
30271
+ function completeSetupPhase(phases, phase, outcome) {
30272
+ phases[phase] = {
30273
+ status: "complete",
30274
+ ...outcome ? { outcome } : {}
30275
+ };
30276
+ }
30277
+ function beginSetupPhase(phases, phase) {
30278
+ phases[phase] = { status: "in_progress" };
30279
+ }
30280
+ function failSetupPhase(phases, phase, code) {
30281
+ phases[phase] = { status: "failed", code };
30282
+ }
30283
+ function rollbackCommand(scope, root) {
30284
+ const prefix = scope === "local" && root ? ` --prefix ${JSON.stringify((0, import_node_path20.join)(root, ".deepline", "runtime"))}` : "";
30285
+ return `npm install -g${prefix} --no-audit --no-fund --include=optional --allow-scripts=esbuild deepline@${SDK_VERSION}`;
30286
+ }
30287
+ function setupQuickstartCommand(baseUrl) {
30288
+ return baseUrl === "https://code.deepline.com" ? "deepline quickstart" : `DEEPLINE_HOST_URL=${JSON.stringify(baseUrl)} deepline quickstart`;
30289
+ }
30290
+ function setupResumeCommand(baseUrl, scope) {
30291
+ const hostPrefix = baseUrl === "https://code.deepline.com" ? "" : `DEEPLINE_HOST_URL=${JSON.stringify(baseUrl)} `;
30292
+ return `${hostPrefix}deepline setup --scope ${scope} --json`;
30293
+ }
30294
+ function setupRetry(input2) {
30295
+ return {
30296
+ phase: input2.phase,
30297
+ command: setupResumeCommand(input2.baseUrl, input2.scope),
30298
+ automatic: true
30299
+ };
30300
+ }
30301
+ function reportSetupPhaseFailure(input2) {
30302
+ failSetupPhase(input2.phases, input2.phase, input2.code);
30303
+ const statePath = persistSetupProgress({
30304
+ baseUrl: input2.baseUrl,
30305
+ scope: input2.scope,
30306
+ root: input2.root,
30307
+ phases: input2.phases,
30308
+ status: "failed"
30309
+ });
30310
+ const retry = setupRetry(input2);
30311
+ printCommandEnvelope(
30312
+ {
30313
+ ok: false,
30314
+ status: "failed",
30315
+ code: input2.code,
30316
+ exitCode: input2.exitCode,
30317
+ scope: input2.scope,
30318
+ message: input2.message,
30319
+ phases: input2.phases,
30320
+ failedPhase: input2.phase,
30321
+ retry,
30322
+ statePath,
30323
+ next: retry.command,
30324
+ ...input2.extra ?? {}
30325
+ },
30326
+ { json: input2.json }
30327
+ );
30328
+ return input2.exitCode;
30329
+ }
30330
+ async function readAuthStatus(authScope) {
30331
+ try {
30332
+ const captured = await captureStdout2(
30333
+ () => handleStatus([
30334
+ "--json",
30335
+ ...authScope ? ["--auth-scope", authScope] : []
30336
+ ])
30337
+ );
30338
+ return {
30339
+ exitCode: captured.exitCode,
30340
+ payload: parseCapturedJson(captured.stdout),
30341
+ error: captured.error
30342
+ };
30343
+ } catch (error) {
30344
+ return { exitCode: 4, payload: null, error };
30345
+ }
30346
+ }
30347
+ function reportSetupAuthStatusFailure(input2) {
30348
+ if (!input2.auth.error) return null;
30349
+ return reportSetupPhaseFailure({
30350
+ baseUrl: input2.baseUrl,
30351
+ scope: input2.scope,
30352
+ root: input2.root,
30353
+ phases: input2.phases,
30354
+ phase: "auth",
30355
+ code: "AUTH_STATUS_FAILED",
30356
+ exitCode: 4,
30357
+ message: "Deepline could not verify authorization with the configured host.",
30358
+ json: input2.json,
30359
+ extra: { authScope: input2.authScope }
30360
+ });
30361
+ }
30362
+ function buildDoctorAssessment(input2) {
30363
+ const skillsStatePath = skillsStatePathForScope(
30364
+ input2.baseUrl,
30365
+ input2.scope,
30366
+ input2.root
30367
+ );
30368
+ const skillsState = parseCapturedJson(safeRead(skillsStatePath));
30369
+ const apiKey = input2.scope === "local" ? resolveProjectApiKeyForBaseUrl(input2.baseUrl) : resolveGlobalApiKeyForBaseUrl(input2.baseUrl);
30370
+ const projectAuth = input2.scope === "local" && apiKey ? getResolvedProjectAuthSource(input2.baseUrl, apiKey) : null;
30371
+ const connected = input2.authStatus.payload?.connected === true;
30372
+ const authScopeOk = input2.scope === "local" ? Boolean(projectAuth) : Boolean(apiKey && !projectAuth);
30373
+ const skillsOk = skillsState?.scope === input2.scope && typeof skillsState.skillsVersion === "string" && Array.isArray(skillsState.agents) && skillsState.agents.length > 0;
30374
+ const runningCliPath = process.argv[1] ? (0, import_node_path20.resolve)(process.argv[1]) : null;
30375
+ const globalCli = input2.scope === "global" ? inspectGlobalCliAvailability() : null;
30376
+ const pathGlobalCli = globalCli?.path ?? null;
30377
+ const cliPath = input2.scope === "global" ? pathGlobalCli : runningCliPath;
30378
+ const cliScopeOk = input2.scope === "global" ? Boolean(pathGlobalCli) : Boolean(
30379
+ input2.root && runningCliPath?.includes((0, import_node_path20.join)(input2.root, ".deepline", "runtime"))
30380
+ );
30381
+ const checks = {
30382
+ cli: {
30383
+ ok: Boolean(cliPath) && cliScopeOk,
30384
+ version: SDK_VERSION,
30385
+ path: cliPath,
30386
+ scope: input2.scope
30387
+ },
30388
+ skills: {
30389
+ ok: skillsOk,
30390
+ statePath: skillsStatePath,
30391
+ version: skillsState?.skillsVersion ?? null,
30392
+ agents: skillsState?.agents ?? []
30393
+ },
30394
+ auth: {
30395
+ ok: connected && authScopeOk,
30396
+ scope: projectAuth ? "local" : apiKey ? "global" : null,
30397
+ status: input2.authStatus.payload?.status ?? "not_connected"
30398
+ },
30399
+ api: {
30400
+ ok: connected && input2.authStatus.exitCode === 0,
30401
+ host: input2.baseUrl,
30402
+ workspace: input2.authStatus.payload?.workspace ?? null,
30403
+ providerSpend: false
30404
+ }
30405
+ };
30406
+ return {
30407
+ ok: Object.values(checks).every((check) => check.ok),
30408
+ checks
30409
+ };
30410
+ }
30411
+ async function runDoctorCommand(options) {
30412
+ let scope;
30413
+ let root;
30414
+ try {
30415
+ scope = normalizeScope2(options.scope);
30416
+ root = resolveScopeRoot(scope);
30417
+ } catch (error) {
30418
+ printCommandEnvelope(
30419
+ {
30420
+ ok: false,
30421
+ status: "failed",
30422
+ code: "INVALID_SETUP_SCOPE",
30423
+ exitCode: 2,
30424
+ message: error instanceof Error ? error.message : String(error)
30425
+ },
30426
+ { json: options.json }
30427
+ );
30428
+ return 2;
30429
+ }
30430
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30431
+ const authScope = authScopeForSetup(scope);
30432
+ const authStatus = await readAuthStatus(authScope);
30433
+ const { ok, checks } = buildDoctorAssessment({
30434
+ baseUrl,
30435
+ scope,
30436
+ root,
30437
+ authStatus
30438
+ });
30439
+ const quickstart = setupQuickstartCommand(baseUrl);
30440
+ printCommandEnvelope(
30441
+ {
30442
+ ok,
30443
+ status: ok ? "complete" : "failed",
30444
+ code: ok ? "DOCTOR_OK" : "DOCTOR_FAILED",
30445
+ exitCode: ok ? 0 : 7,
30446
+ checks,
30447
+ next: ok ? quickstart : "Review failed checks, then rerun: deepline doctor --json",
30448
+ render: {
30449
+ sections: [
30450
+ {
30451
+ title: "doctor",
30452
+ lines: Object.entries(checks).map(
30453
+ ([name, check]) => `${check.ok ? "pass" : "fail"} ${name}`
30454
+ )
30455
+ }
30456
+ ]
30457
+ }
30458
+ },
30459
+ { json: options.json }
30460
+ );
30461
+ return ok ? 0 : 7;
30462
+ }
30463
+ function pendingResult(input2) {
30464
+ input2.phases.auth = {
30465
+ status: "waiting",
30466
+ outcome: "authorization_pending"
30467
+ };
30468
+ const statePath = writeSetupState({
30469
+ baseUrl: input2.baseUrl,
30470
+ scope: input2.scope,
30471
+ root: input2.root,
30472
+ status: "authorization_pending",
30473
+ phases: input2.phases
30474
+ });
30475
+ if (input2.authorizationUrl) {
30476
+ process.stderr.write(`Authorize Deepline: ${input2.authorizationUrl}
30477
+ `);
30478
+ }
30479
+ printCommandEnvelope(
30480
+ {
30481
+ ok: true,
30482
+ status: "authorization_pending",
30483
+ complete: false,
30484
+ scope: input2.scope,
30485
+ authorizationUrl: input2.authorizationUrl || null,
30486
+ statePath,
30487
+ phases: input2.phases,
30488
+ currentPhase: "auth",
30489
+ resumed: input2.resumed,
30490
+ retry: setupRetry({
30491
+ baseUrl: input2.baseUrl,
30492
+ scope: input2.scope,
30493
+ phase: "auth"
30494
+ }),
30495
+ next: `Approve the link, then run: ${setupResumeCommand(input2.baseUrl, input2.scope)}`,
30496
+ render: {
30497
+ sections: [
30498
+ {
30499
+ title: "setup",
30500
+ lines: [
30501
+ "Authorization is waiting for browser approval.",
30502
+ ...input2.authorizationUrl ? [input2.authorizationUrl] : []
30503
+ ]
30504
+ }
30505
+ ]
30506
+ }
30507
+ },
30508
+ { json: input2.json }
30509
+ );
30510
+ return 0;
30511
+ }
30512
+ async function runSetupCommand(options) {
30513
+ let scope;
30514
+ let root;
30515
+ try {
30516
+ scope = normalizeScope2(options.scope);
30517
+ root = resolveScopeRoot(scope);
30518
+ } catch (error) {
30519
+ printCommandEnvelope(
30520
+ {
30521
+ ok: false,
30522
+ status: "failed",
30523
+ code: "INVALID_SETUP_SCOPE",
30524
+ exitCode: 2,
30525
+ message: error instanceof Error ? error.message : String(error),
30526
+ phases: {
30527
+ ...initialSetupPhases(),
30528
+ cli: { status: "failed", code: "INVALID_SETUP_SCOPE" }
30529
+ },
30530
+ failedPhase: "cli"
30531
+ },
30532
+ { json: options.json }
30533
+ );
30534
+ return 2;
30535
+ }
30536
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30537
+ const previousState = readSetupState({ baseUrl, scope, root });
30538
+ const { resumed, phases } = selectSetupProgress(previousState);
30539
+ if (phases.cli.status !== "complete") {
30540
+ beginSetupPhase(phases, "cli");
30541
+ persistSetupProgress({ baseUrl, scope, root, phases });
30542
+ if (scope === "global") {
30543
+ const globalCli = inspectGlobalCliAvailability();
30544
+ if (!globalCli.ok) {
30545
+ const installedButUnreachable = Boolean(globalCli.persistentPath);
30546
+ const code = installedButUnreachable ? "GLOBAL_CLI_NOT_ON_PATH" : "GLOBAL_CLI_NOT_INSTALLED";
30547
+ return reportSetupPhaseFailure({
30548
+ baseUrl,
30549
+ scope,
30550
+ root,
30551
+ phases,
30552
+ phase: "cli",
30553
+ code,
30554
+ exitCode: 7,
30555
+ message: installedButUnreachable ? `The global Deepline executable is not reachable on PATH: ${globalCli.persistentPath}` : "No persistent global Deepline executable was found.",
30556
+ json: options.json,
30557
+ extra: {
30558
+ persistentPath: globalCli.persistentPath,
30559
+ fallback: "https://code.deepline.com/INSTALL.md"
30560
+ }
30561
+ });
30562
+ }
30563
+ }
30564
+ const conflict = inspectPathConflict();
30565
+ if (conflict) {
30566
+ return reportSetupPhaseFailure({
30567
+ baseUrl,
30568
+ scope,
30569
+ root,
30570
+ phases,
30571
+ phase: "cli",
30572
+ code: "PATH_CONFLICT",
30573
+ exitCode: 7,
30574
+ message: `An unknown executable named deepline is first on PATH: ${conflict}`,
30575
+ json: options.json,
30576
+ extra: { path: conflict }
30577
+ });
30578
+ }
30579
+ completeSetupPhase(phases, "cli", "available");
30580
+ persistSetupProgress({ baseUrl, scope, root, phases });
30581
+ }
30582
+ if (phases.cleanup.status !== "complete") {
30583
+ beginSetupPhase(phases, "cleanup");
30584
+ persistSetupProgress({ baseUrl, scope, root, phases });
30585
+ try {
30586
+ const removedLegacyPaths = removeKnownLegacyPaths(baseUrl);
30587
+ if (removedLegacyPaths.length > 0) {
30588
+ process.stderr.write(
30589
+ `Removed ${removedLegacyPaths.length} known legacy Deepline path(s).
30590
+ `
30591
+ );
30592
+ }
30593
+ completeSetupPhase(
30594
+ phases,
30595
+ "cleanup",
30596
+ removedLegacyPaths.length > 0 ? "removed_legacy_paths" : "clean"
30597
+ );
30598
+ persistSetupProgress({ baseUrl, scope, root, phases });
30599
+ } catch (error) {
30600
+ return reportSetupPhaseFailure({
30601
+ baseUrl,
30602
+ scope,
30603
+ root,
30604
+ phases,
30605
+ phase: "cleanup",
30606
+ code: "LEGACY_CLEANUP_FAILED",
30607
+ exitCode: 5,
30608
+ message: error instanceof Error ? error.message : String(error),
30609
+ json: options.json
30610
+ });
30611
+ }
30612
+ }
30613
+ let skillsPayload = null;
30614
+ if (phases.skills.status !== "complete") {
30615
+ beginSetupPhase(phases, "skills");
30616
+ persistSetupProgress({ baseUrl, scope, root, phases });
30617
+ process.stderr.write(`Installing Deepline skills (${scope})...
30618
+ `);
30619
+ const skills = await captureStdout2(
30620
+ () => runSkillsCommand({ scope, json: true })
30621
+ );
30622
+ skillsPayload = parseCapturedJson(skills.stdout);
30623
+ if (skills.exitCode !== 0) {
30624
+ return reportSetupPhaseFailure({
30625
+ baseUrl,
30626
+ scope,
30627
+ root,
30628
+ phases,
30629
+ phase: "skills",
30630
+ code: "SKILLS_INSTALL_FAILED",
30631
+ exitCode: skills.exitCode,
30632
+ message: typeof skillsPayload?.message === "string" ? skillsPayload.message : "Deepline skills could not be installed.",
30633
+ json: options.json,
30634
+ extra: { detail: skillsPayload }
30635
+ });
30636
+ }
30637
+ completeSetupPhase(
30638
+ phases,
30639
+ "skills",
30640
+ skillsPayload?.status === "current" ? "current" : "installed"
30641
+ );
30642
+ writeSetupState({
30643
+ baseUrl,
30644
+ scope,
30645
+ root,
30646
+ status: "skills_installed",
30647
+ phases
30648
+ });
30649
+ } else {
30650
+ skillsPayload = parseCapturedJson(
30651
+ safeRead(skillsStatePathForScope(baseUrl, scope, root))
30652
+ );
30653
+ }
30654
+ const authScope = authScopeForSetup(scope);
30655
+ beginSetupPhase(phases, "auth");
30656
+ persistSetupProgress({ baseUrl, scope, root, phases });
30657
+ let auth = await readAuthStatus(authScope);
30658
+ const initialAuthFailure = reportSetupAuthStatusFailure({
30659
+ auth,
30660
+ baseUrl,
30661
+ authScope,
30662
+ scope,
30663
+ root,
30664
+ phases,
30665
+ json: options.json
30666
+ });
30667
+ if (initialAuthFailure !== null) return initialAuthFailure;
30668
+ if (auth.payload?.connected !== true) {
30669
+ const pending = readPendingAuthClaim(baseUrl, authScope);
30670
+ if (pending) {
30671
+ process.stderr.write("Checking pending Deepline authorization...\n");
30672
+ const waited = await captureStdout2(
30673
+ () => handleWait(["--timeout", "1", "--auth-scope", authScope])
30674
+ );
30675
+ printCapturedAuthorizationUrl(waited.stdout);
30676
+ auth = await readAuthStatus(authScope);
30677
+ const resumedAuthFailure = reportSetupAuthStatusFailure({
30678
+ auth,
30679
+ baseUrl,
30680
+ authScope,
30681
+ scope,
30682
+ root,
30683
+ phases,
30684
+ json: options.json
30685
+ });
30686
+ if (resumedAuthFailure !== null) return resumedAuthFailure;
30687
+ if (auth.payload?.connected !== true) {
30688
+ const stillPending = readPendingAuthClaim(baseUrl, authScope);
30689
+ if (stillPending) {
30690
+ return pendingResult({
30691
+ baseUrl,
30692
+ scope,
30693
+ root,
30694
+ phases,
30695
+ authorizationUrl: stillPending.claimUrl,
30696
+ resumed,
30697
+ json: options.json
30698
+ });
30699
+ }
30700
+ }
30701
+ }
30702
+ }
30703
+ if (auth.payload?.connected !== true) {
30704
+ process.stderr.write("Starting Deepline browser authorization...\n");
30705
+ const agentRuntime = detectAgentRuntime();
30706
+ const agentLed = agentRuntime !== "unknown";
30707
+ const waitMode = options.json || agentLed || !process.stdin.isTTY ? "no" : "auto";
30708
+ const registered = await captureStdout2(
30709
+ () => handleRegister(["--wait", waitMode, "--auth-scope", authScope])
30710
+ );
30711
+ printCapturedAuthorizationUrl(registered.stdout);
30712
+ if (registered.exitCode !== 0) {
30713
+ return reportSetupPhaseFailure({
30714
+ baseUrl,
30715
+ scope,
30716
+ root,
30717
+ phases,
30718
+ phase: "auth",
30719
+ code: "AUTH_REGISTER_FAILED",
30720
+ exitCode: registered.exitCode,
30721
+ message: "Deepline browser authorization could not be started.",
30722
+ json: options.json,
30723
+ extra: {
30724
+ detail: parseCapturedJson(registered.stdout),
30725
+ authScope
30726
+ }
30727
+ });
30728
+ }
30729
+ auth = await readAuthStatus(authScope);
30730
+ const registeredAuthFailure = reportSetupAuthStatusFailure({
30731
+ auth,
30732
+ baseUrl,
30733
+ authScope,
30734
+ scope,
30735
+ root,
30736
+ phases,
30737
+ json: options.json
30738
+ });
30739
+ if (registeredAuthFailure !== null) return registeredAuthFailure;
30740
+ if (auth.payload?.connected !== true) {
30741
+ const pending = readPendingAuthClaim(baseUrl, authScope);
30742
+ return pendingResult({
30743
+ baseUrl,
30744
+ scope,
30745
+ root,
30746
+ phases,
30747
+ authorizationUrl: pending?.claimUrl ?? "",
30748
+ resumed,
30749
+ json: options.json
30750
+ });
30751
+ }
30752
+ }
30753
+ completeSetupPhase(phases, "auth", "connected");
30754
+ beginSetupPhase(phases, "verify");
30755
+ persistSetupProgress({ baseUrl, scope, root, phases });
30756
+ const assessment = buildDoctorAssessment({
30757
+ baseUrl,
30758
+ scope,
30759
+ root,
30760
+ authStatus: auth
30761
+ });
30762
+ const quickstart = setupQuickstartCommand(baseUrl);
30763
+ const doctorPayload = {
30764
+ ok: assessment.ok,
30765
+ status: assessment.ok ? "complete" : "failed",
30766
+ code: assessment.ok ? "DOCTOR_OK" : "DOCTOR_FAILED",
30767
+ exitCode: assessment.ok ? 0 : 7,
30768
+ checks: assessment.checks,
30769
+ next: assessment.ok ? quickstart : `deepline doctor --scope ${scope} --json`
30770
+ };
30771
+ if (!assessment.ok) {
30772
+ return reportSetupPhaseFailure({
30773
+ baseUrl,
30774
+ scope,
30775
+ root,
30776
+ phases,
30777
+ phase: "verify",
30778
+ code: "DOCTOR_FAILED",
30779
+ exitCode: 7,
30780
+ message: "Deepline setup verification found one or more failed checks.",
30781
+ json: options.json,
30782
+ extra: {
30783
+ doctor: doctorPayload,
30784
+ diagnostic: {
30785
+ command: `deepline doctor --scope ${scope} --json`
30786
+ }
30787
+ }
30788
+ });
30789
+ }
30790
+ completeSetupPhase(phases, "verify", "verified");
30791
+ const statePath = writeSetupState({
30792
+ baseUrl,
30793
+ scope,
30794
+ root,
30795
+ status: "complete",
30796
+ phases
30797
+ });
30798
+ const doctorChecks = asRecord2(doctorPayload?.checks);
30799
+ const apiCheck = asRecord2(doctorChecks?.api);
30800
+ printCommandEnvelope(
30801
+ {
30802
+ ok: true,
30803
+ status: "complete",
30804
+ complete: true,
30805
+ scope,
30806
+ cliVersion: SDK_VERSION,
30807
+ agents: skillsPayload?.agents ?? [],
30808
+ workspace: apiCheck?.workspace ?? null,
30809
+ rollbackCommand: rollbackCommand(scope, root),
30810
+ statePath,
30811
+ doctor: doctorPayload,
30812
+ phases,
30813
+ currentPhase: null,
30814
+ failedPhase: null,
30815
+ resumed,
30816
+ next: quickstart,
30817
+ render: {
30818
+ sections: [
30819
+ {
30820
+ title: "setup",
30821
+ lines: [
30822
+ "Deepline is installed and connected.",
30823
+ `Next: ${quickstart}`
30824
+ ]
30825
+ }
30826
+ ]
30827
+ }
30828
+ },
30829
+ { json: options.json }
30830
+ );
30831
+ return 0;
30832
+ }
30833
+ function registerSetupCommands(program) {
30834
+ 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(
30835
+ "after",
30836
+ `
30837
+ Notes:
30838
+ Setup is idempotent. Bare setup resumes the first incomplete or failed phase.
30839
+ It installs skills before auth and does not run quickstart.
30840
+ JSON output includes phase status and an exact retry command.
30841
+
30842
+ Examples:
30843
+ deepline setup --json
30844
+ deepline setup --scope local --json
30845
+ `
30846
+ ).action(async (options) => {
30847
+ process.exitCode = await runSetupCommand(options);
30848
+ });
30849
+ program.command("doctor").description("Verify CLI, skills, auth, workspace, and API connectivity.").option(
30850
+ "--scope <scope>",
30851
+ "Expected setup scope: global or local",
30852
+ "global"
30853
+ ).option("--json", "Emit one JSON result envelope").addHelpText(
30854
+ "after",
30855
+ `
30856
+ Notes:
30857
+ Doctor is read-only and makes no paid provider calls. It reports repairs but
30858
+ does not apply them automatically.
30859
+
30860
+ Examples:
30861
+ deepline doctor --json
30862
+ deepline doctor --scope local --json
30863
+ `
30864
+ ).action(async (options) => {
30865
+ process.exitCode = await runDoctorCommand(options);
30866
+ });
30867
+ }
30868
+
30869
+ // ../shared_libs/cli/command-compatibility.json
30870
+ var command_compatibility_default = {
30871
+ enrich: {
30872
+ family: "python",
30873
+ label: "a legacy Python CLI enrichment command",
30874
+ sdk_alternative: "Use `deepline plays ...` for durable workflows or `deepline tools execute ...` for one tool call."
30875
+ },
30876
+ session: {
30877
+ family: "python",
30878
+ label: "a legacy Python CLI session/playground command",
30879
+ sdk_alternative: "Use `deepline sessions send ...` or `deepline sessions render ...` for transcript workflows."
30880
+ },
30881
+ workflows: {
30882
+ family: "python",
30883
+ label: "a legacy Python CLI workflow command",
30884
+ sdk_alternative: "Use `deepline plays ...` in the SDK CLI."
30885
+ },
30886
+ events: {
30887
+ family: "python",
30888
+ label: "a legacy Python CLI event command"
30889
+ },
30890
+ plays: {
30891
+ family: "sdk",
30892
+ label: "an SDK CLI play command",
30893
+ python_alternative: "Use `deepline workflows ...` only for legacy workflows."
30894
+ },
30895
+ runs: {
30896
+ family: "sdk",
30897
+ label: "an SDK CLI run inspection command"
30898
+ },
30899
+ sessions: {
30900
+ family: "sdk",
30901
+ label: "an SDK CLI session transcript command"
30902
+ },
30903
+ health: {
30904
+ family: "sdk",
30905
+ label: "an SDK CLI health command"
30906
+ }
30907
+ };
30908
+
30909
+ // src/cli/command-compatibility.ts
30910
+ var COMMAND_COMPATIBILITY = command_compatibility_default;
30911
+ function cliFamilyLabel(family) {
30912
+ return family === "sdk" ? "SDK CLI" : "legacy Python CLI";
30913
+ }
30914
+ function commandCompatibilityHint(currentFamily, commandName, baseUrl) {
30915
+ const compatibility = COMMAND_COMPATIBILITY[commandName];
30916
+ if (!compatibility || compatibility.family === currentFamily) {
30917
+ return null;
30918
+ }
30919
+ const expectedFamily = compatibility.family;
30920
+ const currentLabel = cliFamilyLabel(currentFamily);
30921
+ const expectedLabel = cliFamilyLabel(expectedFamily);
30922
+ const lines = [
30923
+ "",
30924
+ "Command compatibility:",
30925
+ ` \`deepline ${commandName}\` is ${compatibility.label}.`,
30926
+ ` Current binary: ${currentLabel}. Required binary: ${expectedLabel}.`,
30927
+ " If this came from an agent skill, the installed skill likely targets the other Deepline CLI."
30928
+ ];
30929
+ if (currentFamily === "sdk") {
30930
+ lines.push(
30931
+ "",
30932
+ " To stay on the SDK CLI, refresh the Deepline agent skills:",
30933
+ ` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
30934
+ " To use the legacy Python CLI instead:",
30935
+ ` ${legacyPythonInstallCommand(baseUrl)}`,
30936
+ " `deepline update` updates this SDK CLI, but it will not switch CLI families."
30937
+ );
30938
+ if (compatibility.sdk_alternative) {
30939
+ lines.push(` SDK alternative: ${compatibility.sdk_alternative}`);
30940
+ }
30941
+ } else {
30942
+ lines.push(
30943
+ "",
30944
+ " To use SDK commands, install the SDK CLI and refresh Deepline agent skills:",
30945
+ ` ${sdkNpmGlobalInstallCommand()}`,
30946
+ ` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
30947
+ " `deepline update` updates this Python CLI and its skills, but it will not switch CLI families."
30948
+ );
30949
+ if (compatibility.python_alternative) {
30950
+ lines.push(` Python alternative: ${compatibility.python_alternative}`);
30951
+ }
30952
+ }
30953
+ return lines.join("\n");
30954
+ }
30955
+ function unknownCommandNameFromMessage(message) {
30956
+ const match = message.match(/unknown command ['"]([^'"]+)['"]/i);
30957
+ const command = match?.[1]?.trim();
30958
+ return command ? command : null;
30959
+ }
30960
+
30961
+ // src/cli/self-update.ts
30962
+ var import_node_child_process6 = require("child_process");
30963
+ function envTruthy(name) {
30964
+ const value = process.env[name]?.trim().toLowerCase();
30965
+ return value === "1" || value === "true" || value === "yes";
30966
+ }
30967
+ function isCi() {
30968
+ return envTruthy("CI") || envTruthy("GITHUB_ACTIONS");
30969
+ }
30970
+ function shouldSkipSelfUpdate() {
30971
+ return envTruthy("DEEPLINE_SKIP_SELF_UPDATE") || envTruthy("DEEPLINE_NO_AUTO_UPDATE") || envTruthy("DEEPLINE_SKIP_SDK_AUTO_UPDATE") || envTruthy("DEEPLINE_DISABLE_AUTO_UPDATE") || isCi();
30972
+ }
30973
+ function parseSemver(version) {
30974
+ const trimmed = version?.trim();
30975
+ if (!trimmed) return null;
30976
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(
30977
+ trimmed
30978
+ );
30979
+ if (!match) return null;
30980
+ return {
30981
+ major: Number(match[1]),
30982
+ minor: Number(match[2]),
30983
+ patch: Number(match[3]),
30984
+ prerelease: match[4] ?? ""
30985
+ };
30986
+ }
30987
+ function compareSemver(left, right) {
30988
+ const a = parseSemver(left);
30989
+ const b = parseSemver(right);
30990
+ if (!a || !b) {
30991
+ return left.localeCompare(right);
30992
+ }
30993
+ for (const key of ["major", "minor", "patch"]) {
30994
+ if (a[key] !== b[key]) return a[key] > b[key] ? 1 : -1;
30995
+ }
30996
+ if (a.prerelease === b.prerelease) return 0;
30997
+ if (!a.prerelease) return 1;
30998
+ if (!b.prerelease) return -1;
30999
+ return a.prerelease.localeCompare(b.prerelease);
31000
+ }
31001
+ function isDowngradeAutoUpdateResponse(response) {
31002
+ const target = response?.latest?.trim();
31003
+ const current = response?.current?.trim() || SDK_VERSION;
31004
+ if (!target) return false;
31005
+ return compareSemver(target, current) < 0;
31006
+ }
31007
+ function relaunchCurrentCommand(plan) {
31008
+ return new Promise((resolve15) => {
31009
+ const command = plan.kind === "python-sidecar" ? plan.sidecarPath : process.execPath;
31010
+ const args = plan.kind === "python-sidecar" ? process.argv.slice(2) : process.argv.slice(1);
31011
+ const child = (0, import_node_child_process6.spawn)(command, args, {
31012
+ stdio: "inherit",
31013
+ shell: process.platform === "win32",
31014
+ env: {
31015
+ ...process.env,
31016
+ DEEPLINE_NO_AUTO_UPDATE: "1"
31017
+ }
31018
+ });
31019
+ child.on("error", (error) => {
31020
+ process.stderr.write(
31021
+ `Deepline SDK/CLI updated, but relaunch failed: ${error.message}
31022
+ `
31023
+ );
31024
+ resolve15(1);
31025
+ });
31026
+ child.on("close", (code) => resolve15(code ?? 1));
31027
+ });
31028
+ }
31029
+ async function maybeAutoUpdateAndRelaunch(response) {
31030
+ const autoUpdate = response?.auto_update;
31031
+ if (!response || !autoUpdate?.should_auto_update || shouldSkipSelfUpdate()) {
31032
+ return false;
31033
+ }
31034
+ if (isDowngradeAutoUpdateResponse(response)) {
31035
+ const target = response.latest;
31036
+ const current = response.current?.trim() || SDK_VERSION;
31037
+ process.stderr.write(
31038
+ `Deepline SDK/CLI auto-update refused: server advertised older ${target} than current ${current}. Continuing without mutating the CLI.
31039
+ `
31040
+ );
31041
+ return false;
31042
+ }
31043
+ const packageSpec = response.latest ? `deepline@${response.latest}` : void 0;
31044
+ const plan = resolveUpdatePlan({ packageSpec });
31045
+ if (plan.kind === "source") {
31046
+ return false;
31047
+ }
31048
+ 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";
31049
+ process.stderr.write(
31050
+ `Deepline SDK/CLI ${label}; running ${plan.manualCommand}
31051
+ `
31052
+ );
31053
+ const updateResult = await runAutomaticUpdatePlan(plan);
31054
+ if (updateResult.status === "skipped_previous_failure") {
31055
+ return false;
31056
+ }
31057
+ if (updateResult.exitCode !== 0) {
31058
+ if (autoUpdate.required) {
31059
+ throw new Error(
31060
+ `Automatic Deepline SDK/CLI update failed with exit code ${updateResult.exitCode}. ${response.message}`
31061
+ );
31062
+ }
31063
+ process.stderr.write(
31064
+ `Deepline SDK/CLI auto-update failed with exit code ${updateResult.exitCode}; continuing with ${response.current ?? "current version"}.
31065
+ `
31066
+ );
31067
+ return false;
31068
+ }
31069
+ process.stderr.write("Deepline SDK/CLI updated; rerunning command.\n");
31070
+ const exitCode = await relaunchCurrentCommand(plan);
31071
+ process.exit(exitCode);
31072
+ return true;
31073
+ }
31074
+
31075
+ // src/cli/skills-sync.ts
31076
+ var import_node_child_process7 = require("child_process");
31077
+ var import_node_fs19 = require("fs");
31078
+ var import_node_path21 = require("path");
31079
+ var CHECK_TIMEOUT_MS2 = 3e3;
31080
+ var attemptedSync = false;
31081
+ function shouldSkipSkillsSync() {
31082
+ if (detectAgentRuntime() === "claude_cowork") {
31083
+ return true;
31084
+ }
31085
+ const value = process.env.DEEPLINE_SKIP_SKILLS_SYNC?.trim().toLowerCase();
31086
+ return value === "1" || value === "true" || value === "yes" || value === "on";
31087
+ }
31088
+ function activePluginSkillsDir() {
31089
+ const pluginMode = process.env.DEEPLINE_PLUGIN_MODE?.trim().toLowerCase();
31090
+ if (pluginMode !== "true" && pluginMode !== "1" && pluginMode !== "yes" && pluginMode !== "on") {
31091
+ return "";
31092
+ }
31093
+ const dir = process.env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim() ?? "";
31094
+ return dir && (0, import_node_fs19.existsSync)(dir) ? dir : "";
31095
+ }
31096
+ function readPluginSkillsVersion() {
31097
+ const dir = activePluginSkillsDir();
31098
+ if (!dir) return "";
31099
+ try {
31100
+ return (0, import_node_fs19.readFileSync)((0, import_node_path21.join)(dir, ".version"), "utf-8").trim();
31101
+ } catch {
31102
+ return "";
31103
+ }
31104
+ }
31105
+ function sdkSkillsVersionPath(baseUrl) {
31106
+ return (0, import_node_path21.join)(sdkCliStateDirPath(baseUrl), "skills-version");
31107
+ }
31108
+ function legacySdkSkillsVersionPath(baseUrl) {
31109
+ return (0, import_node_path21.join)((0, import_node_path21.dirname)(sdkCliStateDirPath(baseUrl)), "sdk-skills", ".version");
31110
+ }
31111
+ function unavailableSkillsNoticePath(baseUrl) {
31112
+ return (0, import_node_path21.join)(sdkCliStateDirPath(baseUrl), "skills-sync-unavailable-version");
31113
+ }
31114
+ function readSdkSkillsLocalVersion(baseUrl) {
31115
+ const pluginVersion = readPluginSkillsVersion();
31116
+ if (pluginVersion) return pluginVersion;
31117
+ const path = (0, import_node_fs19.existsSync)(sdkSkillsVersionPath(baseUrl)) ? sdkSkillsVersionPath(baseUrl) : legacySdkSkillsVersionPath(baseUrl);
31118
+ if (!(0, import_node_fs19.existsSync)(path)) return "";
31119
+ try {
31120
+ return (0, import_node_fs19.readFileSync)(path, "utf-8").trim();
31121
+ } catch {
31122
+ return "";
31123
+ }
31124
+ }
31125
+ function writeLocalSkillsVersion(baseUrl, version) {
31126
+ const path = sdkSkillsVersionPath(baseUrl);
31127
+ (0, import_node_fs19.mkdirSync)((0, import_node_path21.dirname)(path), { recursive: true });
31128
+ (0, import_node_fs19.writeFileSync)(path, `${version}
31129
+ `, "utf-8");
31130
+ }
31131
+ function writeUnavailableSkillsNotice(baseUrl, remoteVersion, skillNames) {
31132
+ const path = unavailableSkillsNoticePath(baseUrl);
31133
+ try {
31134
+ if ((0, import_node_fs19.existsSync)(path) && (0, import_node_fs19.readFileSync)(path, "utf-8").trim() === remoteVersion) {
31135
+ return;
31136
+ }
31137
+ (0, import_node_fs19.mkdirSync)((0, import_node_path21.dirname)(path), { recursive: true });
31138
+ (0, import_node_fs19.writeFileSync)(path, `${remoteVersion}
31139
+ `, "utf-8");
31140
+ } catch {
31141
+ }
31142
+ const manualCommand = `npx ${buildSkillsInstallArgs(baseUrl, skillNames).join(" ")}`;
31143
+ writeSdkSkillsStatusLine(
31144
+ `Deepline agent skills are out of date, but neither \`bunx\` nor \`npx\` is available. Install Node.js/npm or Bun, then run:
31145
+ ${manualCommand}`
31146
+ );
31147
+ }
31148
+ function clearUnavailableSkillsNotice(baseUrl) {
31149
+ try {
31150
+ (0, import_node_fs19.unlinkSync)(unavailableSkillsNoticePath(baseUrl));
31151
+ } catch {
31152
+ }
31153
+ }
31154
+ function sortedUniqueSkillNames(names) {
31155
+ return [...new Set(names.map((name) => name.trim()).filter(Boolean))].sort(
31156
+ (a, b) => a.localeCompare(b)
31157
+ );
31158
+ }
31159
+ async function fetchV1SkillNames(baseUrl) {
31160
+ const controller = new AbortController();
31161
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
31162
+ try {
31163
+ const response = await fetch(
31164
+ new URL("/.well-known/skills/index.json", baseUrl),
31165
+ { signal: controller.signal }
31166
+ );
31167
+ if (!response.ok) return [];
31168
+ const data = await response.json().catch(() => null);
31169
+ const names = (data?.skills ?? []).filter((skill) => skill.install_surface === "v1").map((skill) => skill.name).filter(
31170
+ (name) => typeof name === "string" && name.length > 0
31171
+ );
31172
+ return sortedUniqueSkillNames(names);
31173
+ } catch {
31174
+ return [];
31175
+ } finally {
31176
+ clearTimeout(timeout);
31177
+ }
31178
+ }
31179
+ function buildSdkSkillNames(v1SkillNames) {
31180
+ return sortedUniqueSkillNames(v1SkillNames);
31181
+ }
31182
+ async function fetchSkillsUpdate(baseUrl, localVersion) {
31183
+ const controller = new AbortController();
31184
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
31185
+ try {
31186
+ const response = await fetch(new URL("/api/v2/cli/update-check", baseUrl), {
31187
+ method: "POST",
31188
+ headers: { "Content-Type": "application/json" },
31189
+ body: JSON.stringify({
31190
+ skills: {
31191
+ version: localVersion
31192
+ }
31193
+ }),
31194
+ signal: controller.signal
31195
+ });
31196
+ if (!response.ok) return null;
31197
+ const data = await response.json().catch(() => null);
31198
+ const skills = data?.skills;
31199
+ if (!skills) return null;
31200
+ return {
31201
+ needsUpdate: skills.needs_update === true,
31202
+ remoteVersion: typeof skills.remote?.version === "string" ? skills.remote.version.trim() : ""
31203
+ };
31204
+ } catch {
31205
+ return null;
31206
+ } finally {
31207
+ clearTimeout(timeout);
31208
+ }
31209
+ }
31210
+ function buildSkillsInstallArgs(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
31211
+ return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames));
31212
+ }
31213
+ function buildBunxSkillsInstallArgs(baseUrl, skillNames) {
31214
+ return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames), {
31215
+ firstArg: "--bun"
31216
+ });
31217
+ }
31218
+ function hasCommand(command) {
31219
+ const result = (0, import_node_child_process7.spawnSync)(command, ["--version"], {
31220
+ stdio: "ignore",
31221
+ shell: process.platform === "win32"
31222
+ });
31223
+ return result.status === 0;
31224
+ }
31225
+ function shellQuote6(arg) {
31226
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
31227
+ }
31228
+ function resolveSkillsInstallCommands(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
31229
+ const commands = [];
31230
+ if (hasCommand("bunx")) {
31231
+ const bunxArgs = buildBunxSkillsInstallArgs(baseUrl, skillNames);
31232
+ commands.push({
31233
+ command: "bunx",
31234
+ args: bunxArgs,
31235
+ manualCommand: `bunx ${bunxArgs.map(shellQuote6).join(" ")}`
31236
+ });
31237
+ }
31238
+ if (hasCommand("npx")) {
31239
+ const npxArgs = buildSkillsInstallArgs(baseUrl, skillNames);
31240
+ commands.push({
31241
+ command: "npx",
31242
+ args: npxArgs,
31243
+ manualCommand: `npx ${npxArgs.map(shellQuote6).join(" ")}`
31244
+ });
31245
+ }
31246
+ return commands;
31247
+ }
31248
+ function runOneSkillsInstall(install) {
31249
+ return new Promise((resolve15) => {
31250
+ const child = (0, import_node_child_process7.spawn)(install.command, install.args, {
31251
+ stdio: ["ignore", "ignore", "pipe"],
31252
+ env: process.env
31253
+ });
31254
+ let stderr = "";
31255
+ child.stderr.on("data", (chunk) => {
31256
+ stderr += chunk.toString("utf-8");
31257
+ });
31258
+ child.on("error", (error) => {
31259
+ resolve15({
31260
+ ok: false,
31261
+ detail: `failed to start ${install.command}: ${error.message}`,
31262
+ manualCommand: install.manualCommand
31263
+ });
31264
+ });
31265
+ child.on("close", (code) => {
31266
+ if (code === 0) {
31267
+ resolve15({ ok: true, detail: "", manualCommand: install.manualCommand });
31268
+ return;
31269
+ }
31270
+ const detail = stderr.trim();
31271
+ resolve15({
31272
+ ok: false,
31273
+ detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
31274
+ manualCommand: install.manualCommand
31275
+ });
31276
+ });
31277
+ });
31278
+ }
31279
+ async function runSkillsInstall(installs) {
31280
+ const failures = [];
31281
+ for (const install of installs) {
31282
+ const result = await runOneSkillsInstall(install);
31283
+ if (result.ok) return true;
31284
+ failures.push(result);
31285
+ }
31286
+ const details = failures.map((failure) => failure.detail).filter(Boolean).join("\n");
31287
+ const manualCommand = failures.at(-1)?.manualCommand;
31288
+ process.stderr.write(
31289
+ `SDK skills sync failed${details ? `:
31290
+ ${details}` : ""}
31291
+ ` + (manualCommand ? `Run manually: ${manualCommand}
31292
+ ` : "")
31293
+ );
31294
+ return false;
31295
+ }
31296
+ function runLegacySkillsCleanup() {
31297
+ const candidates = hasCommand("bunx") ? [
31298
+ {
31299
+ command: "bunx",
31300
+ args: [
31301
+ "--bun",
31302
+ "skills",
31303
+ "remove",
31304
+ "--global",
31305
+ "-y",
31306
+ ...LEGACY_SKILL_NAMES_TO_REMOVE
31307
+ ]
31308
+ },
31309
+ {
31310
+ command: "npx",
31311
+ args: [
31312
+ "--yes",
31313
+ "skills",
31314
+ "remove",
31315
+ "--global",
31316
+ "-y",
31317
+ ...LEGACY_SKILL_NAMES_TO_REMOVE
31318
+ ]
31319
+ }
31320
+ ] : [
31321
+ {
31322
+ command: "npx",
31323
+ args: [
31324
+ "--yes",
31325
+ "skills",
31326
+ "remove",
31327
+ "--global",
31328
+ "-y",
31329
+ ...LEGACY_SKILL_NAMES_TO_REMOVE
31330
+ ]
31331
+ }
31332
+ ];
31333
+ for (const candidate of candidates) {
31334
+ const result = (0, import_node_child_process7.spawnSync)(candidate.command, candidate.args, {
31335
+ stdio: "ignore",
31336
+ env: process.env,
31337
+ shell: process.platform === "win32"
31338
+ });
31339
+ if (result.status === 0) return;
31340
+ }
31341
+ }
31342
+ function writeSdkSkillsStatusLine(line) {
31343
+ const progress = getActiveCliProgress();
31344
+ if (progress) {
31345
+ progress.writeLine(line);
31346
+ return;
31347
+ }
31348
+ process.stderr.write(`${line}
31349
+ `);
31350
+ }
31351
+ async function syncSdkSkillsIfNeeded(baseUrl, options = {}) {
31352
+ if (attemptedSync || shouldSkipSkillsSync()) return;
31353
+ attemptedSync = true;
31354
+ const usingPluginSkills = Boolean(activePluginSkillsDir());
31355
+ if (usingPluginSkills) {
31356
+ return;
31357
+ }
31358
+ const localVersion = readSdkSkillsLocalVersion(baseUrl);
31359
+ const update = options.update === void 0 ? await fetchSkillsUpdate(baseUrl, localVersion) : options.update ? {
31360
+ needsUpdate: options.update.needs_update,
31361
+ remoteVersion: options.update.remote.version
31362
+ } : null;
31363
+ if (!update?.needsUpdate || !update.remoteVersion) {
31364
+ return;
31365
+ }
31366
+ const remoteSkillNames = await fetchV1SkillNames(baseUrl);
31367
+ const skillNames = buildSdkSkillNames(
31368
+ remoteSkillNames.length > 0 ? remoteSkillNames : DEFAULT_SDK_SKILL_NAMES
31369
+ );
31370
+ if (skillNames.length === 0) return;
31371
+ const installs = resolveSkillsInstallCommands(baseUrl, skillNames);
31372
+ if (installs.length === 0) {
31373
+ writeUnavailableSkillsNotice(baseUrl, update.remoteVersion, skillNames);
31374
+ return;
31375
+ }
31376
+ writeSdkSkillsStatusLine("Deepline skills changed; syncing agent skills...");
31377
+ const installed = await runSkillsInstall(installs);
31378
+ if (!installed) return;
31379
+ runLegacySkillsCleanup();
31380
+ writeLocalSkillsVersion(baseUrl, update.remoteVersion);
31381
+ clearUnavailableSkillsNotice(baseUrl);
31382
+ writeSdkSkillsStatusLine("Deepline agent skills are up to date.");
31383
+ }
31384
+
31385
+ // src/cli/failure-reporting.ts
31386
+ var import_node_os16 = require("os");
29924
31387
  var FAILURE_REPORT_DISABLE_ENV = "DEEPLINE_DISABLE_FAILURE_REPORTING";
29925
31388
  var REPORT_FAILURE_TIMEOUT_MS = 1e4;
29926
31389
  var MAX_FAILURE_TEXT_CHARS = 4e3;
@@ -30022,12 +31485,12 @@ function isNetworkFailure(error) {
30022
31485
  }
30023
31486
  function buildEnvironmentContext() {
30024
31487
  const context = {
30025
- os: (0, import_node_os14.platform)(),
30026
- os_release: (0, import_node_os14.release)(),
30027
- platform: `${(0, import_node_os14.platform)()}-${(0, import_node_os14.release)()}-${process.arch}`,
31488
+ os: (0, import_node_os16.platform)(),
31489
+ os_release: (0, import_node_os16.release)(),
31490
+ platform: `${(0, import_node_os16.platform)()}-${(0, import_node_os16.release)()}-${process.arch}`,
30028
31491
  node_version: process.version,
30029
31492
  runtime: "Node.js",
30030
- hostname: (0, import_node_os14.hostname)(),
31493
+ hostname: (0, import_node_os16.hostname)(),
30031
31494
  agent_runtime: detectAgentRuntime()
30032
31495
  };
30033
31496
  for (const key of ["CLAUDE_CODE_REMOTE", "DEEPLINE_PLUGIN_MODE"]) {
@@ -30220,8 +31683,8 @@ function topLevelCommandKnown(program, commandName) {
30220
31683
  );
30221
31684
  }
30222
31685
  async function runPlayRunnerHealthCheck() {
30223
- const dir = await (0, import_promises6.mkdtemp)((0, import_node_path20.join)((0, import_node_os15.tmpdir)(), "deepline-health-play-"));
30224
- const file = (0, import_node_path20.join)(dir, "health-check.play.ts");
31686
+ const dir = await (0, import_promises6.mkdtemp)((0, import_node_path22.join)((0, import_node_os17.tmpdir)(), "deepline-health-play-"));
31687
+ const file = (0, import_node_path22.join)(dir, "health-check.play.ts");
30225
31688
  try {
30226
31689
  await (0, import_promises6.writeFile)(
30227
31690
  file,
@@ -30439,7 +31902,7 @@ Exit codes:
30439
31902
  `
30440
31903
  );
30441
31904
  program.hook("preAction", async (_thisCommand, actionCommand) => {
30442
- if (actionCommand.name() === "version" || actionCommand.name() === "update" || actionCommand.name() === "switch" || isLegacyNoopInvocation()) {
31905
+ if (actionCommand.name() === "version" || actionCommand.name() === "update" || actionCommand.name() === "switch" || actionCommand.name() === "setup" || actionCommand.name() === "skills" || actionCommand.name() === "doctor" || isLegacyNoopInvocation()) {
30443
31906
  return;
30444
31907
  }
30445
31908
  if (printStartupPhase) {
@@ -30511,6 +31974,8 @@ Exit codes:
30511
31974
  registerFeedbackCommands(program);
30512
31975
  registerLegacyNoopCommands(program);
30513
31976
  registerUpdateCommand(program);
31977
+ registerSkillsCommand(program);
31978
+ registerSetupCommands(program);
30514
31979
  registerQuickstartCommands(program);
30515
31980
  registerSwitchCommands(program);
30516
31981
  program.command("preflight").description("Run compact health, auth, and Deepline billing checks.").option("--json", "Force JSON output.").addHelpText(