@dosu/cli 0.18.1 → 0.19.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 +342 -151
  2. package/package.json +1 -1
package/bin/dosu.js CHANGED
@@ -4548,7 +4548,7 @@ var init_dist4 = __esm(() => {
4548
4548
  function getVersionString() {
4549
4549
  return `v${VERSION}`;
4550
4550
  }
4551
- var VERSION = "0.18.1";
4551
+ var VERSION = "0.19.0";
4552
4552
 
4553
4553
  // src/debug/logger.ts
4554
4554
  import {
@@ -5444,6 +5444,45 @@ var init_dist6 = __esm(() => {
5444
5444
  J2 = `${import_picocolors7.default.gray(o)} `;
5445
5445
  });
5446
5446
 
5447
+ // src/hooks/prompts.ts
5448
+ var exports_prompts = {};
5449
+ __export(exports_prompts, {
5450
+ buildReadyEnvelope: () => buildReadyEnvelope,
5451
+ STOP_PREFIX: () => STOP_PREFIX,
5452
+ SAVE_NUDGE: () => SAVE_NUDGE,
5453
+ LOOKUP_STARTED_NOTE: () => LOOKUP_STARTED_NOTE
5454
+ });
5455
+ function buildReadyEnvelope(context, saveRecommended = false) {
5456
+ const trimmed = context.trim();
5457
+ const blocks = [];
5458
+ if (trimmed) {
5459
+ blocks.push([
5460
+ "Dosu knowledge context for this task:",
5461
+ "",
5462
+ trimmed,
5463
+ "",
5464
+ "How to use this:",
5465
+ "- Use it quietly to answer faster and with fewer broad searches. Keep working normally.",
5466
+ "- This is the known likely path, not the full boundary. Before finalizing, verify adjacent " + "public/API entrypoints and edge cases that this route map may not cover.",
5467
+ "- Attribution: this context is your team's existing knowledge (docs, past PRs, discussions) " + "surfaced by Dosu — not the live code you're reading. For each main finding it speaks to, " + "add one brief cited note on how the team's knowledge relates: if it agrees, note it in a " + "line and cite the source; if it differs, flag it as worth confirming and reconcile before " + "finalizing; if it adds something the code alone wouldn't tell you (a rationale, a gotcha, " + "a past decision), surface it, cited.",
5468
+ "- Surface this at each main finding the context speaks to — but skip the ones it is " + "genuinely silent on, and never invent agreement or disagreement. Keep each note brief and " + 'next to the finding; do not open with "Dosu told me…", do not add a closing praise ' + "paragraph, and do not reference any ticket or lookup id."
5469
+ ].join(`
5470
+ `));
5471
+ }
5472
+ if (saveRecommended) {
5473
+ blocks.push(SAVE_NUDGE);
5474
+ }
5475
+ return blocks.join(`
5476
+
5477
+ `);
5478
+ }
5479
+ var LOOKUP_STARTED_NOTE, STOP_PREFIX, SAVE_NUDGE;
5480
+ var init_prompts = __esm(() => {
5481
+ LOOKUP_STARTED_NOTE = "Dosu started a background knowledge lookup for this request. Keep working normally — " + "do not wait or pause for it. If Dosu context arrives during this session, fold it in " + "quietly to work faster and avoid wrong assumptions. Mention Dosu when its context " + "helps you.";
5482
+ STOP_PREFIX = "Dosu knowledge finished after your last action. Re-check your current conclusion against " + "it, then continue or finish — do not redo work it merely confirms.";
5483
+ SAVE_NUDGE = "Dosu has little or no existing knowledge on this topic. If you uncover something durable — " + "a non-obvious mechanism, a gotcha, or a route through the code — save it with the Dosu " + "`save_topic` tool when you finish, so future sessions start ahead. Only save genuinely " + "reusable knowledge; skip trivial or one-off details.";
5484
+ });
5485
+
5447
5486
  // src/hooks/ticket-client.ts
5448
5487
  var exports_ticket_client = {};
5449
5488
  __export(exports_ticket_client, {
@@ -5494,45 +5533,6 @@ var init_ticket_client = __esm(() => {
5494
5533
  };
5495
5534
  });
5496
5535
 
5497
- // src/hooks/prompts.ts
5498
- var exports_prompts = {};
5499
- __export(exports_prompts, {
5500
- buildReadyEnvelope: () => buildReadyEnvelope,
5501
- STOP_PREFIX: () => STOP_PREFIX,
5502
- SAVE_NUDGE: () => SAVE_NUDGE,
5503
- LOOKUP_STARTED_NOTE: () => LOOKUP_STARTED_NOTE
5504
- });
5505
- function buildReadyEnvelope(context, saveRecommended = false) {
5506
- const trimmed = context.trim();
5507
- const blocks = [];
5508
- if (trimmed) {
5509
- blocks.push([
5510
- "Dosu knowledge context for this task:",
5511
- "",
5512
- trimmed,
5513
- "",
5514
- "How to use this:",
5515
- "- Use it quietly to answer faster and with fewer broad searches. Keep working normally.",
5516
- "- This is the known likely path, not the full boundary. Before finalizing, verify adjacent " + "public/API entrypoints and edge cases that this route map may not cover.",
5517
- "- Attribution: this context is your team's existing knowledge (docs, past PRs, discussions) " + "surfaced by Dosu — not the live code you're reading. For each main finding it speaks to, " + "add one brief cited note on how the team's knowledge relates: if it agrees, note it in a " + "line and cite the source; if it differs, flag it as worth confirming and reconcile before " + "finalizing; if it adds something the code alone wouldn't tell you (a rationale, a gotcha, " + "a past decision), surface it, cited.",
5518
- "- Surface this at each main finding the context speaks to — but skip the ones it is " + "genuinely silent on, and never invent agreement or disagreement. Keep each note brief and " + 'next to the finding; do not open with "Dosu told me…", do not add a closing praise ' + "paragraph, and do not reference any ticket or lookup id."
5519
- ].join(`
5520
- `));
5521
- }
5522
- if (saveRecommended) {
5523
- blocks.push(SAVE_NUDGE);
5524
- }
5525
- return blocks.join(`
5526
-
5527
- `);
5528
- }
5529
- var LOOKUP_STARTED_NOTE, STOP_PREFIX, SAVE_NUDGE;
5530
- var init_prompts = __esm(() => {
5531
- LOOKUP_STARTED_NOTE = "Dosu started a background knowledge lookup for this request. Keep working normally — " + "do not wait or pause for it. If Dosu context arrives during this session, fold it in " + "quietly to work faster and avoid wrong assumptions. Mention Dosu when its context " + "helps you.";
5532
- STOP_PREFIX = "Dosu knowledge finished after your last action. Re-check your current conclusion against " + "it, then continue or finish — do not redo work it merely confirms.";
5533
- SAVE_NUDGE = "Dosu has little or no existing knowledge on this topic. If you uncover something durable — " + "a non-obvious mechanism, a gotcha, or a route through the code — save it with the Dosu " + "`save_topic` tool when you finish, so future sessions start ahead. Only save genuinely " + "reusable knowledge; skip trivial or one-off details.";
5534
- });
5535
-
5536
5536
  // src/agent/output.ts
5537
5537
  var exports_output = {};
5538
5538
  __export(exports_output, {
@@ -6344,6 +6344,135 @@ var init_claude_code = __esm(() => {
6344
6344
  };
6345
6345
  });
6346
6346
 
6347
+ // src/hooks/codex.ts
6348
+ var exports_codex = {};
6349
+ __export(exports_codex, {
6350
+ removeCodexHooks: () => removeCodexHooks,
6351
+ installCodexHooks: () => installCodexHooks,
6352
+ inspectCodexHooks: () => inspectCodexHooks,
6353
+ codexHooksPath: () => codexHooksPath,
6354
+ codexHookCommand: () => codexHookCommand
6355
+ });
6356
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
6357
+ import { join as join5 } from "node:path";
6358
+ function codexHooksPath(dir) {
6359
+ return join5(dir, ".codex", "hooks.json");
6360
+ }
6361
+ function codexHookCommand(subcommand) {
6362
+ return `dosu hooks ${subcommand} --agent codex`;
6363
+ }
6364
+ function dosuGroup2(event) {
6365
+ return {
6366
+ hooks: [
6367
+ {
6368
+ type: "command",
6369
+ command: codexHookCommand(EVENT_SUBCOMMAND2[event]),
6370
+ timeout: EVENT_TIMEOUT_SEC[event],
6371
+ statusMessage: EVENT_STATUS_MESSAGE[event]
6372
+ }
6373
+ ]
6374
+ };
6375
+ }
6376
+ function readHooksFileOrThrow(path) {
6377
+ if (!existsSync6(path))
6378
+ return {};
6379
+ const raw = readFileSync7(path, "utf-8").trim();
6380
+ if (!raw)
6381
+ return {};
6382
+ try {
6383
+ return JSON.parse(raw);
6384
+ } catch {
6385
+ throw new Error(`refusing to modify ${path}: file exists but is not valid JSON`);
6386
+ }
6387
+ }
6388
+ function installCodexHooks(configPath, opts = {}) {
6389
+ const config = readHooksFileOrThrow(configPath);
6390
+ const events = DEFAULT_HOOK_EVENTS.filter((e2) => e2 !== "Stop" || opts.stop !== false);
6391
+ const hooks = typeof config.hooks === "object" && config.hooks ? config.hooks : {};
6392
+ for (const event of events) {
6393
+ const groups = Array.isArray(hooks[event]) ? hooks[event] : [];
6394
+ const kept = groups.filter((g2) => !isDosuGroup(g2));
6395
+ kept.push(dosuGroup2(event));
6396
+ hooks[event] = kept;
6397
+ }
6398
+ for (const event of DEFAULT_HOOK_EVENTS) {
6399
+ if (!events.includes(event) && Array.isArray(hooks[event])) {
6400
+ const kept = hooks[event].filter((g2) => !isDosuGroup(g2));
6401
+ if (kept.length > 0)
6402
+ hooks[event] = kept;
6403
+ else
6404
+ delete hooks[event];
6405
+ }
6406
+ }
6407
+ config.hooks = hooks;
6408
+ saveJSONConfig(configPath, config);
6409
+ return { events: [...events] };
6410
+ }
6411
+ function removeCodexHooks(configPath) {
6412
+ if (!existsSync6(configPath))
6413
+ return { removed: false };
6414
+ let config;
6415
+ try {
6416
+ config = readHooksFileOrThrow(configPath);
6417
+ } catch {
6418
+ return { removed: false };
6419
+ }
6420
+ const hooks = config.hooks;
6421
+ if (typeof hooks !== "object" || !hooks)
6422
+ return { removed: false };
6423
+ let removed = false;
6424
+ for (const event of Object.keys(hooks)) {
6425
+ if (!Array.isArray(hooks[event]))
6426
+ continue;
6427
+ const kept = hooks[event].filter((g2) => !isDosuGroup(g2));
6428
+ if (kept.length !== hooks[event].length) {
6429
+ removed = true;
6430
+ if (kept.length > 0)
6431
+ hooks[event] = kept;
6432
+ else
6433
+ delete hooks[event];
6434
+ }
6435
+ }
6436
+ if (removed)
6437
+ saveJSONConfig(configPath, config);
6438
+ return { removed };
6439
+ }
6440
+ function inspectCodexHooks(configPath) {
6441
+ if (!existsSync6(configPath))
6442
+ return { fileExists: false, events: [] };
6443
+ let config;
6444
+ try {
6445
+ config = readHooksFileOrThrow(configPath);
6446
+ } catch {
6447
+ return { fileExists: true, parseError: true, events: [] };
6448
+ }
6449
+ const hooks = config.hooks;
6450
+ if (typeof hooks !== "object" || !hooks)
6451
+ return { fileExists: true, events: [] };
6452
+ const events = Object.keys(hooks).filter((event) => Array.isArray(hooks[event]) && hooks[event].some((g2) => isDosuGroup(g2)));
6453
+ return { fileExists: true, events };
6454
+ }
6455
+ var EVENT_SUBCOMMAND2, EVENT_TIMEOUT_SEC, EVENT_STATUS_MESSAGE;
6456
+ var init_codex = __esm(() => {
6457
+ init_config_helpers();
6458
+ init_claude_code();
6459
+ EVENT_SUBCOMMAND2 = {
6460
+ UserPromptSubmit: "user-prompt-submit",
6461
+ PostToolUse: "post-tool-use",
6462
+ Stop: "stop"
6463
+ };
6464
+ EVENT_TIMEOUT_SEC = {
6465
+ UserPromptSubmit: 10,
6466
+ PostToolUse: 10,
6467
+ Stop: 30
6468
+ };
6469
+ EVENT_STATUS_MESSAGE = {
6470
+ UserPromptSubmit: "Dosu knowledge lookup",
6471
+ PostToolUse: "Dosu knowledge delivery",
6472
+ Stop: "Dosu final knowledge check"
6473
+ };
6474
+ });
6475
+
6347
6476
  // src/insights/insights.ts
6348
6477
  async function buildInsights({
6349
6478
  client,
@@ -7432,17 +7561,17 @@ var init_insights2 = __esm(() => {
7432
7561
  });
7433
7562
 
7434
7563
  // src/version/skill-update-check.ts
7435
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
7436
- import { join as join5 } from "node:path";
7564
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
7565
+ import { join as join6 } from "node:path";
7437
7566
  function getCachePath() {
7438
- return join5(getConfigDir(), CACHE_FILENAME);
7567
+ return join6(getConfigDir(), CACHE_FILENAME);
7439
7568
  }
7440
7569
  function readSkillCache() {
7441
7570
  try {
7442
7571
  const path = getCachePath();
7443
- if (!existsSync6(path))
7572
+ if (!existsSync7(path))
7444
7573
  return null;
7445
- const data = JSON.parse(readFileSync8(path, "utf-8"));
7574
+ const data = JSON.parse(readFileSync9(path, "utf-8"));
7446
7575
  if (typeof data.lastCheck === "number" && typeof data.latestSha === "string" && typeof data.installedSha === "string") {
7447
7576
  return data;
7448
7577
  }
@@ -7454,7 +7583,7 @@ function readSkillCache() {
7454
7583
  function writeSkillCache(cache) {
7455
7584
  try {
7456
7585
  const dir = getConfigDir();
7457
- if (!existsSync6(dir)) {
7586
+ if (!existsSync7(dir)) {
7458
7587
  mkdirSync5(dir, { recursive: true, mode: 448 });
7459
7588
  }
7460
7589
  writeFileSync4(getCachePath(), JSON.stringify(cache), { mode: 384 });
@@ -7616,21 +7745,21 @@ var init_skill = __esm(() => {
7616
7745
  var MCP_PROVIDER_SLUG = "dosu_mcp";
7617
7746
 
7618
7747
  // src/mcp/detect.ts
7619
- import { existsSync as existsSync7 } from "node:fs";
7748
+ import { existsSync as existsSync8 } from "node:fs";
7620
7749
  import { homedir, platform } from "node:os";
7621
- import { join as join6 } from "node:path";
7750
+ import { join as join7 } from "node:path";
7622
7751
  function isInstalled(paths) {
7623
- return paths.some((p2) => existsSync7(expandHome(p2)));
7752
+ return paths.some((p2) => existsSync8(expandHome(p2)));
7624
7753
  }
7625
7754
  function expandHome(path) {
7626
7755
  if (!path.startsWith("~"))
7627
7756
  return path;
7628
- return join6(homedir(), path.slice(1));
7757
+ return join7(homedir(), path.slice(1));
7629
7758
  }
7630
7759
  function appSupportDir() {
7631
7760
  switch (platform()) {
7632
7761
  case "darwin": {
7633
- return join6(homedir(), "Library", "Application Support");
7762
+ return join7(homedir(), "Library", "Application Support");
7634
7763
  }
7635
7764
  case "win32": {
7636
7765
  return process.env.APPDATA ?? "";
@@ -7639,7 +7768,7 @@ function appSupportDir() {
7639
7768
  const xdg = process.env.XDG_CONFIG_HOME;
7640
7769
  if (xdg)
7641
7770
  return xdg;
7642
- return join6(homedir(), ".config");
7771
+ return join7(homedir(), ".config");
7643
7772
  }
7644
7773
  }
7645
7774
  }
@@ -7720,7 +7849,7 @@ var init_antigravity = __esm(() => {
7720
7849
  });
7721
7850
 
7722
7851
  // src/mcp/providers/claude.ts
7723
- import { join as join7 } from "node:path";
7852
+ import { join as join8 } from "node:path";
7724
7853
  var ClaudeProvider = () => createJSONProvider({
7725
7854
  providerName: "Claude Code",
7726
7855
  providerID: "claude",
@@ -7729,22 +7858,22 @@ var ClaudeProvider = () => createJSONProvider({
7729
7858
  paths: ["~/.claude"],
7730
7859
  globalPath: "~/.claude.json",
7731
7860
  topKey: "mcpServers",
7732
- localConfigPath: (cwd) => join7(cwd, ".mcp.json")
7861
+ localConfigPath: (cwd) => join8(cwd, ".mcp.json")
7733
7862
  });
7734
7863
  var init_claude = __esm(() => {
7735
7864
  init_base();
7736
7865
  });
7737
7866
 
7738
7867
  // src/mcp/providers/claude-desktop.ts
7739
- import { join as join8 } from "node:path";
7868
+ import { join as join9 } from "node:path";
7740
7869
  var ClaudeDesktopProvider = () => ({
7741
7870
  name: () => "Claude Desktop",
7742
7871
  id: () => "claude-desktop",
7743
7872
  supportsLocal: () => false,
7744
7873
  priority: () => 2,
7745
- detectPaths: () => [join8(appSupportDir(), "Claude")],
7746
- isInstalled: () => isInstalled([join8(appSupportDir(), "Claude")]),
7747
- globalConfigPath: () => join8(appSupportDir(), "Claude", "claude_desktop_config.json"),
7874
+ detectPaths: () => [join9(appSupportDir(), "Claude")],
7875
+ isInstalled: () => isInstalled([join9(appSupportDir(), "Claude")]),
7876
+ globalConfigPath: () => join9(appSupportDir(), "Claude", "claude_desktop_config.json"),
7748
7877
  isConfigured: () => false,
7749
7878
  install() {
7750
7879
  throw new Error("this tool only supports local (stdio) servers and cannot be configured for remote MCP");
@@ -7758,14 +7887,14 @@ var init_claude_desktop = __esm(() => {
7758
7887
  });
7759
7888
 
7760
7889
  // src/mcp/providers/cline.ts
7761
- import { join as join9 } from "node:path";
7762
- var extensionDir = () => join9(appSupportDir(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev"), ClineProvider = () => createJSONProvider({
7890
+ import { join as join10 } from "node:path";
7891
+ var extensionDir = () => join10(appSupportDir(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev"), ClineProvider = () => createJSONProvider({
7763
7892
  providerName: "Cline",
7764
7893
  providerID: "cline",
7765
7894
  local: false,
7766
7895
  priorityValue: 11,
7767
7896
  paths: [extensionDir()],
7768
- globalPath: join9(extensionDir(), "settings", "cline_mcp_settings.json"),
7897
+ globalPath: join10(extensionDir(), "settings", "cline_mcp_settings.json"),
7769
7898
  topKey: "mcpServers",
7770
7899
  buildServer: (cfg) => ({
7771
7900
  url: mcpURL(cfg.deployment_id),
@@ -7781,7 +7910,7 @@ var init_cline = __esm(() => {
7781
7910
  });
7782
7911
 
7783
7912
  // src/mcp/providers/cline-cli.ts
7784
- import { join as join10 } from "node:path";
7913
+ import { join as join11 } from "node:path";
7785
7914
  function clineDir() {
7786
7915
  return process.env.CLINE_DIR ?? expandHome("~/.cline");
7787
7916
  }
@@ -7791,7 +7920,7 @@ var ClineCliProvider = () => createJSONProvider({
7791
7920
  local: false,
7792
7921
  priorityValue: 12,
7793
7922
  paths: [clineDir()],
7794
- globalPath: join10(clineDir(), "data", "settings", "cline_mcp_settings.json"),
7923
+ globalPath: join11(clineDir(), "data", "settings", "cline_mcp_settings.json"),
7795
7924
  topKey: "mcpServers",
7796
7925
  buildServer: (cfg) => ({
7797
7926
  url: mcpURL(cfg.deployment_id),
@@ -7807,20 +7936,20 @@ var init_cline_cli = __esm(() => {
7807
7936
  });
7808
7937
 
7809
7938
  // src/mcp/providers/codex.ts
7810
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
7811
- import { join as join11 } from "node:path";
7939
+ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
7940
+ import { join as join12 } from "node:path";
7812
7941
  function codexHome() {
7813
7942
  return process.env.CODEX_HOME ?? expandHome("~/.codex");
7814
7943
  }
7815
7944
  function getConfigPath2(global) {
7816
7945
  if (global)
7817
- return join11(codexHome(), "config.toml");
7818
- return join11(process.cwd(), ".codex", "config.toml");
7946
+ return join12(codexHome(), "config.toml");
7947
+ return join12(process.cwd(), ".codex", "config.toml");
7819
7948
  }
7820
7949
  function readTOML(path) {
7821
- if (!existsSync8(path))
7950
+ if (!existsSync9(path))
7822
7951
  return "";
7823
- return readFileSync9(path, "utf-8");
7952
+ return readFileSync10(path, "utf-8");
7824
7953
  }
7825
7954
  function writeTOML(path, content) {
7826
7955
  writeSecureFile(path, content);
@@ -7878,9 +8007,9 @@ var CodexProvider = () => ({
7878
8007
  priority: () => 8,
7879
8008
  detectPaths: () => ["~/.codex"],
7880
8009
  isInstalled: () => isInstalled(["~/.codex"]),
7881
- globalConfigPath: () => join11(codexHome(), "config.toml"),
8010
+ globalConfigPath: () => join12(codexHome(), "config.toml"),
7882
8011
  isConfigured: () => {
7883
- const content = readTOML(join11(codexHome(), "config.toml"));
8012
+ const content = readTOML(join12(codexHome(), "config.toml"));
7884
8013
  return content.includes("[mcp_servers.dosu]");
7885
8014
  },
7886
8015
  install(cfg, global) {
@@ -7895,17 +8024,17 @@ var CodexProvider = () => ({
7895
8024
  writeTOML(path, removeDosuFromTOML(content));
7896
8025
  }
7897
8026
  });
7898
- var init_codex = __esm(() => {
8027
+ var init_codex2 = __esm(() => {
7899
8028
  init_config();
7900
8029
  init_config_helpers();
7901
8030
  init_detect();
7902
8031
  });
7903
8032
 
7904
8033
  // src/mcp/providers/copilot.ts
7905
- import { join as join12 } from "node:path";
8034
+ import { join as join13 } from "node:path";
7906
8035
  function globalPath() {
7907
8036
  if (process.env.XDG_CONFIG_HOME) {
7908
- return join12(process.env.XDG_CONFIG_HOME, "mcp-config.json");
8037
+ return join13(process.env.XDG_CONFIG_HOME, "mcp-config.json");
7909
8038
  }
7910
8039
  return expandHome("~/.copilot/mcp-config.json");
7911
8040
  }
@@ -7936,7 +8065,7 @@ var CopilotProvider = () => ({
7936
8065
  };
7937
8066
  installJSONServer(globalPath(), "mcpServers", server);
7938
8067
  } else {
7939
- const configPath = join12(process.cwd(), ".vscode", "mcp.json");
8068
+ const configPath = join13(process.cwd(), ".vscode", "mcp.json");
7940
8069
  const server = {
7941
8070
  type: "http",
7942
8071
  url,
@@ -7949,7 +8078,7 @@ var CopilotProvider = () => ({
7949
8078
  if (global) {
7950
8079
  removeJSONServer(globalPath(), "mcpServers");
7951
8080
  } else {
7952
- const configPath = join12(process.cwd(), ".vscode", "mcp.json");
8081
+ const configPath = join13(process.cwd(), ".vscode", "mcp.json");
7953
8082
  removeJSONServer(configPath, "servers");
7954
8083
  }
7955
8084
  }
@@ -7961,7 +8090,7 @@ var init_copilot = __esm(() => {
7961
8090
  });
7962
8091
 
7963
8092
  // src/mcp/providers/cursor.ts
7964
- import { join as join13 } from "node:path";
8093
+ import { join as join14 } from "node:path";
7965
8094
  var CursorProvider = () => createJSONProvider({
7966
8095
  providerName: "Cursor",
7967
8096
  providerID: "cursor",
@@ -7974,7 +8103,7 @@ var CursorProvider = () => createJSONProvider({
7974
8103
  url: mcpURL(cfg.deployment_id),
7975
8104
  headers: mcpHeaders(cfg.api_key)
7976
8105
  }),
7977
- localConfigPath: (cwd) => join13(cwd, ".cursor", "mcp.json")
8106
+ localConfigPath: (cwd) => join14(cwd, ".cursor", "mcp.json")
7978
8107
  });
7979
8108
  var init_cursor = __esm(() => {
7980
8109
  init_config_helpers();
@@ -7982,7 +8111,7 @@ var init_cursor = __esm(() => {
7982
8111
  });
7983
8112
 
7984
8113
  // src/mcp/providers/gemini.ts
7985
- import { join as join14 } from "node:path";
8114
+ import { join as join15 } from "node:path";
7986
8115
  var GeminiProvider = () => createJSONProvider({
7987
8116
  providerName: "Gemini CLI",
7988
8117
  providerID: "gemini",
@@ -7991,7 +8120,7 @@ var GeminiProvider = () => createJSONProvider({
7991
8120
  paths: ["~/.gemini"],
7992
8121
  globalPath: "~/.gemini/settings.json",
7993
8122
  topKey: "mcpServers",
7994
- localConfigPath: (cwd) => join14(cwd, ".gemini", "settings.json")
8123
+ localConfigPath: (cwd) => join15(cwd, ".gemini", "settings.json")
7995
8124
  });
7996
8125
  var init_gemini = __esm(() => {
7997
8126
  init_base();
@@ -8041,14 +8170,14 @@ var init_manual = __esm(() => {
8041
8170
  });
8042
8171
 
8043
8172
  // src/mcp/providers/mcporter.ts
8044
- import { existsSync as existsSync9 } from "node:fs";
8045
- import { join as join15 } from "node:path";
8173
+ import { existsSync as existsSync10 } from "node:fs";
8174
+ import { join as join16 } from "node:path";
8046
8175
  function resolveGlobalConfigPath() {
8047
8176
  const jsonPath = expandHome("~/.mcporter/mcporter.json");
8048
- if (existsSync9(jsonPath))
8177
+ if (existsSync10(jsonPath))
8049
8178
  return jsonPath;
8050
8179
  const jsoncPath = expandHome("~/.mcporter/mcporter.jsonc");
8051
- if (existsSync9(jsoncPath))
8180
+ if (existsSync10(jsoncPath))
8052
8181
  return jsoncPath;
8053
8182
  return jsonPath;
8054
8183
  }
@@ -8069,7 +8198,7 @@ var MCPorterProvider = () => ({
8069
8198
  globalConfigPath: () => resolveGlobalConfigPath(),
8070
8199
  isConfigured: () => isJSONKeyConfigured(resolveGlobalConfigPath(), "mcpServers"),
8071
8200
  install(cfg, global) {
8072
- const configPath = global ? resolveGlobalConfigPath() : join15(process.cwd(), "config", "mcporter.json");
8201
+ const configPath = global ? resolveGlobalConfigPath() : join16(process.cwd(), "config", "mcporter.json");
8073
8202
  const server = {
8074
8203
  type: "http",
8075
8204
  url: mcpEndpoint4(cfg),
@@ -8078,7 +8207,7 @@ var MCPorterProvider = () => ({
8078
8207
  installJSONServer(configPath, "mcpServers", server);
8079
8208
  },
8080
8209
  remove(global) {
8081
- const configPath = global ? resolveGlobalConfigPath() : join15(process.cwd(), "config", "mcporter.json");
8210
+ const configPath = global ? resolveGlobalConfigPath() : join16(process.cwd(), "config", "mcporter.json");
8082
8211
  removeJSONServer(configPath, "mcpServers");
8083
8212
  }
8084
8213
  });
@@ -8089,7 +8218,7 @@ var init_mcporter = __esm(() => {
8089
8218
  });
8090
8219
 
8091
8220
  // src/mcp/providers/opencode.ts
8092
- import { join as join16 } from "node:path";
8221
+ import { join as join17 } from "node:path";
8093
8222
  var OpenCodeProvider = () => createJSONProvider({
8094
8223
  providerName: "OpenCode",
8095
8224
  providerID: "opencode",
@@ -8104,7 +8233,7 @@ var OpenCodeProvider = () => createJSONProvider({
8104
8233
  enabled: true,
8105
8234
  headers: mcpHeaders(cfg.api_key)
8106
8235
  }),
8107
- localConfigPath: (cwd) => join16(cwd, "opencode.json")
8236
+ localConfigPath: (cwd) => join17(cwd, "opencode.json")
8108
8237
  });
8109
8238
  var init_opencode = __esm(() => {
8110
8239
  init_config_helpers();
@@ -8112,16 +8241,16 @@ var init_opencode = __esm(() => {
8112
8241
  });
8113
8242
 
8114
8243
  // src/mcp/providers/vscode.ts
8115
- import { join as join17 } from "node:path";
8244
+ import { join as join18 } from "node:path";
8116
8245
  var VSCodeProvider = () => createJSONProvider({
8117
8246
  providerName: "VS Code",
8118
8247
  providerID: "vscode",
8119
8248
  local: true,
8120
8249
  priorityValue: 6,
8121
- paths: [join17(appSupportDir(), "Code")],
8122
- globalPath: join17(appSupportDir(), "Code", "User", "mcp.json"),
8250
+ paths: [join18(appSupportDir(), "Code")],
8251
+ globalPath: join18(appSupportDir(), "Code", "User", "mcp.json"),
8123
8252
  topKey: "servers",
8124
- localConfigPath: (cwd) => join17(cwd, ".vscode", "mcp.json")
8253
+ localConfigPath: (cwd) => join18(cwd, ".vscode", "mcp.json")
8125
8254
  });
8126
8255
  var init_vscode = __esm(() => {
8127
8256
  init_detect();
@@ -8130,14 +8259,14 @@ var init_vscode = __esm(() => {
8130
8259
 
8131
8260
  // src/mcp/providers/windsurf.ts
8132
8261
  import { homedir as homedir2 } from "node:os";
8133
- import { join as join18 } from "node:path";
8262
+ import { join as join19 } from "node:path";
8134
8263
  var WindsurfProvider = () => createJSONProvider({
8135
8264
  providerName: "Windsurf",
8136
8265
  providerID: "windsurf",
8137
8266
  local: false,
8138
8267
  priorityValue: 9,
8139
- paths: [join18(homedir2(), ".codeium", "windsurf")],
8140
- globalPath: join18(homedir2(), ".codeium", "windsurf", "mcp_config.json"),
8268
+ paths: [join19(homedir2(), ".codeium", "windsurf")],
8269
+ globalPath: join19(homedir2(), ".codeium", "windsurf", "mcp_config.json"),
8141
8270
  topKey: "mcpServers"
8142
8271
  });
8143
8272
  var init_windsurf = __esm(() => {
@@ -8146,12 +8275,12 @@ var init_windsurf = __esm(() => {
8146
8275
 
8147
8276
  // src/mcp/providers/zed.ts
8148
8277
  import { platform as platform2 } from "node:os";
8149
- import { join as join19 } from "node:path";
8278
+ import { join as join20 } from "node:path";
8150
8279
  function zedConfigDir() {
8151
8280
  const os = platform2();
8152
8281
  if (os === "darwin" || os === "win32")
8153
- return join19(appSupportDir(), "Zed");
8154
- return join19(appSupportDir(), "zed");
8282
+ return join20(appSupportDir(), "Zed");
8283
+ return join20(appSupportDir(), "zed");
8155
8284
  }
8156
8285
  var ZedProvider = () => createJSONProvider({
8157
8286
  providerName: "Zed",
@@ -8159,7 +8288,7 @@ var ZedProvider = () => createJSONProvider({
8159
8288
  local: true,
8160
8289
  priorityValue: 10,
8161
8290
  paths: [zedConfigDir()],
8162
- globalPath: join19(zedConfigDir(), "settings.json"),
8291
+ globalPath: join20(zedConfigDir(), "settings.json"),
8163
8292
  topKey: "context_servers",
8164
8293
  buildServer: (cfg) => ({
8165
8294
  source: "custom",
@@ -8167,7 +8296,7 @@ var ZedProvider = () => createJSONProvider({
8167
8296
  url: mcpURL(cfg.deployment_id),
8168
8297
  headers: mcpHeaders(cfg.api_key)
8169
8298
  }),
8170
- localConfigPath: (cwd) => join19(cwd, ".zed", "settings.json")
8299
+ localConfigPath: (cwd) => join20(cwd, ".zed", "settings.json")
8171
8300
  });
8172
8301
  var init_zed = __esm(() => {
8173
8302
  init_config_helpers();
@@ -8211,7 +8340,7 @@ var init_providers = __esm(() => {
8211
8340
  init_claude_desktop();
8212
8341
  init_cline();
8213
8342
  init_cline_cli();
8214
- init_codex();
8343
+ init_codex2();
8215
8344
  init_copilot();
8216
8345
  init_cursor();
8217
8346
  init_gemini();
@@ -10760,8 +10889,8 @@ __export(exports_flow2, {
10760
10889
  isStdioOnly: () => isStdioOnly
10761
10890
  });
10762
10891
  import { randomUUID } from "node:crypto";
10763
- import { existsSync as existsSync10 } from "node:fs";
10764
- import { join as join20 } from "node:path";
10892
+ import { existsSync as existsSync11 } from "node:fs";
10893
+ import { join as join21 } from "node:path";
10765
10894
  async function runSetup(opts = {}) {
10766
10895
  const onboardingRunID = randomUUID();
10767
10896
  logger.info("setup", `Setup flow started${opts.deploymentID ? ` deployment=${opts.deploymentID}` : ""}${opts.mode ? ` mode=${opts.mode}` : ""}`);
@@ -10933,7 +11062,7 @@ async function runSetup(opts = {}) {
10933
11062
  showTryItOutPrompt({
10934
11063
  mode: cfg.mode,
10935
11064
  docsImported: choices.connectGitHub && githubOnboardingDone,
10936
- hasAgentsMd: existsSync10(join20(process.cwd(), "AGENTS.md"))
11065
+ hasAgentsMd: existsSync11(join21(process.cwd(), "AGENTS.md"))
10937
11066
  });
10938
11067
  }
10939
11068
  if (mcpCompleted || skillCompleted || docsImported) {
@@ -11427,8 +11556,8 @@ var init_flow2 = __esm(() => {
11427
11556
  });
11428
11557
 
11429
11558
  // src/commands/insights.ts
11430
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
11431
- import { dirname as dirname3, join as join21 } from "node:path";
11559
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
11560
+ import { dirname as dirname3, join as join22 } from "node:path";
11432
11561
  function shuffled(arr) {
11433
11562
  const copy2 = [...arr];
11434
11563
  for (let i = copy2.length - 1;i > 0; i--) {
@@ -11532,17 +11661,17 @@ function makeAskFn(cfg) {
11532
11661
  };
11533
11662
  }
11534
11663
  function insightsDir() {
11535
- return join21(getConfigDir(), "insights");
11664
+ return join22(getConfigDir(), "insights");
11536
11665
  }
11537
11666
  function reportPath(timestamp) {
11538
11667
  const dir = insightsDir();
11539
11668
  if (!timestamp)
11540
- return join21(dir, "latest.html");
11669
+ return join22(dir, "latest.html");
11541
11670
  const iso = timestamp.toISOString().slice(0, 19).replace(/:/g, "-");
11542
- return join21(dir, `report-${iso}Z.html`);
11671
+ return join22(dir, `report-${iso}Z.html`);
11543
11672
  }
11544
11673
  function pruneOldReports(dir, keepN) {
11545
- if (!existsSync11(dir))
11674
+ if (!existsSync12(dir))
11546
11675
  return;
11547
11676
  let entries;
11548
11677
  try {
@@ -11554,7 +11683,7 @@ function pruneOldReports(dir, keepN) {
11554
11683
  const reports = entries.filter((f) => REPORT_FILE_PATTERN.test(f)).sort().reverse();
11555
11684
  for (const f of reports.slice(keepN)) {
11556
11685
  try {
11557
- unlinkSync(join21(dir, f));
11686
+ unlinkSync(join22(dir, f));
11558
11687
  } catch (err) {
11559
11688
  logger.debug("insights", `failed to prune ${f}: ${err instanceof Error ? err.message : err}`);
11560
11689
  }
@@ -11627,7 +11756,7 @@ function defaultRunner(cfg) {
11627
11756
  render: renderHTML,
11628
11757
  writeFile: (path2, content) => {
11629
11758
  const dir = dirname3(path2);
11630
- if (!existsSync11(dir))
11759
+ if (!existsSync12(dir))
11631
11760
  mkdirSync6(dir, { recursive: true, mode: 448 });
11632
11761
  writeFileSync5(path2, content, { mode: 384 });
11633
11762
  },
@@ -12292,7 +12421,7 @@ var init_flow3 = __esm(() => {
12292
12421
  // src/cli/cli.ts
12293
12422
  init_esm();
12294
12423
  init_client();
12295
- import { readFileSync as readFileSync11, unlinkSync as unlinkSync2 } from "node:fs";
12424
+ import { readFileSync as readFileSync12, unlinkSync as unlinkSync2 } from "node:fs";
12296
12425
 
12297
12426
  // src/commands/analytics.ts
12298
12427
  init_esm();
@@ -12977,7 +13106,8 @@ ${page.body}`);
12977
13106
  init_esm();
12978
13107
  init_config();
12979
13108
  init_logger();
12980
- import { readFileSync as readFileSync7 } from "node:fs";
13109
+ init_prompts();
13110
+ import { readFileSync as readFileSync8 } from "node:fs";
12981
13111
  import { basename } from "node:path";
12982
13112
 
12983
13113
  // src/hooks/state.ts
@@ -13016,7 +13146,8 @@ var COOLDOWN_DEFAULT_MS = 3000;
13016
13146
  var TTL_DEFAULT_MS = 10 * 60 * 1000;
13017
13147
  var STOP_WAIT_DEFAULT_MS = 8000;
13018
13148
  var STOP_POLL_DEFAULT_MS = 1000;
13019
- var SUPPORTED_AGENTS = ["claude-code"];
13149
+ var SUPPORTED_AGENTS = ["claude-code", "codex"];
13150
+ var DEFAULT_AGENT = "claude-code";
13020
13151
  function cooldownMs() {
13021
13152
  const n = Number.parseInt(process.env.DOSU_HOOK_CHECK_COOLDOWN_MS ?? "", 10);
13022
13153
  return Number.isFinite(n) && n >= 0 ? n : COOLDOWN_DEFAULT_MS;
@@ -13049,13 +13180,17 @@ function errMsg(err) {
13049
13180
  function repoSlug(cwd) {
13050
13181
  return cwd ? basename(cwd) : undefined;
13051
13182
  }
13052
- async function runUserPromptSubmit(input, now = Date.now()) {
13183
+ async function runUserPromptSubmit(input, now = Date.now(), agent = DEFAULT_AGENT) {
13053
13184
  const sessionId = input.session_id;
13054
13185
  if (!sessionId)
13055
13186
  return;
13056
13187
  const prompt = (input.prompt ?? "").trim();
13057
13188
  if (!prompt)
13058
13189
  return;
13190
+ if (prompt.startsWith(STOP_PREFIX)) {
13191
+ logger.debug("hooks", "submit skipped reason=own-stop-envelope");
13192
+ return;
13193
+ }
13059
13194
  const existing = loadState(sessionId);
13060
13195
  if (existing && existing.status === "pending" && now <= existing.expiresAt) {
13061
13196
  logger.debug("hooks", `submit sid=${sid8(sessionId)} reuse tid=${existing.ticketId}`);
@@ -13072,7 +13207,7 @@ async function runUserPromptSubmit(input, now = Date.now()) {
13072
13207
  try {
13073
13208
  resp = await tc.requestCreateTicket(cfg, {
13074
13209
  deployment_id: cfg.deployment_id,
13075
- agent: "claude-code",
13210
+ agent,
13076
13211
  session_id: sessionId,
13077
13212
  turn_id: input.turn_id ?? String(now),
13078
13213
  prompt,
@@ -13219,7 +13354,7 @@ function runStatus(input, opts, now = Date.now()) {
13219
13354
  console.log(`Status: ${data.status}`);
13220
13355
  console.log(`Delivered: ${data.delivered ? "yes" : "no"}`);
13221
13356
  }
13222
- async function runHookEntrypoint(event, raw, now = Date.now()) {
13357
+ async function runHookEntrypoint(event, raw, now = Date.now(), agent = DEFAULT_AGENT) {
13223
13358
  let input = {};
13224
13359
  try {
13225
13360
  input = raw.trim() ? JSON.parse(raw) : {};
@@ -13228,7 +13363,7 @@ async function runHookEntrypoint(event, raw, now = Date.now()) {
13228
13363
  }
13229
13364
  try {
13230
13365
  if (event === "user-prompt-submit")
13231
- await runUserPromptSubmit(input, now);
13366
+ await runUserPromptSubmit(input, now, agent);
13232
13367
  else if (event === "post-tool-use")
13233
13368
  await runPostToolUse(input, now);
13234
13369
  else
@@ -13241,7 +13376,7 @@ async function runHookEntrypoint(event, raw, now = Date.now()) {
13241
13376
  }
13242
13377
  function readStdinRaw() {
13243
13378
  try {
13244
- return readFileSync7(0, "utf8");
13379
+ return readFileSync8(0, "utf8");
13245
13380
  } catch {
13246
13381
  return "";
13247
13382
  }
@@ -13254,12 +13389,13 @@ async function emitErrorLine(step, reason, agentNextSteps) {
13254
13389
  emitError2({ step, reason, agent_next_steps: agentNextSteps });
13255
13390
  }
13256
13391
  async function runInstall(agent, opts) {
13257
- if (agent !== "claude-code") {
13392
+ if (!SUPPORTED_AGENTS.includes(agent)) {
13258
13393
  process.exitCode = 2;
13394
+ const supported = SUPPORTED_AGENTS.join(", ");
13259
13395
  if (opts.json) {
13260
- await emitErrorLine("hooks-install", "unsupported_agent", `Only 'claude-code' is supported.`);
13396
+ await emitErrorLine("hooks-install", "unsupported_agent", `Supported agents: ${supported}.`);
13261
13397
  } else {
13262
- console.error(`Unsupported agent '${agent}'. Supported agents: claude-code.`);
13398
+ console.error(`Unsupported agent '${agent}'. Supported agents: ${supported}.`);
13263
13399
  }
13264
13400
  return;
13265
13401
  }
@@ -13272,13 +13408,28 @@ async function runInstall(agent, opts) {
13272
13408
  }
13273
13409
  return;
13274
13410
  }
13275
- const { installClaudeHooks: installClaudeHooks2, claudeLocalSettingsPath: claudeLocalSettingsPath2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
13276
- const configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
13277
13411
  try {
13412
+ if (agent === "codex") {
13413
+ const { installCodexHooks: installCodexHooks2, codexHooksPath: codexHooksPath2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
13414
+ const configPath2 = codexHooksPath2(resolveDir(opts.dir));
13415
+ const { events: events2 } = installCodexHooks2(configPath2, { stop: opts.stop });
13416
+ if (opts.json) {
13417
+ const { emitStep: emitStep2 } = await Promise.resolve().then(() => exports_output);
13418
+ emitStep2({ step: "hooks-install", agent, path: configPath2, events: events2 });
13419
+ } else {
13420
+ console.log(`✓ Installed Dosu hooks for Codex (${events2.join(", ")}).`);
13421
+ console.log(` → ${configPath2}`);
13422
+ console.log(" One-time step: open Codex in this project and run /hooks to review and");
13423
+ console.log(" trust the Dosu hooks — Codex skips untrusted hooks by design.");
13424
+ }
13425
+ return;
13426
+ }
13427
+ const { installClaudeHooks: installClaudeHooks2, claudeLocalSettingsPath: claudeLocalSettingsPath2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
13428
+ const configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
13278
13429
  const { events } = installClaudeHooks2(configPath, { stop: opts.stop });
13279
13430
  if (opts.json) {
13280
13431
  const { emitStep: emitStep2 } = await Promise.resolve().then(() => exports_output);
13281
- emitStep2({ step: "hooks-install", path: configPath, events });
13432
+ emitStep2({ step: "hooks-install", agent, path: configPath, events });
13282
13433
  } else {
13283
13434
  console.log(`✓ Installed Dosu hooks for Claude Code (${events.join(", ")}).`);
13284
13435
  console.log(` → ${configPath}`);
@@ -13294,18 +13445,27 @@ async function runInstall(agent, opts) {
13294
13445
  }
13295
13446
  }
13296
13447
  async function runUninstall(agent, opts) {
13297
- if (agent !== "claude-code") {
13448
+ if (!SUPPORTED_AGENTS.includes(agent)) {
13298
13449
  process.exitCode = 2;
13450
+ const supported = SUPPORTED_AGENTS.join(", ");
13299
13451
  if (opts.json) {
13300
- await emitErrorLine("hooks-uninstall", "unsupported_agent", "Only 'claude-code' is supported.");
13452
+ await emitErrorLine("hooks-uninstall", "unsupported_agent", `Supported agents: ${supported}.`);
13301
13453
  } else {
13302
- console.error(`Unsupported agent '${agent}'. Supported agents: claude-code.`);
13454
+ console.error(`Unsupported agent '${agent}'. Supported agents: ${supported}.`);
13303
13455
  }
13304
13456
  return;
13305
13457
  }
13306
- const { removeClaudeHooks: removeClaudeHooks2, claudeLocalSettingsPath: claudeLocalSettingsPath2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
13307
- const configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
13308
- const { removed } = removeClaudeHooks2(configPath);
13458
+ let configPath;
13459
+ let removed;
13460
+ if (agent === "codex") {
13461
+ const { removeCodexHooks: removeCodexHooks2, codexHooksPath: codexHooksPath2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
13462
+ configPath = codexHooksPath2(resolveDir(opts.dir));
13463
+ ({ removed } = removeCodexHooks2(configPath));
13464
+ } else {
13465
+ const { removeClaudeHooks: removeClaudeHooks2, claudeLocalSettingsPath: claudeLocalSettingsPath2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
13466
+ configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
13467
+ ({ removed } = removeClaudeHooks2(configPath));
13468
+ }
13309
13469
  if (opts.json) {
13310
13470
  const { emitStep: emitStep2 } = await Promise.resolve().then(() => exports_output);
13311
13471
  emitStep2({ step: "hooks-uninstall", path: configPath, removed });
@@ -13318,12 +13478,16 @@ async function runUninstall(agent, opts) {
13318
13478
  async function collectDoctorChecks(opts) {
13319
13479
  const checks = [];
13320
13480
  const { claudeLocalSettingsPath: claudeLocalSettingsPath2, inspectClaudeHooks: inspectClaudeHooks2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
13481
+ const { codexHooksPath: codexHooksPath2, inspectCodexHooks: inspectCodexHooks2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
13321
13482
  const configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
13322
13483
  const inspection = inspectClaudeHooks2(configPath);
13484
+ const codexPath = codexHooksPath2(resolveDir(opts.dir));
13485
+ const codex = inspectCodexHooks2(codexPath);
13486
+ const codexInstalled = codex.events.includes("UserPromptSubmit") && codex.events.includes("PostToolUse");
13323
13487
  if (!inspection.fileExists) {
13324
13488
  checks.push({
13325
13489
  name: "config",
13326
- status: "fail",
13490
+ status: codexInstalled ? "warn" : "fail",
13327
13491
  detail: `not found: ${configPath} (run 'dosu hooks install claude-code')`
13328
13492
  });
13329
13493
  } else if (inspection.parseError) {
@@ -13342,10 +13506,32 @@ async function collectDoctorChecks(opts) {
13342
13506
  } else {
13343
13507
  checks.push({
13344
13508
  name: "hooks",
13345
- status: "fail",
13509
+ status: codexInstalled ? "warn" : "fail",
13346
13510
  detail: "UserPromptSubmit + PostToolUse not both installed"
13347
13511
  });
13348
13512
  }
13513
+ if (codex.fileExists) {
13514
+ if (codex.parseError) {
13515
+ checks.push({ name: "codex-config", status: "fail", detail: `invalid JSON: ${codexPath}` });
13516
+ } else if (codexInstalled) {
13517
+ checks.push({
13518
+ name: "codex-config",
13519
+ status: "ok",
13520
+ detail: `installed: ${codex.events.join(", ")} (${codexPath})`
13521
+ });
13522
+ checks.push({
13523
+ name: "codex-trust",
13524
+ status: "warn",
13525
+ detail: "if hooks aren't firing, run /hooks inside Codex to review + trust them (one-time)"
13526
+ });
13527
+ } else {
13528
+ checks.push({
13529
+ name: "codex-config",
13530
+ status: "fail",
13531
+ detail: "UserPromptSubmit + PostToolUse not both installed"
13532
+ });
13533
+ }
13534
+ }
13349
13535
  const cfg = loadConfig();
13350
13536
  if (isAuthenticated(cfg)) {
13351
13537
  checks.push({ name: "auth", status: "ok", detail: "logged in" });
@@ -13400,15 +13586,15 @@ async function runDoctor(opts) {
13400
13586
  }
13401
13587
  }
13402
13588
  function hooksCommand() {
13403
- const cmd = new Command("hooks").description("Dosu knowledge-injection hooks for Claude Code");
13404
- cmd.command("user-prompt-submit").description("Hook entrypoint: create a knowledge ticket on prompt submit").action(async () => {
13405
- await runHookEntrypoint("user-prompt-submit", readStdinRaw());
13589
+ const cmd = new Command("hooks").description("Dosu knowledge-injection hooks for coding agents (Claude Code, Codex)");
13590
+ cmd.command("user-prompt-submit").description("Hook entrypoint: create a knowledge ticket on prompt submit").option("--agent <agent>", "Calling agent for ticket attribution", DEFAULT_AGENT).action(async (opts) => {
13591
+ await runHookEntrypoint("user-prompt-submit", readStdinRaw(), Date.now(), opts.agent);
13406
13592
  });
13407
- cmd.command("post-tool-use").description("Hook entrypoint: poll and inject ready knowledge once").action(async () => {
13408
- await runHookEntrypoint("post-tool-use", readStdinRaw());
13593
+ cmd.command("post-tool-use").description("Hook entrypoint: poll and inject ready knowledge once").option("--agent <agent>", "Calling agent for ticket attribution", DEFAULT_AGENT).action(async (opts) => {
13594
+ await runHookEntrypoint("post-tool-use", readStdinRaw(), Date.now(), opts.agent);
13409
13595
  });
13410
- cmd.command("stop").description("Hook entrypoint: last-chance knowledge delivery when the agent stops").action(async () => {
13411
- await runHookEntrypoint("stop", readStdinRaw());
13596
+ cmd.command("stop").description("Hook entrypoint: last-chance knowledge delivery when the agent stops").option("--agent <agent>", "Calling agent for ticket attribution", DEFAULT_AGENT).action(async (opts) => {
13597
+ await runHookEntrypoint("stop", readStdinRaw(), Date.now(), opts.agent);
13412
13598
  });
13413
13599
  cmd.command("status").description("Show the active Dosu knowledge ticket for this session").option("--json", "Output JSON", false).action((opts) => {
13414
13600
  let input = {};
@@ -13426,16 +13612,21 @@ function hooksCommand() {
13426
13612
  "",
13427
13613
  "Examples:",
13428
13614
  " $ dosu hooks install claude-code",
13615
+ " $ dosu hooks install codex",
13429
13616
  " $ dosu hooks install claude-code --no-stop",
13430
- " $ dosu hooks install claude-code --dir ./my-project"
13617
+ " $ dosu hooks install codex --dir ./my-project",
13618
+ "",
13619
+ "Codex requires a one-time trust step after install: open Codex in the",
13620
+ "project and run /hooks to review and trust the Dosu hooks."
13431
13621
  ].join(`
13432
13622
  `)).action((agent, opts) => runInstall(agent, opts));
13433
13623
  cmd.command("uninstall").description("Remove Dosu hooks from a coding agent's local config (Dosu-owned entries only)").addArgument(new Argument("<agent>", "coding agent to clean up").choices(SUPPORTED_AGENTS)).option("--scope <scope>", "Config scope (local only)", "local").option("--dir <path>", "Project root (defaults to current directory)").option("--json", "Emit machine-readable JSON", false).addHelpText("after", [
13434
13624
  "",
13435
13625
  `Supported agents: ${SUPPORTED_AGENTS.join(", ")}`,
13436
13626
  "",
13437
- "Example:",
13438
- " $ dosu hooks uninstall claude-code"
13627
+ "Examples:",
13628
+ " $ dosu hooks uninstall claude-code",
13629
+ " $ dosu hooks uninstall codex"
13439
13630
  ].join(`
13440
13631
  `)).action((agent, opts) => runUninstall(agent, opts));
13441
13632
  cmd.command("doctor").description("Diagnose Dosu hook config, auth, deployment, and backend connectivity").option("--dir <path>", "Project root (defaults to current directory)").option("--json", "Emit machine-readable JSON", false).action((opts) => runDoctor(opts));
@@ -14271,8 +14462,8 @@ init_skill_update_check();
14271
14462
  init_config();
14272
14463
  init_logger();
14273
14464
  var import_picocolors24 = __toESM(require_picocolors(), 1);
14274
- import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
14275
- import { join as join22 } from "node:path";
14465
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
14466
+ import { join as join23 } from "node:path";
14276
14467
  var CACHE_FILENAME2 = "update-check.json";
14277
14468
  var CHECK_INTERVAL_MS2 = 24 * 60 * 60 * 1000;
14278
14469
  var FETCH_TIMEOUT_MS2 = 5000;
@@ -14295,14 +14486,14 @@ function isNewerVersion(latest, current) {
14295
14486
  return false;
14296
14487
  }
14297
14488
  function getCachePath2() {
14298
- return join22(getConfigDir(), CACHE_FILENAME2);
14489
+ return join23(getConfigDir(), CACHE_FILENAME2);
14299
14490
  }
14300
14491
  function readCache() {
14301
14492
  try {
14302
14493
  const path2 = getCachePath2();
14303
- if (!existsSync12(path2))
14494
+ if (!existsSync13(path2))
14304
14495
  return null;
14305
- const data = JSON.parse(readFileSync10(path2, "utf-8"));
14496
+ const data = JSON.parse(readFileSync11(path2, "utf-8"));
14306
14497
  if (typeof data.lastCheck === "number" && typeof data.latestVersion === "string") {
14307
14498
  return data;
14308
14499
  }
@@ -14314,7 +14505,7 @@ function readCache() {
14314
14505
  function writeCache(cache) {
14315
14506
  try {
14316
14507
  const dir = getConfigDir();
14317
- if (!existsSync12(dir)) {
14508
+ if (!existsSync13(dir)) {
14318
14509
  mkdirSync7(dir, { recursive: true, mode: 448 });
14319
14510
  }
14320
14511
  writeFileSync6(getCachePath2(), JSON.stringify(cache), { mode: 384 });
@@ -14604,7 +14795,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
14604
14795
  if (opts.tail !== undefined) {
14605
14796
  const n = typeof opts.tail === "string" ? parseInt(opts.tail, 10) || 50 : 50;
14606
14797
  try {
14607
- const content = readFileSync11(logPath, "utf-8");
14798
+ const content = readFileSync12(logPath, "utf-8");
14608
14799
  const lines = content.split(`
14609
14800
  `);
14610
14801
  console.log(lines.slice(-n).join(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosu/cli",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "description": "Dosu CLI - Manage MCP servers for AI tools",
6
6
  "license": "MIT",