@dosu/cli 0.29.0 → 0.30.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 +401 -241
  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.0", INSTALL_CHANNEL = "npm";
4580
+ var VERSION = "0.30.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 = "140d68a8c401";
4714
+ var CLI_CONTRACT_HASH = "888669da10bd";
4715
4715
 
4716
4716
  // src/client/trpc.ts
4717
4717
  var exports_trpc = {};
@@ -8328,7 +8328,7 @@ var require_cjs = __commonJS((exports) => {
8328
8328
  var require_lib = __commonJS((exports, module) => {
8329
8329
  var __filename = "/home/runner/work/dosu-cli/dosu-cli/node_modules/write-file-atomic/lib/index.js";
8330
8330
  module.exports = writeFile;
8331
- module.exports.sync = writeFileSync5;
8331
+ module.exports.sync = writeFileSync6;
8332
8332
  module.exports._getTmpname = getTmpname;
8333
8333
  module.exports._cleanupOnExit = cleanupOnExit;
8334
8334
  var fs7 = __require("fs");
@@ -8461,7 +8461,7 @@ var require_lib = __commonJS((exports, module) => {
8461
8461
  }
8462
8462
  return promise;
8463
8463
  }
8464
- function writeFileSync5(filename, data, options) {
8464
+ function writeFileSync6(filename, data, options) {
8465
8465
  if (typeof options === "string") {
8466
8466
  options = { encoding: options };
8467
8467
  } else if (!options) {
@@ -8538,7 +8538,7 @@ var require_lib = __commonJS((exports, module) => {
8538
8538
 
8539
8539
  // src/mcp/config-helpers.ts
8540
8540
  import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7 } from "node:fs";
8541
- import { dirname as dirname2 } from "node:path";
8541
+ import { dirname as dirname3 } from "node:path";
8542
8542
  function mcpURL(deploymentID) {
8543
8543
  return `${getBackendURL()}/v1/mcp/deployments/${deploymentID}`;
8544
8544
  }
@@ -8616,7 +8616,7 @@ function saveJSONConfig(path3, cfg) {
8616
8616
  writeSecureFile(path3, JSON.stringify(cfg, null, 2));
8617
8617
  }
8618
8618
  function writeSecureFile(path3, content) {
8619
- const dir = dirname2(path3);
8619
+ const dir = dirname3(path3);
8620
8620
  if (!existsSync6(dir)) {
8621
8621
  mkdirSync5(dir, { recursive: true, mode: 448 });
8622
8622
  }
@@ -8658,6 +8658,57 @@ var init_config_helpers = __esm(() => {
8658
8658
  writeFileAtomic = import_write_file_atomic.default;
8659
8659
  });
8660
8660
 
8661
+ // src/hooks/runtime.ts
8662
+ var exports_runtime = {};
8663
+ __export(exports_runtime, {
8664
+ resolveHookCommandPrefix: () => resolveHookCommandPrefix,
8665
+ materializedRuntimePath: () => materializedRuntimePath,
8666
+ dosuOnPath: () => dosuOnPath
8667
+ });
8668
+ import { spawnSync } from "node:child_process";
8669
+ import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "node:fs";
8670
+ import { dirname as dirname4, join as join6 } from "node:path";
8671
+ function dosuOnPath() {
8672
+ const cmd = process.platform === "win32" ? "where" : "which";
8673
+ try {
8674
+ return spawnSync(cmd, ["dosu"], { stdio: "ignore" }).status === 0;
8675
+ } catch {
8676
+ return false;
8677
+ }
8678
+ }
8679
+ function materializedRuntimePath() {
8680
+ return join6(getConfigDir(), "bin", "dosu.js");
8681
+ }
8682
+ function currentBundlePath() {
8683
+ const entry = process.argv[1];
8684
+ if (!entry || !/\.(c|m)?js$/.test(entry) || !existsSync7(entry))
8685
+ return null;
8686
+ return entry;
8687
+ }
8688
+ function resolveHookCommandPrefix() {
8689
+ if (dosuOnPath())
8690
+ return "dosu";
8691
+ const bundle = currentBundlePath();
8692
+ if (!bundle) {
8693
+ logger.warn("hooks", "dosu not on PATH and no bundle to materialize; using bare 'dosu'");
8694
+ return "dosu";
8695
+ }
8696
+ const target = materializedRuntimePath();
8697
+ try {
8698
+ mkdirSync6(dirname4(target), { recursive: true, mode: 448 });
8699
+ copyFileSync(bundle, target);
8700
+ } catch (err) {
8701
+ logger.warn("hooks", `failed to materialize runtime: ${err instanceof Error ? err.message : String(err)}`);
8702
+ return "dosu";
8703
+ }
8704
+ logger.info("hooks", `materialized hook runtime at ${target}`);
8705
+ return `node "${target}"`;
8706
+ }
8707
+ var init_runtime = __esm(() => {
8708
+ init_config();
8709
+ init_logger();
8710
+ });
8711
+
8661
8712
  // src/hooks/claude-code.ts
8662
8713
  var exports_claude_code = {};
8663
8714
  __export(exports_claude_code, {
@@ -8669,13 +8720,13 @@ __export(exports_claude_code, {
8669
8720
  claudeLocalSettingsPath: () => claudeLocalSettingsPath,
8670
8721
  DEFAULT_HOOK_EVENTS: () => DEFAULT_HOOK_EVENTS
8671
8722
  });
8672
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
8673
- import { join as join6 } from "node:path";
8723
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
8724
+ import { join as join7 } from "node:path";
8674
8725
  function claudeLocalSettingsPath(dir) {
8675
- return join6(dir, ".claude", "settings.local.json");
8726
+ return join7(dir, ".claude", "settings.local.json");
8676
8727
  }
8677
- function hookCommand(subcommand) {
8678
- return `dosu hooks ${subcommand}`;
8728
+ function hookCommand(subcommand, prefix = resolveHookCommandPrefix()) {
8729
+ return `${prefix} hooks ${subcommand}`;
8679
8730
  }
8680
8731
  function dosuGroup(event) {
8681
8732
  const entry = {
@@ -8690,6 +8741,9 @@ function isDosuHookCommand(command) {
8690
8741
  return false;
8691
8742
  if (/@dosu\/cli/.test(command))
8692
8743
  return true;
8744
+ if (/^([^\s]*[/\\])?node(\.exe)?\s+("[^"]*dosu\.(c|m)?js"|[^\s]*dosu\.(c|m)?js)\s/.test(command.trimStart())) {
8745
+ return true;
8746
+ }
8693
8747
  return /^([^\s]*\/)?dosu\s/.test(command.trimStart());
8694
8748
  }
8695
8749
  function isDosuGroup(group) {
@@ -8701,7 +8755,7 @@ function isDosuGroup(group) {
8701
8755
  return hooks.some((h2) => !!h2 && typeof h2 === "object" && (h2[MARKER] === true || typeof h2.command === "string" && isDosuHookCommand(h2.command)));
8702
8756
  }
8703
8757
  function readSettingsOrThrow(path3) {
8704
- if (!existsSync7(path3))
8758
+ if (!existsSync8(path3))
8705
8759
  return {};
8706
8760
  const raw = readFileSync8(path3, "utf-8").trim();
8707
8761
  if (!raw)
@@ -8763,7 +8817,7 @@ function removeClaudeHooks(configPath) {
8763
8817
  return { removed };
8764
8818
  }
8765
8819
  function inspectClaudeHooks(configPath) {
8766
- if (!existsSync7(configPath))
8820
+ if (!existsSync8(configPath))
8767
8821
  return { fileExists: false, parseError: false, events: [] };
8768
8822
  let cfg;
8769
8823
  try {
@@ -8778,6 +8832,7 @@ function inspectClaudeHooks(configPath) {
8778
8832
  var DEFAULT_HOOK_EVENTS, MARKER = "__dosu", EVENT_SUBCOMMAND;
8779
8833
  var init_claude_code = __esm(() => {
8780
8834
  init_config_helpers();
8835
+ init_runtime();
8781
8836
  DEFAULT_HOOK_EVENTS = ["UserPromptSubmit", "PostToolUse", "Stop"];
8782
8837
  EVENT_SUBCOMMAND = {
8783
8838
  UserPromptSubmit: "user-prompt-submit",
@@ -8795,13 +8850,13 @@ __export(exports_codex, {
8795
8850
  codexHooksPath: () => codexHooksPath,
8796
8851
  codexHookCommand: () => codexHookCommand
8797
8852
  });
8798
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
8799
- import { join as join7 } from "node:path";
8853
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
8854
+ import { join as join8 } from "node:path";
8800
8855
  function codexHooksPath(dir) {
8801
- return join7(dir, ".codex", "hooks.json");
8856
+ return join8(dir, ".codex", "hooks.json");
8802
8857
  }
8803
- function codexHookCommand(subcommand) {
8804
- return `dosu hooks ${subcommand} --agent codex`;
8858
+ function codexHookCommand(subcommand, prefix = resolveHookCommandPrefix()) {
8859
+ return `${prefix} hooks ${subcommand} --agent codex`;
8805
8860
  }
8806
8861
  function dosuGroup2(event) {
8807
8862
  return {
@@ -8816,7 +8871,7 @@ function dosuGroup2(event) {
8816
8871
  };
8817
8872
  }
8818
8873
  function readHooksFileOrThrow(path3) {
8819
- if (!existsSync8(path3))
8874
+ if (!existsSync9(path3))
8820
8875
  return {};
8821
8876
  const raw = readFileSync9(path3, "utf-8").trim();
8822
8877
  if (!raw)
@@ -8851,7 +8906,7 @@ function installCodexHooks(configPath, opts = {}) {
8851
8906
  return { events: [...events] };
8852
8907
  }
8853
8908
  function removeCodexHooks(configPath) {
8854
- if (!existsSync8(configPath))
8909
+ if (!existsSync9(configPath))
8855
8910
  return { removed: false };
8856
8911
  let config;
8857
8912
  try {
@@ -8880,7 +8935,7 @@ function removeCodexHooks(configPath) {
8880
8935
  return { removed };
8881
8936
  }
8882
8937
  function inspectCodexHooks(configPath) {
8883
- if (!existsSync8(configPath))
8938
+ if (!existsSync9(configPath))
8884
8939
  return { fileExists: false, events: [] };
8885
8940
  let config;
8886
8941
  try {
@@ -8898,6 +8953,7 @@ var EVENT_SUBCOMMAND2, EVENT_TIMEOUT_SEC, EVENT_STATUS_MESSAGE;
8898
8953
  var init_codex = __esm(() => {
8899
8954
  init_config_helpers();
8900
8955
  init_claude_code();
8956
+ init_runtime();
8901
8957
  EVENT_SUBCOMMAND2 = {
8902
8958
  UserPromptSubmit: "user-prompt-submit",
8903
8959
  PostToolUse: "post-tool-use",
@@ -8924,13 +8980,13 @@ __export(exports_factory, {
8924
8980
  factoryHooksPath: () => factoryHooksPath,
8925
8981
  factoryHookCommand: () => factoryHookCommand
8926
8982
  });
8927
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
8928
- import { join as join8 } from "node:path";
8983
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
8984
+ import { join as join9 } from "node:path";
8929
8985
  function factoryHooksPath(dir) {
8930
- return join8(dir, ".factory", "hooks.json");
8986
+ return join9(dir, ".factory", "hooks.json");
8931
8987
  }
8932
- function factoryHookCommand(subcommand) {
8933
- return `dosu hooks ${subcommand} --agent factory`;
8988
+ function factoryHookCommand(subcommand, prefix = resolveHookCommandPrefix()) {
8989
+ return `${prefix} hooks ${subcommand} --agent factory`;
8934
8990
  }
8935
8991
  function dosuGroup3(event) {
8936
8992
  const group = {
@@ -8941,7 +8997,7 @@ function dosuGroup3(event) {
8941
8997
  return group;
8942
8998
  }
8943
8999
  function readHooksFileOrThrow2(path3) {
8944
- if (!existsSync9(path3))
9000
+ if (!existsSync10(path3))
8945
9001
  return {};
8946
9002
  const raw = readFileSync10(path3, "utf-8").trim();
8947
9003
  if (!raw)
@@ -8976,7 +9032,7 @@ function installFactoryHooks(configPath, opts = {}) {
8976
9032
  return { events: [...events] };
8977
9033
  }
8978
9034
  function removeFactoryHooks(configPath) {
8979
- if (!existsSync9(configPath))
9035
+ if (!existsSync10(configPath))
8980
9036
  return { removed: false };
8981
9037
  let config;
8982
9038
  try {
@@ -9005,7 +9061,7 @@ function removeFactoryHooks(configPath) {
9005
9061
  return { removed };
9006
9062
  }
9007
9063
  function inspectFactoryHooks(configPath) {
9008
- if (!existsSync9(configPath))
9064
+ if (!existsSync10(configPath))
9009
9065
  return { fileExists: false, events: [] };
9010
9066
  let config;
9011
9067
  try {
@@ -9023,6 +9079,7 @@ var EVENT_SUBCOMMAND3;
9023
9079
  var init_factory = __esm(() => {
9024
9080
  init_config_helpers();
9025
9081
  init_claude_code();
9082
+ init_runtime();
9026
9083
  EVENT_SUBCOMMAND3 = {
9027
9084
  UserPromptSubmit: "user-prompt-submit",
9028
9085
  PostToolUse: "post-tool-use",
@@ -10118,15 +10175,15 @@ var init_insights2 = __esm(() => {
10118
10175
  });
10119
10176
 
10120
10177
  // 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";
10178
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
10179
+ import { join as join10 } from "node:path";
10123
10180
  function getCachePath2() {
10124
- return join9(getConfigDir(), CACHE_FILENAME2);
10181
+ return join10(getConfigDir(), CACHE_FILENAME2);
10125
10182
  }
10126
10183
  function readSkillCache() {
10127
10184
  try {
10128
10185
  const path3 = getCachePath2();
10129
- if (!existsSync10(path3))
10186
+ if (!existsSync12(path3))
10130
10187
  return null;
10131
10188
  const data = JSON.parse(readFileSync12(path3, "utf-8"));
10132
10189
  if (typeof data.lastCheck === "number" && typeof data.latestSha === "string" && typeof data.installedSha === "string") {
@@ -10140,10 +10197,10 @@ function readSkillCache() {
10140
10197
  function writeSkillCache(cache) {
10141
10198
  try {
10142
10199
  const dir = getConfigDir();
10143
- if (!existsSync10(dir)) {
10144
- mkdirSync6(dir, { recursive: true, mode: 448 });
10200
+ if (!existsSync12(dir)) {
10201
+ mkdirSync7(dir, { recursive: true, mode: 448 });
10145
10202
  }
10146
- writeFileSync5(getCachePath2(), JSON.stringify(cache), { mode: 384 });
10203
+ writeFileSync6(getCachePath2(), JSON.stringify(cache), { mode: 384 });
10147
10204
  } catch {}
10148
10205
  }
10149
10206
  async function fetchLatestSha() {
@@ -10168,19 +10225,6 @@ async function fetchLatestSha() {
10168
10225
  clearTimeout(timeout);
10169
10226
  }
10170
10227
  }
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
10228
  function displayNotice() {
10185
10229
  const msg = `
10186
10230
  ${import_picocolors12.default.yellow(" Dosu skill update available")}
@@ -10278,16 +10322,12 @@ Failed to remove skill.`));
10278
10322
  });
10279
10323
  cmd.command("update").description("Update the Dosu skill to the latest version").action(async () => {
10280
10324
  console.log(`Updating ${SKILL_NAME} skill...`);
10281
- try {
10282
- execSync2(`npx skills update ${SKILL_NAME} -g`, {
10283
- stdio: "inherit"
10284
- });
10285
- } catch {
10325
+ const result = await installSkill();
10326
+ if (!result.success) {
10286
10327
  console.error(import_picocolors13.default.red(`
10287
10328
  Failed to update skill.`));
10288
10329
  process.exit(1);
10289
10330
  }
10290
- await refreshInstalledSha();
10291
10331
  console.log(import_picocolors13.default.green(`
10292
10332
  ✓ Skill "${SKILL_NAME}" updated.`));
10293
10333
  });
@@ -10317,21 +10357,21 @@ var init_skill = __esm(() => {
10317
10357
  var MCP_PROVIDER_SLUG = "dosu_mcp";
10318
10358
 
10319
10359
  // src/mcp/detect.ts
10320
- import { existsSync as existsSync11 } from "node:fs";
10360
+ import { existsSync as existsSync13 } from "node:fs";
10321
10361
  import { homedir, platform as platform2 } from "node:os";
10322
- import { join as join10 } from "node:path";
10362
+ import { join as join11 } from "node:path";
10323
10363
  function isInstalled(paths) {
10324
- return paths.some((p2) => existsSync11(expandHome(p2)));
10364
+ return paths.some((p2) => existsSync13(expandHome(p2)));
10325
10365
  }
10326
10366
  function expandHome(path3) {
10327
10367
  if (!path3.startsWith("~"))
10328
10368
  return path3;
10329
- return join10(homedir(), path3.slice(1));
10369
+ return join11(homedir(), path3.slice(1));
10330
10370
  }
10331
10371
  function appSupportDir() {
10332
10372
  switch (platform2()) {
10333
10373
  case "darwin": {
10334
- return join10(homedir(), "Library", "Application Support");
10374
+ return join11(homedir(), "Library", "Application Support");
10335
10375
  }
10336
10376
  case "win32": {
10337
10377
  return process.env.APPDATA ?? "";
@@ -10340,7 +10380,7 @@ function appSupportDir() {
10340
10380
  const xdg = process.env.XDG_CONFIG_HOME;
10341
10381
  if (xdg)
10342
10382
  return xdg;
10343
- return join10(homedir(), ".config");
10383
+ return join11(homedir(), ".config");
10344
10384
  }
10345
10385
  }
10346
10386
  }
@@ -10421,7 +10461,7 @@ var init_antigravity = __esm(() => {
10421
10461
  });
10422
10462
 
10423
10463
  // src/mcp/providers/claude.ts
10424
- import { join as join11 } from "node:path";
10464
+ import { join as join12 } from "node:path";
10425
10465
  var ClaudeProvider = () => createJSONProvider({
10426
10466
  providerName: "Claude Code",
10427
10467
  providerID: "claude",
@@ -10430,22 +10470,22 @@ var ClaudeProvider = () => createJSONProvider({
10430
10470
  paths: ["~/.claude"],
10431
10471
  globalPath: "~/.claude.json",
10432
10472
  topKey: "mcpServers",
10433
- localConfigPath: (cwd) => join11(cwd, ".mcp.json")
10473
+ localConfigPath: (cwd) => join12(cwd, ".mcp.json")
10434
10474
  });
10435
10475
  var init_claude = __esm(() => {
10436
10476
  init_base();
10437
10477
  });
10438
10478
 
10439
10479
  // src/mcp/providers/claude-desktop.ts
10440
- import { join as join12 } from "node:path";
10480
+ import { join as join13 } from "node:path";
10441
10481
  var ClaudeDesktopProvider = () => ({
10442
10482
  name: () => "Claude Desktop",
10443
10483
  id: () => "claude-desktop",
10444
10484
  supportsLocal: () => false,
10445
10485
  priority: () => 2,
10446
- detectPaths: () => [join12(appSupportDir(), "Claude")],
10447
- isInstalled: () => isInstalled([join12(appSupportDir(), "Claude")]),
10448
- globalConfigPath: () => join12(appSupportDir(), "Claude", "claude_desktop_config.json"),
10486
+ detectPaths: () => [join13(appSupportDir(), "Claude")],
10487
+ isInstalled: () => isInstalled([join13(appSupportDir(), "Claude")]),
10488
+ globalConfigPath: () => join13(appSupportDir(), "Claude", "claude_desktop_config.json"),
10449
10489
  isConfigured: () => false,
10450
10490
  install() {
10451
10491
  throw new Error("this tool only supports local (stdio) servers and cannot be configured for remote MCP");
@@ -10459,14 +10499,14 @@ var init_claude_desktop = __esm(() => {
10459
10499
  });
10460
10500
 
10461
10501
  // 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({
10502
+ import { join as join14 } from "node:path";
10503
+ var extensionDir = () => join14(appSupportDir(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev"), ClineProvider = () => createJSONProvider({
10464
10504
  providerName: "Cline",
10465
10505
  providerID: "cline",
10466
10506
  local: false,
10467
10507
  priorityValue: 11,
10468
10508
  paths: [extensionDir()],
10469
- globalPath: join13(extensionDir(), "settings", "cline_mcp_settings.json"),
10509
+ globalPath: join14(extensionDir(), "settings", "cline_mcp_settings.json"),
10470
10510
  topKey: "mcpServers",
10471
10511
  buildServer: (cfg) => ({
10472
10512
  url: mcpURL(cfg.deployment_id),
@@ -10482,7 +10522,7 @@ var init_cline = __esm(() => {
10482
10522
  });
10483
10523
 
10484
10524
  // src/mcp/providers/cline-cli.ts
10485
- import { join as join14 } from "node:path";
10525
+ import { join as join15 } from "node:path";
10486
10526
  function clineDir() {
10487
10527
  return process.env.CLINE_DIR ?? expandHome("~/.cline");
10488
10528
  }
@@ -10492,7 +10532,7 @@ var ClineCliProvider = () => createJSONProvider({
10492
10532
  local: false,
10493
10533
  priorityValue: 12,
10494
10534
  paths: [clineDir()],
10495
- globalPath: join14(clineDir(), "data", "settings", "cline_mcp_settings.json"),
10535
+ globalPath: join15(clineDir(), "data", "settings", "cline_mcp_settings.json"),
10496
10536
  topKey: "mcpServers",
10497
10537
  buildServer: (cfg) => ({
10498
10538
  url: mcpURL(cfg.deployment_id),
@@ -10508,18 +10548,18 @@ var init_cline_cli = __esm(() => {
10508
10548
  });
10509
10549
 
10510
10550
  // src/mcp/providers/codex.ts
10511
- import { existsSync as existsSync12, readFileSync as readFileSync13 } from "node:fs";
10512
- import { join as join15 } from "node:path";
10551
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
10552
+ import { join as join16 } from "node:path";
10513
10553
  function codexHome() {
10514
10554
  return process.env.CODEX_HOME ?? expandHome("~/.codex");
10515
10555
  }
10516
10556
  function getConfigPath2(global) {
10517
10557
  if (global)
10518
- return join15(codexHome(), "config.toml");
10519
- return join15(process.cwd(), ".codex", "config.toml");
10558
+ return join16(codexHome(), "config.toml");
10559
+ return join16(process.cwd(), ".codex", "config.toml");
10520
10560
  }
10521
10561
  function readTOML(path3) {
10522
- if (!existsSync12(path3))
10562
+ if (!existsSync14(path3))
10523
10563
  return "";
10524
10564
  return readFileSync13(path3, "utf-8");
10525
10565
  }
@@ -10579,9 +10619,9 @@ var CodexProvider = () => ({
10579
10619
  priority: () => 8,
10580
10620
  detectPaths: () => ["~/.codex"],
10581
10621
  isInstalled: () => isInstalled(["~/.codex"]),
10582
- globalConfigPath: () => join15(codexHome(), "config.toml"),
10622
+ globalConfigPath: () => join16(codexHome(), "config.toml"),
10583
10623
  isConfigured: () => {
10584
- const content = readTOML(join15(codexHome(), "config.toml"));
10624
+ const content = readTOML(join16(codexHome(), "config.toml"));
10585
10625
  return content.includes("[mcp_servers.dosu]");
10586
10626
  },
10587
10627
  install(cfg, global) {
@@ -10603,10 +10643,10 @@ var init_codex2 = __esm(() => {
10603
10643
  });
10604
10644
 
10605
10645
  // src/mcp/providers/copilot.ts
10606
- import { join as join16 } from "node:path";
10646
+ import { join as join17 } from "node:path";
10607
10647
  function globalPath() {
10608
10648
  if (process.env.XDG_CONFIG_HOME) {
10609
- return join16(process.env.XDG_CONFIG_HOME, "mcp-config.json");
10649
+ return join17(process.env.XDG_CONFIG_HOME, "mcp-config.json");
10610
10650
  }
10611
10651
  return expandHome("~/.copilot/mcp-config.json");
10612
10652
  }
@@ -10637,7 +10677,7 @@ var CopilotProvider = () => ({
10637
10677
  };
10638
10678
  installJSONServer(globalPath(), "mcpServers", server);
10639
10679
  } else {
10640
- const configPath = join16(process.cwd(), ".vscode", "mcp.json");
10680
+ const configPath = join17(process.cwd(), ".vscode", "mcp.json");
10641
10681
  const server = {
10642
10682
  type: "http",
10643
10683
  url,
@@ -10650,7 +10690,7 @@ var CopilotProvider = () => ({
10650
10690
  if (global) {
10651
10691
  removeJSONServer(globalPath(), "mcpServers");
10652
10692
  } else {
10653
- const configPath = join16(process.cwd(), ".vscode", "mcp.json");
10693
+ const configPath = join17(process.cwd(), ".vscode", "mcp.json");
10654
10694
  removeJSONServer(configPath, "servers");
10655
10695
  }
10656
10696
  }
@@ -10662,7 +10702,7 @@ var init_copilot = __esm(() => {
10662
10702
  });
10663
10703
 
10664
10704
  // src/mcp/providers/cursor.ts
10665
- import { join as join17 } from "node:path";
10705
+ import { join as join18 } from "node:path";
10666
10706
  var CursorProvider = () => createJSONProvider({
10667
10707
  providerName: "Cursor",
10668
10708
  providerID: "cursor",
@@ -10675,7 +10715,7 @@ var CursorProvider = () => createJSONProvider({
10675
10715
  url: mcpURL(cfg.deployment_id),
10676
10716
  headers: mcpHeaders(cfg.api_key)
10677
10717
  }),
10678
- localConfigPath: (cwd) => join17(cwd, ".cursor", "mcp.json")
10718
+ localConfigPath: (cwd) => join18(cwd, ".cursor", "mcp.json")
10679
10719
  });
10680
10720
  var init_cursor = __esm(() => {
10681
10721
  init_config_helpers();
@@ -10683,7 +10723,7 @@ var init_cursor = __esm(() => {
10683
10723
  });
10684
10724
 
10685
10725
  // src/mcp/providers/factory.ts
10686
- import { join as join18 } from "node:path";
10726
+ import { join as join19 } from "node:path";
10687
10727
  var FactoryProvider = () => createJSONProvider({
10688
10728
  providerName: "Factory",
10689
10729
  providerID: "factory",
@@ -10692,14 +10732,14 @@ var FactoryProvider = () => createJSONProvider({
10692
10732
  paths: ["~/.factory"],
10693
10733
  globalPath: "~/.factory/mcp.json",
10694
10734
  topKey: "mcpServers",
10695
- localConfigPath: (cwd) => join18(cwd, ".factory", "mcp.json")
10735
+ localConfigPath: (cwd) => join19(cwd, ".factory", "mcp.json")
10696
10736
  });
10697
10737
  var init_factory2 = __esm(() => {
10698
10738
  init_base();
10699
10739
  });
10700
10740
 
10701
10741
  // src/mcp/providers/gemini.ts
10702
- import { join as join19 } from "node:path";
10742
+ import { join as join20 } from "node:path";
10703
10743
  var GeminiProvider = () => createJSONProvider({
10704
10744
  providerName: "Gemini CLI",
10705
10745
  providerID: "gemini",
@@ -10708,7 +10748,7 @@ var GeminiProvider = () => createJSONProvider({
10708
10748
  paths: ["~/.gemini"],
10709
10749
  globalPath: "~/.gemini/settings.json",
10710
10750
  topKey: "mcpServers",
10711
- localConfigPath: (cwd) => join19(cwd, ".gemini", "settings.json")
10751
+ localConfigPath: (cwd) => join20(cwd, ".gemini", "settings.json")
10712
10752
  });
10713
10753
  var init_gemini = __esm(() => {
10714
10754
  init_base();
@@ -10758,14 +10798,14 @@ var init_manual = __esm(() => {
10758
10798
  });
10759
10799
 
10760
10800
  // src/mcp/providers/mcporter.ts
10761
- import { existsSync as existsSync13 } from "node:fs";
10762
- import { join as join20 } from "node:path";
10801
+ import { existsSync as existsSync15 } from "node:fs";
10802
+ import { join as join21 } from "node:path";
10763
10803
  function resolveGlobalConfigPath() {
10764
10804
  const jsonPath = expandHome("~/.mcporter/mcporter.json");
10765
- if (existsSync13(jsonPath))
10805
+ if (existsSync15(jsonPath))
10766
10806
  return jsonPath;
10767
10807
  const jsoncPath = expandHome("~/.mcporter/mcporter.jsonc");
10768
- if (existsSync13(jsoncPath))
10808
+ if (existsSync15(jsoncPath))
10769
10809
  return jsoncPath;
10770
10810
  return jsonPath;
10771
10811
  }
@@ -10786,7 +10826,7 @@ var MCPorterProvider = () => ({
10786
10826
  globalConfigPath: () => resolveGlobalConfigPath(),
10787
10827
  isConfigured: () => isJSONKeyConfigured(resolveGlobalConfigPath(), "mcpServers"),
10788
10828
  install(cfg, global) {
10789
- const configPath = global ? resolveGlobalConfigPath() : join20(process.cwd(), "config", "mcporter.json");
10829
+ const configPath = global ? resolveGlobalConfigPath() : join21(process.cwd(), "config", "mcporter.json");
10790
10830
  const server = {
10791
10831
  type: "http",
10792
10832
  url: mcpEndpoint4(cfg),
@@ -10795,7 +10835,7 @@ var MCPorterProvider = () => ({
10795
10835
  installJSONServer(configPath, "mcpServers", server);
10796
10836
  },
10797
10837
  remove(global) {
10798
- const configPath = global ? resolveGlobalConfigPath() : join20(process.cwd(), "config", "mcporter.json");
10838
+ const configPath = global ? resolveGlobalConfigPath() : join21(process.cwd(), "config", "mcporter.json");
10799
10839
  removeJSONServer(configPath, "mcpServers");
10800
10840
  }
10801
10841
  });
@@ -10806,7 +10846,7 @@ var init_mcporter = __esm(() => {
10806
10846
  });
10807
10847
 
10808
10848
  // src/mcp/providers/opencode.ts
10809
- import { join as join21 } from "node:path";
10849
+ import { join as join22 } from "node:path";
10810
10850
  var OpenCodeProvider = () => createJSONProvider({
10811
10851
  providerName: "OpenCode",
10812
10852
  providerID: "opencode",
@@ -10821,7 +10861,7 @@ var OpenCodeProvider = () => createJSONProvider({
10821
10861
  enabled: true,
10822
10862
  headers: mcpHeaders(cfg.api_key)
10823
10863
  }),
10824
- localConfigPath: (cwd) => join21(cwd, "opencode.json")
10864
+ localConfigPath: (cwd) => join22(cwd, "opencode.json")
10825
10865
  });
10826
10866
  var init_opencode = __esm(() => {
10827
10867
  init_config_helpers();
@@ -10829,16 +10869,16 @@ var init_opencode = __esm(() => {
10829
10869
  });
10830
10870
 
10831
10871
  // src/mcp/providers/vscode.ts
10832
- import { join as join22 } from "node:path";
10872
+ import { join as join23 } from "node:path";
10833
10873
  var VSCodeProvider = () => createJSONProvider({
10834
10874
  providerName: "VS Code",
10835
10875
  providerID: "vscode",
10836
10876
  local: true,
10837
10877
  priorityValue: 6,
10838
- paths: [join22(appSupportDir(), "Code")],
10839
- globalPath: join22(appSupportDir(), "Code", "User", "mcp.json"),
10878
+ paths: [join23(appSupportDir(), "Code")],
10879
+ globalPath: join23(appSupportDir(), "Code", "User", "mcp.json"),
10840
10880
  topKey: "servers",
10841
- localConfigPath: (cwd) => join22(cwd, ".vscode", "mcp.json")
10881
+ localConfigPath: (cwd) => join23(cwd, ".vscode", "mcp.json")
10842
10882
  });
10843
10883
  var init_vscode = __esm(() => {
10844
10884
  init_detect();
@@ -10847,14 +10887,14 @@ var init_vscode = __esm(() => {
10847
10887
 
10848
10888
  // src/mcp/providers/windsurf.ts
10849
10889
  import { homedir as homedir2 } from "node:os";
10850
- import { join as join23 } from "node:path";
10890
+ import { join as join24 } from "node:path";
10851
10891
  var WindsurfProvider = () => createJSONProvider({
10852
10892
  providerName: "Windsurf",
10853
10893
  providerID: "windsurf",
10854
10894
  local: false,
10855
10895
  priorityValue: 9,
10856
- paths: [join23(homedir2(), ".codeium", "windsurf")],
10857
- globalPath: join23(homedir2(), ".codeium", "windsurf", "mcp_config.json"),
10896
+ paths: [join24(homedir2(), ".codeium", "windsurf")],
10897
+ globalPath: join24(homedir2(), ".codeium", "windsurf", "mcp_config.json"),
10858
10898
  topKey: "mcpServers"
10859
10899
  });
10860
10900
  var init_windsurf = __esm(() => {
@@ -10863,12 +10903,12 @@ var init_windsurf = __esm(() => {
10863
10903
 
10864
10904
  // src/mcp/providers/zed.ts
10865
10905
  import { platform as platform3 } from "node:os";
10866
- import { join as join24 } from "node:path";
10906
+ import { join as join25 } from "node:path";
10867
10907
  function zedConfigDir() {
10868
10908
  const os2 = platform3();
10869
10909
  if (os2 === "darwin" || os2 === "win32")
10870
- return join24(appSupportDir(), "Zed");
10871
- return join24(appSupportDir(), "zed");
10910
+ return join25(appSupportDir(), "Zed");
10911
+ return join25(appSupportDir(), "zed");
10872
10912
  }
10873
10913
  var ZedProvider = () => createJSONProvider({
10874
10914
  providerName: "Zed",
@@ -10876,7 +10916,7 @@ var ZedProvider = () => createJSONProvider({
10876
10916
  local: true,
10877
10917
  priorityValue: 10,
10878
10918
  paths: [zedConfigDir()],
10879
- globalPath: join24(zedConfigDir(), "settings.json"),
10919
+ globalPath: join25(zedConfigDir(), "settings.json"),
10880
10920
  topKey: "context_servers",
10881
10921
  buildServer: (cfg) => ({
10882
10922
  source: "custom",
@@ -10884,7 +10924,7 @@ var ZedProvider = () => createJSONProvider({
10884
10924
  url: mcpURL(cfg.deployment_id),
10885
10925
  headers: mcpHeaders(cfg.api_key)
10886
10926
  }),
10887
- localConfigPath: (cwd) => join24(cwd, ".zed", "settings.json")
10927
+ localConfigPath: (cwd) => join25(cwd, ".zed", "settings.json")
10888
10928
  });
10889
10929
  var init_zed = __esm(() => {
10890
10930
  init_config_helpers();
@@ -11027,6 +11067,74 @@ var init_analytics = __esm(() => {
11027
11067
  init_logger();
11028
11068
  });
11029
11069
 
11070
+ // src/setup/audit-handoff.ts
11071
+ import { spawnSync as spawnSync2 } from "node:child_process";
11072
+ function buildAuditHandoffPrompt(dosuCmd = dosuInvocation()) {
11073
+ return [
11074
+ "Audit this repository to see what docs Dosu can generate for it.",
11075
+ "1. Run the codebase audit from the Dosu skill. It writes findings to .dosu/audit.json.",
11076
+ "2. Show me the findings and which docs you recommend Dosu generate.",
11077
+ `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.`
11078
+ ].join(`
11079
+ `);
11080
+ }
11081
+ function binOnPath(bin) {
11082
+ const cmd = process.platform === "win32" ? "where" : "which";
11083
+ try {
11084
+ return spawnSync2(cmd, [bin], { stdio: "ignore" }).status === 0;
11085
+ } catch {
11086
+ return false;
11087
+ }
11088
+ }
11089
+ function claudeCliAvailable() {
11090
+ return binOnPath("claude");
11091
+ }
11092
+ function dosuInvocation() {
11093
+ return binOnPath("dosu") ? "dosu" : "npx -y @dosu/cli";
11094
+ }
11095
+ function printManualAuditNudge() {
11096
+ log.message(`See what docs Dosu can generate for this repo:
11097
+
11098
+ ${info("Ask Dosu to audit this repo and show what docs it can generate.")}
11099
+
11100
+ Or run ${info(`${dosuInvocation()} audit`)} after your agent writes the audit.`);
11101
+ }
11102
+ async function offerAuditHandoff() {
11103
+ if (!detectGitRepo())
11104
+ return false;
11105
+ if (!claudeCliAvailable()) {
11106
+ printManualAuditNudge();
11107
+ return false;
11108
+ }
11109
+ const go = await confirm({
11110
+ message: "Kick off the codebase audit in Claude Code now?",
11111
+ initialValue: true
11112
+ });
11113
+ if (isCancel(go) || !go) {
11114
+ printManualAuditNudge();
11115
+ return false;
11116
+ }
11117
+ return true;
11118
+ }
11119
+ function launchAuditAgent() {
11120
+ logger.info("setup", "Handing off to Claude Code for the codebase audit");
11121
+ const result = spawnSync2("claude", ["--model", AUDIT_AGENT_MODEL, buildAuditHandoffPrompt()], {
11122
+ stdio: "inherit",
11123
+ shell: process.platform === "win32"
11124
+ });
11125
+ if (result.error) {
11126
+ logger.warn("setup", `Claude Code launch failed: ${result.error.message}`);
11127
+ printManualAuditNudge();
11128
+ }
11129
+ }
11130
+ var AUDIT_AGENT_MODEL = "haiku";
11131
+ var init_audit_handoff = __esm(() => {
11132
+ init_dist8();
11133
+ init_logger();
11134
+ init_github_step();
11135
+ init_styles();
11136
+ });
11137
+
11030
11138
  // src/setup/github-doc-import-prompt.ts
11031
11139
  async function promptGitHubDocsImport({
11032
11140
  repositories
@@ -12068,8 +12176,8 @@ __export(exports_flow2, {
12068
12176
  isStdioOnly: () => isStdioOnly
12069
12177
  });
12070
12178
  import { randomUUID } from "node:crypto";
12071
- import { existsSync as existsSync14 } from "node:fs";
12072
- import { join as join25 } from "node:path";
12179
+ import { existsSync as existsSync16 } from "node:fs";
12180
+ import { join as join26 } from "node:path";
12073
12181
  async function runSetup(opts = {}) {
12074
12182
  const onboardingRunID = randomUUID();
12075
12183
  logger.info("setup", `Setup flow started${opts.deploymentID ? ` deployment=${opts.deploymentID}` : ""}${opts.mode ? ` mode=${opts.mode}` : ""}`);
@@ -12237,12 +12345,16 @@ async function runSetup(opts = {}) {
12237
12345
  log.warn("Could not mark onboarding complete on the server — you can retry later by running `dosu setup` again.");
12238
12346
  }
12239
12347
  }
12348
+ let handoffToAudit = false;
12240
12349
  if (mcpConfiguredThisRun) {
12241
12350
  showTryItOutPrompt({
12242
12351
  mode: cfg.mode,
12243
12352
  docsImported: choices.connectGitHub && githubOnboardingDone,
12244
- hasAgentsMd: existsSync14(join25(process.cwd(), "AGENTS.md"))
12353
+ hasAgentsMd: existsSync16(join26(process.cwd(), "AGENTS.md"))
12245
12354
  });
12355
+ if (cfg.mode !== MODE_OSS) {
12356
+ handoffToAudit = await offerAuditHandoff();
12357
+ }
12246
12358
  }
12247
12359
  if (mcpCompleted || skillCompleted || docsImported) {
12248
12360
  await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_completed", {
@@ -12256,6 +12368,9 @@ async function runSetup(opts = {}) {
12256
12368
  } else {
12257
12369
  outro("\uD83C\uDF89 Setup complete!");
12258
12370
  }
12371
+ if (handoffToAudit) {
12372
+ launchAuditAgent();
12373
+ }
12259
12374
  }
12260
12375
  function applyDeployment(cfg, d) {
12261
12376
  cfg.deployment_id = d.deployment_id;
@@ -12728,13 +12843,6 @@ function showTryItOutPrompt(opts = {}) {
12728
12843
  log.message(`Try it out! Paste this into your agent:
12729
12844
 
12730
12845
  ${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
12846
  }
12739
12847
  var init_flow2 = __esm(() => {
12740
12848
  init_dist8();
@@ -12744,12 +12852,13 @@ var init_flow2 = __esm(() => {
12744
12852
  init_logger();
12745
12853
  init_providers();
12746
12854
  init_analytics();
12855
+ init_audit_handoff();
12747
12856
  init_styles();
12748
12857
  });
12749
12858
 
12750
12859
  // 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";
12860
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync, unlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
12861
+ import { dirname as dirname5, join as join27 } from "node:path";
12753
12862
  function shuffled(arr) {
12754
12863
  const copy2 = [...arr];
12755
12864
  for (let i2 = copy2.length - 1;i2 > 0; i2--) {
@@ -12853,17 +12962,17 @@ function makeAskFn(cfg) {
12853
12962
  };
12854
12963
  }
12855
12964
  function insightsDir() {
12856
- return join26(getConfigDir(), "insights");
12965
+ return join27(getConfigDir(), "insights");
12857
12966
  }
12858
12967
  function reportPath(timestamp) {
12859
12968
  const dir = insightsDir();
12860
12969
  if (!timestamp)
12861
- return join26(dir, "latest.html");
12970
+ return join27(dir, "latest.html");
12862
12971
  const iso = timestamp.toISOString().slice(0, 19).replace(/:/g, "-");
12863
- return join26(dir, `report-${iso}Z.html`);
12972
+ return join27(dir, `report-${iso}Z.html`);
12864
12973
  }
12865
12974
  function pruneOldReports(dir, keepN) {
12866
- if (!existsSync15(dir))
12975
+ if (!existsSync17(dir))
12867
12976
  return;
12868
12977
  let entries;
12869
12978
  try {
@@ -12875,7 +12984,7 @@ function pruneOldReports(dir, keepN) {
12875
12984
  const reports = entries.filter((f2) => REPORT_FILE_PATTERN.test(f2)).sort().reverse();
12876
12985
  for (const f2 of reports.slice(keepN)) {
12877
12986
  try {
12878
- unlinkSync(join26(dir, f2));
12987
+ unlinkSync(join27(dir, f2));
12879
12988
  } catch (err) {
12880
12989
  logger.debug("insights", `failed to prune ${f2}: ${err instanceof Error ? err.message : err}`);
12881
12990
  }
@@ -12947,10 +13056,10 @@ function defaultRunner(cfg) {
12947
13056
  build: buildInsights,
12948
13057
  render: renderHTML,
12949
13058
  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 });
13059
+ const dir = dirname5(path3);
13060
+ if (!existsSync17(dir))
13061
+ mkdirSync8(dir, { recursive: true, mode: 448 });
13062
+ writeFileSync7(path3, content, { mode: 384 });
12954
13063
  },
12955
13064
  prune: pruneOldReports,
12956
13065
  openInBrowser: async (path3) => {
@@ -13889,8 +13998,8 @@ init_trpc();
13889
13998
  init_logger();
13890
13999
  init_github_step();
13891
14000
  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";
14001
+ import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
14002
+ import { dirname, join as join4 } from "node:path";
13894
14003
 
13895
14004
  // src/version/pending-tasks-check.ts
13896
14005
  init_config();
@@ -14174,6 +14283,33 @@ function loadFindings(findingsPath) {
14174
14283
  }
14175
14284
  return parsed;
14176
14285
  }
14286
+ function cleanupFindings(findingsPath, cwd = process.cwd()) {
14287
+ const gitignorePath = join4(cwd, ".gitignore");
14288
+ try {
14289
+ const content = existsSync4(gitignorePath) ? readFileSync4(gitignorePath, "utf-8") : "";
14290
+ const hasEntry = content.split(/\r?\n/).some((line) => /^\.dosu\/?$/.test(line.trim()));
14291
+ if (!hasEntry) {
14292
+ const sep = content === "" || content.endsWith(`
14293
+ `) ? "" : `
14294
+ `;
14295
+ writeFileSync4(gitignorePath, `${content}${sep}.dosu/
14296
+ `);
14297
+ }
14298
+ } catch (err) {
14299
+ logger.warn("audit", `could not update .gitignore: ${errText(err)}`);
14300
+ }
14301
+ const defaultDir = join4(cwd, ".dosu");
14302
+ if (dirname(findingsPath) !== defaultDir)
14303
+ return;
14304
+ try {
14305
+ rmSync(defaultDir, { recursive: true, force: true });
14306
+ } catch (err) {
14307
+ logger.warn("audit", `could not remove ${defaultDir}: ${errText(err)}`);
14308
+ }
14309
+ }
14310
+ function errText(err) {
14311
+ return err instanceof Error ? err.message : String(err);
14312
+ }
14177
14313
  function intersectActionable(items, capabilities) {
14178
14314
  const known = new Set(capabilities.map((c3) => c3.id));
14179
14315
  return items.filter((item) => known.has(item.task));
@@ -14182,9 +14318,21 @@ function isPreselected(item) {
14182
14318
  return item.can_help && item.action !== "skip";
14183
14319
  }
14184
14320
  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) => {
14321
+ 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
14322
  const cfg = requireConfig3();
14187
14323
  const apiKey = requireAPIKey(cfg);
14324
+ if (opts.listTasks) {
14325
+ const resp = await backendGet("/v1/cli/tasks", apiKey);
14326
+ const tasks = resp.tasks ?? [];
14327
+ if (opts.json) {
14328
+ printResult({ tasks }, opts);
14329
+ return;
14330
+ }
14331
+ for (const t2 of tasks) {
14332
+ console.log(`${t2.id} ${import_picocolors9.default.dim(`(${t2.doc_type})`)} ${t2.label} — ${t2.description}`);
14333
+ }
14334
+ return;
14335
+ }
14188
14336
  const detected = detectGitRepo();
14189
14337
  if (!detected) {
14190
14338
  console.error(import_picocolors9.default.red("Not a GitHub repo. Run 'dosu audit' from a GitHub repository."));
@@ -14265,6 +14413,9 @@ function auditCommand() {
14265
14413
  repo: detected.slug
14266
14414
  });
14267
14415
  }
14416
+ if (taskIds.length > 0) {
14417
+ cleanupFindings(findingsPath);
14418
+ }
14268
14419
  if (opts.json) {
14269
14420
  printResult({ task_ids: taskIds }, opts);
14270
14421
  return;
@@ -14761,13 +14912,13 @@ init_commander();
14761
14912
  init_config();
14762
14913
  init_logger();
14763
14914
  init_prompts();
14764
- import { readFileSync as readFileSync11 } from "node:fs";
14915
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
14765
14916
  import { basename } from "node:path";
14766
14917
 
14767
14918
  // src/hooks/state.ts
14768
14919
  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";
14920
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
14921
+ import { dirname as dirname2, join as join5 } from "node:path";
14771
14922
  function getStateDir() {
14772
14923
  return process.env.DOSU_HOOK_STATE_DIR ?? join5(getConfigDir(), "hooks");
14773
14924
  }
@@ -14787,11 +14938,11 @@ function loadState(sessionId) {
14787
14938
  function saveState(state) {
14788
14939
  try {
14789
14940
  const file = stateFile(state.sessionId);
14790
- const dir = dirname(file);
14941
+ const dir = dirname2(file);
14791
14942
  if (!existsSync5(dir)) {
14792
14943
  mkdirSync4(dir, { recursive: true, mode: 448 });
14793
14944
  }
14794
- writeFileSync4(file, JSON.stringify(state, null, 2), { mode: 384 });
14945
+ writeFileSync5(file, JSON.stringify(state, null, 2), { mode: 384 });
14795
14946
  } catch {}
14796
14947
  }
14797
14948
 
@@ -15230,6 +15381,21 @@ async function collectDoctorChecks(opts) {
15230
15381
  });
15231
15382
  }
15232
15383
  }
15384
+ if (hasSubmit && hasPostTool || alternativeInstalled) {
15385
+ const { dosuOnPath: dosuOnPath2, materializedRuntimePath: materializedRuntimePath2 } = await Promise.resolve().then(() => (init_runtime(), exports_runtime));
15386
+ const materialized = materializedRuntimePath2();
15387
+ if (dosuOnPath2()) {
15388
+ checks.push({ name: "runtime", status: "ok", detail: "dosu on PATH" });
15389
+ } else if (existsSync11(materialized)) {
15390
+ checks.push({ name: "runtime", status: "ok", detail: `materialized: ${materialized}` });
15391
+ } else {
15392
+ checks.push({
15393
+ name: "runtime",
15394
+ status: "fail",
15395
+ detail: "dosu is not on PATH and no materialized runtime exists — re-run 'npx -y @dosu/cli hooks install <agent>'"
15396
+ });
15397
+ }
15398
+ }
15233
15399
  const cfg = loadConfig();
15234
15400
  if (isAuthenticated(cfg)) {
15235
15401
  checks.push({ name: "auth", status: "ok", detail: "logged in" });
@@ -15787,12 +15953,13 @@ function reviewCommand() {
15787
15953
  }
15788
15954
  const client = createTypedClient(cfg);
15789
15955
  const ksId = await getKnowledgeStoreId2(client, cfg.space_id);
15790
- const items = await client.review.listPending.query({
15956
+ const result = await client.review.listPending.query({
15791
15957
  knowledgeStoreId: ksId,
15792
15958
  deploymentId: cfg.deployment_id
15793
15959
  });
15960
+ const { items, truncated, total } = result;
15794
15961
  if (opts.json) {
15795
- printResult(items, opts);
15962
+ printResult(result, opts);
15796
15963
  return;
15797
15964
  }
15798
15965
  if (!items || items.length === 0) {
@@ -15814,6 +15981,9 @@ function reviewCommand() {
15814
15981
  i2.pendingStatus,
15815
15982
  formatDate(i2.createdAt)
15816
15983
  ]), { rawData: items });
15984
+ if (truncated) {
15985
+ console.log(import_picocolors20.default.dim(`Showing ${items.length} of ${total}+ pending items (list truncated).`));
15986
+ }
15817
15987
  });
15818
15988
  cmd.command("diff").description("Show a pending review item (doc-change diff or draft reply body)").argument("<id>", "Review item ID (from `dosu review list`)").option("--json", "Output as JSON").action(async (id, opts) => {
15819
15989
  const cfg = requireConfig10();
@@ -16169,7 +16339,7 @@ function suggestCommand() {
16169
16339
  return cmd;
16170
16340
  }
16171
16341
 
16172
- // src/commands/tags.ts
16342
+ // src/commands/threads.ts
16173
16343
  init_commander();
16174
16344
  init_trpc();
16175
16345
  var import_picocolors23 = __toESM(require_picocolors(), 1);
@@ -16181,92 +16351,10 @@ function requireConfig13() {
16181
16351
  }
16182
16352
  return cfg;
16183
16353
  }
16184
- async function getKnowledgeStoreId4(client, spaceId) {
16185
- const store = await client.knowledgeStore.getBySpaceId.query({
16186
- space_id: spaceId
16187
- });
16188
- if (!store) {
16189
- console.error(import_picocolors23.default.red("No knowledge store found for this deployment."));
16190
- process.exit(1);
16191
- }
16192
- return store.id;
16193
- }
16194
- function tagsCommand() {
16195
- const cmd = new Command("tags").description("Manage knowledge base tags");
16196
- cmd.command("list").description("List all tags").option("--json", "Output as JSON").action(async (opts) => {
16197
- const cfg = requireConfig13();
16198
- const client = createTypedClient(cfg);
16199
- const ksId = await getKnowledgeStoreId4(client, cfg.space_id);
16200
- const tags = await client.topic.listTopicsByKnowledgeStore.query({
16201
- knowledge_store_id: ksId
16202
- });
16203
- if (opts.json) {
16204
- printResult(tags, opts);
16205
- return;
16206
- }
16207
- if (!tags || tags.length === 0) {
16208
- console.log(import_picocolors23.default.dim("No tags found."));
16209
- return;
16210
- }
16211
- printTable(["ID", "Name", "Description"], tags.map((t2) => [
16212
- t2.topic_id.slice(0, 8),
16213
- t2.name,
16214
- t2.description ?? "—"
16215
- ]), { rawData: tags });
16216
- });
16217
- 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) => {
16218
- const cfg = requireConfig13();
16219
- const client = createTypedClient(cfg);
16220
- await client.topic.removeFromPage.mutate({ topic_id: tagId, page_id: pageId });
16221
- if (opts.json) {
16222
- printResult({ success: true, tag_id: tagId, page_id: pageId }, opts);
16223
- return;
16224
- }
16225
- console.log(import_picocolors23.default.green("Tag removed from page."));
16226
- });
16227
- 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) => {
16228
- const cfg = requireConfig13();
16229
- const client = createTypedClient(cfg);
16230
- const ksId = await getKnowledgeStoreId4(client, cfg.space_id);
16231
- const result = await client.topic.getPagesByTopicId.query({
16232
- knowledge_store_id: ksId,
16233
- topic_id: tagId,
16234
- searchTerm: opts.search,
16235
- limit: Number.parseInt(opts.limit ?? "10", 10)
16236
- });
16237
- const pages = result.data;
16238
- if (opts.json) {
16239
- printResult(pages, opts);
16240
- return;
16241
- }
16242
- if (!pages || pages.length === 0) {
16243
- console.log(import_picocolors23.default.dim("No pages found with this tag."));
16244
- return;
16245
- }
16246
- printTable(["ID", "Title"], pages.map((p2) => [
16247
- p2.id.slice(0, 8),
16248
- p2.title ?? "(untitled)"
16249
- ]), { rawData: pages });
16250
- });
16251
- return cmd;
16252
- }
16253
-
16254
- // src/commands/threads.ts
16255
- init_commander();
16256
- init_trpc();
16257
- var import_picocolors24 = __toESM(require_picocolors(), 1);
16258
- function requireConfig14() {
16259
- const cfg = requireLoginConfig();
16260
- if (!cfg.space_id) {
16261
- console.error(import_picocolors24.default.red("Missing space config. Run 'dosu setup' to reconfigure."));
16262
- process.exit(1);
16263
- }
16264
- return cfg;
16265
- }
16266
16354
  function threadsCommand() {
16267
16355
  const cmd = new Command("threads").description("List and manage conversation threads");
16268
16356
  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) => {
16269
- const cfg = requireConfig14();
16357
+ const cfg = requireConfig13();
16270
16358
  const client = createTypedClient(cfg);
16271
16359
  const input = {
16272
16360
  __testing_scope__: undefined,
@@ -16305,7 +16393,7 @@ function threadsCommand() {
16305
16393
  return;
16306
16394
  }
16307
16395
  if (!threads || threads.length === 0) {
16308
- console.log(import_picocolors24.default.dim("No threads found."));
16396
+ console.log(import_picocolors23.default.dim("No threads found."));
16309
16397
  return;
16310
16398
  }
16311
16399
  printTable(["ID", "Title", "Status", "Created"], threads.map((t2) => [
@@ -16316,7 +16404,7 @@ function threadsCommand() {
16316
16404
  ]), { rawData: threads });
16317
16405
  });
16318
16406
  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) => {
16319
- const cfg = requireConfig14();
16407
+ const cfg = requireConfig13();
16320
16408
  const client = createTypedClient(cfg);
16321
16409
  const [thread, messagesData] = await Promise.all([
16322
16410
  client.thread.get.query(id),
@@ -16330,10 +16418,10 @@ function threadsCommand() {
16330
16418
  return;
16331
16419
  }
16332
16420
  if (!thread) {
16333
- console.log(import_picocolors24.default.dim("Thread not found."));
16421
+ console.log(import_picocolors23.default.dim("Thread not found."));
16334
16422
  return;
16335
16423
  }
16336
- console.log(import_picocolors24.default.bold(thread.generated_title ?? "(untitled thread)"));
16424
+ console.log(import_picocolors23.default.bold(thread.generated_title ?? "(untitled thread)"));
16337
16425
  printInfo([
16338
16426
  ["ID", thread.id],
16339
16427
  ["Created", formatDate(thread.created_at)],
@@ -16344,13 +16432,13 @@ function threadsCommand() {
16344
16432
  if (messages && messages.length > 0) {
16345
16433
  const allMessages = messages.flatMap((group) => group.messages);
16346
16434
  console.log(`
16347
- ${import_picocolors24.default.bold("Messages")} (${allMessages.length})`);
16348
- console.log(import_picocolors24.default.dim("─".repeat(60)));
16435
+ ${import_picocolors23.default.bold("Messages")} (${allMessages.length})`);
16436
+ console.log(import_picocolors23.default.dim("─".repeat(60)));
16349
16437
  for (const msg of allMessages) {
16350
16438
  const role = msg.author_role ?? "unknown";
16351
16439
  const date = formatDate(msg.created_at);
16352
16440
  console.log(`
16353
- ${import_picocolors24.default.bold(role)} ${import_picocolors24.default.dim(date)}`);
16441
+ ${import_picocolors23.default.bold(role)} ${import_picocolors23.default.dim(date)}`);
16354
16442
  if (msg.body) {
16355
16443
  console.log(truncate(msg.body, 500));
16356
16444
  }
@@ -16358,14 +16446,86 @@ ${import_picocolors24.default.bold(role)} ${import_picocolors24.default.dim(date
16358
16446
  }
16359
16447
  });
16360
16448
  cmd.command("archive").description("Archive a thread").argument("<id>", "Thread ID").option("--json", "Output as JSON").action(async (id, opts) => {
16361
- const cfg = requireConfig14();
16449
+ const cfg = requireConfig13();
16362
16450
  const client = createTypedClient(cfg);
16363
16451
  await client.thread.archive.mutate({ threadId: id, archived: true });
16364
16452
  if (opts.json) {
16365
16453
  printResult({ success: true, id }, opts);
16366
16454
  return;
16367
16455
  }
16368
- console.log(import_picocolors24.default.green(`Thread ${id.slice(0, 8)} archived.`));
16456
+ console.log(import_picocolors23.default.green(`Thread ${id.slice(0, 8)} archived.`));
16457
+ });
16458
+ return cmd;
16459
+ }
16460
+
16461
+ // src/commands/topics.ts
16462
+ init_commander();
16463
+ init_trpc();
16464
+ var import_picocolors24 = __toESM(require_picocolors(), 1);
16465
+ function requireConfig14() {
16466
+ const cfg = requireLoginConfig();
16467
+ if (!cfg.space_id) {
16468
+ console.error(import_picocolors24.default.red("Missing space config. Run 'dosu setup' to reconfigure."));
16469
+ process.exit(1);
16470
+ }
16471
+ return cfg;
16472
+ }
16473
+ async function getKnowledgeStoreId4(client, spaceId) {
16474
+ const store = await client.knowledgeStore.getBySpaceId.query({
16475
+ space_id: spaceId
16476
+ });
16477
+ if (!store) {
16478
+ console.error(import_picocolors24.default.red("No knowledge store found for this deployment."));
16479
+ process.exit(1);
16480
+ }
16481
+ return store.id;
16482
+ }
16483
+ function topicsCommand() {
16484
+ const cmd = new Command("topics").alias("tags").description("Browse Topics in your knowledge base");
16485
+ cmd.command("list").description("List all topics").option("--json", "Output as JSON").action(async (opts) => {
16486
+ const cfg = requireConfig14();
16487
+ const client = createTypedClient(cfg);
16488
+ const ksId = await getKnowledgeStoreId4(client, cfg.space_id);
16489
+ const topics = await client.topic.listTopicsByKnowledgeStore.query({
16490
+ knowledge_store_id: ksId
16491
+ });
16492
+ if (opts.json) {
16493
+ printResult(topics, opts);
16494
+ return;
16495
+ }
16496
+ if (!topics || topics.length === 0) {
16497
+ console.log(import_picocolors24.default.dim("No topics found."));
16498
+ return;
16499
+ }
16500
+ printTable(["ID", "Name", "Description"], topics.map((t2) => [
16501
+ t2.topic_id.slice(0, 8),
16502
+ t2.name,
16503
+ t2.description ?? "—"
16504
+ ]), { rawData: topics });
16505
+ });
16506
+ 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) => {
16507
+ const cfg = requireConfig14();
16508
+ const client = createTypedClient(cfg);
16509
+ const ksId = await getKnowledgeStoreId4(client, cfg.space_id);
16510
+ const result = await client.topic.getPagesByTopicId.query({
16511
+ knowledge_store_id: ksId,
16512
+ topic_id: topicId,
16513
+ searchTerm: opts.search,
16514
+ limit: Number.parseInt(opts.limit ?? "10", 10)
16515
+ });
16516
+ const pages = result.data;
16517
+ if (opts.json) {
16518
+ printResult(pages, opts);
16519
+ return;
16520
+ }
16521
+ if (!pages || pages.length === 0) {
16522
+ console.log(import_picocolors24.default.dim("No pages found with this topic."));
16523
+ return;
16524
+ }
16525
+ printTable(["ID", "Title"], pages.map((p2) => [
16526
+ p2.id.slice(0, 8),
16527
+ p2.title ?? "(untitled)"
16528
+ ]), { rawData: pages });
16369
16529
  });
16370
16530
  return cmd;
16371
16531
  }
@@ -16380,8 +16540,8 @@ init_skill_update_check();
16380
16540
  init_config();
16381
16541
  init_logger();
16382
16542
  var import_picocolors25 = __toESM(require_picocolors(), 1);
16383
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "node:fs";
16384
- import { join as join27 } from "node:path";
16543
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
16544
+ import { join as join28 } from "node:path";
16385
16545
  var CACHE_FILENAME3 = "update-check.json";
16386
16546
  var CHECK_INTERVAL_MS3 = 24 * 60 * 60 * 1000;
16387
16547
  var FETCH_TIMEOUT_MS3 = 5000;
@@ -16404,12 +16564,12 @@ function isNewerVersion(latest, current) {
16404
16564
  return false;
16405
16565
  }
16406
16566
  function getCachePath3() {
16407
- return join27(getConfigDir(), CACHE_FILENAME3);
16567
+ return join28(getConfigDir(), CACHE_FILENAME3);
16408
16568
  }
16409
16569
  function readCache() {
16410
16570
  try {
16411
16571
  const path3 = getCachePath3();
16412
- if (!existsSync16(path3))
16572
+ if (!existsSync18(path3))
16413
16573
  return null;
16414
16574
  const data = JSON.parse(readFileSync15(path3, "utf-8"));
16415
16575
  if (typeof data.lastCheck === "number" && typeof data.latestVersion === "string") {
@@ -16423,10 +16583,10 @@ function readCache() {
16423
16583
  function writeCache(cache) {
16424
16584
  try {
16425
16585
  const dir = getConfigDir();
16426
- if (!existsSync16(dir)) {
16427
- mkdirSync8(dir, { recursive: true, mode: 448 });
16586
+ if (!existsSync18(dir)) {
16587
+ mkdirSync9(dir, { recursive: true, mode: 448 });
16428
16588
  }
16429
- writeFileSync7(getCachePath3(), JSON.stringify(cache), { mode: 384 });
16589
+ writeFileSync8(getCachePath3(), JSON.stringify(cache), { mode: 384 });
16430
16590
  } catch {}
16431
16591
  }
16432
16592
  async function fetchLatestVersion() {
@@ -16697,7 +16857,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
16697
16857
  program2.addCommand(reviewCommand());
16698
16858
  program2.addCommand(sourcesCommand());
16699
16859
  program2.addCommand(suggestCommand());
16700
- program2.addCommand(tagsCommand());
16860
+ program2.addCommand(topicsCommand());
16701
16861
  program2.addCommand(threadsCommand());
16702
16862
  program2.addCommand(skillCommand());
16703
16863
  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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosu/cli",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "type": "module",
5
5
  "description": "Dosu CLI - Manage MCP servers for AI tools",
6
6
  "license": "MIT",