@dosu/cli 0.67.1 → 0.68.0

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/bin/dosu.js +515 -340
  3. package/package.json +1 -1
package/bin/dosu.js CHANGED
@@ -961,6 +961,10 @@ function mcpHeaders(apiKey) {
961
961
  }
962
962
  return { "X-Dosu-API-Key": apiKey };
963
963
  }
964
+ function npxRemoteEntry(url, apiKey, npx, path) {
965
+ const remote = mcpRemoteServer(url, apiKey);
966
+ return { command: npx, args: remote.args, env: { PATH: path, ...remote.env } };
967
+ }
964
968
  function mcpRemoteServer(url, apiKey) {
965
969
  const env = {};
966
970
  const headerArgs = Object.entries(mcpHeaders(apiKey)).flatMap(([key, value]) => {
@@ -1081,6 +1085,12 @@ function isJSONKeyConfigured(configPath, topLevelKey) {
1081
1085
  return false;
1082
1086
  return "dosu" in section;
1083
1087
  }
1088
+ function readJSONServer(configPath, topLevelKey) {
1089
+ const section = loadJSONConfig(configPath)[topLevelKey];
1090
+ if (typeof section !== "object" || section === null)
1091
+ return;
1092
+ return section.dosu;
1093
+ }
1084
1094
  function installJSONServer(configPath, topKey, server) {
1085
1095
  const jsonCfg = readJSONConfig(configPath);
1086
1096
  let section = jsonCfg[topKey];
@@ -1118,7 +1128,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
1118
1128
  function getVersionString() {
1119
1129
  return `v${VERSION}`;
1120
1130
  }
1121
- var VERSION = "0.67.1", INSTALL_CHANNEL = "npm";
1131
+ var VERSION = "0.68.0", INSTALL_CHANNEL = "npm";
1122
1132
 
1123
1133
  // src/debug/logger.ts
1124
1134
  import {
@@ -14323,7 +14333,7 @@ var init_conflicts = __esm(() => {
14323
14333
  });
14324
14334
 
14325
14335
  // src/telemetry/debug-id.ts
14326
- var BUNDLE_DEBUG_ID_PLACEHOLDER = "152ccbaa-13cc-83fc-6475-6e2164756e21";
14336
+ var BUNDLE_DEBUG_ID_PLACEHOLDER = "2a77eea3-ffbe-b0f0-6475-6e2164756e21";
14327
14337
 
14328
14338
  // src/telemetry/telemetry.ts
14329
14339
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -62482,6 +62492,68 @@ var init_topics = __esm(() => {
62482
62492
  import_picocolors31 = __toESM(require_picocolors(), 1);
62483
62493
  });
62484
62494
 
62495
+ // src/mcp/shape.ts
62496
+ function shapeEndpoint(cfg) {
62497
+ const url = cfg?.mode === MODE_OSS ? mcpEndpoint(cfg) : mcpURL(ANY);
62498
+ return ANY + url.slice(getBackendURL().length);
62499
+ }
62500
+ function escapeRegExp(text) {
62501
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
62502
+ }
62503
+ function hasShape(expected, actual) {
62504
+ if (typeof expected === "string") {
62505
+ if (typeof actual !== "string")
62506
+ return false;
62507
+ if (!expected.includes(ANY))
62508
+ return expected === actual;
62509
+ const pattern = expected.split(ANY).map(escapeRegExp).join("[\\s\\S]*");
62510
+ return new RegExp(`^${pattern}$`).test(actual);
62511
+ }
62512
+ if (Array.isArray(expected)) {
62513
+ return Array.isArray(actual) && actual.length === expected.length && expected.every((item, i) => hasShape(item, actual[i]));
62514
+ }
62515
+ if (typeof expected === "object" && expected !== null) {
62516
+ if (typeof actual !== "object" || actual === null || Array.isArray(actual))
62517
+ return false;
62518
+ const expectedKeys = Object.keys(expected);
62519
+ const actualRecord = actual;
62520
+ return expectedKeys.length === Object.keys(actualRecord).length && expectedKeys.every((key) => (key in actualRecord) && hasShape(expected[key], actualRecord[key]));
62521
+ }
62522
+ return expected === actual;
62523
+ }
62524
+ function isUserChoiceKey(key) {
62525
+ return USER_CHOICE_KEYS.has(key);
62526
+ }
62527
+ function withoutUserChoices(entry) {
62528
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
62529
+ return entry;
62530
+ return Object.fromEntries(Object.entries(entry).filter(([key]) => !isUserChoiceKey(key)));
62531
+ }
62532
+ function entryHasShape(expected, actual) {
62533
+ return hasShape(withoutUserChoices(expected), withoutUserChoices(actual));
62534
+ }
62535
+ var ANY = "\x00any\x00", USER_CHOICE_KEYS;
62536
+ var init_shape = __esm(() => {
62537
+ init_config();
62538
+ init_config_helpers();
62539
+ USER_CHOICE_KEYS = new Set([
62540
+ "disabled",
62541
+ "enabled",
62542
+ "autoApprove",
62543
+ "disabledTools",
62544
+ "timeout",
62545
+ "trust",
62546
+ "includeTools",
62547
+ "excludeTools",
62548
+ "enabled_tools",
62549
+ "disabled_tools",
62550
+ "default_tools_approval_mode",
62551
+ "required",
62552
+ "startup_timeout_sec",
62553
+ "tool_timeout_sec"
62554
+ ]);
62555
+ });
62556
+
62485
62557
  // src/mcp/providers/base.ts
62486
62558
  function createJSONProvider(opts) {
62487
62559
  const buildServer = opts.buildServer ?? defaultBuildServer;
@@ -62494,6 +62566,7 @@ function createJSONProvider(opts) {
62494
62566
  isInstalled: () => isInstalled(opts.paths),
62495
62567
  globalConfigPath: () => expandHome(opts.globalPath),
62496
62568
  isConfigured: () => isJSONKeyConfigured(expandHome(opts.globalPath), opts.topKey),
62569
+ isCurrent: (cfg) => entryHasShape(buildServer({ url: shapeEndpoint(cfg), headers: mcpHeaders(ANY) }), readJSONServer(expandHome(opts.globalPath), opts.topKey)),
62497
62570
  install(cfg, global2) {
62498
62571
  const endpoint = {
62499
62572
  url: mcpEndpoint(cfg),
@@ -62507,7 +62580,7 @@ function createJSONProvider(opts) {
62507
62580
  } else {
62508
62581
  throw new Error(`${opts.providerName} does not support local installation`);
62509
62582
  }
62510
- installJSONServer(configPath, opts.topKey, buildServer(endpoint, cfg));
62583
+ installJSONServer(configPath, opts.topKey, buildServer(endpoint));
62511
62584
  },
62512
62585
  remove(global2) {
62513
62586
  let configPath;
@@ -62530,6 +62603,7 @@ var defaultBuildServer = ({ url, headers }) => ({
62530
62603
  var init_base = __esm(() => {
62531
62604
  init_config_helpers();
62532
62605
  init_detect();
62606
+ init_shape();
62533
62607
  });
62534
62608
 
62535
62609
  // src/mcp/providers/antigravity.ts
@@ -62581,17 +62655,13 @@ var ClaudeDesktopProvider = () => ({
62581
62655
  isInstalled: () => isInstalled([join26(appSupportDir(), "Claude")]),
62582
62656
  globalConfigPath: () => configPath(),
62583
62657
  isConfigured: () => isJSONKeyConfigured(configPath(), "mcpServers"),
62658
+ isCurrent: (cfg) => entryHasShape(npxRemoteEntry(shapeEndpoint(cfg), ANY, ANY, npxPathEnv(join26(ANY, "npx"))), readJSONServer(configPath(), "mcpServers")),
62584
62659
  install(cfg, global2) {
62585
62660
  if (!global2)
62586
62661
  throw new Error("Claude Desktop does not support local installation");
62587
62662
  const url = mcpEndpoint(cfg);
62588
62663
  const npx = findNpx();
62589
- const remote = mcpRemoteServer(url, cfg.active_account?.target?.api_key);
62590
- installJSONServer(configPath(), "mcpServers", {
62591
- command: npx,
62592
- args: remote.args,
62593
- env: { PATH: npxPathEnv(npx), ...remote.env }
62594
- });
62664
+ installJSONServer(configPath(), "mcpServers", npxRemoteEntry(url, cfg.active_account?.target?.api_key, npx, npxPathEnv(npx)));
62595
62665
  },
62596
62666
  remove(global2) {
62597
62667
  if (!global2)
@@ -62602,6 +62672,7 @@ var ClaudeDesktopProvider = () => ({
62602
62672
  var init_claude_desktop = __esm(() => {
62603
62673
  init_config_helpers();
62604
62674
  init_detect();
62675
+ init_shape();
62605
62676
  });
62606
62677
 
62607
62678
  // src/mcp/providers/cline.ts
@@ -62655,7 +62726,7 @@ var init_cline_cli = __esm(() => {
62655
62726
  import { existsSync as existsSync22, readFileSync as readFileSync22 } from "node:fs";
62656
62727
  import { dirname as dirname11, join as join29 } from "node:path";
62657
62728
  function codexHome3() {
62658
- return process.env.CODEX_HOME ?? expandHome("~/.codex");
62729
+ return process.env.CODEX_HOME || expandHome("~/.codex");
62659
62730
  }
62660
62731
  function getConfigPath2(global2) {
62661
62732
  if (global2)
@@ -62673,25 +62744,134 @@ function writeTOML(path, content) {
62673
62744
  function tomlString(value) {
62674
62745
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
62675
62746
  }
62747
+ function tomlValue(value) {
62748
+ return Array.isArray(value) ? `[${value.map(tomlString).join(", ")}]` : tomlString(value);
62749
+ }
62750
+ function dosuEntry(url, apiKey, npx, path) {
62751
+ return { ...npxRemoteEntry(url, apiKey, npx, path), omit_tools_from: ["deferred"] };
62752
+ }
62753
+ function isUserKey(key) {
62754
+ return isUserChoiceKey(key) || key === "tools" || key.startsWith("tools.");
62755
+ }
62756
+ function isUserTable(name) {
62757
+ return name === `${DOSU_TABLE}.tools` || name.startsWith(`${DOSU_TABLE}.tools.`);
62758
+ }
62759
+ function renderDosuEntry(entry, kept) {
62760
+ const tables = [[DOSU_TABLE, []]];
62761
+ for (const [key, value] of Object.entries(entry)) {
62762
+ if (typeof value === "string" || Array.isArray(value)) {
62763
+ tables[0][1].push(`${key} = ${tomlValue(value)}`);
62764
+ } else {
62765
+ const lines = Object.entries(value).map(([k, v]) => `${k} = ${tomlValue(v)}`);
62766
+ tables.push([`${DOSU_TABLE}.${key}`, lines]);
62767
+ }
62768
+ }
62769
+ tables[0][1].push(...kept.root);
62770
+ return tables.map(([name, lines]) => `
62771
+ [${name}]
62772
+ ${lines.join(`
62773
+ `)}
62774
+ `).join("") + kept.tables.map((table) => `
62775
+ ${table}
62776
+ `).join("");
62777
+ }
62778
+ function openBrackets(text) {
62779
+ let depth = 0;
62780
+ let quote = null;
62781
+ for (let i = 0;i < text.length; i++) {
62782
+ const c = text[i];
62783
+ if (quote) {
62784
+ if (c === "\\" && quote === '"')
62785
+ i++;
62786
+ else if (c === quote)
62787
+ quote = null;
62788
+ } else if (c === '"' || c === "'")
62789
+ quote = c;
62790
+ else if (c === "#")
62791
+ break;
62792
+ else if (c === "[" || c === "{")
62793
+ depth++;
62794
+ else if (c === "]" || c === "}")
62795
+ depth--;
62796
+ }
62797
+ return depth;
62798
+ }
62799
+ function dosuTables(content) {
62800
+ const tables = [];
62801
+ let table;
62802
+ let open;
62803
+ let depth = 0;
62804
+ for (const line of content.split(`
62805
+ `)) {
62806
+ const name = sectionName(line);
62807
+ if (name !== null) {
62808
+ open = undefined;
62809
+ table = isDosuSection(name) ? { name, header: line.trim(), statements: [] } : undefined;
62810
+ if (table)
62811
+ tables.push(table);
62812
+ continue;
62813
+ }
62814
+ if (open && depth > 0) {
62815
+ open.value += `
62816
+ ${line}`;
62817
+ open.text += `
62818
+ ${line}`;
62819
+ depth += openBrackets(line);
62820
+ continue;
62821
+ }
62822
+ open = undefined;
62823
+ const text = line.trim();
62824
+ if (!table || !text || text.startsWith("#"))
62825
+ continue;
62826
+ const eq = text.indexOf("=");
62827
+ const key = eq === -1 ? text : text.slice(0, eq).trim();
62828
+ const value = eq === -1 ? "" : text.slice(eq + 1).trim();
62829
+ open = { key, value, text: line };
62830
+ table.statements.push(open);
62831
+ depth = openBrackets(value);
62832
+ }
62833
+ return tables;
62834
+ }
62835
+ function readDosuEntry(content) {
62836
+ let entry;
62837
+ for (const { name, statements } of dosuTables(content)) {
62838
+ entry ??= {};
62839
+ if (isUserTable(name))
62840
+ continue;
62841
+ const isRoot = name === DOSU_TABLE;
62842
+ const table = isRoot ? entry : {};
62843
+ if (!isRoot)
62844
+ entry[name.slice(DOSU_TABLE.length + 1)] = table;
62845
+ for (const { key, value } of statements) {
62846
+ if (isRoot && isUserKey(key))
62847
+ continue;
62848
+ try {
62849
+ table[key] = JSON.parse(value);
62850
+ } catch {
62851
+ table[key] = value;
62852
+ }
62853
+ }
62854
+ }
62855
+ return entry;
62856
+ }
62857
+ function userSettings(content) {
62858
+ const kept = { root: [], tables: [] };
62859
+ for (const { name, header, statements } of dosuTables(content)) {
62860
+ if (name === DOSU_TABLE) {
62861
+ kept.root.push(...statements.filter(({ key }) => isUserKey(key)).map(({ text }) => text));
62862
+ } else if (isUserTable(name)) {
62863
+ kept.tables.push([header, ...statements.map(({ text }) => text)].join(`
62864
+ `));
62865
+ }
62866
+ }
62867
+ return kept;
62868
+ }
62676
62869
  function installDosuToTOML(path, cfg) {
62677
- let content = readTOML(path);
62678
- content = removeDosuFromTOML(content);
62870
+ const existing = readTOML(path);
62871
+ const content = removeDosuFromTOML(existing);
62679
62872
  const npx = findNpx();
62680
- const remote = mcpRemoteServer(mcpEndpoint(cfg), cfg.active_account?.target?.api_key);
62681
- const env = { PATH: npxPathEnv(npx), ...remote.env };
62682
- const envEntries = Object.entries(env).map(([key, value]) => `${key} = ${tomlString(value)}`).join(`
62683
- `);
62684
- const args = remote.args.map(tomlString).join(", ");
62685
- const section = `
62686
- [mcp_servers.dosu]
62687
- command = ${tomlString(npx)}
62688
- args = [${args}]
62689
- ` + `
62690
- [mcp_servers.dosu.env]
62691
- ${envEntries}
62692
- `;
62693
- content += section;
62694
- writeTOML(path, content);
62873
+ const entry = dosuEntry(mcpEndpoint(cfg), cfg.active_account?.target?.api_key, npx, npxPathEnv(npx));
62874
+ writeTOML(path, content + renderDosuEntry(entry, userSettings(existing)));
62695
62875
  }
62696
62876
  function sectionName(line) {
62697
62877
  const header = line.trim();
@@ -62699,7 +62879,7 @@ function sectionName(line) {
62699
62879
  return match ? match[1].trim() : null;
62700
62880
  }
62701
62881
  function isDosuSection(name) {
62702
- return name === "mcp_servers.dosu" || name.startsWith("mcp_servers.dosu.");
62882
+ return name === DOSU_TABLE || name.startsWith(`${DOSU_TABLE}.`);
62703
62883
  }
62704
62884
  function removeDosuFromTOML(content) {
62705
62885
  const result = [];
@@ -62757,7 +62937,7 @@ function pruneLegacyRemoteEntry(path) {
62757
62937
  return;
62758
62938
  writeTOML(path, removeDosuFromTOML(content));
62759
62939
  }
62760
- var REMOTE_HTTP_KEY, CodexProvider = () => ({
62940
+ var DOSU_TABLE = "mcp_servers.dosu", REMOTE_HTTP_KEY, CodexProvider = () => ({
62761
62941
  name: () => "Codex (CLI + Desktop)",
62762
62942
  id: () => "codex",
62763
62943
  supportsLocal: () => true,
@@ -62769,6 +62949,7 @@ var REMOTE_HTTP_KEY, CodexProvider = () => ({
62769
62949
  const content = readTOML(join29(codexHome3(), "config.toml"));
62770
62950
  return content.includes("[mcp_servers.dosu]");
62771
62951
  },
62952
+ isCurrent: (cfg) => entryHasShape(dosuEntry(shapeEndpoint(cfg), ANY, ANY, npxPathEnv(join29(ANY, "npx"))), readDosuEntry(readTOML(join29(codexHome3(), "config.toml")))),
62772
62953
  install(cfg, global2) {
62773
62954
  if (cfg.mode !== MODE_OSS && !cfg.active_account?.target?.deployment_id)
62774
62955
  throw new Error("deployment ID is required");
@@ -62793,11 +62974,15 @@ var init_codex = __esm(() => {
62793
62974
  init_config();
62794
62975
  init_config_helpers();
62795
62976
  init_detect();
62977
+ init_shape();
62796
62978
  REMOTE_HTTP_KEY = /^(url|type|bearer_token_env_var)\s*=/;
62797
62979
  });
62798
62980
 
62799
62981
  // src/mcp/providers/copilot.ts
62800
62982
  import { join as join30 } from "node:path";
62983
+ function globalServer(url, apiKey) {
62984
+ return { type: "http", url, tools: ["*"], headers: mcpHeaders(apiKey) };
62985
+ }
62801
62986
  function globalPath() {
62802
62987
  if (process.env.XDG_CONFIG_HOME) {
62803
62988
  return join30(process.env.XDG_CONFIG_HOME, "mcp-config.json");
@@ -62813,16 +62998,11 @@ var CopilotProvider = () => ({
62813
62998
  isInstalled: () => isInstalled([expandHome("~/.copilot")]),
62814
62999
  globalConfigPath: () => globalPath(),
62815
63000
  isConfigured: () => isJSONKeyConfigured(globalPath(), "mcpServers"),
63001
+ isCurrent: (cfg) => entryHasShape(globalServer(shapeEndpoint(cfg), ANY), readJSONServer(globalPath(), "mcpServers")),
62816
63002
  install(cfg, global2) {
62817
63003
  const url = mcpEndpoint(cfg);
62818
63004
  if (global2) {
62819
- const server = {
62820
- type: "http",
62821
- url,
62822
- tools: ["*"],
62823
- headers: mcpHeaders(cfg.active_account.target.api_key)
62824
- };
62825
- installJSONServer(globalPath(), "mcpServers", server);
63005
+ installJSONServer(globalPath(), "mcpServers", globalServer(url, cfg.active_account?.target?.api_key));
62826
63006
  } else {
62827
63007
  const configPath = join30(process.cwd(), ".vscode", "mcp.json");
62828
63008
  const server = {
@@ -62845,6 +63025,7 @@ var CopilotProvider = () => ({
62845
63025
  var init_copilot = __esm(() => {
62846
63026
  init_config_helpers();
62847
63027
  init_detect();
63028
+ init_shape();
62848
63029
  });
62849
63030
 
62850
63031
  // src/mcp/providers/cursor.ts
@@ -62937,6 +63118,9 @@ var init_manual = __esm(() => {
62937
63118
  // src/mcp/providers/mcporter.ts
62938
63119
  import { existsSync as existsSync23 } from "node:fs";
62939
63120
  import { join as join34 } from "node:path";
63121
+ function server(url, apiKey) {
63122
+ return { type: "http", url, headers: mcpHeaders(apiKey) };
63123
+ }
62940
63124
  function resolveGlobalConfigPath() {
62941
63125
  const jsonPath = expandHome("~/.mcporter/mcporter.json");
62942
63126
  if (existsSync23(jsonPath))
@@ -62955,14 +63139,10 @@ var MCPorterProvider = () => ({
62955
63139
  isInstalled: () => isInstalled(["~/.mcporter"]),
62956
63140
  globalConfigPath: () => resolveGlobalConfigPath(),
62957
63141
  isConfigured: () => isJSONKeyConfigured(resolveGlobalConfigPath(), "mcpServers"),
63142
+ isCurrent: (cfg) => entryHasShape(server(shapeEndpoint(cfg), ANY), readJSONServer(resolveGlobalConfigPath(), "mcpServers")),
62958
63143
  install(cfg, global2) {
62959
63144
  const configPath = global2 ? resolveGlobalConfigPath() : join34(process.cwd(), "config", "mcporter.json");
62960
- const server = {
62961
- type: "http",
62962
- url: mcpEndpoint(cfg),
62963
- headers: mcpHeaders(cfg.active_account.target.api_key)
62964
- };
62965
- installJSONServer(configPath, "mcpServers", server);
63145
+ installJSONServer(configPath, "mcpServers", server(mcpEndpoint(cfg), cfg.active_account?.target?.api_key));
62966
63146
  },
62967
63147
  remove(global2) {
62968
63148
  const configPath = global2 ? resolveGlobalConfigPath() : join34(process.cwd(), "config", "mcporter.json");
@@ -62972,6 +63152,7 @@ var MCPorterProvider = () => ({
62972
63152
  var init_mcporter = __esm(() => {
62973
63153
  init_config_helpers();
62974
63154
  init_detect();
63155
+ init_shape();
62975
63156
  });
62976
63157
 
62977
63158
  // src/mcp/providers/opencode.ts
@@ -63085,6 +63266,7 @@ var ZedProvider = (legacyPath = legacyDarwinSettingsPath()) => {
63085
63266
  return provider;
63086
63267
  return {
63087
63268
  ...provider,
63269
+ isCurrent: (cfg) => provider.isCurrent(cfg) && readJSONServer(legacyPath, "context_servers") === undefined,
63088
63270
  install(cfg, global2) {
63089
63271
  provider.install(cfg, global2);
63090
63272
  if (global2)
@@ -63163,9 +63345,21 @@ function configuredProviders() {
63163
63345
  }
63164
63346
  });
63165
63347
  }
63348
+ function staleProviders(cfg) {
63349
+ return configuredProviders().filter((provider) => {
63350
+ try {
63351
+ return !provider.isCurrent(cfg);
63352
+ } catch {
63353
+ return true;
63354
+ }
63355
+ });
63356
+ }
63166
63357
  function refreshConfiguredProviders(cfg) {
63358
+ return refreshProviders(cfg, configuredProviders());
63359
+ }
63360
+ function refreshProviders(cfg, providers) {
63167
63361
  const result = { updated: [], failed: [] };
63168
- for (const provider of configuredProviders()) {
63362
+ for (const provider of providers) {
63169
63363
  try {
63170
63364
  provider.install(cfg, true);
63171
63365
  logger.info("mcp", `Refreshed ${provider.name()} MCP entry`);
@@ -63377,199 +63571,18 @@ var init_auto_update = __esm(() => {
63377
63571
  VERSION_PATTERN = /^[0-9A-Za-z.+-]{1,64}$/;
63378
63572
  });
63379
63573
 
63380
- // src/version/update-check.ts
63574
+ // src/version/mcp-refresh-check.ts
63381
63575
  import { existsSync as existsSync25, mkdirSync as mkdirSync15, readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "node:fs";
63382
63576
  import { join as join40 } from "node:path";
63383
- function isValidVersion2(value) {
63384
- return typeof value === "string" && SEMVER_PATTERN.test(value);
63385
- }
63386
- function stripPrerelease(version) {
63387
- return version.replace(/[-+].*$/, "");
63388
- }
63389
- function isNewerVersion(latest, current) {
63390
- const a = stripPrerelease(latest).split(".").map(Number);
63391
- const b = stripPrerelease(current).split(".").map(Number);
63392
- const len = Math.max(a.length, b.length);
63393
- for (let i = 0;i < len; i++) {
63394
- const av = a[i] ?? 0;
63395
- const bv = b[i] ?? 0;
63396
- if (av > bv)
63397
- return true;
63398
- if (av < bv)
63399
- return false;
63400
- }
63401
- return false;
63402
- }
63403
63577
  function getCachePath2() {
63404
63578
  return join40(getConfigDir(), CACHE_FILENAME3);
63405
63579
  }
63406
- function readCache() {
63580
+ function readMcpRefreshCache() {
63407
63581
  try {
63408
63582
  const path = getCachePath2();
63409
63583
  if (!existsSync25(path))
63410
63584
  return null;
63411
63585
  const data = JSON.parse(readFileSync24(path, "utf-8"));
63412
- if (typeof data.lastCheck === "number" && isValidVersion2(data.latestVersion)) {
63413
- return data;
63414
- }
63415
- return null;
63416
- } catch {
63417
- return null;
63418
- }
63419
- }
63420
- function writeCache(cache) {
63421
- try {
63422
- const dir = getConfigDir();
63423
- if (!existsSync25(dir)) {
63424
- mkdirSync15(dir, { recursive: true, mode: 448 });
63425
- }
63426
- writeFileSync16(getCachePath2(), JSON.stringify(cache), { mode: 384 });
63427
- } catch {}
63428
- }
63429
- async function fetchLatestVersion() {
63430
- const controller = new AbortController;
63431
- const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
63432
- try {
63433
- const resp = await fetch(REGISTRY_URL, { signal: controller.signal });
63434
- if (!resp.ok)
63435
- return null;
63436
- const data = await resp.json();
63437
- const latest = data.latest;
63438
- return isValidVersion2(latest) ? latest : null;
63439
- } catch {
63440
- return null;
63441
- } finally {
63442
- clearTimeout(timeout);
63443
- }
63444
- }
63445
- function buildUpdateHint(channel, npx = false) {
63446
- if (channel === "npm" && npx) {
63447
- return 'Use "npx -y @dosu/cli@latest" for the next Dosu command';
63448
- }
63449
- return 'Run "dosu upgrade"';
63450
- }
63451
- function styleHint(hint) {
63452
- const match = hint.match(/^(.*)("[^"]+")(.*)$/);
63453
- if (!match)
63454
- return import_picocolors32.default.dim(hint);
63455
- const [, before, command, after] = match;
63456
- return [before && import_picocolors32.default.dim(before), import_picocolors32.default.cyan(command), after && import_picocolors32.default.dim(after)].filter(Boolean).join("");
63457
- }
63458
- function framedNotice(content, width) {
63459
- const innerWidth = Math.max(...content.map(visibleWidth2)) + BOX_PADDING * 2;
63460
- const framed = (line) => {
63461
- const pad = innerWidth - visibleWidth2(line);
63462
- const left = Math.floor(pad / 2);
63463
- return `${import_picocolors32.default.yellow("│")}${" ".repeat(left)}${line}${" ".repeat(pad - left)}${import_picocolors32.default.yellow("│")}`;
63464
- };
63465
- const box = [
63466
- import_picocolors32.default.yellow(`╭${"─".repeat(innerWidth)}╮`),
63467
- framed(""),
63468
- ...content.map(framed),
63469
- framed(""),
63470
- import_picocolors32.default.yellow(`╰${"─".repeat(innerWidth)}╯`)
63471
- ];
63472
- return `
63473
- ${centerBlock(box, width).join(`
63474
- `)}
63475
- `;
63476
- }
63477
- function versionArrow(current, latest) {
63478
- return `${import_picocolors32.default.dim(current)} ${import_picocolors32.default.dim("→")} ${import_picocolors32.default.bold(brand(latest))}`;
63479
- }
63480
- function buildAutoUpdateNotice(current, latest, interactive, width = process.stderr.columns ?? 80) {
63481
- if (interactive) {
63482
- return framedNotice([
63483
- `Updating Dosu: ${versionArrow(current, latest)}`,
63484
- import_picocolors32.default.dim("Installing in the background; your next command uses it")
63485
- ], width);
63486
- }
63487
- return `
63488
- [dosu:update] Installing Dosu ${latest} in the background (running ${current}). ` + `The next Dosu command uses it; no action needed.
63489
- `;
63490
- }
63491
- function buildUpdateNotice(current, latest, channel, interactive, npx = false, width = process.stderr.columns ?? 80) {
63492
- const hint = buildUpdateHint(channel, npx);
63493
- if (interactive) {
63494
- return framedNotice([`Update available: ${versionArrow(current, latest)}`, styleHint(hint)], width);
63495
- }
63496
- const agentAction = hint[0].toLowerCase() + hint.slice(1);
63497
- const verifyCommand = npx ? "npx -y @dosu/cli@latest --version" : "dosu --version";
63498
- return `
63499
- [dosu:update] Update available: ${current} → ${latest}
63500
- ` + `Tell the user Dosu CLI is outdated. After they approve, ${agentAction}, ` + `then verify with "${verifyCommand}".
63501
- `;
63502
- }
63503
- function getAvailableUpdate() {
63504
- const cache = readCache();
63505
- return cache && isNewerVersion(cache.latestVersion, VERSION) ? cache.latestVersion : null;
63506
- }
63507
- function handleNewerVersion(latest, notify) {
63508
- const autoUpdate = startAutoUpdate(latest);
63509
- if (!notify)
63510
- return;
63511
- const interactive = process.stderr.isTTY === true;
63512
- console.error(autoUpdate === "unavailable" ? buildUpdateNotice(VERSION, latest, INSTALL_CHANNEL, interactive, isNpxInvocation()) : buildAutoUpdateNotice(VERSION, latest, interactive));
63513
- }
63514
- async function checkForUpdates(options = {}) {
63515
- const notify = options.notify ?? true;
63516
- try {
63517
- const cache = readCache();
63518
- const isStale = !cache || Date.now() - cache.lastCheck > CHECK_INTERVAL_MS2;
63519
- if (!isStale) {
63520
- if (isNewerVersion(cache.latestVersion, VERSION)) {
63521
- handleNewerVersion(cache.latestVersion, notify);
63522
- }
63523
- return;
63524
- }
63525
- const latest = await fetchLatestVersion();
63526
- const latestKnownVersion = latest ?? cache?.latestVersion ?? VERSION;
63527
- writeCache({
63528
- lastCheck: Date.now(),
63529
- latestVersion: latestKnownVersion
63530
- });
63531
- if (latest) {
63532
- logger.debug("update-check", `Cached latest version: ${latest}`);
63533
- }
63534
- if (isNewerVersion(latestKnownVersion, VERSION)) {
63535
- handleNewerVersion(latestKnownVersion, notify);
63536
- }
63537
- } catch (err) {
63538
- logger.error("update-check", `Update check failed: ${err}`);
63539
- }
63540
- }
63541
- var import_picocolors32, CACHE_FILENAME3 = "update-check.json", CHECK_INTERVAL_MS2, FETCH_TIMEOUT_MS2 = 1000, REGISTRY_URL = "https://registry.npmjs.org/-/package/@dosu/cli/dist-tags", SEMVER_PATTERN, BOX_PADDING = 3;
63542
- var init_update_check = __esm(() => {
63543
- init_config();
63544
- init_logger();
63545
- init_styles();
63546
- init_layout();
63547
- init_auto_update();
63548
- import_picocolors32 = __toESM(require_picocolors(), 1);
63549
- CHECK_INTERVAL_MS2 = 6 * 60 * 60 * 1000;
63550
- SEMVER_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
63551
- });
63552
-
63553
- // src/version/mcp-refresh-check.ts
63554
- import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "node:fs";
63555
- import { join as join41 } from "node:path";
63556
- function needsMcpRefresh(previous, current) {
63557
- if (previous === null)
63558
- return true;
63559
- if (previous === current)
63560
- return false;
63561
- const [older, newer] = isNewerVersion(current, previous) ? [previous, current] : [current, previous];
63562
- return MCP_FORMAT_CHANGES.some((change) => isNewerVersion(change, older) && !isNewerVersion(change, newer));
63563
- }
63564
- function getCachePath3() {
63565
- return join41(getConfigDir(), CACHE_FILENAME4);
63566
- }
63567
- function readMcpRefreshCache() {
63568
- try {
63569
- const path = getCachePath3();
63570
- if (!existsSync26(path))
63571
- return null;
63572
- const data = JSON.parse(readFileSync25(path, "utf-8"));
63573
63586
  if (typeof data?.version === "string")
63574
63587
  return { version: data.version };
63575
63588
  return null;
@@ -63580,10 +63593,10 @@ function readMcpRefreshCache() {
63580
63593
  function writeMcpRefreshCache(cache) {
63581
63594
  try {
63582
63595
  const dir = getConfigDir();
63583
- if (!existsSync26(dir)) {
63584
- mkdirSync16(dir, { recursive: true, mode: 448 });
63596
+ if (!existsSync25(dir)) {
63597
+ mkdirSync15(dir, { recursive: true, mode: 448 });
63585
63598
  }
63586
- writeFileSync17(getCachePath3(), JSON.stringify(cache), { mode: 384 });
63599
+ writeFileSync16(getCachePath2(), JSON.stringify(cache), { mode: 384 });
63587
63600
  } catch {}
63588
63601
  }
63589
63602
  function canRefreshMcp(cfg) {
@@ -63594,26 +63607,21 @@ function canRefreshMcp(cfg) {
63594
63607
  }
63595
63608
  function displayNotice(names) {
63596
63609
  console.error(`
63597
- ${import_picocolors33.default.green(`✓ Dosu ${VERSION}: refreshed MCP config for ${names.join(", ")}`)}
63598
- ` + `${import_picocolors33.default.dim(` Run ${import_picocolors33.default.cyan('"dosu setup"')} to also update hooks and rules, then restart your AI agents.`)}
63610
+ ${import_picocolors32.default.green(`✓ Dosu ${VERSION}: refreshed MCP config for ${names.join(", ")}`)}
63611
+ ` + `${import_picocolors32.default.dim(` Run ${import_picocolors32.default.cyan('"dosu setup"')} to also update hooks and rules, then restart your AI agents.`)}
63599
63612
  `);
63600
63613
  }
63601
63614
  function checkForMcpRefresh(options = {}) {
63602
63615
  const notify = options.notify ?? true;
63603
63616
  try {
63604
- const previous = readMcpRefreshCache()?.version ?? null;
63605
- if (previous === VERSION)
63617
+ if (readMcpRefreshCache()?.version === VERSION)
63606
63618
  return;
63607
- if (!needsMcpRefresh(previous, VERSION)) {
63608
- writeMcpRefreshCache({ version: VERSION });
63609
- return;
63610
- }
63611
63619
  const cfg = loadConfigNonBlocking();
63612
63620
  if (!cfg || !canRefreshMcp(cfg)) {
63613
63621
  logger.debug("mcp-refresh", "Skipping MCP refresh: no API key or deployment configured");
63614
63622
  return;
63615
63623
  }
63616
- const result = refreshConfiguredProviders(cfg);
63624
+ const result = refreshProviders(cfg, staleProviders(cfg));
63617
63625
  writeMcpRefreshCache({ version: VERSION });
63618
63626
  logger.info("mcp-refresh", `Version ${VERSION}: refreshed ${result.updated.length}, failed ${result.failed.length}`);
63619
63627
  if (notify && result.updated.length > 0) {
@@ -63623,24 +63631,19 @@ function checkForMcpRefresh(options = {}) {
63623
63631
  logger.error("mcp-refresh", `MCP refresh check failed: ${err}`);
63624
63632
  }
63625
63633
  }
63626
- var import_picocolors33, CACHE_FILENAME4 = "mcp-refresh.json", MCP_FORMAT_CHANGES;
63634
+ var import_picocolors32, CACHE_FILENAME3 = "mcp-refresh.json";
63627
63635
  var init_mcp_refresh_check = __esm(() => {
63628
63636
  init_config();
63629
63637
  init_logger();
63630
63638
  init_refresh();
63631
- init_update_check();
63632
- import_picocolors33 = __toESM(require_picocolors(), 1);
63633
- MCP_FORMAT_CHANGES = [
63634
- "0.53.0",
63635
- "0.62.0"
63636
- ];
63639
+ import_picocolors32 = __toESM(require_picocolors(), 1);
63637
63640
  });
63638
63641
 
63639
63642
  // src/version/skill-update-check.ts
63640
63643
  function displayNotice2() {
63641
63644
  console.error(`
63642
- ${import_picocolors34.default.green(`✓ Dosu ${VERSION}: refreshed the bundled agent skills`)}
63643
- ` + `${import_picocolors34.default.dim(" Restart your AI agents to pick up the new skill content.")}
63645
+ ${import_picocolors33.default.green(`✓ Dosu ${VERSION}: refreshed the bundled agent skills`)}
63646
+ ` + `${import_picocolors33.default.dim(" Restart your AI agents to pick up the new skill content.")}
63644
63647
  `);
63645
63648
  }
63646
63649
  function checkForSkillUpdates(options = {}) {
@@ -63661,11 +63664,11 @@ function checkForSkillUpdates(options = {}) {
63661
63664
  logger.error("skill-update-check", `Skill update check failed: ${err}`);
63662
63665
  }
63663
63666
  }
63664
- var import_picocolors34;
63667
+ var import_picocolors33;
63665
63668
  var init_skill_update_check = __esm(() => {
63666
63669
  init_skill();
63667
63670
  init_logger();
63668
- import_picocolors34 = __toESM(require_picocolors(), 1);
63671
+ import_picocolors33 = __toESM(require_picocolors(), 1);
63669
63672
  });
63670
63673
 
63671
63674
  // src/auth/errors.ts
@@ -63731,15 +63734,15 @@ function createLogFollower(path, emit) {
63731
63734
  var init_follow = () => {};
63732
63735
 
63733
63736
  // src/sessions/session-title.ts
63734
- import { existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync26, renameSync as renameSync7, statSync as statSync10, writeFileSync as writeFileSync18 } from "node:fs";
63737
+ import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync25, renameSync as renameSync7, statSync as statSync10, writeFileSync as writeFileSync17 } from "node:fs";
63735
63738
  import { homedir as homedir11 } from "node:os";
63736
- import { join as join42 } from "node:path";
63739
+ import { join as join41 } from "node:path";
63737
63740
  function cachePath2(configDir) {
63738
- return join42(configDir, CACHE_FILENAME5);
63741
+ return join41(configDir, CACHE_FILENAME4);
63739
63742
  }
63740
63743
  function loadCacheFile2(configDir) {
63741
63744
  try {
63742
- const raw = JSON.parse(readFileSync26(cachePath2(configDir), "utf-8"));
63745
+ const raw = JSON.parse(readFileSync25(cachePath2(configDir), "utf-8"));
63743
63746
  if (raw.schema_version !== CACHE_SCHEMA_VERSION2 || typeof raw.entries !== "object")
63744
63747
  return {};
63745
63748
  return raw.entries;
@@ -63749,12 +63752,12 @@ function loadCacheFile2(configDir) {
63749
63752
  }
63750
63753
  function saveCacheFile2(configDir, entries) {
63751
63754
  try {
63752
- if (!existsSync27(configDir))
63753
- mkdirSync17(configDir, { recursive: true, mode: 448 });
63755
+ if (!existsSync26(configDir))
63756
+ mkdirSync16(configDir, { recursive: true, mode: 448 });
63754
63757
  const path = cachePath2(configDir);
63755
63758
  const tmp = `${path}.${process.pid}.tmp`;
63756
63759
  const file = { schema_version: CACHE_SCHEMA_VERSION2, entries };
63757
- writeFileSync18(tmp, JSON.stringify(file), { mode: 384 });
63760
+ writeFileSync17(tmp, JSON.stringify(file), { mode: 384 });
63758
63761
  renameSync7(tmp, path);
63759
63762
  } catch {}
63760
63763
  }
@@ -63787,15 +63790,15 @@ function claudeSummaryFromHead(text) {
63787
63790
  }
63788
63791
  return null;
63789
63792
  }
63790
- function reconstructSession(harness, id, slug, home = homedir11(), exists = existsSync27) {
63793
+ function reconstructSession(harness, id, slug, home = homedir11(), exists = existsSync26) {
63791
63794
  if (!slug)
63792
63795
  return null;
63793
63796
  const candidates = [];
63794
63797
  if (harness === "claude") {
63795
- candidates.push(join42(home, ".claude", "projects", slug, `${id}.jsonl`));
63798
+ candidates.push(join41(home, ".claude", "projects", slug, `${id}.jsonl`));
63796
63799
  } else if (harness === "cursor") {
63797
- const project = join42(home, ".cursor", "projects", slug);
63798
- candidates.push(join42(project, "agent-transcripts", id, `${id}.jsonl`), join42(project, "agent-transcripts", `${id}.jsonl`));
63800
+ const project = join41(home, ".cursor", "projects", slug);
63801
+ candidates.push(join41(project, "agent-transcripts", id, `${id}.jsonl`), join41(project, "agent-transcripts", `${id}.jsonl`));
63799
63802
  } else {
63800
63803
  return null;
63801
63804
  }
@@ -63807,7 +63810,7 @@ function reconstructSession(harness, id, slug, home = homedir11(), exists = exis
63807
63810
  }
63808
63811
  function readHead2(path) {
63809
63812
  try {
63810
- return readFileSync26(path, "utf-8").slice(0, 128 * 1024);
63813
+ return readFileSync25(path, "utf-8").slice(0, 128 * 1024);
63811
63814
  } catch {
63812
63815
  return null;
63813
63816
  }
@@ -63858,7 +63861,7 @@ function createSessionTitleResolver(configDir = getConfigDir(), deps = {}) {
63858
63861
  }
63859
63862
  };
63860
63863
  }
63861
- var CACHE_FILENAME5 = "session-titles.json", CACHE_SCHEMA_VERSION2 = 3, HEAD_LINES2 = 50, SESSION_NAME_MAX = 80;
63864
+ var CACHE_FILENAME4 = "session-titles.json", CACHE_SCHEMA_VERSION2 = 3, HEAD_LINES2 = 50, SESSION_NAME_MAX = 80;
63862
63865
  var init_session_title = __esm(() => {
63863
63866
  init_config();
63864
63867
  init_read();
@@ -63887,7 +63890,7 @@ var init_alt_screen = __esm(() => {
63887
63890
  });
63888
63891
 
63889
63892
  // src/tui/activity-view.ts
63890
- import { existsSync as existsSync28, readFileSync as readFileSync27 } from "node:fs";
63893
+ import { existsSync as existsSync27, readFileSync as readFileSync26 } from "node:fs";
63891
63894
  import { basename as basename6 } from "node:path";
63892
63895
  function reduceActivityViewKey(key) {
63893
63896
  if (key === "q" || key === ESC7 || key === CTRL_C4)
@@ -64028,15 +64031,15 @@ function statusLine(status) {
64028
64031
  if (status.running) {
64029
64032
  const since = status.startedAt ? ` · since ${localTime(status.startedAt)}` : "";
64030
64033
  const pid = status.pid !== undefined ? ` (pid ${status.pid})` : "";
64031
- return `\uD83D\uDCDA ${import_picocolors35.default.bold(brand("Studying sessions..."))}${import_picocolors35.default.dim(`${pid}${since}`)}`;
64034
+ return `\uD83D\uDCDA ${import_picocolors34.default.bold(brand("Studying sessions..."))}${import_picocolors34.default.dim(`${pid}${since}`)}`;
64032
64035
  }
64033
64036
  if (status.state.paused) {
64034
- return `${import_picocolors35.default.yellow("○")} ${import_picocolors35.default.bold("Paused")} ${import_picocolors35.default.dim("· studying stays off until you resume")}`;
64037
+ return `${import_picocolors34.default.yellow("○")} ${import_picocolors34.default.bold("Paused")} ${import_picocolors34.default.dim("· studying stays off until you resume")}`;
64035
64038
  }
64036
64039
  if (status.staleLock) {
64037
- return `${import_picocolors35.default.yellow("○")} ${import_picocolors35.default.bold("Not running")} ${import_picocolors35.default.dim("· a previous run exited without cleaning up")}`;
64040
+ return `${import_picocolors34.default.yellow("○")} ${import_picocolors34.default.bold("Not running")} ${import_picocolors34.default.dim("· a previous run exited without cleaning up")}`;
64038
64041
  }
64039
- return `${import_picocolors35.default.dim("○")} ${import_picocolors35.default.bold("Idle")} ${import_picocolors35.default.dim("· hooks study new sessions automatically")}`;
64042
+ return `${import_picocolors34.default.dim("○")} ${import_picocolors34.default.bold("Idle")} ${import_picocolors34.default.dim("· hooks study new sessions automatically")}`;
64040
64043
  }
64041
64044
  function progressLine(done, ready, width, notes = 0) {
64042
64045
  const total = done + ready;
@@ -64045,7 +64048,7 @@ function progressLine(done, ready, width, notes = 0) {
64045
64048
  const cells = Math.max(10, Math.min(30, width - 44));
64046
64049
  const ratio = Math.max(0, Math.min(1, done / total));
64047
64050
  const filled = Math.min(cells, Math.round(ratio * cells));
64048
- const bar = brand("█".repeat(filled)) + import_picocolors35.default.dim("░".repeat(cells - filled));
64051
+ const bar = brand("█".repeat(filled)) + import_picocolors34.default.dim("░".repeat(cells - filled));
64049
64052
  const pct = Math.floor(ratio * 100);
64050
64053
  const suffix = notes > 0 ? ` · ${notes} suggested page${notes === 1 ? "" : "s"}` : "";
64051
64054
  return `${bar} ${done}/${total} studied · ${pct}%${suffix}`;
@@ -64084,17 +64087,17 @@ function confirmBox(queuedCount, backlog, width, mode = "start") {
64084
64087
  const verb = mode === "clear" ? "clear" : mode === "stop" ? "stop" : mode === "resume" ? "resume" : "start";
64085
64088
  const maxInner = Math.max(20, Math.min(width, contentWidth()) - 4);
64086
64089
  const rows = [
64087
- import_picocolors35.default.bold(brand(title)),
64088
- ...wrapLine(scope, maxInner, "").map((line) => import_picocolors35.default.dim(line)),
64090
+ import_picocolors34.default.bold(brand(title)),
64091
+ ...wrapLine(scope, maxInner, "").map((line) => import_picocolors34.default.dim(line)),
64089
64092
  "",
64090
- `${import_picocolors35.default.bold("enter")} ${verb} · ${import_picocolors35.default.bold("esc")} cancel`
64093
+ `${import_picocolors34.default.bold("enter")} ${verb} · ${import_picocolors34.default.bold("esc")} cancel`
64091
64094
  ];
64092
64095
  const inner = Math.min(maxInner, Math.max(...rows.map(visibleWidth2)));
64093
64096
  const pad = (row) => row + " ".repeat(Math.max(0, inner - visibleWidth2(row)));
64094
64097
  return [
64095
- import_picocolors35.default.dim(`╭${"─".repeat(inner + 2)}╮`),
64096
- ...rows.map((row) => `${import_picocolors35.default.dim("│")} ${pad(row)} ${import_picocolors35.default.dim("│")}`),
64097
- import_picocolors35.default.dim(`╰${"─".repeat(inner + 2)}╯`)
64098
+ import_picocolors34.default.dim(`╭${"─".repeat(inner + 2)}╮`),
64099
+ ...rows.map((row) => `${import_picocolors34.default.dim("│")} ${pad(row)} ${import_picocolors34.default.dim("│")}`),
64100
+ import_picocolors34.default.dim(`╰${"─".repeat(inner + 2)}╯`)
64098
64101
  ];
64099
64102
  }
64100
64103
  function buildActivityFrame(status, activity, width, backlog = null, pane = DEFAULT_PANE, queued = [], studiedBeforeRun = 0, open = [], runProgress = null, projectNames = {}, sessionNames = {}, frameLines) {
@@ -64110,7 +64113,7 @@ function buildActivityFrame(status, activity, width, backlog = null, pane = DEFA
64110
64113
  progress = progressLine(studiedDelta + stepDone, Math.max(0, backlog.ready - stepDone), width, runProgress?.notes ?? 0);
64111
64114
  }
64112
64115
  const refusal = !status.running && status.state.last_refusal;
64113
- const refusalLines = refusal ? wrapLine(`! Studying paused: ${refusal.message} (${localTime(refusal.at)})`, width).map((line) => import_picocolors35.default.yellow(line)) : [];
64116
+ const refusalLines = refusal ? wrapLine(`! Studying paused: ${refusal.message} (${localTime(refusal.at)})`, width).map((line) => import_picocolors34.default.yellow(line)) : [];
64114
64117
  const fullRows = Boolean(pane.fullRows);
64115
64118
  const withName = (item, key) => projectNames[key] ? { ...item, project: projectNames[key] } : item;
64116
64119
  const latestPass = new Map;
@@ -64136,8 +64139,8 @@ function buildActivityFrame(status, activity, width, backlog = null, pane = DEFA
64136
64139
  breadcrumb(["home", "activity"], width),
64137
64140
  "",
64138
64141
  statusLine(status),
64139
- import_picocolors35.default.dim(studied),
64140
- ...queueDetail.length > 0 ? wrapLine(queueDetail.join(" · "), width).map((line) => import_picocolors35.default.dim(line)) : [],
64142
+ import_picocolors34.default.dim(studied),
64143
+ ...queueDetail.length > 0 ? wrapLine(queueDetail.join(" · "), width).map((line) => import_picocolors34.default.dim(line)) : [],
64141
64144
  ...progress ? [progress] : [],
64142
64145
  ...refusalLines,
64143
64146
  "",
@@ -64153,7 +64156,7 @@ function buildActivityFrame(status, activity, width, backlog = null, pane = DEFA
64153
64156
  status.running ? "s stop" : status.state.paused ? "s resume" : "s sync now",
64154
64157
  ...!status.running && status.state.watermark ? ["c clear"] : [],
64155
64158
  "esc back"
64156
- ].join(" · "), width, "").map((line) => import_picocolors35.default.dim(line))
64159
+ ].join(" · "), width, "").map((line) => import_picocolors34.default.dim(line))
64157
64160
  ]
64158
64161
  ];
64159
64162
  const defaultHeight = fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
@@ -64166,7 +64169,7 @@ function buildActivityFrame(status, activity, width, backlog = null, pane = DEFA
64166
64169
  ({ visible, above, below } = windowList(source, pane.scroll, height));
64167
64170
  display = visible.flatMap((row) => wrapRow(row, width));
64168
64171
  }
64169
- const listRows = display.length > 0 ? display.map((row) => import_picocolors35.default.dim(row)) : [import_picocolors35.default.dim(empty)];
64172
+ const listRows = display.length > 0 ? display.map((row) => import_picocolors34.default.dim(row)) : [import_picocolors34.default.dim(empty)];
64170
64173
  const scrollParts = [];
64171
64174
  if (above > 0)
64172
64175
  scrollParts.push(`↑ ${above} earlier`);
@@ -64175,7 +64178,7 @@ function buildActivityFrame(status, activity, width, backlog = null, pane = DEFA
64175
64178
  const lines = [
64176
64179
  ...header,
64177
64180
  ...listRows,
64178
- ...scrollParts.length > 0 ? [import_picocolors35.default.dim(clip(scrollParts.join(" · "), width))] : [],
64181
+ ...scrollParts.length > 0 ? [import_picocolors34.default.dim(clip(scrollParts.join(" · "), width))] : [],
64179
64182
  ...footer
64180
64183
  ];
64181
64184
  return { lines, listHeight: height };
@@ -64208,7 +64211,7 @@ function createRowNamer() {
64208
64211
  names.titles[key] = t;
64209
64212
  };
64210
64213
  for (const s of [...backlog.queued, ...backlog.open]) {
64211
- if (!existsSync28(s.path))
64214
+ if (!existsSync27(s.path))
64212
64215
  continue;
64213
64216
  const key = `${s.harness}/${s.id}`;
64214
64217
  const dir = dirs.resolve(s);
@@ -64245,7 +64248,7 @@ function createRowNamer() {
64245
64248
  }
64246
64249
  function defaultReadLog2() {
64247
64250
  try {
64248
- return readFileSync27(logger.getLogPath(), "utf-8");
64251
+ return readFileSync26(logger.getLogPath(), "utf-8");
64249
64252
  } catch {
64250
64253
  return "";
64251
64254
  }
@@ -64430,7 +64433,7 @@ ${CLEAR_BELOW}`);
64430
64433
  input.on("data", onData);
64431
64434
  });
64432
64435
  }
64433
- var import_picocolors35, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY_DOWN4, KEY_RIGHT3, KEY_LEFT3, CURSOR_HOME, CLEAR_BELOW, CLEAR_EOL, ACTIVITY_VIEW_POLL_MS = 500, ACTIVITY_VIEW_LIST_LINES = 10, ACTIVITY_VIEW_FULL_LIST_ROWS = 5, ACTIVITY_VIEW_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE, TITLE_READS_PER_DRAW = 25;
64436
+ var import_picocolors34, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY_DOWN4, KEY_RIGHT3, KEY_LEFT3, CURSOR_HOME, CLEAR_BELOW, CLEAR_EOL, ACTIVITY_VIEW_POLL_MS = 500, ACTIVITY_VIEW_LIST_LINES = 10, ACTIVITY_VIEW_FULL_LIST_ROWS = 5, ACTIVITY_VIEW_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE, TITLE_READS_PER_DRAW = 25;
64434
64437
  var init_activity_view = __esm(() => {
64435
64438
  init_follow();
64436
64439
  init_logger();
@@ -64445,7 +64448,7 @@ var init_activity_view = __esm(() => {
64445
64448
  init_alt_screen();
64446
64449
  init_layout();
64447
64450
  init_menu();
64448
- import_picocolors35 = __toESM(require_picocolors(), 1);
64451
+ import_picocolors34 = __toESM(require_picocolors(), 1);
64449
64452
  ESC7 = String.fromCharCode(27);
64450
64453
  HIDE_CURSOR3 = `${ESC7}[?25l`;
64451
64454
  SHOW_CURSOR3 = `${ESC7}[?25h`;
@@ -64462,14 +64465,14 @@ var init_activity_view = __esm(() => {
64462
64465
  });
64463
64466
 
64464
64467
  // src/rules/installer.ts
64465
- import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync28, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "node:fs";
64468
+ import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync27, unlinkSync as unlinkSync5, writeFileSync as writeFileSync18 } from "node:fs";
64466
64469
  import { homedir as homedir12 } from "node:os";
64467
- import { dirname as dirname12, join as join43 } from "node:path";
64470
+ import { dirname as dirname12, join as join42 } from "node:path";
64468
64471
  function claudeConfigDir4() {
64469
- return process.env.CLAUDE_CONFIG_DIR || join43(homedir12(), ".claude");
64472
+ return process.env.CLAUDE_CONFIG_DIR || join42(homedir12(), ".claude");
64470
64473
  }
64471
64474
  function codexHome4() {
64472
- return process.env.CODEX_HOME || join43(homedir12(), ".codex");
64475
+ return process.env.CODEX_HOME || join42(homedir12(), ".codex");
64473
64476
  }
64474
64477
  function isRuleAgent(agent) {
64475
64478
  return Object.hasOwn(RULE_TARGETS, agent);
@@ -64495,17 +64498,17 @@ async function fetchDosuRule(fetchImpl = globalThis.fetch) {
64495
64498
  return normalizeRule(FALLBACK_DOSU_RULE);
64496
64499
  }
64497
64500
  function ensureParent(path) {
64498
- mkdirSync18(dirname12(path), { recursive: true });
64501
+ mkdirSync17(dirname12(path), { recursive: true });
64499
64502
  }
64500
64503
  function writeIfChanged(path, content) {
64501
- if (!existsSync29(path)) {
64504
+ if (!existsSync28(path)) {
64502
64505
  ensureParent(path);
64503
- writeFileSync19(path, content, "utf-8");
64506
+ writeFileSync18(path, content, "utf-8");
64504
64507
  return "created";
64505
64508
  }
64506
- if (readFileSync28(path, "utf-8") === content)
64509
+ if (readFileSync27(path, "utf-8") === content)
64507
64510
  return "unchanged";
64508
- writeFileSync19(path, content, "utf-8");
64511
+ writeFileSync18(path, content, "utf-8");
64509
64512
  return "updated";
64510
64513
  }
64511
64514
  function findSection(content) {
@@ -64531,14 +64534,14 @@ function buildSection(content, lineEnding = `
64531
64534
  return `${DOSU_RULE_SECTION_START}${lineEnding}${body}${lineEnding}${DOSU_RULE_SECTION_END}`;
64532
64535
  }
64533
64536
  function upsertSection(path, content) {
64534
- if (!existsSync29(path)) {
64537
+ if (!existsSync28(path)) {
64535
64538
  const section = buildSection(content);
64536
64539
  ensureParent(path);
64537
- writeFileSync19(path, `${section}
64540
+ writeFileSync18(path, `${section}
64538
64541
  `, "utf-8");
64539
64542
  return "created";
64540
64543
  }
64541
- const existing = readFileSync28(path, "utf-8");
64544
+ const existing = readFileSync27(path, "utf-8");
64542
64545
  const lineEnding = lineEndingFor(existing);
64543
64546
  const section = buildSection(content, lineEnding);
64544
64547
  const location = findSection(existing);
@@ -64551,7 +64554,7 @@ function upsertSection(path, content) {
64551
64554
  }
64552
64555
  if (next === existing)
64553
64556
  return "unchanged";
64554
- writeFileSync19(path, next, "utf-8");
64557
+ writeFileSync18(path, next, "utf-8");
64555
64558
  return "updated";
64556
64559
  }
64557
64560
  function installRuleForAgent(agent, content) {
@@ -64568,13 +64571,13 @@ function removeRuleForAgent(agent) {
64568
64571
  return null;
64569
64572
  const target = RULE_TARGETS[agent];
64570
64573
  const path = target.path();
64571
- if (!existsSync29(path))
64574
+ if (!existsSync28(path))
64572
64575
  return { agent, path, action: "not_found" };
64573
64576
  if (target.kind === "file") {
64574
64577
  unlinkSync5(path);
64575
64578
  return { agent, path, action: "removed" };
64576
64579
  }
64577
- const existing = readFileSync28(path, "utf-8");
64580
+ const existing = readFileSync27(path, "utf-8");
64578
64581
  const lineEnding = lineEndingFor(existing);
64579
64582
  const location = findSection(existing);
64580
64583
  if (!location)
@@ -64583,7 +64586,7 @@ function removeRuleForAgent(agent) {
64583
64586
  if (!next) {
64584
64587
  unlinkSync5(path);
64585
64588
  } else {
64586
- writeFileSync19(path, `${next}${lineEnding}`, "utf-8");
64589
+ writeFileSync18(path, `${next}${lineEnding}`, "utf-8");
64587
64590
  }
64588
64591
  return { agent, path, action: "removed" };
64589
64592
  }
@@ -64611,36 +64614,36 @@ var init_installer = __esm(() => {
64611
64614
  RULE_TARGETS = {
64612
64615
  claude: {
64613
64616
  kind: "file",
64614
- path: () => join43(claudeConfigDir4(), "rules", "dosu.md")
64617
+ path: () => join42(claudeConfigDir4(), "rules", "dosu.md")
64615
64618
  },
64616
64619
  cursor: {
64617
64620
  kind: "file",
64618
- path: () => join43(homedir12(), ".cursor", "rules", "dosu.mdc"),
64621
+ path: () => join42(homedir12(), ".cursor", "rules", "dosu.mdc"),
64619
64622
  cursorFrontmatter: true
64620
64623
  },
64621
64624
  codex: {
64622
64625
  kind: "section",
64623
- path: () => join43(codexHome4(), "AGENTS.md")
64626
+ path: () => join42(codexHome4(), "AGENTS.md")
64624
64627
  },
64625
64628
  opencode: {
64626
64629
  kind: "section",
64627
- path: () => join43(homedir12(), ".config", "opencode", "AGENTS.md")
64630
+ path: () => join42(homedir12(), ".config", "opencode", "AGENTS.md")
64628
64631
  },
64629
64632
  gemini: {
64630
64633
  kind: "section",
64631
- path: () => join43(homedir12(), ".gemini", "GEMINI.md")
64634
+ path: () => join42(homedir12(), ".gemini", "GEMINI.md")
64632
64635
  },
64633
64636
  antigravity: {
64634
64637
  kind: "section",
64635
- path: () => join43(homedir12(), ".gemini", "GEMINI.md")
64638
+ path: () => join42(homedir12(), ".gemini", "GEMINI.md")
64636
64639
  }
64637
64640
  };
64638
64641
  });
64639
64642
 
64640
64643
  // src/setup/agents-md-step.ts
64641
64644
  import { execSync as execSync2 } from "node:child_process";
64642
- import { existsSync as existsSync30, readFileSync as readFileSync29, writeFileSync as writeFileSync20 } from "node:fs";
64643
- import { join as join44 } from "node:path";
64645
+ import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync19 } from "node:fs";
64646
+ import { join as join43 } from "node:path";
64644
64647
  function inGitWorkTree(cwd = process.cwd()) {
64645
64648
  try {
64646
64649
  const stdout = execSync2("git rev-parse --is-inside-work-tree", {
@@ -64654,10 +64657,10 @@ function inGitWorkTree(cwd = process.cwd()) {
64654
64657
  }
64655
64658
  function dosuAgentsSectionState(cwd = process.cwd()) {
64656
64659
  try {
64657
- const path = join44(cwd, "AGENTS.md");
64658
- if (!existsSync30(path))
64660
+ const path = join43(cwd, "AGENTS.md");
64661
+ if (!existsSync29(path))
64659
64662
  return "missing";
64660
- const content = readFileSync29(path, "utf-8");
64663
+ const content = readFileSync28(path, "utf-8");
64661
64664
  const startMatch = content.match(SECTION_START_RE);
64662
64665
  if (!startMatch || !content.includes(DOSU_SECTION_END))
64663
64666
  return "missing";
@@ -64692,14 +64695,14 @@ function findSection2(content) {
64692
64695
  return { start, end };
64693
64696
  }
64694
64697
  function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RULE) {
64695
- const path = join44(cwd, "AGENTS.md");
64696
- if (!existsSync30(path)) {
64698
+ const path = join43(cwd, "AGENTS.md");
64699
+ if (!existsSync29(path)) {
64697
64700
  const section = buildDosuAgentsSection(content);
64698
- writeFileSync20(path, `${section}
64701
+ writeFileSync19(path, `${section}
64699
64702
  `);
64700
64703
  return { path, action: "created" };
64701
64704
  }
64702
- const existing = readFileSync29(path, "utf-8");
64705
+ const existing = readFileSync28(path, "utf-8");
64703
64706
  const lineEnding = lineEndingFor2(existing);
64704
64707
  const section = renderDosuAgentsSection(content, lineEnding);
64705
64708
  const location = findSection2(existing);
@@ -64711,7 +64714,7 @@ function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RU
64711
64714
  }
64712
64715
  if (next === existing)
64713
64716
  return { path, action: "unchanged" };
64714
- writeFileSync20(path, next);
64717
+ writeFileSync19(path, next);
64715
64718
  return { path, action: "updated" };
64716
64719
  }
64717
64720
  async function stepUpdateAgentsMd(cwd = process.cwd(), content) {
@@ -66163,7 +66166,7 @@ async function stepSelectTools(detected) {
66163
66166
  initialValues: preselected,
66164
66167
  statusFor: (id, picked) => {
66165
66168
  if (configuredMap.get(id))
66166
- return picked ? dim("configured") : import_picocolors36.default.yellow("will remove");
66169
+ return picked ? dim("configured") : import_picocolors35.default.yellow("will remove");
66167
66170
  return picked ? brand("will configure") : undefined;
66168
66171
  },
66169
66172
  summary: (picked) => {
@@ -66338,7 +66341,7 @@ ${dim(wrapLog("Type it in a chat to keep that chat out of Dosu. To keep a whole
66338
66341
  log2.success("All agents already configured. No changes needed.");
66339
66342
  }
66340
66343
  }
66341
- var import_picocolors36, OAUTH_ANALYTICS_REASONS, SETUP_HANDSHAKE_TIMEOUT_MS = 1800000, LIBRARY_SELECT_CANCELLED;
66344
+ var import_picocolors35, OAUTH_ANALYTICS_REASONS, SETUP_HANDSHAKE_TIMEOUT_MS = 1800000, LIBRARY_SELECT_CANCELLED;
66342
66345
  var init_flow2 = __esm(() => {
66343
66346
  init_errors4();
66344
66347
  init_client();
@@ -66361,7 +66364,7 @@ var init_flow2 = __esm(() => {
66361
66364
  init_analytics2();
66362
66365
  init_rules_step();
66363
66366
  init_styles();
66364
- import_picocolors36 = __toESM(require_picocolors(), 1);
66367
+ import_picocolors35 = __toESM(require_picocolors(), 1);
66365
66368
  OAUTH_ANALYTICS_REASONS = new Set([
66366
66369
  "access_denied",
66367
66370
  "bad_oauth_state",
@@ -66525,7 +66528,7 @@ Could not update Dosu automatically.`);
66525
66528
  ${plan.manualCommand}`);
66526
66529
  return result.status && result.status > 0 ? result.status : 1;
66527
66530
  }
66528
- console.log(import_picocolors37.default.green(`
66531
+ console.log(import_picocolors36.default.green(`
66529
66532
  ✓ Update command completed.`));
66530
66533
  console.log('Run "dosu --version" to verify.');
66531
66534
  return 0;
@@ -66567,12 +66570,13 @@ async function completeUpgrade(channel = INSTALL_CHANNEL, options = {}) {
66567
66570
  finishWithNewBinary(channel, options);
66568
66571
  return 0;
66569
66572
  }
66570
- async function finishUpgrade(from, options = {}) {
66571
- const previous = SEMVER.test(from) ? from : null;
66572
- if (!needsMcpRefresh(previous, VERSION)) {
66573
+ async function finishUpgrade(_from, options = {}) {
66574
+ const cfg = loadConfigNonBlocking();
66575
+ const stale = staleProviders(cfg);
66576
+ if (stale.length === 0) {
66573
66577
  checkForSkillUpdates();
66574
66578
  writeMcpRefreshCache({ version: VERSION });
66575
- console.log(import_picocolors37.default.green(`✓ Dosu ${VERSION} is ready. Your AI agents need no changes.`));
66579
+ console.log(import_picocolors36.default.green(`✓ Dosu ${VERSION} is ready. Your AI agents need no changes.`));
66576
66580
  return 0;
66577
66581
  }
66578
66582
  if (options.interactive ?? isInteractiveTerminal()) {
@@ -66584,14 +66588,13 @@ This update changes how agents connect to Dosu. Running setup...
66584
66588
  return 0;
66585
66589
  }
66586
66590
  checkForSkillUpdates();
66587
- const cfg = loadConfigNonBlocking();
66588
66591
  if (!cfg || !canRefreshMcp(cfg)) {
66589
66592
  console.error(SETUP_FALLBACK);
66590
66593
  return 0;
66591
66594
  }
66592
66595
  console.log(`
66593
66596
  Refreshing agent MCP configs with the new version...`);
66594
- const result = refreshConfiguredProviders(cfg);
66597
+ const result = refreshProviders(cfg, stale);
66595
66598
  writeMcpRefreshCache({ version: VERSION });
66596
66599
  for (const provider of result.updated)
66597
66600
  console.log(` ✓ ${provider.name()}`);
@@ -66637,7 +66640,7 @@ function upgradeCommand() {
66637
66640
  process.exitCode = status;
66638
66641
  });
66639
66642
  }
66640
- var import_picocolors37, PACKAGE_NAME = "@dosu/cli", LATEST_PACKAGE, BREW_MANUAL_COMMAND = "brew upgrade dosu-ai/dosu/dosu", NPX_COMMAND = "npx -y @dosu/cli@latest", PROBE_TIMEOUT_MS = 5000, RELEASES_URL = "https://github.com/dosu-ai/dosu-cli/releases/latest", PACKAGE_MANAGERS, SETUP_FALLBACK = 'Run "dosu setup" to finish updating your AI agents.', SEMVER;
66643
+ var import_picocolors36, PACKAGE_NAME = "@dosu/cli", LATEST_PACKAGE, BREW_MANUAL_COMMAND = "brew upgrade dosu-ai/dosu/dosu", NPX_COMMAND = "npx -y @dosu/cli@latest", PROBE_TIMEOUT_MS = 5000, RELEASES_URL = "https://github.com/dosu-ai/dosu-cli/releases/latest", PACKAGE_MANAGERS, SETUP_FALLBACK = 'Run "dosu setup" to finish updating your AI agents.';
66641
66644
  var init_upgrade = __esm(() => {
66642
66645
  init_commander();
66643
66646
  init_config();
@@ -66645,7 +66648,7 @@ var init_upgrade = __esm(() => {
66645
66648
  init_auto_update();
66646
66649
  init_mcp_refresh_check();
66647
66650
  init_skill_update_check();
66648
- import_picocolors37 = __toESM(require_picocolors(), 1);
66651
+ import_picocolors36 = __toESM(require_picocolors(), 1);
66649
66652
  LATEST_PACKAGE = `${PACKAGE_NAME}@latest`;
66650
66653
  PACKAGE_MANAGERS = {
66651
66654
  npm: {
@@ -66664,31 +66667,30 @@ var init_upgrade = __esm(() => {
66664
66667
  installArgs: ["global", "add", LATEST_PACKAGE]
66665
66668
  }
66666
66669
  };
66667
- SEMVER = /^\d+\.\d+\.\d+/;
66668
66670
  });
66669
66671
 
66670
66672
  // src/version/incognito-backfill-check.ts
66671
- import { existsSync as existsSync31, mkdirSync as mkdirSync19, writeFileSync as writeFileSync21 } from "node:fs";
66672
- import { join as join45 } from "node:path";
66673
+ import { existsSync as existsSync30, mkdirSync as mkdirSync18, writeFileSync as writeFileSync20 } from "node:fs";
66674
+ import { join as join44 } from "node:path";
66673
66675
  function markerPath() {
66674
- return join45(getConfigDir(), MARKER_FILENAME);
66676
+ return join44(getConfigDir(), MARKER_FILENAME);
66675
66677
  }
66676
66678
  function writeMarker() {
66677
66679
  const dir = getConfigDir();
66678
- if (!existsSync31(dir))
66679
- mkdirSync19(dir, { recursive: true, mode: 448 });
66680
- writeFileSync21(markerPath(), JSON.stringify({ done: true }), { mode: 384 });
66680
+ if (!existsSync30(dir))
66681
+ mkdirSync18(dir, { recursive: true, mode: 448 });
66682
+ writeFileSync20(markerPath(), JSON.stringify({ done: true }), { mode: 384 });
66681
66683
  }
66682
66684
  function displayNotice3(names) {
66683
66685
  console.error(`
66684
- ${import_picocolors38.default.green(`✓ Dosu: added the /${INCOGNITO_COMMAND_NAME} command to ${names.join(", ")}`)}
66685
- ` + `${import_picocolors38.default.dim(` Type /${INCOGNITO_COMMAND_NAME} in a chat to keep that chat out of Dosu.`)}
66686
+ ${import_picocolors37.default.green(`✓ Dosu: added the /${INCOGNITO_COMMAND_NAME} command to ${names.join(", ")}`)}
66687
+ ` + `${import_picocolors37.default.dim(` Type /${INCOGNITO_COMMAND_NAME} in a chat to keep that chat out of Dosu.`)}
66686
66688
  `);
66687
66689
  }
66688
66690
  function checkForIncognitoBackfill(options = {}) {
66689
66691
  const notify = options.notify ?? true;
66690
66692
  try {
66691
- if (existsSync31(markerPath()))
66693
+ if (existsSync30(markerPath()))
66692
66694
  return;
66693
66695
  const added = [];
66694
66696
  let failed = false;
@@ -66715,14 +66717,187 @@ function checkForIncognitoBackfill(options = {}) {
66715
66717
  logger.error("incognito-backfill", `Incognito backfill failed: ${err}`);
66716
66718
  }
66717
66719
  }
66718
- var import_picocolors38, MARKER_FILENAME = "incognito-backfill.json";
66720
+ var import_picocolors37, MARKER_FILENAME = "incognito-backfill.json";
66719
66721
  var init_incognito_backfill_check = __esm(() => {
66720
66722
  init_config();
66721
66723
  init_logger();
66722
66724
  init_agents();
66723
66725
  init_agents3();
66724
66726
  init_incognito();
66727
+ import_picocolors37 = __toESM(require_picocolors(), 1);
66728
+ });
66729
+
66730
+ // src/version/update-check.ts
66731
+ import { existsSync as existsSync31, mkdirSync as mkdirSync19, readFileSync as readFileSync29, writeFileSync as writeFileSync21 } from "node:fs";
66732
+ import { join as join45 } from "node:path";
66733
+ function isValidVersion2(value) {
66734
+ return typeof value === "string" && SEMVER_PATTERN.test(value);
66735
+ }
66736
+ function stripPrerelease(version) {
66737
+ return version.replace(/[-+].*$/, "");
66738
+ }
66739
+ function isNewerVersion(latest, current) {
66740
+ const a = stripPrerelease(latest).split(".").map(Number);
66741
+ const b = stripPrerelease(current).split(".").map(Number);
66742
+ const len = Math.max(a.length, b.length);
66743
+ for (let i = 0;i < len; i++) {
66744
+ const av = a[i] ?? 0;
66745
+ const bv = b[i] ?? 0;
66746
+ if (av > bv)
66747
+ return true;
66748
+ if (av < bv)
66749
+ return false;
66750
+ }
66751
+ return false;
66752
+ }
66753
+ function getCachePath3() {
66754
+ return join45(getConfigDir(), CACHE_FILENAME5);
66755
+ }
66756
+ function readCache() {
66757
+ try {
66758
+ const path = getCachePath3();
66759
+ if (!existsSync31(path))
66760
+ return null;
66761
+ const data = JSON.parse(readFileSync29(path, "utf-8"));
66762
+ if (typeof data.lastCheck === "number" && isValidVersion2(data.latestVersion)) {
66763
+ return data;
66764
+ }
66765
+ return null;
66766
+ } catch {
66767
+ return null;
66768
+ }
66769
+ }
66770
+ function writeCache(cache) {
66771
+ try {
66772
+ const dir = getConfigDir();
66773
+ if (!existsSync31(dir)) {
66774
+ mkdirSync19(dir, { recursive: true, mode: 448 });
66775
+ }
66776
+ writeFileSync21(getCachePath3(), JSON.stringify(cache), { mode: 384 });
66777
+ } catch {}
66778
+ }
66779
+ async function fetchLatestVersion() {
66780
+ const controller = new AbortController;
66781
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
66782
+ try {
66783
+ const resp = await fetch(REGISTRY_URL, { signal: controller.signal });
66784
+ if (!resp.ok)
66785
+ return null;
66786
+ const data = await resp.json();
66787
+ const latest = data.latest;
66788
+ return isValidVersion2(latest) ? latest : null;
66789
+ } catch {
66790
+ return null;
66791
+ } finally {
66792
+ clearTimeout(timeout);
66793
+ }
66794
+ }
66795
+ function buildUpdateHint(channel, npx = false) {
66796
+ if (channel === "npm" && npx) {
66797
+ return 'Use "npx -y @dosu/cli@latest" for the next Dosu command';
66798
+ }
66799
+ return 'Run "dosu upgrade"';
66800
+ }
66801
+ function styleHint(hint) {
66802
+ const match = hint.match(/^(.*)("[^"]+")(.*)$/);
66803
+ if (!match)
66804
+ return import_picocolors38.default.dim(hint);
66805
+ const [, before, command, after] = match;
66806
+ return [before && import_picocolors38.default.dim(before), import_picocolors38.default.cyan(command), after && import_picocolors38.default.dim(after)].filter(Boolean).join("");
66807
+ }
66808
+ function framedNotice(content, width) {
66809
+ const innerWidth = Math.max(...content.map(visibleWidth2)) + BOX_PADDING * 2;
66810
+ const framed = (line) => {
66811
+ const pad = innerWidth - visibleWidth2(line);
66812
+ const left = Math.floor(pad / 2);
66813
+ return `${import_picocolors38.default.yellow("│")}${" ".repeat(left)}${line}${" ".repeat(pad - left)}${import_picocolors38.default.yellow("│")}`;
66814
+ };
66815
+ const box = [
66816
+ import_picocolors38.default.yellow(`╭${"─".repeat(innerWidth)}╮`),
66817
+ framed(""),
66818
+ ...content.map(framed),
66819
+ framed(""),
66820
+ import_picocolors38.default.yellow(`╰${"─".repeat(innerWidth)}╯`)
66821
+ ];
66822
+ return `
66823
+ ${centerBlock(box, width).join(`
66824
+ `)}
66825
+ `;
66826
+ }
66827
+ function versionArrow(current, latest) {
66828
+ return `${import_picocolors38.default.dim(current)} ${import_picocolors38.default.dim("→")} ${import_picocolors38.default.bold(brand(latest))}`;
66829
+ }
66830
+ function buildAutoUpdateNotice(current, latest, interactive, width = process.stderr.columns ?? 80) {
66831
+ if (interactive) {
66832
+ return framedNotice([
66833
+ `Updating Dosu: ${versionArrow(current, latest)}`,
66834
+ import_picocolors38.default.dim("Installing in the background; your next command uses it")
66835
+ ], width);
66836
+ }
66837
+ return `
66838
+ [dosu:update] Installing Dosu ${latest} in the background (running ${current}). ` + `The next Dosu command uses it; no action needed.
66839
+ `;
66840
+ }
66841
+ function buildUpdateNotice(current, latest, channel, interactive, npx = false, width = process.stderr.columns ?? 80) {
66842
+ const hint = buildUpdateHint(channel, npx);
66843
+ if (interactive) {
66844
+ return framedNotice([`Update available: ${versionArrow(current, latest)}`, styleHint(hint)], width);
66845
+ }
66846
+ const agentAction = hint[0].toLowerCase() + hint.slice(1);
66847
+ const verifyCommand = npx ? "npx -y @dosu/cli@latest --version" : "dosu --version";
66848
+ return `
66849
+ [dosu:update] Update available: ${current} → ${latest}
66850
+ ` + `Tell the user Dosu CLI is outdated. After they approve, ${agentAction}, ` + `then verify with "${verifyCommand}".
66851
+ `;
66852
+ }
66853
+ function getAvailableUpdate() {
66854
+ const cache = readCache();
66855
+ return cache && isNewerVersion(cache.latestVersion, VERSION) ? cache.latestVersion : null;
66856
+ }
66857
+ function handleNewerVersion(latest, notify) {
66858
+ const autoUpdate = startAutoUpdate(latest);
66859
+ if (!notify)
66860
+ return;
66861
+ const interactive = process.stderr.isTTY === true;
66862
+ console.error(autoUpdate === "unavailable" ? buildUpdateNotice(VERSION, latest, INSTALL_CHANNEL, interactive, isNpxInvocation()) : buildAutoUpdateNotice(VERSION, latest, interactive));
66863
+ }
66864
+ async function checkForUpdates(options = {}) {
66865
+ const notify = options.notify ?? true;
66866
+ try {
66867
+ const cache = readCache();
66868
+ const isStale = !cache || Date.now() - cache.lastCheck > CHECK_INTERVAL_MS2;
66869
+ if (!isStale) {
66870
+ if (isNewerVersion(cache.latestVersion, VERSION)) {
66871
+ handleNewerVersion(cache.latestVersion, notify);
66872
+ }
66873
+ return;
66874
+ }
66875
+ const latest = await fetchLatestVersion();
66876
+ const latestKnownVersion = latest ?? cache?.latestVersion ?? VERSION;
66877
+ writeCache({
66878
+ lastCheck: Date.now(),
66879
+ latestVersion: latestKnownVersion
66880
+ });
66881
+ if (latest) {
66882
+ logger.debug("update-check", `Cached latest version: ${latest}`);
66883
+ }
66884
+ if (isNewerVersion(latestKnownVersion, VERSION)) {
66885
+ handleNewerVersion(latestKnownVersion, notify);
66886
+ }
66887
+ } catch (err) {
66888
+ logger.error("update-check", `Update check failed: ${err}`);
66889
+ }
66890
+ }
66891
+ var import_picocolors38, CACHE_FILENAME5 = "update-check.json", CHECK_INTERVAL_MS2, FETCH_TIMEOUT_MS2 = 1000, REGISTRY_URL = "https://registry.npmjs.org/-/package/@dosu/cli/dist-tags", SEMVER_PATTERN, BOX_PADDING = 3;
66892
+ var init_update_check = __esm(() => {
66893
+ init_config();
66894
+ init_logger();
66895
+ init_styles();
66896
+ init_layout();
66897
+ init_auto_update();
66725
66898
  import_picocolors38 = __toESM(require_picocolors(), 1);
66899
+ CHECK_INTERVAL_MS2 = 6 * 60 * 60 * 1000;
66900
+ SEMVER_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
66726
66901
  });
66727
66902
 
66728
66903
  // src/cli/fatal-error.ts
@@ -69109,4 +69284,4 @@ main().catch((err) => {
69109
69284
  process.exit(1);
69110
69285
  });
69111
69286
 
69112
- //# debugId=152ccbaa-13cc-83fc-6475-6e2164756e21
69287
+ //# debugId=2a77eea3-ffbe-b0f0-6475-6e2164756e21