caveat-cli 0.17.8 → 0.18.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.
package/dist/index.js CHANGED
@@ -76,7 +76,7 @@ import {
76
76
  userPromptSubmitReminderText,
77
77
  writeDigestMarker,
78
78
  writeUserConfigPatch
79
- } from "./chunk-ZBHCOA6R.js";
79
+ } from "./chunk-KAULUIC4.js";
80
80
 
81
81
  // ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
82
82
  var require_code = __commonJS({
@@ -455,11 +455,11 @@ var require_codegen = __commonJS({
455
455
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
456
456
  return `${varKind} ${this.name}${rhs};` + _n;
457
457
  }
458
- optimizeNames(names, constants4) {
458
+ optimizeNames(names, constants5) {
459
459
  if (!names[this.name.str])
460
460
  return;
461
461
  if (this.rhs)
462
- this.rhs = optimizeExpr(this.rhs, names, constants4);
462
+ this.rhs = optimizeExpr(this.rhs, names, constants5);
463
463
  return this;
464
464
  }
465
465
  get names() {
@@ -476,10 +476,10 @@ var require_codegen = __commonJS({
476
476
  render({ _n }) {
477
477
  return `${this.lhs} = ${this.rhs};` + _n;
478
478
  }
479
- optimizeNames(names, constants4) {
479
+ optimizeNames(names, constants5) {
480
480
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
481
481
  return;
482
- this.rhs = optimizeExpr(this.rhs, names, constants4);
482
+ this.rhs = optimizeExpr(this.rhs, names, constants5);
483
483
  return this;
484
484
  }
485
485
  get names() {
@@ -540,8 +540,8 @@ var require_codegen = __commonJS({
540
540
  optimizeNodes() {
541
541
  return `${this.code}` ? this : void 0;
542
542
  }
543
- optimizeNames(names, constants4) {
544
- this.code = optimizeExpr(this.code, names, constants4);
543
+ optimizeNames(names, constants5) {
544
+ this.code = optimizeExpr(this.code, names, constants5);
545
545
  return this;
546
546
  }
547
547
  get names() {
@@ -570,12 +570,12 @@ var require_codegen = __commonJS({
570
570
  }
571
571
  return nodes.length > 0 ? this : void 0;
572
572
  }
573
- optimizeNames(names, constants4) {
573
+ optimizeNames(names, constants5) {
574
574
  const { nodes } = this;
575
575
  let i = nodes.length;
576
576
  while (i--) {
577
577
  const n = nodes[i];
578
- if (n.optimizeNames(names, constants4))
578
+ if (n.optimizeNames(names, constants5))
579
579
  continue;
580
580
  subtractNames(names, n.names);
581
581
  nodes.splice(i, 1);
@@ -628,12 +628,12 @@ var require_codegen = __commonJS({
628
628
  return void 0;
629
629
  return this;
630
630
  }
631
- optimizeNames(names, constants4) {
631
+ optimizeNames(names, constants5) {
632
632
  var _a3;
633
- this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants4);
634
- if (!(super.optimizeNames(names, constants4) || this.else))
633
+ this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants5);
634
+ if (!(super.optimizeNames(names, constants5) || this.else))
635
635
  return;
636
- this.condition = optimizeExpr(this.condition, names, constants4);
636
+ this.condition = optimizeExpr(this.condition, names, constants5);
637
637
  return this;
638
638
  }
639
639
  get names() {
@@ -656,10 +656,10 @@ var require_codegen = __commonJS({
656
656
  render(opts) {
657
657
  return `for(${this.iteration})` + super.render(opts);
658
658
  }
659
- optimizeNames(names, constants4) {
660
- if (!super.optimizeNames(names, constants4))
659
+ optimizeNames(names, constants5) {
660
+ if (!super.optimizeNames(names, constants5))
661
661
  return;
662
- this.iteration = optimizeExpr(this.iteration, names, constants4);
662
+ this.iteration = optimizeExpr(this.iteration, names, constants5);
663
663
  return this;
664
664
  }
665
665
  get names() {
@@ -695,10 +695,10 @@ var require_codegen = __commonJS({
695
695
  render(opts) {
696
696
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
697
697
  }
698
- optimizeNames(names, constants4) {
699
- if (!super.optimizeNames(names, constants4))
698
+ optimizeNames(names, constants5) {
699
+ if (!super.optimizeNames(names, constants5))
700
700
  return;
701
- this.iterable = optimizeExpr(this.iterable, names, constants4);
701
+ this.iterable = optimizeExpr(this.iterable, names, constants5);
702
702
  return this;
703
703
  }
704
704
  get names() {
@@ -740,11 +740,11 @@ var require_codegen = __commonJS({
740
740
  (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
741
741
  return this;
742
742
  }
743
- optimizeNames(names, constants4) {
743
+ optimizeNames(names, constants5) {
744
744
  var _a3, _b;
745
- super.optimizeNames(names, constants4);
746
- (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants4);
747
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants4);
745
+ super.optimizeNames(names, constants5);
746
+ (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants5);
747
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants5);
748
748
  return this;
749
749
  }
750
750
  get names() {
@@ -1045,7 +1045,7 @@ var require_codegen = __commonJS({
1045
1045
  function addExprNames(names, from) {
1046
1046
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
1047
1047
  }
1048
- function optimizeExpr(expr, names, constants4) {
1048
+ function optimizeExpr(expr, names, constants5) {
1049
1049
  if (expr instanceof code_1.Name)
1050
1050
  return replaceName(expr);
1051
1051
  if (!canOptimize(expr))
@@ -1060,14 +1060,14 @@ var require_codegen = __commonJS({
1060
1060
  return items;
1061
1061
  }, []));
1062
1062
  function replaceName(n) {
1063
- const c = constants4[n.str];
1063
+ const c = constants5[n.str];
1064
1064
  if (c === void 0 || names[n.str] !== 1)
1065
1065
  return n;
1066
1066
  delete names[n.str];
1067
1067
  return c;
1068
1068
  }
1069
1069
  function canOptimize(e) {
1070
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants4[c.str] !== void 0);
1070
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants5[c.str] !== void 0);
1071
1071
  }
1072
1072
  }
1073
1073
  function subtractNames(names, from) {
@@ -6889,8 +6889,8 @@ var stdoutLogger = {
6889
6889
 
6890
6890
  // src/commands/init.ts
6891
6891
  import { spawnSync as spawnSync4 } from "node:child_process";
6892
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync, renameSync, rmdirSync, writeFileSync as writeFileSync3 } from "node:fs";
6893
- import { dirname as dirname3, join as join8 } from "node:path";
6892
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync, renameSync, rmdirSync, writeFileSync as writeFileSync3 } from "node:fs";
6893
+ import { dirname as dirname3, join as join9 } from "node:path";
6894
6894
 
6895
6895
  // src/claudeInstall.ts
6896
6896
  import { spawnSync } from "node:child_process";
@@ -7512,6 +7512,150 @@ function detectCodexHookInstallation(codexHome) {
7512
7512
  };
7513
7513
  }
7514
7514
 
7515
+ // src/cursorInstall.ts
7516
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "node:fs";
7517
+ import { join as join5 } from "node:path";
7518
+ import { constants as constants3 } from "node:fs";
7519
+ var TIMEOUT_SEC = 10;
7520
+ function hookCommand3(nodePath, cliScriptPath, event) {
7521
+ return `${quoteCommandPath(nodePath)} ${quoteCommandPath(cliScriptPath)} cursor-hook ${event}`;
7522
+ }
7523
+ function eventCommandFragment2(event) {
7524
+ return `cursor-hook ${event}`;
7525
+ }
7526
+ function isCaveatCursorHookCommand(actual, event) {
7527
+ const lower = actual.toLowerCase();
7528
+ return lower.includes("caveat") && actual.includes(eventCommandFragment2(event));
7529
+ }
7530
+ function readHooks2(path) {
7531
+ if (!existsSync4(path)) return { version: 1, hooks: {} };
7532
+ const parsed = JSON.parse(readFileSync4(path, "utf-8"));
7533
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
7534
+ throw new Error(`${path} \u306F object \u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059`);
7535
+ }
7536
+ const file2 = parsed;
7537
+ if (file2.hooks == null) file2.hooks = {};
7538
+ if (typeof file2.hooks !== "object" || Array.isArray(file2.hooks)) {
7539
+ throw new Error(`${path} \u306E hooks \u306F object \u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059`);
7540
+ }
7541
+ return file2;
7542
+ }
7543
+ function listFor(file2, event) {
7544
+ const current = file2.hooks?.[event];
7545
+ if (Array.isArray(current)) return current;
7546
+ return [];
7547
+ }
7548
+ function isSameCommand(actual, expected) {
7549
+ const left = commandTokens(actual);
7550
+ const right = commandTokens(expected);
7551
+ return left !== null && right !== null && left.length === right.length && left.every((token, index) => token === right[index]);
7552
+ }
7553
+ function upsertHook3(file2, event, command, subcommand) {
7554
+ file2.hooks ??= {};
7555
+ const list = listFor(file2, event);
7556
+ for (const entry of list) {
7557
+ if (typeof entry.command !== "string") continue;
7558
+ if (isSameCommand(entry.command, command) && entry.timeout === TIMEOUT_SEC) {
7559
+ return "unchanged";
7560
+ }
7561
+ if (isSameCommand(entry.command, command) || isCaveatCursorHookCommand(entry.command, subcommand)) {
7562
+ entry.command = command;
7563
+ entry.timeout = TIMEOUT_SEC;
7564
+ file2.hooks[event] = list;
7565
+ return "added";
7566
+ }
7567
+ }
7568
+ list.push({ command, timeout: TIMEOUT_SEC });
7569
+ file2.hooks[event] = list;
7570
+ return "added";
7571
+ }
7572
+ function removeHook3(file2, event, command, subcommand) {
7573
+ const list = listFor(file2, event);
7574
+ if (list.length === 0) return false;
7575
+ const kept = list.filter((entry) => {
7576
+ if (typeof entry.command !== "string") return true;
7577
+ return !(isSameCommand(entry.command, command) || isCaveatCursorHookCommand(entry.command, subcommand));
7578
+ });
7579
+ if (kept.length === list.length) return false;
7580
+ if (!file2.hooks) return true;
7581
+ if (kept.length > 0) file2.hooks[event] = kept;
7582
+ else delete file2.hooks[event];
7583
+ return true;
7584
+ }
7585
+ function hasCaveatHook2(file2, event, fragment) {
7586
+ return listFor(file2, event).some(
7587
+ (entry) => typeof entry.command === "string" && entry.command.includes(fragment)
7588
+ );
7589
+ }
7590
+ function persist(opts, file2, changed) {
7591
+ const hooksPath = join5(opts.cursorDir, "hooks.json");
7592
+ if (opts.dryRun) {
7593
+ opts.logger.info(`[dry-run] would write Cursor hooks to ${hooksPath}`);
7594
+ return void 0;
7595
+ }
7596
+ if (!changed) return void 0;
7597
+ mkdirSync2(opts.cursorDir, { recursive: true });
7598
+ file2.version = 1;
7599
+ const backup = writeJsonWithBackup(hooksPath, file2);
7600
+ return backup || void 0;
7601
+ }
7602
+ function installCursorHooks(opts) {
7603
+ const hooksPath = join5(opts.cursorDir, "hooks.json");
7604
+ const file2 = readHooks2(hooksPath);
7605
+ const promptCmd = hookCommand3(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
7606
+ const toolCmd = hookCommand3(opts.nodePath, opts.cliScriptPath, "post-tool-use");
7607
+ const stopCmd = hookCommand3(opts.nodePath, opts.cliScriptPath, "stop");
7608
+ const hooks = {
7609
+ beforeSubmitPrompt: upsertHook3(file2, "beforeSubmitPrompt", promptCmd, "user-prompt-submit"),
7610
+ postToolUse: upsertHook3(file2, "postToolUse", toolCmd, "post-tool-use"),
7611
+ postToolUseFailure: upsertHook3(file2, "postToolUseFailure", toolCmd, "post-tool-use"),
7612
+ stop: upsertHook3(file2, "stop", stopCmd, "stop")
7613
+ };
7614
+ const changed = Object.values(hooks).some((status2) => status2 === "added");
7615
+ const backupPath = persist(opts, file2, changed);
7616
+ return { hooks, backupPath };
7617
+ }
7618
+ function uninstallCursorHooks(opts) {
7619
+ const hooksPath = join5(opts.cursorDir, "hooks.json");
7620
+ const file2 = readHooks2(hooksPath);
7621
+ const promptCmd = hookCommand3(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
7622
+ const toolCmd = hookCommand3(opts.nodePath, opts.cliScriptPath, "post-tool-use");
7623
+ const stopCmd = hookCommand3(opts.nodePath, opts.cliScriptPath, "stop");
7624
+ const removed = {
7625
+ beforeSubmitPrompt: removeHook3(file2, "beforeSubmitPrompt", promptCmd, "user-prompt-submit"),
7626
+ postToolUse: removeHook3(file2, "postToolUse", toolCmd, "post-tool-use"),
7627
+ postToolUseFailure: removeHook3(file2, "postToolUseFailure", toolCmd, "post-tool-use"),
7628
+ stop: removeHook3(file2, "stop", stopCmd, "stop")
7629
+ };
7630
+ const changed = Object.values(removed).some(Boolean);
7631
+ const backupPath = persist(opts, file2, changed);
7632
+ return {
7633
+ hooks: {
7634
+ beforeSubmitPrompt: removed.beforeSubmitPrompt ? "added" : "unchanged",
7635
+ postToolUse: removed.postToolUse ? "added" : "unchanged",
7636
+ postToolUseFailure: removed.postToolUseFailure ? "added" : "unchanged",
7637
+ stop: removed.stop ? "added" : "unchanged"
7638
+ },
7639
+ backupPath
7640
+ };
7641
+ }
7642
+ function detectCursorHookInstallation(cursorDir) {
7643
+ const hooksPath = join5(cursorDir, "hooks.json");
7644
+ const file2 = existsSync4(hooksPath) ? readHooks2(hooksPath) : { version: 1, hooks: {} };
7645
+ const hooks = {
7646
+ beforeSubmitPrompt: hasCaveatHook2(file2, "beforeSubmitPrompt", eventCommandFragment2("user-prompt-submit")),
7647
+ postToolUse: hasCaveatHook2(file2, "postToolUse", eventCommandFragment2("post-tool-use")),
7648
+ postToolUseFailure: hasCaveatHook2(file2, "postToolUseFailure", eventCommandFragment2("post-tool-use")),
7649
+ stop: hasCaveatHook2(file2, "stop", eventCommandFragment2("stop"))
7650
+ };
7651
+ const count = Object.values(hooks).filter(Boolean).length;
7652
+ return {
7653
+ installation: count === 0 ? "not-installed" : count === 4 ? "installed" : "partial",
7654
+ hooksPath,
7655
+ hooks
7656
+ };
7657
+ }
7658
+
7515
7659
  // src/ghSetup.ts
7516
7660
  import { spawnSync as spawnSync2 } from "node:child_process";
7517
7661
  import { readSync } from "node:fs";
@@ -7559,8 +7703,8 @@ function askOnce(question) {
7559
7703
  }
7560
7704
 
7561
7705
  // src/nodePath.ts
7562
- import { existsSync as existsSync4, realpathSync as realpathSync2 } from "node:fs";
7563
- import { delimiter, join as join5 } from "node:path";
7706
+ import { existsSync as existsSync5, realpathSync as realpathSync2 } from "node:fs";
7707
+ import { delimiter, join as join6 } from "node:path";
7564
7708
  var defaultRealpath = (path) => realpathSync2.native(path);
7565
7709
  function safeRealpath(path, realpath = defaultRealpath) {
7566
7710
  try {
@@ -7573,7 +7717,7 @@ function resolveHookNodePath({
7573
7717
  env = process.env,
7574
7718
  execPath = process.execPath,
7575
7719
  platform = process.platform,
7576
- exists = existsSync4,
7720
+ exists = existsSync5,
7577
7721
  realpath = defaultRealpath
7578
7722
  } = {}) {
7579
7723
  const execRealpath = safeRealpath(execPath, realpath);
@@ -7581,7 +7725,7 @@ function resolveHookNodePath({
7581
7725
  const names = nodeExecutableNames(platform);
7582
7726
  for (const dir of pathEnv.split(delimiter).filter(Boolean)) {
7583
7727
  for (const name of names) {
7584
- const candidate = join5(dir, name);
7728
+ const candidate = join6(dir, name);
7585
7729
  if (!exists(candidate)) continue;
7586
7730
  const candidateRealpath = safeRealpath(candidate, realpath);
7587
7731
  if (execRealpath && candidateRealpath && candidateRealpath === execRealpath) {
@@ -7593,14 +7737,14 @@ function resolveHookNodePath({
7593
7737
  }
7594
7738
 
7595
7739
  // src/commands/publish.ts
7596
- import { existsSync as existsSync5 } from "node:fs";
7597
- import { join as join7 } from "node:path";
7740
+ import { existsSync as existsSync6 } from "node:fs";
7741
+ import { join as join8 } from "node:path";
7598
7742
 
7599
7743
  // src/commands/codexSidecarAdvisory.ts
7600
7744
  import { spawnSync as spawnSync3 } from "node:child_process";
7601
- import { mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
7745
+ import { mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
7602
7746
  import { tmpdir } from "node:os";
7603
- import { join as join6 } from "node:path";
7747
+ import { join as join7 } from "node:path";
7604
7748
  var DEFAULT_HOOK_SIDECAR_TIMEOUT_MS = 12e4;
7605
7749
  var MAX_HOOK_SIDECAR_TIMEOUT_MS = 24e4;
7606
7750
  function resolveHookSidecarTimeoutMs(raw) {
@@ -7656,11 +7800,11 @@ function runCodexSidecarAdvisory(input) {
7656
7800
  let resultDir;
7657
7801
  let advisory;
7658
7802
  try {
7659
- resultDir = mkdtempSync(join6(tmpdir(), "caveat-codex-advisory-"));
7660
- const resultFile = join6(resultDir, "result.json");
7803
+ resultDir = mkdtempSync(join7(tmpdir(), "caveat-codex-advisory-"));
7804
+ const resultFile = join7(resultDir, "result.json");
7661
7805
  args.push("--save-result", resultFile);
7662
7806
  if (input.additionalContext) {
7663
- const additionalContextFile = join6(resultDir, "hook-signal.json");
7807
+ const additionalContextFile = join7(resultDir, "hook-signal.json");
7664
7808
  writeFileSync2(additionalContextFile, JSON.stringify({ context: [input.additionalContext] }) + "\n", { encoding: "utf-8", mode: 384, flag: "wx" });
7665
7809
  args.push("--additional-context-file", additionalContextFile);
7666
7810
  }
@@ -7688,7 +7832,7 @@ function runCodexSidecarAdvisory(input) {
7688
7832
  message: `sidecar command failed: ${sidecarFailureDetail(detail)}`
7689
7833
  };
7690
7834
  } else {
7691
- const parsed = JSON.parse(readFileSync4(resultFile, "utf-8"));
7835
+ const parsed = JSON.parse(readFileSync5(resultFile, "utf-8"));
7692
7836
  if (parsed.status !== "ok" || typeof parsed.summary !== "string") {
7693
7837
  advisory = {
7694
7838
  status: "failed",
@@ -7756,7 +7900,7 @@ async function runPublish(ctx, opts, dependencies = {}) {
7756
7900
  const isTty = dependencies.isTty ?? (() => Boolean(process.stdin.isTTY));
7757
7901
  const confirm = dependencies.confirm ?? askOnce;
7758
7902
  const projectRoot = process.cwd();
7759
- const hasCodexSidecarConfig = dependencies.hasCodexSidecarConfig ?? ((root) => existsSync5(join7(root, ".codex-sidecar.yml")));
7903
+ const hasCodexSidecarConfig = dependencies.hasCodexSidecarConfig ?? ((root) => existsSync6(join8(root, ".codex-sidecar.yml")));
7760
7904
  const publishAdvisory = hasCodexSidecarConfig(projectRoot) ? (changes) => {
7761
7905
  try {
7762
7906
  return dependencies.runCodexSidecarAdvisory?.(changes, projectRoot) ?? formatCodexSidecarAdvisory(runCodexSidecarAdvisory({
@@ -7813,24 +7957,25 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
7813
7957
  const ghRunner = dependencies.ghRunner ?? runGh;
7814
7958
  let publishTarget = ctx.config.publishTarget;
7815
7959
  let codexHookState = "not-installed";
7960
+ let cursorHookState = "not-installed";
7816
7961
  ensureUserConfig(ctx.userConfigPath);
7817
7962
  ctx.logger.info(`user config: ${ctx.userConfigPath}`);
7818
- if (!existsSync6(ctx.paths.knowledgeRepo)) {
7819
- mkdirSync2(ctx.paths.knowledgeRepo, { recursive: true });
7820
- mkdirSync2(ctx.paths.entriesDir, { recursive: true });
7963
+ if (!existsSync7(ctx.paths.knowledgeRepo)) {
7964
+ mkdirSync3(ctx.paths.knowledgeRepo, { recursive: true });
7965
+ mkdirSync3(ctx.paths.entriesDir, { recursive: true });
7821
7966
  ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
7822
7967
  } else {
7823
7968
  ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
7824
7969
  }
7825
7970
  migrateLegacyCommunityDir(ctx);
7826
- const gitignorePath = join8(ctx.paths.knowledgeRepo, ".gitignore");
7827
- if (!existsSync6(gitignorePath)) {
7971
+ const gitignorePath = join9(ctx.paths.knowledgeRepo, ".gitignore");
7972
+ if (!existsSync7(gitignorePath)) {
7828
7973
  writeFileSync3(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
7829
7974
  ctx.logger.info(`.gitignore created: ${gitignorePath}`);
7830
7975
  }
7831
7976
  if (!opts.dryRun) {
7832
7977
  const dbDir = dirname3(ctx.paths.dbPath);
7833
- if (!existsSync6(dbDir)) mkdirSync2(dbDir, { recursive: true });
7978
+ if (!existsSync7(dbDir)) mkdirSync3(dbDir, { recursive: true });
7834
7979
  const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
7835
7980
  const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
7836
7981
  for (const failure of failures) {
@@ -7944,7 +8089,7 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
7944
8089
  ctx.logger.warn("cannot determine CLI script path; skipping Claude integration");
7945
8090
  } else {
7946
8091
  const result = installClaudeIntegration({
7947
- claudeDir: join8(ctx.userHome, ".claude"),
8092
+ claudeDir: join9(ctx.userHome, ".claude"),
7948
8093
  cliScriptPath,
7949
8094
  nodePath: resolveHookNodePath(),
7950
8095
  dryRun: opts.dryRun,
@@ -7962,7 +8107,7 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
7962
8107
  codexHookState = "skipped";
7963
8108
  ctx.logger.warn("cannot determine CLI script path; skipping Codex hook integration");
7964
8109
  } else {
7965
- const codexHome = join8(ctx.userHome, ".codex");
8110
+ const codexHome = join9(ctx.userHome, ".codex");
7966
8111
  const result = installCodexHooks({
7967
8112
  codexHome,
7968
8113
  cliScriptPath,
@@ -7973,7 +8118,7 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
7973
8118
  if (result.feature === "blocked") {
7974
8119
  codexHookState = "skipped";
7975
8120
  ctx.logger.warn(
7976
- `${result.blockedReason}; preserving explicit consent. Set \`hooks = true\` in ${join8(codexHome, "config.toml")}, then rerun \`caveat init\`.`
8121
+ `${result.blockedReason}; preserving explicit consent. Set \`hooks = true\` in ${join9(codexHome, "config.toml")}, then rerun \`caveat init\`.`
7977
8122
  );
7978
8123
  } else if (opts.dryRun) {
7979
8124
  codexHookState = "skipped";
@@ -7983,7 +8128,35 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }, depende
7983
8128
  }
7984
8129
  }
7985
8130
  }
7986
- reportEnvironmentSummary(ctx, publishTarget, codexHookState, opts.dryRun);
8131
+ if (opts.skipCursorHook) {
8132
+ cursorHookState = "skipped";
8133
+ ctx.logger.info("Cursor hook integration skipped (--skip-cursor-hook)");
8134
+ } else if (existsSync7(join9(ctx.userHome, ".cursor"))) {
8135
+ const cliScriptPath = process.argv[1];
8136
+ if (!cliScriptPath) {
8137
+ cursorHookState = "skipped";
8138
+ ctx.logger.warn("cannot determine CLI script path; skipping Cursor hook integration");
8139
+ } else {
8140
+ const cursorDir = join9(ctx.userHome, ".cursor");
8141
+ const result = installCursorHooks({
8142
+ cursorDir,
8143
+ cliScriptPath,
8144
+ nodePath: resolveHookNodePath(),
8145
+ dryRun: opts.dryRun,
8146
+ logger: ctx.logger
8147
+ });
8148
+ if (opts.dryRun) {
8149
+ cursorHookState = "skipped";
8150
+ ctx.logger.info("[dry-run] would install Cursor hooks");
8151
+ } else {
8152
+ cursorHookState = detectCursorHookInstallation(cursorDir).installation;
8153
+ ctx.logger.info(`Cursor beforeSubmitPrompt hook: ${result.hooks.beforeSubmitPrompt}`);
8154
+ ctx.logger.info(`Cursor postToolUse hook: ${result.hooks.postToolUse}`);
8155
+ ctx.logger.info(`Cursor stop hook: ${result.hooks.stop}`);
8156
+ }
8157
+ }
8158
+ }
8159
+ reportEnvironmentSummary(ctx, publishTarget, codexHookState, cursorHookState, opts.dryRun);
7987
8160
  }
7988
8161
  function detectCodexAvailability() {
7989
8162
  const result = spawnSync4("codex --version", {
@@ -8004,36 +8177,37 @@ function gitOutput(args) {
8004
8177
  const result = spawnSync4("git", args, { encoding: "utf-8" });
8005
8178
  return result.status === 0 && typeof result.stdout === "string" ? result.stdout.trim() : null;
8006
8179
  }
8007
- function reportEnvironmentSummary(ctx, publishTarget, codexHookState, dryRun) {
8180
+ function reportEnvironmentSummary(ctx, publishTarget, codexHookState, cursorHookState, dryRun) {
8008
8181
  const prefix = dryRun ? "[dry-run] would have " : "";
8009
8182
  const isRepo = gitOutput(["-C", ctx.paths.knowledgeRepo, "rev-parse", "--is-inside-work-tree"]) === "true";
8010
8183
  const remote = isRepo ? gitOutput(["-C", ctx.paths.knowledgeRepo, "config", "--get", "remote.origin.url"]) : null;
8011
- const communityCount = existsSync6(ctx.paths.communityDir) ? readdirSync(ctx.paths.communityDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length : 0;
8184
+ const communityCount = existsSync7(ctx.paths.communityDir) ? readdirSync(ctx.paths.communityDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length : 0;
8012
8185
  ctx.logger.info(`${prefix}environment summary:`);
8013
8186
  ctx.logger.info(` own git: ${isRepo ? "repository" : "not initialized"}`);
8014
8187
  ctx.logger.info(` private remote: ${remote || "not configured"}`);
8015
8188
  ctx.logger.info(` publish target: ${publishTarget || "not configured"}`);
8016
8189
  ctx.logger.info(` community sources: ${communityCount}`);
8017
8190
  ctx.logger.info(` codex hook: ${codexHookState}`);
8191
+ ctx.logger.info(` cursor hook: ${cursorHookState}`);
8018
8192
  }
8019
8193
  function errorMessage(err) {
8020
8194
  return err instanceof Error ? err.message : String(err);
8021
8195
  }
8022
8196
  function migrateLegacyCommunityDir(ctx) {
8023
- const legacy = join8(ctx.paths.knowledgeRepo, "community");
8197
+ const legacy = join9(ctx.paths.knowledgeRepo, "community");
8024
8198
  const current = ctx.paths.communityDir;
8025
8199
  if (legacy === current) return;
8026
- if (!existsSync6(legacy)) return;
8027
- if (existsSync6(current)) {
8200
+ if (!existsSync7(legacy)) return;
8201
+ if (existsSync7(current)) {
8028
8202
  ctx.logger.warn(
8029
8203
  `legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
8030
8204
  );
8031
8205
  return;
8032
8206
  }
8033
- mkdirSync2(current, { recursive: true });
8207
+ mkdirSync3(current, { recursive: true });
8034
8208
  for (const entry of readdirSync(legacy, { withFileTypes: true })) {
8035
8209
  if (!entry.isDirectory()) continue;
8036
- renameSync(join8(legacy, entry.name), join8(current, entry.name));
8210
+ renameSync(join9(legacy, entry.name), join9(current, entry.name));
8037
8211
  }
8038
8212
  try {
8039
8213
  rmdirSync(legacy);
@@ -8048,7 +8222,7 @@ function runUninstall(ctx, opts) {
8048
8222
  process.exit(1);
8049
8223
  }
8050
8224
  const result = uninstallClaudeIntegration({
8051
- claudeDir: join8(ctx.userHome, ".claude"),
8225
+ claudeDir: join9(ctx.userHome, ".claude"),
8052
8226
  cliScriptPath,
8053
8227
  nodePath: resolveHookNodePath(),
8054
8228
  dryRun: opts.dryRun,
@@ -8095,11 +8269,11 @@ function reportInstallResult(ctx, result, dryRun) {
8095
8269
  }
8096
8270
 
8097
8271
  // src/commands/indexCmd.ts
8098
- import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "node:fs";
8272
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "node:fs";
8099
8273
  import { dirname as dirname4 } from "node:path";
8100
8274
  async function runIndex(ctx, opts) {
8101
8275
  const dbDir = dirname4(ctx.paths.dbPath);
8102
- if (!existsSync7(dbDir)) mkdirSync3(dbDir, { recursive: true });
8276
+ if (!existsSync8(dbDir)) mkdirSync4(dbDir, { recursive: true });
8103
8277
  const keyProvider = createKeyserverKeyProvider({ caveatHome: ctx.caveatHome });
8104
8278
  const failures = await prewarmSealedKeys({ paths: ctx.paths, keyProvider });
8105
8279
  for (const failure of failures) {
@@ -8283,7 +8457,7 @@ function runStats(ctx) {
8283
8457
 
8284
8458
  // src/commands/serve.ts
8285
8459
  async function runServe(opts) {
8286
- const { startServer } = await import("./server-GBVHOF67.js");
8460
+ const { startServer } = await import("./server-HKXLHXNN.js");
8287
8461
  const { port, host } = startServer({ port: opts.port });
8288
8462
  process.stdout.write(`[caveat] web portal: http://${host}:${port}/
8289
8463
  `);
@@ -32355,7 +32529,7 @@ var StdioServerTransport = class {
32355
32529
 
32356
32530
  // ../mcp/dist/context.js
32357
32531
  import { homedir as homedir2 } from "node:os";
32358
- import { join as join9 } from "node:path";
32532
+ import { join as join10 } from "node:path";
32359
32533
 
32360
32534
  // ../mcp/dist/autoSyncTrigger.js
32361
32535
  function triggerAutoSyncAfterWrite(caveatHome, logger) {
@@ -32374,7 +32548,7 @@ function triggerAutoSyncAfterWrite(caveatHome, logger) {
32374
32548
  function buildMcpContext(overrides = {}) {
32375
32549
  const userHome = overrides.userHome ?? homedir2();
32376
32550
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
32377
- const userConfigPath = join9(userHome, ".caveatrc.json");
32551
+ const userConfigPath = join10(userHome, ".caveatrc.json");
32378
32552
  const logger = overrides.logger ?? stderrLogger;
32379
32553
  const config2 = loadConfig(userConfigPath);
32380
32554
  const paths = resolvePaths(caveatHome, config2.knowledgeRepo, userHome);
@@ -32523,12 +32697,12 @@ function handleListRecent(ctx, args) {
32523
32697
  }
32524
32698
 
32525
32699
  // ../mcp/dist/tools/pull.js
32526
- import { existsSync as existsSync8 } from "node:fs";
32700
+ import { existsSync as existsSync9 } from "node:fs";
32527
32701
  var pullInputShape = {};
32528
32702
  async function handlePull(ctx, _args = {}) {
32529
32703
  const pulled = [];
32530
32704
  const indexed = [];
32531
- if (existsSync8(ctx.paths.communityDir)) {
32705
+ if (existsSync9(ctx.paths.communityDir)) {
32532
32706
  const results = await communityPull({
32533
32707
  communityDir: ctx.paths.communityDir,
32534
32708
  logger: ctx.logger
@@ -32684,28 +32858,28 @@ async function runMcpServer() {
32684
32858
  import { spawn as spawn2 } from "node:child_process";
32685
32859
  import {
32686
32860
  chmodSync,
32687
- existsSync as existsSync11,
32861
+ existsSync as existsSync12,
32688
32862
  lstatSync,
32689
32863
  mkdtempSync as mkdtempSync2,
32690
- mkdirSync as mkdirSync5,
32864
+ mkdirSync as mkdirSync6,
32691
32865
  readdirSync as readdirSync2,
32692
- readFileSync as readFileSync6,
32866
+ readFileSync as readFileSync7,
32693
32867
  realpathSync as realpathSync3,
32694
32868
  rmSync as rmSync2,
32695
32869
  writeFileSync as writeFileSync5
32696
32870
  } from "node:fs";
32697
32871
  import { tmpdir as tmpdir2 } from "node:os";
32698
- import { basename, dirname as dirname5, join as join12 } from "node:path";
32872
+ import { basename, dirname as dirname5, join as join13 } from "node:path";
32699
32873
  import { randomBytes } from "node:crypto";
32700
32874
 
32701
32875
  // src/autoReindexTrigger.ts
32702
32876
  import { spawn } from "node:child_process";
32703
- import { existsSync as existsSync9 } from "node:fs";
32704
- import { join as join10 } from "node:path";
32877
+ import { existsSync as existsSync10 } from "node:fs";
32878
+ import { join as join11 } from "node:path";
32705
32879
  function maybeTriggerAutoReindex(ctx) {
32706
32880
  if (process.env.CAVEAT_INDEX_AUTOSYNC === "off") return;
32707
- if (!existsSync9(ctx.paths.dbPath)) return;
32708
- if (existsSync9(join10(ctx.caveatHome, "index", ".reindex-lock"))) return;
32881
+ if (!existsSync10(ctx.paths.dbPath)) return;
32882
+ if (existsSync10(join11(ctx.caveatHome, "index", ".reindex-lock"))) return;
32709
32883
  const current = computeEntriesDigest(ctx.paths);
32710
32884
  const marker = readDigestMarker(ctx.caveatHome);
32711
32885
  if (marker?.digest === current.digest && marker.fileCount === current.fileCount) return;
@@ -32731,8 +32905,8 @@ function maybeTriggerAutoSync(ctx, debounceMs = AUTO_SYNC_DEBOUNCE_MS) {
32731
32905
  }
32732
32906
 
32733
32907
  // src/hookShared.ts
32734
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
32735
- import { join as join11 } from "node:path";
32908
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
32909
+ import { join as join12 } from "node:path";
32736
32910
  import { createHash } from "node:crypto";
32737
32911
  var MAX_CONTEXT_BLOCKS = 3;
32738
32912
  var STOP_REMINDER_PREFIX = "[caveat] \u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\u306B\u5F53\u305F\u3063\u305F\u53EF\u80FD\u6027\u3092\u793A\u3059\u30B7\u30B0\u30CA\u30EB:";
@@ -32787,7 +32961,7 @@ function searchCaveatsSafely(host, input) {
32787
32961
  let hits;
32788
32962
  try {
32789
32963
  const ctx = buildContextSafely(host);
32790
- if (!ctx || !existsSync10(ctx.paths.dbPath)) return [];
32964
+ if (!ctx || !existsSync11(ctx.paths.dbPath)) return [];
32791
32965
  caveatHome = ctx.caveatHome;
32792
32966
  db = openDb({ path: ctx.paths.dbPath });
32793
32967
  const searchOptions = {
@@ -32873,19 +33047,19 @@ function stopSignalKey(signals, related) {
32873
33047
  return createHash("sha256").update(body).digest("hex");
32874
33048
  }
32875
33049
  function stopStatePath(host, caveatHome, sessionId) {
32876
- return join11(caveatHome, host.stopStateDir, `${sanitizeStateId(sessionId)}.txt`);
33050
+ return join12(caveatHome, host.stopStateDir, `${sanitizeStateId(sessionId)}.txt`);
32877
33051
  }
32878
33052
  function wasStopReminderQueued(host, caveatHome, sessionId, key) {
32879
33053
  const path = stopStatePath(host, caveatHome, sessionId);
32880
33054
  try {
32881
- return readFileSync5(path, "utf-8") === key;
33055
+ return readFileSync6(path, "utf-8") === key;
32882
33056
  } catch {
32883
33057
  return false;
32884
33058
  }
32885
33059
  }
32886
33060
  function markStopReminderQueued(host, caveatHome, sessionId, key) {
32887
33061
  const path = stopStatePath(host, caveatHome, sessionId);
32888
- mkdirSync4(join11(caveatHome, host.stopStateDir), { recursive: true });
33062
+ mkdirSync5(join12(caveatHome, host.stopStateDir), { recursive: true });
32889
33063
  writeFileSync4(path, key, "utf-8");
32890
33064
  }
32891
33065
  function queueStopForSession(host, sessionId, signals, related, buildText) {
@@ -32997,9 +33171,9 @@ function spawnWorker(job) {
32997
33171
  try {
32998
33172
  root = workerRoot();
32999
33173
  sweepStaleWorkerDirs(Date.now(), root);
33000
- workDir = mkdtempSync2(join12(root, "job-"));
33174
+ workDir = mkdtempSync2(join13(root, "job-"));
33001
33175
  chmodSync(workDir, 448);
33002
- workFile = join12(workDir, `${randomBytes(4).toString("hex")}.json`);
33176
+ workFile = join13(workDir, `${randomBytes(4).toString("hex")}.json`);
33003
33177
  writeFileSync5(workFile, JSON.stringify({ ...job, schemaVersion: "caveat-worker-job/v2" }), { encoding: "utf-8", mode: 384, flag: "wx" });
33004
33178
  } catch (err) {
33005
33179
  process.stderr.write(`[caveat:hook] worker writefile error: ${errorMessage4(err)}
@@ -33031,7 +33205,7 @@ function spawnWorker(job) {
33031
33205
  async function runWorker(workFile) {
33032
33206
  let raw;
33033
33207
  try {
33034
- raw = readFileSync6(workFile, "utf-8");
33208
+ raw = readFileSync7(workFile, "utf-8");
33035
33209
  } catch {
33036
33210
  process.exit(0);
33037
33211
  }
@@ -33095,7 +33269,7 @@ function sweepStaleWorkerDirs(now = Date.now(), root = workerRoot()) {
33095
33269
  }
33096
33270
  for (const entry of entries) {
33097
33271
  if (!entry.startsWith("job-")) continue;
33098
- const path = join12(root, entry);
33272
+ const path = join13(root, entry);
33099
33273
  try {
33100
33274
  if (!isOwnedWorkerDir(path, root)) continue;
33101
33275
  const stat = lstatSync(path);
@@ -33107,29 +33281,29 @@ function sweepStaleWorkerDirs(now = Date.now(), root = workerRoot()) {
33107
33281
  }
33108
33282
  }
33109
33283
  function workerRoot(base = tmpdir2()) {
33110
- const root = join12(base, WORKER_ROOT);
33111
- mkdirSync5(root, { recursive: true, mode: 448 });
33284
+ const root = join13(base, WORKER_ROOT);
33285
+ mkdirSync6(root, { recursive: true, mode: 448 });
33112
33286
  const stat = lstatSync(root);
33113
33287
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33114
33288
  if (!stat.isDirectory() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) throw new Error("worker root is unsafe");
33115
- const marker = join12(root, WORKER_MARKER);
33289
+ const marker = join13(root, WORKER_MARKER);
33116
33290
  try {
33117
33291
  writeFileSync5(marker, "caveat-worker/v1\n", { mode: 384, flag: "wx" });
33118
33292
  } catch (error51) {
33119
33293
  if (!(error51 && typeof error51 === "object" && "code" in error51 && error51.code === "EEXIST")) throw error51;
33120
33294
  }
33121
33295
  const markerStat = lstatSync(marker);
33122
- if (!markerStat.isFile() || markerStat.isSymbolicLink() || !hasPrivateOwnership(markerStat, uid) || readFileSync6(marker, "utf-8") !== "caveat-worker/v1\n") throw new Error("worker root marker is invalid");
33296
+ if (!markerStat.isFile() || markerStat.isSymbolicLink() || !hasPrivateOwnership(markerStat, uid) || readFileSync7(marker, "utf-8") !== "caveat-worker/v1\n") throw new Error("worker root marker is invalid");
33123
33297
  return root;
33124
33298
  }
33125
33299
  function isStaleWorkerJobDir(path) {
33126
33300
  const entries = readdirSync2(path);
33127
33301
  if (entries.length !== 1 || !entries[0].endsWith(".json")) return false;
33128
- const file2 = join12(path, entries[0]);
33302
+ const file2 = join13(path, entries[0]);
33129
33303
  const stat = lstatSync(file2);
33130
33304
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33131
33305
  if (!stat.isFile() || stat.isSymbolicLink() || !hasPrivateOwnership(stat, uid)) return false;
33132
- return isKnownStaleWorkerJob(JSON.parse(readFileSync6(file2, "utf-8")));
33306
+ return isKnownStaleWorkerJob(JSON.parse(readFileSync7(file2, "utf-8")));
33133
33307
  }
33134
33308
  function hasPrivateOwnership(stat, uid) {
33135
33309
  return isPrivateOwnerStat(stat, uid);
@@ -33151,7 +33325,7 @@ function isKnownStaleWorkerJob(value) {
33151
33325
  }
33152
33326
  function writeLastReindex(caveatHome, value) {
33153
33327
  try {
33154
- writeFileSync5(join12(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
33328
+ writeFileSync5(join13(caveatHome, "index", ".last-reindex.json"), JSON.stringify(value), "utf-8");
33155
33329
  } catch (err) {
33156
33330
  process.stderr.write(`[caveat:hook] reindex status write error: ${errorMessage4(err)}
33157
33331
  `);
@@ -33163,7 +33337,7 @@ async function runReindexWorker() {
33163
33337
  return;
33164
33338
  }
33165
33339
  const ctx = buildContextSafely(CLAUDE_HOST);
33166
- if (!ctx || !existsSync11(ctx.paths.dbPath)) {
33340
+ if (!ctx || !existsSync12(ctx.paths.dbPath)) {
33167
33341
  process.stderr.write("[caveat:hook] auto reindex skipped: index database does not exist\n");
33168
33342
  return;
33169
33343
  }
@@ -33230,7 +33404,7 @@ function buildToolErrorReminder(job, hits) {
33230
33404
  const mode = hookCodexSidecarMode();
33231
33405
  if (mode === "off") return base;
33232
33406
  const projectRoot = process.cwd();
33233
- const hasSidecarConfig = existsSync11(join12(projectRoot, ".codex-sidecar.yml"));
33407
+ const hasSidecarConfig = existsSync12(join13(projectRoot, ".codex-sidecar.yml"));
33234
33408
  if (mode === "auto" && !hasSidecarConfig) return base;
33235
33409
  const advisory = runCodexSidecarAdvisory({
33236
33410
  searchText: job.failureText,
@@ -33266,7 +33440,7 @@ function buildStopReminder(signals, related) {
33266
33440
  const mode = hookCodexSidecarMode();
33267
33441
  if (mode === "off") return base;
33268
33442
  const projectRoot = process.cwd();
33269
- const hasSidecarConfig = existsSync11(join12(projectRoot, ".codex-sidecar.yml"));
33443
+ const hasSidecarConfig = existsSync12(join13(projectRoot, ".codex-sidecar.yml"));
33270
33444
  if (mode === "auto" && !hasSidecarConfig) return base;
33271
33445
  const advisory = runCodexSidecarAdvisory({
33272
33446
  searchText: struggleSearchText(signals),
@@ -33406,9 +33580,9 @@ async function runHook(name, arg) {
33406
33580
 
33407
33581
  // src/commands/codexHookCmd.ts
33408
33582
  import { spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
33409
- import { existsSync as existsSync12, readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
33583
+ import { existsSync as existsSync13, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
33410
33584
  import { homedir as homedir3, tmpdir as tmpdir3 } from "node:os";
33411
- import { join as join13 } from "node:path";
33585
+ import { join as join14 } from "node:path";
33412
33586
  import { randomBytes as randomBytes2 } from "node:crypto";
33413
33587
  var CODEX_HOST = {
33414
33588
  agent: "codex",
@@ -33438,10 +33612,10 @@ function processExitCodeFromText(text) {
33438
33612
  return m ? Number(m[1]) : null;
33439
33613
  }
33440
33614
  function transcriptToolOutput(transcriptPath, toolUseId) {
33441
- if (!transcriptPath || !toolUseId || !existsSync12(transcriptPath)) return null;
33615
+ if (!transcriptPath || !toolUseId || !existsSync13(transcriptPath)) return null;
33442
33616
  let raw = "";
33443
33617
  try {
33444
- raw = readFileSync7(transcriptPath, "utf-8");
33618
+ raw = readFileSync8(transcriptPath, "utf-8");
33445
33619
  } catch {
33446
33620
  return null;
33447
33621
  }
@@ -33585,7 +33759,7 @@ async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
33585
33759
  async function runCodexWorker(workFile) {
33586
33760
  let raw;
33587
33761
  try {
33588
- raw = readFileSync7(workFile, "utf-8");
33762
+ raw = readFileSync8(workFile, "utf-8");
33589
33763
  } catch {
33590
33764
  process.exit(0);
33591
33765
  }
@@ -33602,7 +33776,7 @@ async function runCodexWorker(workFile) {
33602
33776
  await processCodexWorkerJob(job);
33603
33777
  process.exit(0);
33604
33778
  }
33605
- function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join13(homedir3(), ".codex")) {
33779
+ function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join14(homedir3(), ".codex")) {
33606
33780
  const features = spawnSync5("codex", ["features", "list"], {
33607
33781
  encoding: "utf-8",
33608
33782
  maxBuffer: 1024 * 1024,
@@ -33707,10 +33881,151 @@ async function runCodexHook(name, arg) {
33707
33881
  process.exit(0);
33708
33882
  }
33709
33883
 
33884
+ // src/commands/cursorHookCmd.ts
33885
+ import { homedir as homedir4 } from "node:os";
33886
+ import { join as join15 } from "node:path";
33887
+ var CURSOR_HOST = {
33888
+ agent: "cursor",
33889
+ stderrTag: "caveat:cursor-hook",
33890
+ errorCode: "CAVEAT.CURSOR_HOOK_FAILED",
33891
+ stopStateDir: "cursor-stop-state",
33892
+ stopDedupeKey: "cursor-stop-reminder"
33893
+ };
33894
+ function cursorContextOutput(text) {
33895
+ return JSON.stringify({ additional_context: text });
33896
+ }
33897
+ function cursorSessionId(payload) {
33898
+ for (const key of ["conversation_id", "session_id", "sessionId"]) {
33899
+ const value = payload[key];
33900
+ if (typeof value === "string" && value.length > 0) return value;
33901
+ }
33902
+ return "_unknown";
33903
+ }
33904
+ function cursorPrompt(payload) {
33905
+ return typeof payload.prompt === "string" ? payload.prompt : "";
33906
+ }
33907
+ function isCursorToolError(payload) {
33908
+ if (payload.hook_event_name === "postToolUseFailure") return true;
33909
+ if (payload.is_error === true) return true;
33910
+ if (typeof payload.error === "string" && payload.error.length > 0) return true;
33911
+ const resp = payload.tool_response ?? payload.toolResponse;
33912
+ if (resp !== null && typeof resp === "object" && !Array.isArray(resp)) {
33913
+ if (resp.is_error === true) return true;
33914
+ }
33915
+ return false;
33916
+ }
33917
+ function loadSignalsSafely3(path) {
33918
+ try {
33919
+ return readSessionSignals(path);
33920
+ } catch (err) {
33921
+ process.stderr.write(`[caveat:cursor-hook] transcript read error: ${errorMessage4(err)}
33922
+ `);
33923
+ return null;
33924
+ }
33925
+ }
33926
+ function runDiagnostics2(cursorDir = join15(homedir4(), ".cursor")) {
33927
+ const installation = detectCursorHookInstallation(cursorDir);
33928
+ process.stdout.write(`${JSON.stringify({
33929
+ installation: installation.installation,
33930
+ cursorDir,
33931
+ hooksPath: installation.hooksPath,
33932
+ installedHooks: installation.hooks
33933
+ }, null, 2)}
33934
+ `);
33935
+ }
33936
+ async function runCursorHook(name, arg) {
33937
+ if (name === "diagnostics") {
33938
+ runDiagnostics2(arg);
33939
+ return;
33940
+ }
33941
+ let raw = "";
33942
+ try {
33943
+ raw = await readStdin();
33944
+ } catch (err) {
33945
+ process.stderr.write(`[caveat:cursor-hook] stdin read error: ${errorMessage4(err)}
33946
+ `);
33947
+ process.exit(0);
33948
+ }
33949
+ const payload = parsePayload(CURSOR_HOST, raw);
33950
+ const sessionId = cursorSessionId(payload);
33951
+ const contexts = name === "stop" ? [] : drainForSession(CURSOR_HOST, sessionId);
33952
+ if (name === "user-prompt-submit") {
33953
+ const prompt = cursorPrompt(payload);
33954
+ const hits = searchCaveatsSafely(CURSOR_HOST, {
33955
+ topicText: prompt,
33956
+ failureText: prompt,
33957
+ surface: "user_prompt"
33958
+ });
33959
+ if (hits.length > 0) contexts.push(userPromptSubmitReminderText(hits));
33960
+ const compacted = compactContexts(CURSOR_HOST, contexts);
33961
+ if (compacted.length > 0) {
33962
+ process.stdout.write(`${cursorContextOutput(compacted.join("\n\n"))}
33963
+ `);
33964
+ }
33965
+ process.exit(0);
33966
+ }
33967
+ if (name === "post-tool-use") {
33968
+ if (isCursorToolError(payload)) {
33969
+ const errText = extractToolResponseText(
33970
+ payload.tool_response ?? payload.toolResponse ?? payload.error ?? payload
33971
+ );
33972
+ const hits = searchCaveatsSafely(CURSOR_HOST, {
33973
+ topicText: errText,
33974
+ failureText: errText,
33975
+ surface: "tool_error"
33976
+ });
33977
+ if (hits.length > 0) contexts.push(toolErrorReminderText(hits));
33978
+ }
33979
+ const compacted = compactContexts(CURSOR_HOST, contexts);
33980
+ if (compacted.length > 0) {
33981
+ process.stdout.write(`${cursorContextOutput(compacted.join("\n\n"))}
33982
+ `);
33983
+ }
33984
+ process.exit(0);
33985
+ }
33986
+ if (name === "stop") {
33987
+ const ctx = buildContextSafely(CURSOR_HOST);
33988
+ if (ctx) {
33989
+ try {
33990
+ maybeSweepPendingDirs(ctx.caveatHome);
33991
+ } catch (err) {
33992
+ process.stderr.write(`[caveat:cursor-hook] pending sweep error: ${errorMessage4(err)}
33993
+ `);
33994
+ }
33995
+ try {
33996
+ maybeTriggerAutoReindex(ctx);
33997
+ } catch (err) {
33998
+ process.stderr.write(`[caveat:cursor-hook] auto reindex trigger error: ${errorMessage4(err)}
33999
+ `);
34000
+ }
34001
+ try {
34002
+ maybeTriggerAutoSync(ctx);
34003
+ } catch (err) {
34004
+ process.stderr.write(`[caveat:cursor-hook] auto sync trigger error: ${errorMessage4(err)}
34005
+ `);
34006
+ }
34007
+ }
34008
+ if (payload.stop_hook_active === true) process.exit(0);
34009
+ const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
34010
+ const signals = transcriptPath ? loadSignalsSafely3(transcriptPath) : null;
34011
+ if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
34012
+ const related = searchCaveatsSafely(CURSOR_HOST, signals.errorSnippets.map((failureText) => ({
34013
+ topicText: "",
34014
+ failureText,
34015
+ surface: "stop"
34016
+ })));
34017
+ queueStopForSession(CURSOR_HOST, sessionId, signals, related, () => stopReminderText(signals, related));
34018
+ process.exit(0);
34019
+ }
34020
+ process.stderr.write(`[caveat:cursor-hook] unknown hook name: ${name}
34021
+ `);
34022
+ process.exit(0);
34023
+ }
34024
+
33710
34025
  // src/commands/pull.ts
33711
- import { existsSync as existsSync13 } from "node:fs";
34026
+ import { existsSync as existsSync14 } from "node:fs";
33712
34027
  async function runPull(ctx) {
33713
- const hasCommunityDir = existsSync13(ctx.paths.communityDir);
34028
+ const hasCommunityDir = existsSync14(ctx.paths.communityDir);
33714
34029
  if (!hasCommunityDir) {
33715
34030
  ctx.logger.info(
33716
34031
  "no community repos yet \u2014 add one with `caveat community add <github-url>`."
@@ -33810,9 +34125,9 @@ async function runSync(ctx, opts, dependencies = {}) {
33810
34125
 
33811
34126
  // src/commands/codexSidecar.ts
33812
34127
  import { spawnSync as spawnSync6 } from "node:child_process";
33813
- import { closeSync, constants as constants3, fstatSync, lstatSync as lstatSync2, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync3, openSync, readSync as readSync2, realpathSync as realpathSync4, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
34128
+ import { closeSync, constants as constants4, fstatSync, lstatSync as lstatSync2, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, openSync, readSync as readSync2, realpathSync as realpathSync4, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
33814
34129
  import { tmpdir as tmpdir4 } from "node:os";
33815
- import { basename as basename2, dirname as dirname6, join as join14 } from "node:path";
34130
+ import { basename as basename2, dirname as dirname6, join as join16 } from "node:path";
33816
34131
  import { cwd, exit } from "node:process";
33817
34132
  function runCodexSidecarDiagnostics(logger, opts) {
33818
34133
  const plan = buildCodexSidecarDiagnosticsCommand({
@@ -33844,8 +34159,8 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
33844
34159
  process.stdout.write(JSON.stringify({ status: "skipped", decision }, null, 2) + "\n");
33845
34160
  exit(0);
33846
34161
  }
33847
- const contextDir = mkdtempSync3(join14(tmpdir4(), "caveat-sidecar-context-"));
33848
- const contextFile = join14(contextDir, "context.json");
34162
+ const contextDir = mkdtempSync3(join16(tmpdir4(), "caveat-sidecar-context-"));
34163
+ const contextFile = join16(contextDir, "context.json");
33849
34164
  let status2 = 1;
33850
34165
  try {
33851
34166
  const blocks = collectCaveatContextBlocks(ctx, {
@@ -33878,7 +34193,7 @@ function readHookSignalAdditionalContextFile(path, testProbe) {
33878
34193
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
33879
34194
  assertPrivateRegular(before, uid, platform);
33880
34195
  testProbe?.afterLstat?.();
33881
- const fd = openSync(path, constants3.O_RDONLY | constants3.O_NONBLOCK | (constants3.O_NOFOLLOW ?? 0));
34196
+ const fd = openSync(path, constants4.O_RDONLY | constants4.O_NONBLOCK | (constants4.O_NOFOLLOW ?? 0));
33882
34197
  let raw;
33883
34198
  try {
33884
34199
  const opened = fstatSync(fd);
@@ -34008,7 +34323,7 @@ function executePlan(logger, command, args, options = {}) {
34008
34323
  }
34009
34324
  function saveStructuredResult(path, stdout) {
34010
34325
  const parsed = JSON.parse(stdout);
34011
- mkdirSync6(dirname6(path), { recursive: true });
34326
+ mkdirSync7(dirname6(path), { recursive: true });
34012
34327
  writeFileSync7(path, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
34013
34328
  }
34014
34329
  function shellDisplayQuote(value) {
@@ -34097,8 +34412,8 @@ function runCommunityRemove(ctx, handle, opts) {
34097
34412
 
34098
34413
  // src/commands/factoryDiagnostics.ts
34099
34414
  import { execFileSync } from "node:child_process";
34100
- import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
34101
- import { join as join15 } from "node:path";
34415
+ import { existsSync as existsSync15, readFileSync as readFileSync9 } from "node:fs";
34416
+ import { join as join17 } from "node:path";
34102
34417
  import { DatabaseSync } from "node:sqlite";
34103
34418
  import { parse as parseToml2 } from "smol-toml";
34104
34419
  var status = (ok, reason) => ({ status: ok ? "ready" : "not_ready", reason_code: ok ? "ready" : reason });
@@ -34110,10 +34425,10 @@ function isRecord2(value) {
34110
34425
  return typeof value === "object" && value !== null && !Array.isArray(value);
34111
34426
  }
34112
34427
  function claudeRegistration(home, nodePath, cliScriptPath) {
34113
- const path = join15(home, ".claude.json");
34114
- if (!existsSync14(path)) return status(false, "not_registered");
34428
+ const path = join17(home, ".claude.json");
34429
+ if (!existsSync15(path)) return status(false, "not_registered");
34115
34430
  try {
34116
- const value = JSON.parse(readFileSync8(path, "utf8"));
34431
+ const value = JSON.parse(readFileSync9(path, "utf8"));
34117
34432
  if (!isRecord2(value) || !isRecord2(value.mcpServers)) return status(false, "not_registered");
34118
34433
  return status(isCaveatClaudeMcpRegistration(value.mcpServers.caveat, nodePath, cliScriptPath), "not_registered");
34119
34434
  } catch {
@@ -34122,7 +34437,7 @@ function claudeRegistration(home, nodePath, cliScriptPath) {
34122
34437
  }
34123
34438
  function claudeHooks(home, nodePath, cliScriptPath) {
34124
34439
  try {
34125
- const settings = JSON.parse(readFileSync8(join15(home, ".claude", "settings.json"), "utf8"));
34440
+ const settings = JSON.parse(readFileSync9(join17(home, ".claude", "settings.json"), "utf8"));
34126
34441
  const present = (event, subcommand) => settings.hooks?.[event]?.some((entry) => entry.hooks?.some((item) => typeof item.command === "string" && isCanonicalCaveatClaudeHookCommand(item.command, subcommand, nodePath, cliScriptPath))) ?? false;
34127
34442
  return { user_prompt_submit: hook(present("UserPromptSubmit", "user-prompt-submit")), post_tool_use: hook(present("PostToolUse", "post-tool-use")), post_tool_use_failure: hook(present("PostToolUseFailure", "post-tool-use")), stop: hook(present("Stop", "stop")) };
34128
34443
  } catch {
@@ -34144,7 +34459,7 @@ function strictSearchSchema(db) {
34144
34459
  return columns.map((column) => `${column.name}:${column.hidden}`).join(",") === "id:0,title:0,body:0,tags:0,entries_fts:1,rank:1";
34145
34460
  }
34146
34461
  function database(path) {
34147
- if (!existsSync14(path)) return { status: "not_ready", reason_code: "missing", schema_version: null, supported_schema_version: 3, migration_status: "unverified" };
34462
+ if (!existsSync15(path)) return { status: "not_ready", reason_code: "missing", schema_version: null, supported_schema_version: 3, migration_status: "unverified" };
34148
34463
  try {
34149
34464
  const db = new DatabaseSync(path, { readOnly: true });
34150
34465
  try {
@@ -34169,10 +34484,10 @@ function database(path) {
34169
34484
  }
34170
34485
  }
34171
34486
  function codexFeature(codexHome) {
34172
- const path = join15(codexHome, "config.toml");
34173
- if (!existsSync14(path)) return status(false, "feature_disabled");
34487
+ const path = join17(codexHome, "config.toml");
34488
+ if (!existsSync15(path)) return status(false, "feature_disabled");
34174
34489
  try {
34175
- const config2 = parseToml2(readFileSync8(path, "utf8"));
34490
+ const config2 = parseToml2(readFileSync9(path, "utf8"));
34176
34491
  if (!isRecord2(config2.features)) return status(false, "feature_disabled");
34177
34492
  const features = config2.features;
34178
34493
  return status(features.hooks === true && features.codex_hooks === void 0, "feature_disabled");
@@ -34181,7 +34496,7 @@ function codexFeature(codexHome) {
34181
34496
  }
34182
34497
  }
34183
34498
  function codexHooks(codexHome, nodePath, cliScriptPath) {
34184
- const value = JSON.parse(readFileSync8(join15(codexHome, "hooks.json"), "utf8"));
34499
+ const value = JSON.parse(readFileSync9(join17(codexHome, "hooks.json"), "utf8"));
34185
34500
  if (!isRecord2(value) || !isRecord2(value.hooks)) throw Error("config_unreadable");
34186
34501
  const hooks = value.hooks;
34187
34502
  const present = (event, subcommand) => Array.isArray(hooks[event]) && hooks[event].some((entry) => isRecord2(entry) && Array.isArray(entry.hooks) && entry.hooks.some((item) => isCanonicalCaveatCodexHookEntry(item, subcommand, nodePath, cliScriptPath)));
@@ -34216,7 +34531,7 @@ function sync(own) {
34216
34531
  return unverified("upstream_unavailable");
34217
34532
  }
34218
34533
  }
34219
- function factoryDiagnostics(ctx, codexHome = process.env.CODEX_HOME ?? join15(ctx.userHome, ".codex")) {
34534
+ function factoryDiagnostics(ctx, codexHome = process.env.CODEX_HOME ?? join17(ctx.userHome, ".codex")) {
34220
34535
  const nodePath = process.execPath;
34221
34536
  const cliScriptPath = process.argv[1] ?? "";
34222
34537
  const db = database(ctx.paths.dbPath);
@@ -34291,7 +34606,7 @@ for (const action of ["resolve", "reopen"]) runtimeErrors.command(`${action} <fi
34291
34606
  });
34292
34607
  program.command("init").description(
34293
34608
  "Initialize ~/.caveatrc.json, ~/.caveat/, and register Claude Code integration. Add knowledge sources later with `caveat community add <github-url>`."
34294
- ).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--sync [url]", "initialize private sync (optionally with its URL)").option("--publish-target [url]", "configure the sealed public mirror target").option("--yes", "approve creating conventional GitHub repositories", false).option("--skip-codex-hook", "skip Codex hook installation", false).option("--dry-run", "show planned changes without writing", false).option(
34609
+ ).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--sync [url]", "initialize private sync (optionally with its URL)").option("--publish-target [url]", "configure the sealed public mirror target").option("--yes", "approve creating conventional GitHub repositories", false).option("--skip-codex-hook", "skip Codex hook installation", false).option("--skip-cursor-hook", "skip Cursor hook installation", false).option("--dry-run", "show planned changes without writing", false).option(
34295
34610
  "--pending-stale-days <days>",
34296
34611
  "sweep `<caveatHome>/pending/<sessionId>/` whose newest entry is older than this (default 7)",
34297
34612
  (raw) => {
@@ -34311,7 +34626,8 @@ program.command("init").description(
34311
34626
  sync: opts.sync,
34312
34627
  publishTarget: opts.publishTarget,
34313
34628
  yes: opts.yes,
34314
- skipCodexHook: opts.skipCodexHook
34629
+ skipCodexHook: opts.skipCodexHook,
34630
+ skipCursorHook: opts.skipCursorHook
34315
34631
  });
34316
34632
  }
34317
34633
  );
@@ -34439,6 +34755,57 @@ codexHook.command("stop").description("Run the Codex Stop hook").action(async ()
34439
34755
  codexHook.command("worker <workFile>").description("Run the detached Codex hook worker").action(async (workFile) => {
34440
34756
  await runCodexHook("worker", workFile);
34441
34757
  });
34758
+ var cursorHook = program.command("cursor-hook").description("Install or run Cursor hooks for Caveat");
34759
+ cursorHook.command("install").description("Install Caveat hooks into ~/.cursor/hooks.json (factory hooks are kept)").option("--dry-run", "show planned changes without writing", false).option("--cursor-dir <path>", "Cursor home directory", `${process.env.HOME}/.cursor`).action((opts) => {
34760
+ const cliScriptPath = process.argv[1];
34761
+ if (!cliScriptPath) {
34762
+ process.stderr.write("[caveat:error] cannot determine CLI script path\n");
34763
+ process.exit(1);
34764
+ }
34765
+ const result = installCursorHooks({
34766
+ cursorDir: opts.cursorDir,
34767
+ cliScriptPath,
34768
+ nodePath: resolveHookNodePath(),
34769
+ dryRun: opts.dryRun,
34770
+ logger: stdoutLogger
34771
+ });
34772
+ stdoutLogger.info(`beforeSubmitPrompt hook: ${result.hooks.beforeSubmitPrompt}`);
34773
+ stdoutLogger.info(`postToolUse hook: ${result.hooks.postToolUse}`);
34774
+ stdoutLogger.info(`postToolUseFailure hook: ${result.hooks.postToolUseFailure}`);
34775
+ stdoutLogger.info(`stop hook: ${result.hooks.stop}`);
34776
+ if (result.backupPath) stdoutLogger.info(`hooks.json backed up: ${result.backupPath}`);
34777
+ });
34778
+ cursorHook.command("uninstall").description("Remove Caveat-owned Cursor hooks from ~/.cursor/hooks.json").option("--dry-run", "show planned changes without writing", false).option("--cursor-dir <path>", "Cursor home directory", `${process.env.HOME}/.cursor`).action((opts) => {
34779
+ const cliScriptPath = process.argv[1];
34780
+ if (!cliScriptPath) {
34781
+ process.stderr.write("[caveat:error] cannot determine CLI script path\n");
34782
+ process.exit(1);
34783
+ }
34784
+ const result = uninstallCursorHooks({
34785
+ cursorDir: opts.cursorDir,
34786
+ cliScriptPath,
34787
+ nodePath: resolveHookNodePath(),
34788
+ dryRun: opts.dryRun,
34789
+ logger: stdoutLogger
34790
+ });
34791
+ stdoutLogger.info(`beforeSubmitPrompt hook: ${result.hooks.beforeSubmitPrompt === "added" ? "removed" : "not present"}`);
34792
+ stdoutLogger.info(`postToolUse hook: ${result.hooks.postToolUse === "added" ? "removed" : "not present"}`);
34793
+ stdoutLogger.info(`postToolUseFailure hook: ${result.hooks.postToolUseFailure === "added" ? "removed" : "not present"}`);
34794
+ stdoutLogger.info(`stop hook: ${result.hooks.stop === "added" ? "removed" : "not present"}`);
34795
+ if (result.backupPath) stdoutLogger.info(`hooks.json backed up: ${result.backupPath}`);
34796
+ });
34797
+ cursorHook.command("diagnostics").description("Check local Cursor hook installation").option("--cursor-dir <path>", "Cursor home directory", `${process.env.HOME}/.cursor`).action(async (opts) => {
34798
+ await runCursorHook("diagnostics", opts.cursorDir);
34799
+ });
34800
+ cursorHook.command("user-prompt-submit").description("Run the Cursor beforeSubmitPrompt hook").action(async () => {
34801
+ await runCursorHook("user-prompt-submit");
34802
+ });
34803
+ cursorHook.command("post-tool-use").description("Run the Cursor postToolUse / postToolUseFailure hook").action(async () => {
34804
+ await runCursorHook("post-tool-use");
34805
+ });
34806
+ cursorHook.command("stop").description("Run the Cursor stop hook").action(async () => {
34807
+ await runCursorHook("stop");
34808
+ });
34442
34809
  var codexSidecar = program.command("codex-sidecar").description("Check Codex sidecar availability for the current repository");
34443
34810
  codexSidecar.command("diagnostics").description("Run codex-sidecar diagnostics for this repository").option("--project <path>", "repository root to check").option("--preset <preset>", "codex-sidecar preset", "review").option("--command <command>", "codex-sidecar executable", "codex-sidecar").option("--node-cli <path>", "development path to codex-sidecar CLI JS").option("--save-result <path>", "write structured SidecarResult JSON to this path").action((opts) => {
34444
34811
  runCodexSidecarDiagnostics(stdoutLogger, opts);