deepline 0.1.258 → 0.1.260

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