@dosu/cli 0.29.1 → 0.31.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 (2) hide show
  1. package/bin/dosu.js +415 -245
  2. package/package.json +1 -1
package/bin/dosu.js CHANGED
@@ -4577,7 +4577,7 @@ var init_dist4 = __esm(() => {
4577
4577
  function getVersionString() {
4578
4578
  return `v${VERSION}`;
4579
4579
  }
4580
- var VERSION = "0.29.1", INSTALL_CHANNEL = "npm";
4580
+ var VERSION = "0.31.0", INSTALL_CHANNEL = "npm";
4581
4581
 
4582
4582
  // src/debug/logger.ts
4583
4583
  import {
@@ -4711,7 +4711,7 @@ var init_logger = __esm(() => {
4711
4711
  });
4712
4712
 
4713
4713
  // src/client/contract.ts
4714
- var CLI_CONTRACT_HASH = "8d9ba113842a";
4714
+ var CLI_CONTRACT_HASH = "f5c0727a7884";
4715
4715
 
4716
4716
  // src/client/trpc.ts
4717
4717
  var exports_trpc = {};
@@ -6961,6 +6961,10 @@ var init_installation_server = __esm(() => {
6961
6961
  function dim(msg) {
6962
6962
  return import_picocolors7.default.dim(msg);
6963
6963
  }
6964
+ function browserFallbackHint(url) {
6965
+ return dim(`If your browser doesn't open automatically, visit:
6966
+ ${url}`);
6967
+ }
6964
6968
  function info(msg) {
6965
6969
  return import_picocolors7.default.cyan(msg);
6966
6970
  }
@@ -7722,6 +7726,7 @@ async function openGitHubInstallFlow(onInstalled, opts) {
7722
7726
  middleURL.searchParams.set("callback", callbackURL);
7723
7727
  log.info(`Opening your browser to GitHub.
7724
7728
  ` + "Add repositories or install the Dosu GitHub App, and we'll pick up automatically.");
7729
+ log.message(browserFallbackHint(middleURL.toString()));
7725
7730
  try {
7726
7731
  const open2 = await Promise.resolve().then(() => (init_open(), exports_open));
7727
7732
  await open2.default(middleURL.toString());
@@ -8328,7 +8333,7 @@ var require_cjs = __commonJS((exports) => {
8328
8333
  var require_lib = __commonJS((exports, module) => {
8329
8334
  var __filename = "/home/runner/work/dosu-cli/dosu-cli/node_modules/write-file-atomic/lib/index.js";
8330
8335
  module.exports = writeFile;
8331
- module.exports.sync = writeFileSync5;
8336
+ module.exports.sync = writeFileSync6;
8332
8337
  module.exports._getTmpname = getTmpname;
8333
8338
  module.exports._cleanupOnExit = cleanupOnExit;
8334
8339
  var fs7 = __require("fs");
@@ -8461,7 +8466,7 @@ var require_lib = __commonJS((exports, module) => {
8461
8466
  }
8462
8467
  return promise;
8463
8468
  }
8464
- function writeFileSync5(filename, data, options) {
8469
+ function writeFileSync6(filename, data, options) {
8465
8470
  if (typeof options === "string") {
8466
8471
  options = { encoding: options };
8467
8472
  } else if (!options) {
@@ -8538,7 +8543,7 @@ var require_lib = __commonJS((exports, module) => {
8538
8543
 
8539
8544
  // src/mcp/config-helpers.ts
8540
8545
  import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7 } from "node:fs";
8541
- import { dirname as dirname2 } from "node:path";
8546
+ import { dirname as dirname3 } from "node:path";
8542
8547
  function mcpURL(deploymentID) {
8543
8548
  return `${getBackendURL()}/v1/mcp/deployments/${deploymentID}`;
8544
8549
  }
@@ -8616,7 +8621,7 @@ function saveJSONConfig(path3, cfg) {
8616
8621
  writeSecureFile(path3, JSON.stringify(cfg, null, 2));
8617
8622
  }
8618
8623
  function writeSecureFile(path3, content) {
8619
- const dir = dirname2(path3);
8624
+ const dir = dirname3(path3);
8620
8625
  if (!existsSync6(dir)) {
8621
8626
  mkdirSync5(dir, { recursive: true, mode: 448 });
8622
8627
  }
@@ -8658,6 +8663,57 @@ var init_config_helpers = __esm(() => {
8658
8663
  writeFileAtomic = import_write_file_atomic.default;
8659
8664
  });
8660
8665
 
8666
+ // src/hooks/runtime.ts
8667
+ var exports_runtime = {};
8668
+ __export(exports_runtime, {
8669
+ resolveHookCommandPrefix: () => resolveHookCommandPrefix,
8670
+ materializedRuntimePath: () => materializedRuntimePath,
8671
+ dosuOnPath: () => dosuOnPath
8672
+ });
8673
+ import { spawnSync } from "node:child_process";
8674
+ import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "node:fs";
8675
+ import { dirname as dirname4, join as join6 } from "node:path";
8676
+ function dosuOnPath() {
8677
+ const cmd = process.platform === "win32" ? "where" : "which";
8678
+ try {
8679
+ return spawnSync(cmd, ["dosu"], { stdio: "ignore" }).status === 0;
8680
+ } catch {
8681
+ return false;
8682
+ }
8683
+ }
8684
+ function materializedRuntimePath() {
8685
+ return join6(getConfigDir(), "bin", "dosu.js");
8686
+ }
8687
+ function currentBundlePath() {
8688
+ const entry = process.argv[1];
8689
+ if (!entry || !/\.(c|m)?js$/.test(entry) || !existsSync7(entry))
8690
+ return null;
8691
+ return entry;
8692
+ }
8693
+ function resolveHookCommandPrefix() {
8694
+ if (dosuOnPath())
8695
+ return "dosu";
8696
+ const bundle = currentBundlePath();
8697
+ if (!bundle) {
8698
+ logger.warn("hooks", "dosu not on PATH and no bundle to materialize; using bare 'dosu'");
8699
+ return "dosu";
8700
+ }
8701
+ const target = materializedRuntimePath();
8702
+ try {
8703
+ mkdirSync6(dirname4(target), { recursive: true, mode: 448 });
8704
+ copyFileSync(bundle, target);
8705
+ } catch (err) {
8706
+ logger.warn("hooks", `failed to materialize runtime: ${err instanceof Error ? err.message : String(err)}`);
8707
+ return "dosu";
8708
+ }
8709
+ logger.info("hooks", `materialized hook runtime at ${target}`);
8710
+ return `node "${target}"`;
8711
+ }
8712
+ var init_runtime = __esm(() => {
8713
+ init_config();
8714
+ init_logger();
8715
+ });
8716
+
8661
8717
  // src/hooks/claude-code.ts
8662
8718
  var exports_claude_code = {};
8663
8719
  __export(exports_claude_code, {
@@ -8669,13 +8725,13 @@ __export(exports_claude_code, {
8669
8725
  claudeLocalSettingsPath: () => claudeLocalSettingsPath,
8670
8726
  DEFAULT_HOOK_EVENTS: () => DEFAULT_HOOK_EVENTS
8671
8727
  });
8672
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
8673
- import { join as join6 } from "node:path";
8728
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
8729
+ import { join as join7 } from "node:path";
8674
8730
  function claudeLocalSettingsPath(dir) {
8675
- return join6(dir, ".claude", "settings.local.json");
8731
+ return join7(dir, ".claude", "settings.local.json");
8676
8732
  }
8677
- function hookCommand(subcommand) {
8678
- return `dosu hooks ${subcommand}`;
8733
+ function hookCommand(subcommand, prefix = resolveHookCommandPrefix()) {
8734
+ return `${prefix} hooks ${subcommand}`;
8679
8735
  }
8680
8736
  function dosuGroup(event) {
8681
8737
  const entry = {
@@ -8690,6 +8746,9 @@ function isDosuHookCommand(command) {
8690
8746
  return false;
8691
8747
  if (/@dosu\/cli/.test(command))
8692
8748
  return true;
8749
+ if (/^([^\s]*[/\\])?node(\.exe)?\s+("[^"]*dosu\.(c|m)?js"|[^\s]*dosu\.(c|m)?js)\s/.test(command.trimStart())) {
8750
+ return true;
8751
+ }
8693
8752
  return /^([^\s]*\/)?dosu\s/.test(command.trimStart());
8694
8753
  }
8695
8754
  function isDosuGroup(group) {
@@ -8701,7 +8760,7 @@ function isDosuGroup(group) {
8701
8760
  return hooks.some((h2) => !!h2 && typeof h2 === "object" && (h2[MARKER] === true || typeof h2.command === "string" && isDosuHookCommand(h2.command)));
8702
8761
  }
8703
8762
  function readSettingsOrThrow(path3) {
8704
- if (!existsSync7(path3))
8763
+ if (!existsSync8(path3))
8705
8764
  return {};
8706
8765
  const raw = readFileSync8(path3, "utf-8").trim();
8707
8766
  if (!raw)
@@ -8763,7 +8822,7 @@ function removeClaudeHooks(configPath) {
8763
8822
  return { removed };
8764
8823
  }
8765
8824
  function inspectClaudeHooks(configPath) {
8766
- if (!existsSync7(configPath))
8825
+ if (!existsSync8(configPath))
8767
8826
  return { fileExists: false, parseError: false, events: [] };
8768
8827
  let cfg;
8769
8828
  try {
@@ -8778,6 +8837,7 @@ function inspectClaudeHooks(configPath) {
8778
8837
  var DEFAULT_HOOK_EVENTS, MARKER = "__dosu", EVENT_SUBCOMMAND;
8779
8838
  var init_claude_code = __esm(() => {
8780
8839
  init_config_helpers();
8840
+ init_runtime();
8781
8841
  DEFAULT_HOOK_EVENTS = ["UserPromptSubmit", "PostToolUse", "Stop"];
8782
8842
  EVENT_SUBCOMMAND = {
8783
8843
  UserPromptSubmit: "user-prompt-submit",
@@ -8795,13 +8855,13 @@ __export(exports_codex, {
8795
8855
  codexHooksPath: () => codexHooksPath,
8796
8856
  codexHookCommand: () => codexHookCommand
8797
8857
  });
8798
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
8799
- import { join as join7 } from "node:path";
8858
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
8859
+ import { join as join8 } from "node:path";
8800
8860
  function codexHooksPath(dir) {
8801
- return join7(dir, ".codex", "hooks.json");
8861
+ return join8(dir, ".codex", "hooks.json");
8802
8862
  }
8803
- function codexHookCommand(subcommand) {
8804
- return `dosu hooks ${subcommand} --agent codex`;
8863
+ function codexHookCommand(subcommand, prefix = resolveHookCommandPrefix()) {
8864
+ return `${prefix} hooks ${subcommand} --agent codex`;
8805
8865
  }
8806
8866
  function dosuGroup2(event) {
8807
8867
  return {
@@ -8816,7 +8876,7 @@ function dosuGroup2(event) {
8816
8876
  };
8817
8877
  }
8818
8878
  function readHooksFileOrThrow(path3) {
8819
- if (!existsSync8(path3))
8879
+ if (!existsSync9(path3))
8820
8880
  return {};
8821
8881
  const raw = readFileSync9(path3, "utf-8").trim();
8822
8882
  if (!raw)
@@ -8851,7 +8911,7 @@ function installCodexHooks(configPath, opts = {}) {
8851
8911
  return { events: [...events] };
8852
8912
  }
8853
8913
  function removeCodexHooks(configPath) {
8854
- if (!existsSync8(configPath))
8914
+ if (!existsSync9(configPath))
8855
8915
  return { removed: false };
8856
8916
  let config;
8857
8917
  try {
@@ -8880,7 +8940,7 @@ function removeCodexHooks(configPath) {
8880
8940
  return { removed };
8881
8941
  }
8882
8942
  function inspectCodexHooks(configPath) {
8883
- if (!existsSync8(configPath))
8943
+ if (!existsSync9(configPath))
8884
8944
  return { fileExists: false, events: [] };
8885
8945
  let config;
8886
8946
  try {
@@ -8898,6 +8958,7 @@ var EVENT_SUBCOMMAND2, EVENT_TIMEOUT_SEC, EVENT_STATUS_MESSAGE;
8898
8958
  var init_codex = __esm(() => {
8899
8959
  init_config_helpers();
8900
8960
  init_claude_code();
8961
+ init_runtime();
8901
8962
  EVENT_SUBCOMMAND2 = {
8902
8963
  UserPromptSubmit: "user-prompt-submit",
8903
8964
  PostToolUse: "post-tool-use",
@@ -8924,13 +8985,13 @@ __export(exports_factory, {
8924
8985
  factoryHooksPath: () => factoryHooksPath,
8925
8986
  factoryHookCommand: () => factoryHookCommand
8926
8987
  });
8927
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
8928
- import { join as join8 } from "node:path";
8988
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
8989
+ import { join as join9 } from "node:path";
8929
8990
  function factoryHooksPath(dir) {
8930
- return join8(dir, ".factory", "hooks.json");
8991
+ return join9(dir, ".factory", "hooks.json");
8931
8992
  }
8932
- function factoryHookCommand(subcommand) {
8933
- return `dosu hooks ${subcommand} --agent factory`;
8993
+ function factoryHookCommand(subcommand, prefix = resolveHookCommandPrefix()) {
8994
+ return `${prefix} hooks ${subcommand} --agent factory`;
8934
8995
  }
8935
8996
  function dosuGroup3(event) {
8936
8997
  const group = {
@@ -8941,7 +9002,7 @@ function dosuGroup3(event) {
8941
9002
  return group;
8942
9003
  }
8943
9004
  function readHooksFileOrThrow2(path3) {
8944
- if (!existsSync9(path3))
9005
+ if (!existsSync10(path3))
8945
9006
  return {};
8946
9007
  const raw = readFileSync10(path3, "utf-8").trim();
8947
9008
  if (!raw)
@@ -8976,7 +9037,7 @@ function installFactoryHooks(configPath, opts = {}) {
8976
9037
  return { events: [...events] };
8977
9038
  }
8978
9039
  function removeFactoryHooks(configPath) {
8979
- if (!existsSync9(configPath))
9040
+ if (!existsSync10(configPath))
8980
9041
  return { removed: false };
8981
9042
  let config;
8982
9043
  try {
@@ -9005,7 +9066,7 @@ function removeFactoryHooks(configPath) {
9005
9066
  return { removed };
9006
9067
  }
9007
9068
  function inspectFactoryHooks(configPath) {
9008
- if (!existsSync9(configPath))
9069
+ if (!existsSync10(configPath))
9009
9070
  return { fileExists: false, events: [] };
9010
9071
  let config;
9011
9072
  try {
@@ -9023,6 +9084,7 @@ var EVENT_SUBCOMMAND3;
9023
9084
  var init_factory = __esm(() => {
9024
9085
  init_config_helpers();
9025
9086
  init_claude_code();
9087
+ init_runtime();
9026
9088
  EVENT_SUBCOMMAND3 = {
9027
9089
  UserPromptSubmit: "user-prompt-submit",
9028
9090
  PostToolUse: "post-tool-use",
@@ -10118,15 +10180,15 @@ var init_insights2 = __esm(() => {
10118
10180
  });
10119
10181
 
10120
10182
  // src/version/skill-update-check.ts
10121
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "node:fs";
10122
- import { join as join9 } from "node:path";
10183
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
10184
+ import { join as join10 } from "node:path";
10123
10185
  function getCachePath2() {
10124
- return join9(getConfigDir(), CACHE_FILENAME2);
10186
+ return join10(getConfigDir(), CACHE_FILENAME2);
10125
10187
  }
10126
10188
  function readSkillCache() {
10127
10189
  try {
10128
10190
  const path3 = getCachePath2();
10129
- if (!existsSync10(path3))
10191
+ if (!existsSync12(path3))
10130
10192
  return null;
10131
10193
  const data = JSON.parse(readFileSync12(path3, "utf-8"));
10132
10194
  if (typeof data.lastCheck === "number" && typeof data.latestSha === "string" && typeof data.installedSha === "string") {
@@ -10140,10 +10202,10 @@ function readSkillCache() {
10140
10202
  function writeSkillCache(cache) {
10141
10203
  try {
10142
10204
  const dir = getConfigDir();
10143
- if (!existsSync10(dir)) {
10144
- mkdirSync6(dir, { recursive: true, mode: 448 });
10205
+ if (!existsSync12(dir)) {
10206
+ mkdirSync7(dir, { recursive: true, mode: 448 });
10145
10207
  }
10146
- writeFileSync5(getCachePath2(), JSON.stringify(cache), { mode: 384 });
10208
+ writeFileSync6(getCachePath2(), JSON.stringify(cache), { mode: 384 });
10147
10209
  } catch {}
10148
10210
  }
10149
10211
  async function fetchLatestSha() {
@@ -10168,19 +10230,6 @@ async function fetchLatestSha() {
10168
10230
  clearTimeout(timeout);
10169
10231
  }
10170
10232
  }
10171
- async function refreshInstalledSha() {
10172
- const sha = await fetchLatestSha();
10173
- if (!sha) {
10174
- logger.debug("skill-update-check", "Failed to fetch latest SHA during refresh");
10175
- return;
10176
- }
10177
- writeSkillCache({
10178
- lastCheck: Date.now(),
10179
- latestSha: sha,
10180
- installedSha: sha
10181
- });
10182
- logger.debug("skill-update-check", `Refreshed installedSha=${sha}`);
10183
- }
10184
10233
  function displayNotice() {
10185
10234
  const msg = `
10186
10235
  ${import_picocolors12.default.yellow(" Dosu skill update available")}
@@ -10278,16 +10327,12 @@ Failed to remove skill.`));
10278
10327
  });
10279
10328
  cmd.command("update").description("Update the Dosu skill to the latest version").action(async () => {
10280
10329
  console.log(`Updating ${SKILL_NAME} skill...`);
10281
- try {
10282
- execSync2(`npx skills update ${SKILL_NAME} -g`, {
10283
- stdio: "inherit"
10284
- });
10285
- } catch {
10330
+ const result = await installSkill();
10331
+ if (!result.success) {
10286
10332
  console.error(import_picocolors13.default.red(`
10287
10333
  Failed to update skill.`));
10288
10334
  process.exit(1);
10289
10335
  }
10290
- await refreshInstalledSha();
10291
10336
  console.log(import_picocolors13.default.green(`
10292
10337
  ✓ Skill "${SKILL_NAME}" updated.`));
10293
10338
  });
@@ -10317,21 +10362,21 @@ var init_skill = __esm(() => {
10317
10362
  var MCP_PROVIDER_SLUG = "dosu_mcp";
10318
10363
 
10319
10364
  // src/mcp/detect.ts
10320
- import { existsSync as existsSync11 } from "node:fs";
10365
+ import { existsSync as existsSync13 } from "node:fs";
10321
10366
  import { homedir, platform as platform2 } from "node:os";
10322
- import { join as join10 } from "node:path";
10367
+ import { join as join11 } from "node:path";
10323
10368
  function isInstalled(paths) {
10324
- return paths.some((p2) => existsSync11(expandHome(p2)));
10369
+ return paths.some((p2) => existsSync13(expandHome(p2)));
10325
10370
  }
10326
10371
  function expandHome(path3) {
10327
10372
  if (!path3.startsWith("~"))
10328
10373
  return path3;
10329
- return join10(homedir(), path3.slice(1));
10374
+ return join11(homedir(), path3.slice(1));
10330
10375
  }
10331
10376
  function appSupportDir() {
10332
10377
  switch (platform2()) {
10333
10378
  case "darwin": {
10334
- return join10(homedir(), "Library", "Application Support");
10379
+ return join11(homedir(), "Library", "Application Support");
10335
10380
  }
10336
10381
  case "win32": {
10337
10382
  return process.env.APPDATA ?? "";
@@ -10340,7 +10385,7 @@ function appSupportDir() {
10340
10385
  const xdg = process.env.XDG_CONFIG_HOME;
10341
10386
  if (xdg)
10342
10387
  return xdg;
10343
- return join10(homedir(), ".config");
10388
+ return join11(homedir(), ".config");
10344
10389
  }
10345
10390
  }
10346
10391
  }
@@ -10421,7 +10466,7 @@ var init_antigravity = __esm(() => {
10421
10466
  });
10422
10467
 
10423
10468
  // src/mcp/providers/claude.ts
10424
- import { join as join11 } from "node:path";
10469
+ import { join as join12 } from "node:path";
10425
10470
  var ClaudeProvider = () => createJSONProvider({
10426
10471
  providerName: "Claude Code",
10427
10472
  providerID: "claude",
@@ -10430,22 +10475,22 @@ var ClaudeProvider = () => createJSONProvider({
10430
10475
  paths: ["~/.claude"],
10431
10476
  globalPath: "~/.claude.json",
10432
10477
  topKey: "mcpServers",
10433
- localConfigPath: (cwd) => join11(cwd, ".mcp.json")
10478
+ localConfigPath: (cwd) => join12(cwd, ".mcp.json")
10434
10479
  });
10435
10480
  var init_claude = __esm(() => {
10436
10481
  init_base();
10437
10482
  });
10438
10483
 
10439
10484
  // src/mcp/providers/claude-desktop.ts
10440
- import { join as join12 } from "node:path";
10485
+ import { join as join13 } from "node:path";
10441
10486
  var ClaudeDesktopProvider = () => ({
10442
10487
  name: () => "Claude Desktop",
10443
10488
  id: () => "claude-desktop",
10444
10489
  supportsLocal: () => false,
10445
10490
  priority: () => 2,
10446
- detectPaths: () => [join12(appSupportDir(), "Claude")],
10447
- isInstalled: () => isInstalled([join12(appSupportDir(), "Claude")]),
10448
- globalConfigPath: () => join12(appSupportDir(), "Claude", "claude_desktop_config.json"),
10491
+ detectPaths: () => [join13(appSupportDir(), "Claude")],
10492
+ isInstalled: () => isInstalled([join13(appSupportDir(), "Claude")]),
10493
+ globalConfigPath: () => join13(appSupportDir(), "Claude", "claude_desktop_config.json"),
10449
10494
  isConfigured: () => false,
10450
10495
  install() {
10451
10496
  throw new Error("this tool only supports local (stdio) servers and cannot be configured for remote MCP");
@@ -10459,14 +10504,14 @@ var init_claude_desktop = __esm(() => {
10459
10504
  });
10460
10505
 
10461
10506
  // src/mcp/providers/cline.ts
10462
- import { join as join13 } from "node:path";
10463
- var extensionDir = () => join13(appSupportDir(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev"), ClineProvider = () => createJSONProvider({
10507
+ import { join as join14 } from "node:path";
10508
+ var extensionDir = () => join14(appSupportDir(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev"), ClineProvider = () => createJSONProvider({
10464
10509
  providerName: "Cline",
10465
10510
  providerID: "cline",
10466
10511
  local: false,
10467
10512
  priorityValue: 11,
10468
10513
  paths: [extensionDir()],
10469
- globalPath: join13(extensionDir(), "settings", "cline_mcp_settings.json"),
10514
+ globalPath: join14(extensionDir(), "settings", "cline_mcp_settings.json"),
10470
10515
  topKey: "mcpServers",
10471
10516
  buildServer: (cfg) => ({
10472
10517
  url: mcpURL(cfg.deployment_id),
@@ -10482,7 +10527,7 @@ var init_cline = __esm(() => {
10482
10527
  });
10483
10528
 
10484
10529
  // src/mcp/providers/cline-cli.ts
10485
- import { join as join14 } from "node:path";
10530
+ import { join as join15 } from "node:path";
10486
10531
  function clineDir() {
10487
10532
  return process.env.CLINE_DIR ?? expandHome("~/.cline");
10488
10533
  }
@@ -10492,7 +10537,7 @@ var ClineCliProvider = () => createJSONProvider({
10492
10537
  local: false,
10493
10538
  priorityValue: 12,
10494
10539
  paths: [clineDir()],
10495
- globalPath: join14(clineDir(), "data", "settings", "cline_mcp_settings.json"),
10540
+ globalPath: join15(clineDir(), "data", "settings", "cline_mcp_settings.json"),
10496
10541
  topKey: "mcpServers",
10497
10542
  buildServer: (cfg) => ({
10498
10543
  url: mcpURL(cfg.deployment_id),
@@ -10508,18 +10553,18 @@ var init_cline_cli = __esm(() => {
10508
10553
  });
10509
10554
 
10510
10555
  // src/mcp/providers/codex.ts
10511
- import { existsSync as existsSync12, readFileSync as readFileSync13 } from "node:fs";
10512
- import { join as join15 } from "node:path";
10556
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
10557
+ import { join as join16 } from "node:path";
10513
10558
  function codexHome() {
10514
10559
  return process.env.CODEX_HOME ?? expandHome("~/.codex");
10515
10560
  }
10516
10561
  function getConfigPath2(global) {
10517
10562
  if (global)
10518
- return join15(codexHome(), "config.toml");
10519
- return join15(process.cwd(), ".codex", "config.toml");
10563
+ return join16(codexHome(), "config.toml");
10564
+ return join16(process.cwd(), ".codex", "config.toml");
10520
10565
  }
10521
10566
  function readTOML(path3) {
10522
- if (!existsSync12(path3))
10567
+ if (!existsSync14(path3))
10523
10568
  return "";
10524
10569
  return readFileSync13(path3, "utf-8");
10525
10570
  }
@@ -10579,9 +10624,9 @@ var CodexProvider = () => ({
10579
10624
  priority: () => 8,
10580
10625
  detectPaths: () => ["~/.codex"],
10581
10626
  isInstalled: () => isInstalled(["~/.codex"]),
10582
- globalConfigPath: () => join15(codexHome(), "config.toml"),
10627
+ globalConfigPath: () => join16(codexHome(), "config.toml"),
10583
10628
  isConfigured: () => {
10584
- const content = readTOML(join15(codexHome(), "config.toml"));
10629
+ const content = readTOML(join16(codexHome(), "config.toml"));
10585
10630
  return content.includes("[mcp_servers.dosu]");
10586
10631
  },
10587
10632
  install(cfg, global) {
@@ -10603,10 +10648,10 @@ var init_codex2 = __esm(() => {
10603
10648
  });
10604
10649
 
10605
10650
  // src/mcp/providers/copilot.ts
10606
- import { join as join16 } from "node:path";
10651
+ import { join as join17 } from "node:path";
10607
10652
  function globalPath() {
10608
10653
  if (process.env.XDG_CONFIG_HOME) {
10609
- return join16(process.env.XDG_CONFIG_HOME, "mcp-config.json");
10654
+ return join17(process.env.XDG_CONFIG_HOME, "mcp-config.json");
10610
10655
  }
10611
10656
  return expandHome("~/.copilot/mcp-config.json");
10612
10657
  }
@@ -10637,7 +10682,7 @@ var CopilotProvider = () => ({
10637
10682
  };
10638
10683
  installJSONServer(globalPath(), "mcpServers", server);
10639
10684
  } else {
10640
- const configPath = join16(process.cwd(), ".vscode", "mcp.json");
10685
+ const configPath = join17(process.cwd(), ".vscode", "mcp.json");
10641
10686
  const server = {
10642
10687
  type: "http",
10643
10688
  url,
@@ -10650,7 +10695,7 @@ var CopilotProvider = () => ({
10650
10695
  if (global) {
10651
10696
  removeJSONServer(globalPath(), "mcpServers");
10652
10697
  } else {
10653
- const configPath = join16(process.cwd(), ".vscode", "mcp.json");
10698
+ const configPath = join17(process.cwd(), ".vscode", "mcp.json");
10654
10699
  removeJSONServer(configPath, "servers");
10655
10700
  }
10656
10701
  }
@@ -10662,7 +10707,7 @@ var init_copilot = __esm(() => {
10662
10707
  });
10663
10708
 
10664
10709
  // src/mcp/providers/cursor.ts
10665
- import { join as join17 } from "node:path";
10710
+ import { join as join18 } from "node:path";
10666
10711
  var CursorProvider = () => createJSONProvider({
10667
10712
  providerName: "Cursor",
10668
10713
  providerID: "cursor",
@@ -10675,7 +10720,7 @@ var CursorProvider = () => createJSONProvider({
10675
10720
  url: mcpURL(cfg.deployment_id),
10676
10721
  headers: mcpHeaders(cfg.api_key)
10677
10722
  }),
10678
- localConfigPath: (cwd) => join17(cwd, ".cursor", "mcp.json")
10723
+ localConfigPath: (cwd) => join18(cwd, ".cursor", "mcp.json")
10679
10724
  });
10680
10725
  var init_cursor = __esm(() => {
10681
10726
  init_config_helpers();
@@ -10683,7 +10728,7 @@ var init_cursor = __esm(() => {
10683
10728
  });
10684
10729
 
10685
10730
  // src/mcp/providers/factory.ts
10686
- import { join as join18 } from "node:path";
10731
+ import { join as join19 } from "node:path";
10687
10732
  var FactoryProvider = () => createJSONProvider({
10688
10733
  providerName: "Factory",
10689
10734
  providerID: "factory",
@@ -10692,14 +10737,14 @@ var FactoryProvider = () => createJSONProvider({
10692
10737
  paths: ["~/.factory"],
10693
10738
  globalPath: "~/.factory/mcp.json",
10694
10739
  topKey: "mcpServers",
10695
- localConfigPath: (cwd) => join18(cwd, ".factory", "mcp.json")
10740
+ localConfigPath: (cwd) => join19(cwd, ".factory", "mcp.json")
10696
10741
  });
10697
10742
  var init_factory2 = __esm(() => {
10698
10743
  init_base();
10699
10744
  });
10700
10745
 
10701
10746
  // src/mcp/providers/gemini.ts
10702
- import { join as join19 } from "node:path";
10747
+ import { join as join20 } from "node:path";
10703
10748
  var GeminiProvider = () => createJSONProvider({
10704
10749
  providerName: "Gemini CLI",
10705
10750
  providerID: "gemini",
@@ -10708,7 +10753,7 @@ var GeminiProvider = () => createJSONProvider({
10708
10753
  paths: ["~/.gemini"],
10709
10754
  globalPath: "~/.gemini/settings.json",
10710
10755
  topKey: "mcpServers",
10711
- localConfigPath: (cwd) => join19(cwd, ".gemini", "settings.json")
10756
+ localConfigPath: (cwd) => join20(cwd, ".gemini", "settings.json")
10712
10757
  });
10713
10758
  var init_gemini = __esm(() => {
10714
10759
  init_base();
@@ -10758,14 +10803,14 @@ var init_manual = __esm(() => {
10758
10803
  });
10759
10804
 
10760
10805
  // src/mcp/providers/mcporter.ts
10761
- import { existsSync as existsSync13 } from "node:fs";
10762
- import { join as join20 } from "node:path";
10806
+ import { existsSync as existsSync15 } from "node:fs";
10807
+ import { join as join21 } from "node:path";
10763
10808
  function resolveGlobalConfigPath() {
10764
10809
  const jsonPath = expandHome("~/.mcporter/mcporter.json");
10765
- if (existsSync13(jsonPath))
10810
+ if (existsSync15(jsonPath))
10766
10811
  return jsonPath;
10767
10812
  const jsoncPath = expandHome("~/.mcporter/mcporter.jsonc");
10768
- if (existsSync13(jsoncPath))
10813
+ if (existsSync15(jsoncPath))
10769
10814
  return jsoncPath;
10770
10815
  return jsonPath;
10771
10816
  }
@@ -10786,7 +10831,7 @@ var MCPorterProvider = () => ({
10786
10831
  globalConfigPath: () => resolveGlobalConfigPath(),
10787
10832
  isConfigured: () => isJSONKeyConfigured(resolveGlobalConfigPath(), "mcpServers"),
10788
10833
  install(cfg, global) {
10789
- const configPath = global ? resolveGlobalConfigPath() : join20(process.cwd(), "config", "mcporter.json");
10834
+ const configPath = global ? resolveGlobalConfigPath() : join21(process.cwd(), "config", "mcporter.json");
10790
10835
  const server = {
10791
10836
  type: "http",
10792
10837
  url: mcpEndpoint4(cfg),
@@ -10795,7 +10840,7 @@ var MCPorterProvider = () => ({
10795
10840
  installJSONServer(configPath, "mcpServers", server);
10796
10841
  },
10797
10842
  remove(global) {
10798
- const configPath = global ? resolveGlobalConfigPath() : join20(process.cwd(), "config", "mcporter.json");
10843
+ const configPath = global ? resolveGlobalConfigPath() : join21(process.cwd(), "config", "mcporter.json");
10799
10844
  removeJSONServer(configPath, "mcpServers");
10800
10845
  }
10801
10846
  });
@@ -10806,7 +10851,7 @@ var init_mcporter = __esm(() => {
10806
10851
  });
10807
10852
 
10808
10853
  // src/mcp/providers/opencode.ts
10809
- import { join as join21 } from "node:path";
10854
+ import { join as join22 } from "node:path";
10810
10855
  var OpenCodeProvider = () => createJSONProvider({
10811
10856
  providerName: "OpenCode",
10812
10857
  providerID: "opencode",
@@ -10821,7 +10866,7 @@ var OpenCodeProvider = () => createJSONProvider({
10821
10866
  enabled: true,
10822
10867
  headers: mcpHeaders(cfg.api_key)
10823
10868
  }),
10824
- localConfigPath: (cwd) => join21(cwd, "opencode.json")
10869
+ localConfigPath: (cwd) => join22(cwd, "opencode.json")
10825
10870
  });
10826
10871
  var init_opencode = __esm(() => {
10827
10872
  init_config_helpers();
@@ -10829,16 +10874,16 @@ var init_opencode = __esm(() => {
10829
10874
  });
10830
10875
 
10831
10876
  // src/mcp/providers/vscode.ts
10832
- import { join as join22 } from "node:path";
10877
+ import { join as join23 } from "node:path";
10833
10878
  var VSCodeProvider = () => createJSONProvider({
10834
10879
  providerName: "VS Code",
10835
10880
  providerID: "vscode",
10836
10881
  local: true,
10837
10882
  priorityValue: 6,
10838
- paths: [join22(appSupportDir(), "Code")],
10839
- globalPath: join22(appSupportDir(), "Code", "User", "mcp.json"),
10883
+ paths: [join23(appSupportDir(), "Code")],
10884
+ globalPath: join23(appSupportDir(), "Code", "User", "mcp.json"),
10840
10885
  topKey: "servers",
10841
- localConfigPath: (cwd) => join22(cwd, ".vscode", "mcp.json")
10886
+ localConfigPath: (cwd) => join23(cwd, ".vscode", "mcp.json")
10842
10887
  });
10843
10888
  var init_vscode = __esm(() => {
10844
10889
  init_detect();
@@ -10847,14 +10892,14 @@ var init_vscode = __esm(() => {
10847
10892
 
10848
10893
  // src/mcp/providers/windsurf.ts
10849
10894
  import { homedir as homedir2 } from "node:os";
10850
- import { join as join23 } from "node:path";
10895
+ import { join as join24 } from "node:path";
10851
10896
  var WindsurfProvider = () => createJSONProvider({
10852
10897
  providerName: "Windsurf",
10853
10898
  providerID: "windsurf",
10854
10899
  local: false,
10855
10900
  priorityValue: 9,
10856
- paths: [join23(homedir2(), ".codeium", "windsurf")],
10857
- globalPath: join23(homedir2(), ".codeium", "windsurf", "mcp_config.json"),
10901
+ paths: [join24(homedir2(), ".codeium", "windsurf")],
10902
+ globalPath: join24(homedir2(), ".codeium", "windsurf", "mcp_config.json"),
10858
10903
  topKey: "mcpServers"
10859
10904
  });
10860
10905
  var init_windsurf = __esm(() => {
@@ -10863,12 +10908,12 @@ var init_windsurf = __esm(() => {
10863
10908
 
10864
10909
  // src/mcp/providers/zed.ts
10865
10910
  import { platform as platform3 } from "node:os";
10866
- import { join as join24 } from "node:path";
10911
+ import { join as join25 } from "node:path";
10867
10912
  function zedConfigDir() {
10868
10913
  const os2 = platform3();
10869
10914
  if (os2 === "darwin" || os2 === "win32")
10870
- return join24(appSupportDir(), "Zed");
10871
- return join24(appSupportDir(), "zed");
10915
+ return join25(appSupportDir(), "Zed");
10916
+ return join25(appSupportDir(), "zed");
10872
10917
  }
10873
10918
  var ZedProvider = () => createJSONProvider({
10874
10919
  providerName: "Zed",
@@ -10876,7 +10921,7 @@ var ZedProvider = () => createJSONProvider({
10876
10921
  local: true,
10877
10922
  priorityValue: 10,
10878
10923
  paths: [zedConfigDir()],
10879
- globalPath: join24(zedConfigDir(), "settings.json"),
10924
+ globalPath: join25(zedConfigDir(), "settings.json"),
10880
10925
  topKey: "context_servers",
10881
10926
  buildServer: (cfg) => ({
10882
10927
  source: "custom",
@@ -10884,7 +10929,7 @@ var ZedProvider = () => createJSONProvider({
10884
10929
  url: mcpURL(cfg.deployment_id),
10885
10930
  headers: mcpHeaders(cfg.api_key)
10886
10931
  }),
10887
- localConfigPath: (cwd) => join24(cwd, ".zed", "settings.json")
10932
+ localConfigPath: (cwd) => join25(cwd, ".zed", "settings.json")
10888
10933
  });
10889
10934
  var init_zed = __esm(() => {
10890
10935
  init_config_helpers();
@@ -11027,6 +11072,74 @@ var init_analytics = __esm(() => {
11027
11072
  init_logger();
11028
11073
  });
11029
11074
 
11075
+ // src/setup/audit-handoff.ts
11076
+ import { spawnSync as spawnSync2 } from "node:child_process";
11077
+ function buildAuditHandoffPrompt(dosuCmd = dosuInvocation()) {
11078
+ return [
11079
+ "Audit this repository to see what docs Dosu can generate for it.",
11080
+ "1. Run the codebase audit from the Dosu skill. It writes findings to .dosu/audit.json.",
11081
+ "2. Show me the findings and which docs you recommend Dosu generate.",
11082
+ `3. Once I confirm, run \`${dosuCmd} audit --tasks <comma-separated task ids> --json\` to kick off generation. Dosu opens a PR and the CLI notifies me when it's ready.`
11083
+ ].join(`
11084
+ `);
11085
+ }
11086
+ function binOnPath(bin) {
11087
+ const cmd = process.platform === "win32" ? "where" : "which";
11088
+ try {
11089
+ return spawnSync2(cmd, [bin], { stdio: "ignore" }).status === 0;
11090
+ } catch {
11091
+ return false;
11092
+ }
11093
+ }
11094
+ function claudeCliAvailable() {
11095
+ return binOnPath("claude");
11096
+ }
11097
+ function dosuInvocation() {
11098
+ return binOnPath("dosu") ? "dosu" : "npx -y @dosu/cli";
11099
+ }
11100
+ function printManualAuditNudge() {
11101
+ log.message(`See what docs Dosu can generate for this repo:
11102
+
11103
+ ${info("Ask Dosu to audit this repo and show what docs it can generate.")}
11104
+
11105
+ Or run ${info(`${dosuInvocation()} audit`)} after your agent writes the audit.`);
11106
+ }
11107
+ async function offerAuditHandoff() {
11108
+ if (!detectGitRepo())
11109
+ return false;
11110
+ if (!claudeCliAvailable()) {
11111
+ printManualAuditNudge();
11112
+ return false;
11113
+ }
11114
+ const go = await confirm({
11115
+ message: "Kick off the codebase audit in Claude Code now?",
11116
+ initialValue: true
11117
+ });
11118
+ if (isCancel(go) || !go) {
11119
+ printManualAuditNudge();
11120
+ return false;
11121
+ }
11122
+ return true;
11123
+ }
11124
+ function launchAuditAgent() {
11125
+ logger.info("setup", "Handing off to Claude Code for the codebase audit");
11126
+ const result = spawnSync2("claude", ["--model", AUDIT_AGENT_MODEL, buildAuditHandoffPrompt()], {
11127
+ stdio: "inherit",
11128
+ shell: process.platform === "win32"
11129
+ });
11130
+ if (result.error) {
11131
+ logger.warn("setup", `Claude Code launch failed: ${result.error.message}`);
11132
+ printManualAuditNudge();
11133
+ }
11134
+ }
11135
+ var AUDIT_AGENT_MODEL = "haiku";
11136
+ var init_audit_handoff = __esm(() => {
11137
+ init_dist8();
11138
+ init_logger();
11139
+ init_github_step();
11140
+ init_styles();
11141
+ });
11142
+
11030
11143
  // src/setup/github-doc-import-prompt.ts
11031
11144
  async function promptGitHubDocsImport({
11032
11145
  repositories
@@ -12004,7 +12117,7 @@ var exports_flow = {};
12004
12117
  __export(exports_flow, {
12005
12118
  startOAuthFlow: () => startOAuthFlow
12006
12119
  });
12007
- async function startOAuthFlow(signal, path3 = "/cli/auth", params = {}) {
12120
+ async function startOAuthFlow(signal, path3 = "/cli/auth", params = {}, onAuthURL) {
12008
12121
  const { server, tokenPromise } = await startCallbackServer();
12009
12122
  let timeoutId;
12010
12123
  try {
@@ -12018,6 +12131,7 @@ async function startOAuthFlow(signal, path3 = "/cli/auth", params = {}) {
12018
12131
  await open2.default(authURL);
12019
12132
  browserOpened = true;
12020
12133
  logger.info("auth.flow", "Browser open command executed");
12134
+ onAuthURL?.(authURL);
12021
12135
  } catch (openErr) {
12022
12136
  logger.warn("auth.flow", `Could not open browser automatically: ${openErr instanceof Error ? openErr.message : String(openErr)}`);
12023
12137
  }
@@ -12068,8 +12182,8 @@ __export(exports_flow2, {
12068
12182
  isStdioOnly: () => isStdioOnly
12069
12183
  });
12070
12184
  import { randomUUID } from "node:crypto";
12071
- import { existsSync as existsSync14 } from "node:fs";
12072
- import { join as join25 } from "node:path";
12185
+ import { existsSync as existsSync16 } from "node:fs";
12186
+ import { join as join26 } from "node:path";
12073
12187
  async function runSetup(opts = {}) {
12074
12188
  const onboardingRunID = randomUUID();
12075
12189
  logger.info("setup", `Setup flow started${opts.deploymentID ? ` deployment=${opts.deploymentID}` : ""}${opts.mode ? ` mode=${opts.mode}` : ""}`);
@@ -12237,12 +12351,16 @@ async function runSetup(opts = {}) {
12237
12351
  log.warn("Could not mark onboarding complete on the server — you can retry later by running `dosu setup` again.");
12238
12352
  }
12239
12353
  }
12354
+ let handoffToAudit = false;
12240
12355
  if (mcpConfiguredThisRun) {
12241
12356
  showTryItOutPrompt({
12242
12357
  mode: cfg.mode,
12243
12358
  docsImported: choices.connectGitHub && githubOnboardingDone,
12244
- hasAgentsMd: existsSync14(join25(process.cwd(), "AGENTS.md"))
12359
+ hasAgentsMd: existsSync16(join26(process.cwd(), "AGENTS.md"))
12245
12360
  });
12361
+ if (cfg.mode !== MODE_OSS) {
12362
+ handoffToAudit = await offerAuditHandoff();
12363
+ }
12246
12364
  }
12247
12365
  if (mcpCompleted || skillCompleted || docsImported) {
12248
12366
  await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_completed", {
@@ -12256,6 +12374,9 @@ async function runSetup(opts = {}) {
12256
12374
  } else {
12257
12375
  outro("\uD83C\uDF89 Setup complete!");
12258
12376
  }
12377
+ if (handoffToAudit) {
12378
+ launchAuditAgent();
12379
+ }
12259
12380
  }
12260
12381
  function applyDeployment(cfg, d) {
12261
12382
  cfg.deployment_id = d.deployment_id;
@@ -12382,8 +12503,10 @@ async function openBrowserForSetup(cfg, onboardingRunID) {
12382
12503
  try {
12383
12504
  const { startOAuthFlow: startOAuthFlow2 } = await Promise.resolve().then(() => (init_flow(), exports_flow));
12384
12505
  const s = spinner();
12385
- s.start("Waiting for authentication...");
12386
- const result = await startOAuthFlow2(undefined, "/cli/auth", onboardingRunID ? { onboarding_run_id: onboardingRunID } : {});
12506
+ const result = await startOAuthFlow2(undefined, "/cli/auth", onboardingRunID ? { onboarding_run_id: onboardingRunID } : {}, (url) => {
12507
+ log.message(browserFallbackHint(url));
12508
+ s.start("Waiting for authentication...");
12509
+ });
12387
12510
  if (!result.browserOpened) {
12388
12511
  s.stop("Could not open a browser");
12389
12512
  log.error("Run 'dosu login --no-browser' from the terminal to authenticate over SSH.");
@@ -12728,13 +12851,6 @@ function showTryItOutPrompt(opts = {}) {
12728
12851
  log.message(`Try it out! Paste this into your agent:
12729
12852
 
12730
12853
  ${info(prompt)}`);
12731
- if (opts.mode !== MODE_OSS) {
12732
- log.message(`See what docs Dosu can generate for this repo:
12733
-
12734
- ${info("Ask Dosu to audit this repo and show what docs it can generate.")}
12735
-
12736
- Or run ${info("dosu audit")} after your agent writes the audit.`);
12737
- }
12738
12854
  }
12739
12855
  var init_flow2 = __esm(() => {
12740
12856
  init_dist8();
@@ -12744,12 +12860,13 @@ var init_flow2 = __esm(() => {
12744
12860
  init_logger();
12745
12861
  init_providers();
12746
12862
  init_analytics();
12863
+ init_audit_handoff();
12747
12864
  init_styles();
12748
12865
  });
12749
12866
 
12750
12867
  // src/commands/insights.ts
12751
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
12752
- import { dirname as dirname3, join as join26 } from "node:path";
12868
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync, unlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
12869
+ import { dirname as dirname5, join as join27 } from "node:path";
12753
12870
  function shuffled(arr) {
12754
12871
  const copy2 = [...arr];
12755
12872
  for (let i2 = copy2.length - 1;i2 > 0; i2--) {
@@ -12853,17 +12970,17 @@ function makeAskFn(cfg) {
12853
12970
  };
12854
12971
  }
12855
12972
  function insightsDir() {
12856
- return join26(getConfigDir(), "insights");
12973
+ return join27(getConfigDir(), "insights");
12857
12974
  }
12858
12975
  function reportPath(timestamp) {
12859
12976
  const dir = insightsDir();
12860
12977
  if (!timestamp)
12861
- return join26(dir, "latest.html");
12978
+ return join27(dir, "latest.html");
12862
12979
  const iso = timestamp.toISOString().slice(0, 19).replace(/:/g, "-");
12863
- return join26(dir, `report-${iso}Z.html`);
12980
+ return join27(dir, `report-${iso}Z.html`);
12864
12981
  }
12865
12982
  function pruneOldReports(dir, keepN) {
12866
- if (!existsSync15(dir))
12983
+ if (!existsSync17(dir))
12867
12984
  return;
12868
12985
  let entries;
12869
12986
  try {
@@ -12875,7 +12992,7 @@ function pruneOldReports(dir, keepN) {
12875
12992
  const reports = entries.filter((f2) => REPORT_FILE_PATTERN.test(f2)).sort().reverse();
12876
12993
  for (const f2 of reports.slice(keepN)) {
12877
12994
  try {
12878
- unlinkSync(join26(dir, f2));
12995
+ unlinkSync(join27(dir, f2));
12879
12996
  } catch (err) {
12880
12997
  logger.debug("insights", `failed to prune ${f2}: ${err instanceof Error ? err.message : err}`);
12881
12998
  }
@@ -12947,10 +13064,10 @@ function defaultRunner(cfg) {
12947
13064
  build: buildInsights,
12948
13065
  render: renderHTML,
12949
13066
  writeFile: (path3, content) => {
12950
- const dir = dirname3(path3);
12951
- if (!existsSync15(dir))
12952
- mkdirSync7(dir, { recursive: true, mode: 448 });
12953
- writeFileSync6(path3, content, { mode: 384 });
13067
+ const dir = dirname5(path3);
13068
+ if (!existsSync17(dir))
13069
+ mkdirSync8(dir, { recursive: true, mode: 448 });
13070
+ writeFileSync7(path3, content, { mode: 384 });
12954
13071
  },
12955
13072
  prune: pruneOldReports,
12956
13073
  openInBrowser: async (path3) => {
@@ -13094,8 +13211,10 @@ async function handleAuthenticate(cfg) {
13094
13211
  try {
13095
13212
  const { startOAuthFlow: startOAuthFlow2 } = await Promise.resolve().then(() => (init_flow(), exports_flow));
13096
13213
  const s = spinner();
13097
- s.start("Waiting for authentication...");
13098
- const result = await startOAuthFlow2(undefined, "/cli/auth");
13214
+ const result = await startOAuthFlow2(undefined, "/cli/auth", {}, (url) => {
13215
+ log.message(browserFallbackHint(url));
13216
+ s.start("Waiting for authentication...");
13217
+ });
13099
13218
  if (!result.browserOpened) {
13100
13219
  s.stop("Could not open a browser");
13101
13220
  log.error("Run 'dosu login --no-browser' from the terminal to authenticate over SSH.");
@@ -13147,6 +13266,7 @@ var init_tui = __esm(() => {
13147
13266
  init_insights3();
13148
13267
  init_config();
13149
13268
  init_flow2();
13269
+ init_styles();
13150
13270
  import_picocolors26 = __toESM(require_picocolors(), 1);
13151
13271
  });
13152
13272
 
@@ -13889,8 +14009,8 @@ init_trpc();
13889
14009
  init_logger();
13890
14010
  init_github_step();
13891
14011
  var import_picocolors9 = __toESM(require_picocolors(), 1);
13892
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
13893
- import { join as join4 } from "node:path";
14012
+ import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
14013
+ import { dirname, join as join4 } from "node:path";
13894
14014
 
13895
14015
  // src/version/pending-tasks-check.ts
13896
14016
  init_config();
@@ -14174,6 +14294,33 @@ function loadFindings(findingsPath) {
14174
14294
  }
14175
14295
  return parsed;
14176
14296
  }
14297
+ function cleanupFindings(findingsPath, cwd = process.cwd()) {
14298
+ const gitignorePath = join4(cwd, ".gitignore");
14299
+ try {
14300
+ const content = existsSync4(gitignorePath) ? readFileSync4(gitignorePath, "utf-8") : "";
14301
+ const hasEntry = content.split(/\r?\n/).some((line) => /^\.dosu\/?$/.test(line.trim()));
14302
+ if (!hasEntry) {
14303
+ const sep = content === "" || content.endsWith(`
14304
+ `) ? "" : `
14305
+ `;
14306
+ writeFileSync4(gitignorePath, `${content}${sep}.dosu/
14307
+ `);
14308
+ }
14309
+ } catch (err) {
14310
+ logger.warn("audit", `could not update .gitignore: ${errText(err)}`);
14311
+ }
14312
+ const defaultDir = join4(cwd, ".dosu");
14313
+ if (dirname(findingsPath) !== defaultDir)
14314
+ return;
14315
+ try {
14316
+ rmSync(defaultDir, { recursive: true, force: true });
14317
+ } catch (err) {
14318
+ logger.warn("audit", `could not remove ${defaultDir}: ${errText(err)}`);
14319
+ }
14320
+ }
14321
+ function errText(err) {
14322
+ return err instanceof Error ? err.message : String(err);
14323
+ }
14177
14324
  function intersectActionable(items, capabilities) {
14178
14325
  const known = new Set(capabilities.map((c3) => c3.id));
14179
14326
  return items.filter((item) => known.has(item.task));
@@ -14182,9 +14329,21 @@ function isPreselected(item) {
14182
14329
  return item.can_help && item.action !== "skip";
14183
14330
  }
14184
14331
  function auditCommand() {
14185
- const cmd = new Command("audit").description("Generate docs from a coding-agent audit (.dosu/audit.json)").option("--data-source-id <id>", "Skip auto-match and use this data source ID").option("--findings <path>", "Path to the audit findings JSON").option("--tasks <ids>", "Comma-separated task ids to generate (non-interactive; for agent-driven use)").option("--yes", "Skip the prompt and select all suggested items").option("--json", "Output as JSON").action(async (opts) => {
14332
+ const cmd = new Command("audit").description("Generate docs from a coding-agent audit (.dosu/audit.json)").option("--data-source-id <id>", "Skip auto-match and use this data source ID").option("--findings <path>", "Path to the audit findings JSON").option("--tasks <ids>", "Comma-separated task ids to generate (non-interactive; for agent-driven use)").option("--list-tasks", "List the doc-generation capabilities (valid task ids) and exit").option("--yes", "Skip the prompt and select all suggested items").option("--json", "Output as JSON").action(async (opts) => {
14186
14333
  const cfg = requireConfig3();
14187
14334
  const apiKey = requireAPIKey(cfg);
14335
+ if (opts.listTasks) {
14336
+ const resp = await backendGet("/v1/cli/tasks", apiKey);
14337
+ const tasks = resp.tasks ?? [];
14338
+ if (opts.json) {
14339
+ printResult({ tasks }, opts);
14340
+ return;
14341
+ }
14342
+ for (const t2 of tasks) {
14343
+ console.log(`${t2.id} ${import_picocolors9.default.dim(`(${t2.doc_type})`)} ${t2.label} — ${t2.description}`);
14344
+ }
14345
+ return;
14346
+ }
14188
14347
  const detected = detectGitRepo();
14189
14348
  if (!detected) {
14190
14349
  console.error(import_picocolors9.default.red("Not a GitHub repo. Run 'dosu audit' from a GitHub repository."));
@@ -14265,6 +14424,9 @@ function auditCommand() {
14265
14424
  repo: detected.slug
14266
14425
  });
14267
14426
  }
14427
+ if (taskIds.length > 0) {
14428
+ cleanupFindings(findingsPath);
14429
+ }
14268
14430
  if (opts.json) {
14269
14431
  printResult({ task_ids: taskIds }, opts);
14270
14432
  return;
@@ -14761,13 +14923,13 @@ init_commander();
14761
14923
  init_config();
14762
14924
  init_logger();
14763
14925
  init_prompts();
14764
- import { readFileSync as readFileSync11 } from "node:fs";
14926
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
14765
14927
  import { basename } from "node:path";
14766
14928
 
14767
14929
  // src/hooks/state.ts
14768
14930
  init_config();
14769
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
14770
- import { dirname, join as join5 } from "node:path";
14931
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
14932
+ import { dirname as dirname2, join as join5 } from "node:path";
14771
14933
  function getStateDir() {
14772
14934
  return process.env.DOSU_HOOK_STATE_DIR ?? join5(getConfigDir(), "hooks");
14773
14935
  }
@@ -14787,11 +14949,11 @@ function loadState(sessionId) {
14787
14949
  function saveState(state) {
14788
14950
  try {
14789
14951
  const file = stateFile(state.sessionId);
14790
- const dir = dirname(file);
14952
+ const dir = dirname2(file);
14791
14953
  if (!existsSync5(dir)) {
14792
14954
  mkdirSync4(dir, { recursive: true, mode: 448 });
14793
14955
  }
14794
- writeFileSync4(file, JSON.stringify(state, null, 2), { mode: 384 });
14956
+ writeFileSync5(file, JSON.stringify(state, null, 2), { mode: 384 });
14795
14957
  } catch {}
14796
14958
  }
14797
14959
 
@@ -15230,6 +15392,21 @@ async function collectDoctorChecks(opts) {
15230
15392
  });
15231
15393
  }
15232
15394
  }
15395
+ if (hasSubmit && hasPostTool || alternativeInstalled) {
15396
+ const { dosuOnPath: dosuOnPath2, materializedRuntimePath: materializedRuntimePath2 } = await Promise.resolve().then(() => (init_runtime(), exports_runtime));
15397
+ const materialized = materializedRuntimePath2();
15398
+ if (dosuOnPath2()) {
15399
+ checks.push({ name: "runtime", status: "ok", detail: "dosu on PATH" });
15400
+ } else if (existsSync11(materialized)) {
15401
+ checks.push({ name: "runtime", status: "ok", detail: `materialized: ${materialized}` });
15402
+ } else {
15403
+ checks.push({
15404
+ name: "runtime",
15405
+ status: "fail",
15406
+ detail: "dosu is not on PATH and no materialized runtime exists — re-run 'npx -y @dosu/cli hooks install <agent>'"
15407
+ });
15408
+ }
15409
+ }
15233
15410
  const cfg = loadConfig();
15234
15411
  if (isAuthenticated(cfg)) {
15235
15412
  checks.push({ name: "auth", status: "ok", detail: "logged in" });
@@ -16173,7 +16350,7 @@ function suggestCommand() {
16173
16350
  return cmd;
16174
16351
  }
16175
16352
 
16176
- // src/commands/tags.ts
16353
+ // src/commands/threads.ts
16177
16354
  init_commander();
16178
16355
  init_trpc();
16179
16356
  var import_picocolors23 = __toESM(require_picocolors(), 1);
@@ -16185,92 +16362,10 @@ function requireConfig13() {
16185
16362
  }
16186
16363
  return cfg;
16187
16364
  }
16188
- async function getKnowledgeStoreId4(client, spaceId) {
16189
- const store = await client.knowledgeStore.getBySpaceId.query({
16190
- space_id: spaceId
16191
- });
16192
- if (!store) {
16193
- console.error(import_picocolors23.default.red("No knowledge store found for this deployment."));
16194
- process.exit(1);
16195
- }
16196
- return store.id;
16197
- }
16198
- function tagsCommand() {
16199
- const cmd = new Command("tags").description("Manage knowledge base tags");
16200
- cmd.command("list").description("List all tags").option("--json", "Output as JSON").action(async (opts) => {
16201
- const cfg = requireConfig13();
16202
- const client = createTypedClient(cfg);
16203
- const ksId = await getKnowledgeStoreId4(client, cfg.space_id);
16204
- const tags = await client.topic.listTopicsByKnowledgeStore.query({
16205
- knowledge_store_id: ksId
16206
- });
16207
- if (opts.json) {
16208
- printResult(tags, opts);
16209
- return;
16210
- }
16211
- if (!tags || tags.length === 0) {
16212
- console.log(import_picocolors23.default.dim("No tags found."));
16213
- return;
16214
- }
16215
- printTable(["ID", "Name", "Description"], tags.map((t2) => [
16216
- t2.topic_id.slice(0, 8),
16217
- t2.name,
16218
- t2.description ?? "—"
16219
- ]), { rawData: tags });
16220
- });
16221
- cmd.command("remove").description("Remove a tag from a page").argument("<tag-id>", "Tag ID").argument("<page-id>", "Page ID").option("--json", "Output as JSON").action(async (tagId, pageId, opts) => {
16222
- const cfg = requireConfig13();
16223
- const client = createTypedClient(cfg);
16224
- await client.topic.removeFromPage.mutate({ topic_id: tagId, page_id: pageId });
16225
- if (opts.json) {
16226
- printResult({ success: true, tag_id: tagId, page_id: pageId }, opts);
16227
- return;
16228
- }
16229
- console.log(import_picocolors23.default.green("Tag removed from page."));
16230
- });
16231
- cmd.command("pages").description("List pages with a specific tag").argument("<tag-id>", "Tag ID").option("--search <query>", "Search within tagged pages").option("--limit <n>", "Maximum results", "10").option("--json", "Output as JSON").action(async (tagId, opts) => {
16232
- const cfg = requireConfig13();
16233
- const client = createTypedClient(cfg);
16234
- const ksId = await getKnowledgeStoreId4(client, cfg.space_id);
16235
- const result = await client.topic.getPagesByTopicId.query({
16236
- knowledge_store_id: ksId,
16237
- topic_id: tagId,
16238
- searchTerm: opts.search,
16239
- limit: Number.parseInt(opts.limit ?? "10", 10)
16240
- });
16241
- const pages = result.data;
16242
- if (opts.json) {
16243
- printResult(pages, opts);
16244
- return;
16245
- }
16246
- if (!pages || pages.length === 0) {
16247
- console.log(import_picocolors23.default.dim("No pages found with this tag."));
16248
- return;
16249
- }
16250
- printTable(["ID", "Title"], pages.map((p2) => [
16251
- p2.id.slice(0, 8),
16252
- p2.title ?? "(untitled)"
16253
- ]), { rawData: pages });
16254
- });
16255
- return cmd;
16256
- }
16257
-
16258
- // src/commands/threads.ts
16259
- init_commander();
16260
- init_trpc();
16261
- var import_picocolors24 = __toESM(require_picocolors(), 1);
16262
- function requireConfig14() {
16263
- const cfg = requireLoginConfig();
16264
- if (!cfg.space_id) {
16265
- console.error(import_picocolors24.default.red("Missing space config. Run 'dosu setup' to reconfigure."));
16266
- process.exit(1);
16267
- }
16268
- return cfg;
16269
- }
16270
16365
  function threadsCommand() {
16271
16366
  const cmd = new Command("threads").description("List and manage conversation threads");
16272
16367
  cmd.command("list").description("List threads").option("--status <status>", "Filter by status: pending, resolved, archived").option("--search <query>", "Search threads").option("--limit <n>", "Maximum results (default: 20)", "20").option("--json", "Output as JSON").action(async (opts) => {
16273
- const cfg = requireConfig14();
16368
+ const cfg = requireConfig13();
16274
16369
  const client = createTypedClient(cfg);
16275
16370
  const input = {
16276
16371
  __testing_scope__: undefined,
@@ -16309,7 +16404,7 @@ function threadsCommand() {
16309
16404
  return;
16310
16405
  }
16311
16406
  if (!threads || threads.length === 0) {
16312
- console.log(import_picocolors24.default.dim("No threads found."));
16407
+ console.log(import_picocolors23.default.dim("No threads found."));
16313
16408
  return;
16314
16409
  }
16315
16410
  printTable(["ID", "Title", "Status", "Created"], threads.map((t2) => [
@@ -16320,7 +16415,7 @@ function threadsCommand() {
16320
16415
  ]), { rawData: threads });
16321
16416
  });
16322
16417
  cmd.command("get").description("View a thread and its messages").argument("<id>", "Thread ID").option("--limit <n>", "Number of messages to show (default: 20)", "20").option("--json", "Output as JSON").action(async (id, opts) => {
16323
- const cfg = requireConfig14();
16418
+ const cfg = requireConfig13();
16324
16419
  const client = createTypedClient(cfg);
16325
16420
  const [thread, messagesData] = await Promise.all([
16326
16421
  client.thread.get.query(id),
@@ -16334,10 +16429,10 @@ function threadsCommand() {
16334
16429
  return;
16335
16430
  }
16336
16431
  if (!thread) {
16337
- console.log(import_picocolors24.default.dim("Thread not found."));
16432
+ console.log(import_picocolors23.default.dim("Thread not found."));
16338
16433
  return;
16339
16434
  }
16340
- console.log(import_picocolors24.default.bold(thread.generated_title ?? "(untitled thread)"));
16435
+ console.log(import_picocolors23.default.bold(thread.generated_title ?? "(untitled thread)"));
16341
16436
  printInfo([
16342
16437
  ["ID", thread.id],
16343
16438
  ["Created", formatDate(thread.created_at)],
@@ -16348,13 +16443,13 @@ function threadsCommand() {
16348
16443
  if (messages && messages.length > 0) {
16349
16444
  const allMessages = messages.flatMap((group) => group.messages);
16350
16445
  console.log(`
16351
- ${import_picocolors24.default.bold("Messages")} (${allMessages.length})`);
16352
- console.log(import_picocolors24.default.dim("─".repeat(60)));
16446
+ ${import_picocolors23.default.bold("Messages")} (${allMessages.length})`);
16447
+ console.log(import_picocolors23.default.dim("─".repeat(60)));
16353
16448
  for (const msg of allMessages) {
16354
16449
  const role = msg.author_role ?? "unknown";
16355
16450
  const date = formatDate(msg.created_at);
16356
16451
  console.log(`
16357
- ${import_picocolors24.default.bold(role)} ${import_picocolors24.default.dim(date)}`);
16452
+ ${import_picocolors23.default.bold(role)} ${import_picocolors23.default.dim(date)}`);
16358
16453
  if (msg.body) {
16359
16454
  console.log(truncate(msg.body, 500));
16360
16455
  }
@@ -16362,14 +16457,86 @@ ${import_picocolors24.default.bold(role)} ${import_picocolors24.default.dim(date
16362
16457
  }
16363
16458
  });
16364
16459
  cmd.command("archive").description("Archive a thread").argument("<id>", "Thread ID").option("--json", "Output as JSON").action(async (id, opts) => {
16365
- const cfg = requireConfig14();
16460
+ const cfg = requireConfig13();
16366
16461
  const client = createTypedClient(cfg);
16367
16462
  await client.thread.archive.mutate({ threadId: id, archived: true });
16368
16463
  if (opts.json) {
16369
16464
  printResult({ success: true, id }, opts);
16370
16465
  return;
16371
16466
  }
16372
- console.log(import_picocolors24.default.green(`Thread ${id.slice(0, 8)} archived.`));
16467
+ console.log(import_picocolors23.default.green(`Thread ${id.slice(0, 8)} archived.`));
16468
+ });
16469
+ return cmd;
16470
+ }
16471
+
16472
+ // src/commands/topics.ts
16473
+ init_commander();
16474
+ init_trpc();
16475
+ var import_picocolors24 = __toESM(require_picocolors(), 1);
16476
+ function requireConfig14() {
16477
+ const cfg = requireLoginConfig();
16478
+ if (!cfg.space_id) {
16479
+ console.error(import_picocolors24.default.red("Missing space config. Run 'dosu setup' to reconfigure."));
16480
+ process.exit(1);
16481
+ }
16482
+ return cfg;
16483
+ }
16484
+ async function getKnowledgeStoreId4(client, spaceId) {
16485
+ const store = await client.knowledgeStore.getBySpaceId.query({
16486
+ space_id: spaceId
16487
+ });
16488
+ if (!store) {
16489
+ console.error(import_picocolors24.default.red("No knowledge store found for this deployment."));
16490
+ process.exit(1);
16491
+ }
16492
+ return store.id;
16493
+ }
16494
+ function topicsCommand() {
16495
+ const cmd = new Command("topics").alias("tags").description("Browse Topics in your knowledge base");
16496
+ cmd.command("list").description("List all topics").option("--json", "Output as JSON").action(async (opts) => {
16497
+ const cfg = requireConfig14();
16498
+ const client = createTypedClient(cfg);
16499
+ const ksId = await getKnowledgeStoreId4(client, cfg.space_id);
16500
+ const topics = await client.topic.listTopicsByKnowledgeStore.query({
16501
+ knowledge_store_id: ksId
16502
+ });
16503
+ if (opts.json) {
16504
+ printResult(topics, opts);
16505
+ return;
16506
+ }
16507
+ if (!topics || topics.length === 0) {
16508
+ console.log(import_picocolors24.default.dim("No topics found."));
16509
+ return;
16510
+ }
16511
+ printTable(["ID", "Name", "Description"], topics.map((t2) => [
16512
+ t2.topic_id.slice(0, 8),
16513
+ t2.name,
16514
+ t2.description ?? "—"
16515
+ ]), { rawData: topics });
16516
+ });
16517
+ cmd.command("pages").description("List pages with a specific topic").argument("<topic-id>", "Topic ID").option("--search <query>", "Search within the topic's pages").option("--limit <n>", "Maximum results", "10").option("--json", "Output as JSON").action(async (topicId, opts) => {
16518
+ const cfg = requireConfig14();
16519
+ const client = createTypedClient(cfg);
16520
+ const ksId = await getKnowledgeStoreId4(client, cfg.space_id);
16521
+ const result = await client.topic.getPagesByTopicId.query({
16522
+ knowledge_store_id: ksId,
16523
+ topic_id: topicId,
16524
+ searchTerm: opts.search,
16525
+ limit: Number.parseInt(opts.limit ?? "10", 10)
16526
+ });
16527
+ const pages = result.data;
16528
+ if (opts.json) {
16529
+ printResult(pages, opts);
16530
+ return;
16531
+ }
16532
+ if (!pages || pages.length === 0) {
16533
+ console.log(import_picocolors24.default.dim("No pages found with this topic."));
16534
+ return;
16535
+ }
16536
+ printTable(["ID", "Title"], pages.map((p2) => [
16537
+ p2.id.slice(0, 8),
16538
+ p2.title ?? "(untitled)"
16539
+ ]), { rawData: pages });
16373
16540
  });
16374
16541
  return cmd;
16375
16542
  }
@@ -16378,14 +16545,15 @@ ${import_picocolors24.default.bold(role)} ${import_picocolors24.default.dim(date
16378
16545
  init_config();
16379
16546
  init_logger();
16380
16547
  init_providers();
16548
+ init_styles();
16381
16549
  init_skill_update_check();
16382
16550
 
16383
16551
  // src/version/update-check.ts
16384
16552
  init_config();
16385
16553
  init_logger();
16386
16554
  var import_picocolors25 = __toESM(require_picocolors(), 1);
16387
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "node:fs";
16388
- import { join as join27 } from "node:path";
16555
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
16556
+ import { join as join28 } from "node:path";
16389
16557
  var CACHE_FILENAME3 = "update-check.json";
16390
16558
  var CHECK_INTERVAL_MS3 = 24 * 60 * 60 * 1000;
16391
16559
  var FETCH_TIMEOUT_MS3 = 5000;
@@ -16408,12 +16576,12 @@ function isNewerVersion(latest, current) {
16408
16576
  return false;
16409
16577
  }
16410
16578
  function getCachePath3() {
16411
- return join27(getConfigDir(), CACHE_FILENAME3);
16579
+ return join28(getConfigDir(), CACHE_FILENAME3);
16412
16580
  }
16413
16581
  function readCache() {
16414
16582
  try {
16415
16583
  const path3 = getCachePath3();
16416
- if (!existsSync16(path3))
16584
+ if (!existsSync18(path3))
16417
16585
  return null;
16418
16586
  const data = JSON.parse(readFileSync15(path3, "utf-8"));
16419
16587
  if (typeof data.lastCheck === "number" && typeof data.latestVersion === "string") {
@@ -16427,10 +16595,10 @@ function readCache() {
16427
16595
  function writeCache(cache) {
16428
16596
  try {
16429
16597
  const dir = getConfigDir();
16430
- if (!existsSync16(dir)) {
16431
- mkdirSync8(dir, { recursive: true, mode: 448 });
16598
+ if (!existsSync18(dir)) {
16599
+ mkdirSync9(dir, { recursive: true, mode: 448 });
16432
16600
  }
16433
- writeFileSync7(getCachePath3(), JSON.stringify(cache), { mode: 384 });
16601
+ writeFileSync8(getCachePath3(), JSON.stringify(cache), { mode: 384 });
16434
16602
  } catch {}
16435
16603
  }
16436
16604
  async function fetchLatestVersion() {
@@ -16558,7 +16726,9 @@ function createProgram() {
16558
16726
  const { startOAuthFlow: startOAuthFlow2 } = await Promise.resolve().then(() => (init_flow(), exports_flow));
16559
16727
  let result;
16560
16728
  try {
16561
- result = await startOAuthFlow2();
16729
+ result = await startOAuthFlow2(undefined, undefined, undefined, (url) => {
16730
+ console.log(browserFallbackHint(url));
16731
+ });
16562
16732
  } catch (err) {
16563
16733
  if (err instanceof OAuthCallbackError2) {
16564
16734
  console.error(err.userMessage);
@@ -16701,7 +16871,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
16701
16871
  program2.addCommand(reviewCommand());
16702
16872
  program2.addCommand(sourcesCommand());
16703
16873
  program2.addCommand(suggestCommand());
16704
- program2.addCommand(tagsCommand());
16874
+ program2.addCommand(topicsCommand());
16705
16875
  program2.addCommand(threadsCommand());
16706
16876
  program2.addCommand(skillCommand());
16707
16877
  program2.command("setup").description("Set up Dosu MCP for your AI tools").option("--deployment <id>", "Skip to tool configuration for a specific MCP").option("--mode <mode>", "Force OSS or Cloud mode, skipping the interactive prompt (oss|cloud)").option("--agent", "Run non-interactive setup designed for coding agents (requires --tool)").option("--tool <id>", "Configure a single AI tool by id (claude, cursor, codex, …). Required with --agent.").option("--login-ticket <ticket>", "Resume an --agent setup by redeeming a ticket from a previous run").action(async (opts) => {