@astrasyncai/verification-gateway 3.10.1 → 3.11.1

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 (77) hide show
  1. package/dist/adapter-interface/interface.d.mts +1 -1
  2. package/dist/adapter-interface/interface.d.ts +1 -1
  3. package/dist/adapters/express.js +1 -1
  4. package/dist/adapters/express.js.map +1 -1
  5. package/dist/adapters/express.mjs +1 -1
  6. package/dist/adapters/express.mjs.map +1 -1
  7. package/dist/adapters/mcp.js +1 -1
  8. package/dist/adapters/mcp.js.map +1 -1
  9. package/dist/adapters/mcp.mjs +1 -1
  10. package/dist/adapters/mcp.mjs.map +1 -1
  11. package/dist/adapters/nextjs.js +1 -1
  12. package/dist/adapters/nextjs.js.map +1 -1
  13. package/dist/adapters/nextjs.mjs +1 -1
  14. package/dist/adapters/nextjs.mjs.map +1 -1
  15. package/dist/adapters/sdk.js +1 -1
  16. package/dist/adapters/sdk.js.map +1 -1
  17. package/dist/adapters/sdk.mjs +1 -1
  18. package/dist/adapters/sdk.mjs.map +1 -1
  19. package/dist/bin/astrasync-claude-hook.js +105 -27
  20. package/dist/bin/astrasync-codex-hook.js +5151 -0
  21. package/dist/bin/astrasync-guard.js +234 -32
  22. package/dist/bin/astrasync.js +242 -32
  23. package/dist/browser/background.js +84 -17
  24. package/dist/browser/background.js.map +1 -1
  25. package/dist/browser/background.mjs +84 -17
  26. package/dist/browser/background.mjs.map +1 -1
  27. package/dist/browser/browser-adapter.d.mts +1 -1
  28. package/dist/browser/browser-adapter.d.ts +1 -1
  29. package/dist/claude-code/claude-code-adapter.d.mts +1 -1
  30. package/dist/claude-code/claude-code-adapter.d.ts +1 -1
  31. package/dist/claude-code/claude-code-adapter.js +9 -1
  32. package/dist/claude-code/claude-code-adapter.js.map +1 -1
  33. package/dist/claude-code/claude-code-adapter.mjs +9 -1
  34. package/dist/claude-code/claude-code-adapter.mjs.map +1 -1
  35. package/dist/cli/index.d.mts +1 -1
  36. package/dist/cli/index.d.ts +1 -1
  37. package/dist/codex/index.d.mts +230 -0
  38. package/dist/codex/index.d.ts +230 -0
  39. package/dist/codex/index.js +5269 -0
  40. package/dist/codex/index.js.map +1 -0
  41. package/dist/codex/index.mjs +5241 -0
  42. package/dist/codex/index.mjs.map +1 -0
  43. package/dist/cursor/cursor-adapter.d.mts +1 -1
  44. package/dist/cursor/cursor-adapter.d.ts +1 -1
  45. package/dist/cursor/extension.d.mts +1 -1
  46. package/dist/cursor/extension.d.ts +1 -1
  47. package/dist/cursor/extension.js +84 -17
  48. package/dist/cursor/extension.js.map +1 -1
  49. package/dist/cursor/extension.mjs +84 -17
  50. package/dist/cursor/extension.mjs.map +1 -1
  51. package/dist/gateway/gateway.d.mts +6 -1
  52. package/dist/gateway/gateway.d.ts +6 -1
  53. package/dist/gateway/gateway.js +84 -17
  54. package/dist/gateway/gateway.js.map +1 -1
  55. package/dist/gateway/gateway.mjs +84 -17
  56. package/dist/gateway/gateway.mjs.map +1 -1
  57. package/dist/git-trigger/git-hooks.d.mts +1 -1
  58. package/dist/git-trigger/git-hooks.d.ts +1 -1
  59. package/dist/index.d.mts +1 -1
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.js +1 -1
  62. package/dist/index.js.map +1 -1
  63. package/dist/index.mjs +1 -1
  64. package/dist/index.mjs.map +1 -1
  65. package/dist/local-evaluator/evaluator.d.mts +10 -1
  66. package/dist/local-evaluator/evaluator.d.ts +10 -1
  67. package/dist/local-evaluator/evaluator.js +20 -9
  68. package/dist/local-evaluator/evaluator.js.map +1 -1
  69. package/dist/local-evaluator/evaluator.mjs +20 -9
  70. package/dist/local-evaluator/evaluator.mjs.map +1 -1
  71. package/dist/{types-C23_uaY8.d.ts → types-DhFPiZ3t.d.ts} +6 -1
  72. package/dist/{types-BV6abBnF.d.mts → types-cQVCMeNV.d.mts} +6 -1
  73. package/dist/verify.js +1 -1
  74. package/dist/verify.js.map +1 -1
  75. package/dist/verify.mjs +1 -1
  76. package/dist/verify.mjs.map +1 -1
  77. package/package.json +8 -2
@@ -4288,8 +4288,9 @@ function extractBacktickSubstitutions(text, collect) {
4288
4288
  return kept.join(" ");
4289
4289
  }
4290
4290
  function basename(token) {
4291
- const slash = token.lastIndexOf("/");
4292
- return slash === -1 ? token : token.slice(slash + 1);
4291
+ const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
4292
+ const name = slash === -1 ? token : token.slice(slash + 1);
4293
+ return name.replace(/\.(exe|cmd|bat|ps1)$/i, "");
4293
4294
  }
4294
4295
  function matchSegment(segment, list) {
4295
4296
  const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
@@ -4422,7 +4423,9 @@ var LocalEvaluator = class {
4422
4423
  }
4423
4424
  return {
4424
4425
  recommendation: "ALLOW",
4425
- reason: "All PDLSS checks passed",
4426
+ // "evaluated": dimensions absent from the policy are skipped, not
4427
+ // failed — evaluatedDimensions below records which ones ran.
4428
+ reason: "All evaluated PDLSS checks passed",
4426
4429
  evaluatedDimensions: {
4427
4430
  purpose: true,
4428
4431
  scope: !!this.policy.scope,
@@ -4521,7 +4524,7 @@ var LocalEvaluator = class {
4521
4524
  }
4522
4525
  }
4523
4526
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
4524
- const targetLower = context.target.toLowerCase();
4527
+ const targetLower = context.target.toLowerCase().replace(/\\/g, "/");
4525
4528
  for (const sensitivePath of SENSITIVE_PATHS) {
4526
4529
  if (targetLower.includes(sensitivePath.toLowerCase())) {
4527
4530
  score = Math.max(score, 50);
@@ -4550,12 +4553,20 @@ var LocalEvaluator = class {
4550
4553
  matchesAnyPattern(value, patterns) {
4551
4554
  return patterns.some((pattern) => this.matchGlob(value, pattern));
4552
4555
  }
4553
- /**
4554
- * Simple glob pattern matching.
4555
- * Supports * (matches any characters including / and spaces) and ? (single char).
4556
- * Uses the same approach as the backend's matchGlobPattern.
4557
- */
4556
+ homePrefix() {
4557
+ if (this.cachedHomePrefix !== void 0) return this.cachedHomePrefix;
4558
+ const env = typeof process !== "undefined" ? process.env : void 0;
4559
+ const home = env?.HOME || env?.USERPROFILE;
4560
+ this.cachedHomePrefix = home ? home.replace(/\\/g, "/") : null;
4561
+ return this.cachedHomePrefix;
4562
+ }
4558
4563
  matchGlob(value, pattern) {
4564
+ value = value.replace(/\\/g, "/");
4565
+ pattern = pattern.replace(/\\/g, "/");
4566
+ if (pattern === "~" || pattern.startsWith("~/")) {
4567
+ const home = this.homePrefix();
4568
+ if (home) pattern = home + pattern.slice(1);
4569
+ }
4559
4570
  if (pattern === value) return true;
4560
4571
  const starCount = (pattern.match(/\*/g) ?? []).length;
4561
4572
  if (starCount > 8) {
@@ -4598,7 +4609,7 @@ function getTrustLevel(score) {
4598
4609
  }
4599
4610
 
4600
4611
  // src/version.ts
4601
- var SDK_VERSION = "3.10.1";
4612
+ var SDK_VERSION = "3.11.1";
4602
4613
 
4603
4614
  // src/well-known.ts
4604
4615
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -5113,10 +5124,12 @@ function toGatewayConfig(config) {
5113
5124
  };
5114
5125
  }
5115
5126
  function toVerificationRequest(context, astraId) {
5127
+ const dotIndex = context.purpose.indexOf(".");
5128
+ const dotted = dotIndex > 0;
5116
5129
  return {
5117
5130
  credentials: { astraId },
5118
- purpose: context.purpose,
5119
- action: context.action,
5131
+ purpose: dotted ? context.purpose.slice(0, dotIndex) : context.purpose,
5132
+ action: dotted ? context.purpose : context.action,
5120
5133
  resourceType: context.resourceType,
5121
5134
  resource: context.target,
5122
5135
  transactionValue: context.transactionValue,
@@ -5359,13 +5372,43 @@ var TraceLogger = class {
5359
5372
  * Log an evaluation event.
5360
5373
  */
5361
5374
  logEvaluation(context, decision) {
5375
+ if (!this.enabled) return;
5376
+ let event;
5377
+ try {
5378
+ event = {
5379
+ id: this.generateId(),
5380
+ timestamp: /* @__PURE__ */ new Date(),
5381
+ type: "evaluation",
5382
+ context: redactDeep(context),
5383
+ decision: redactDeep(decision)
5384
+ };
5385
+ } catch (err) {
5386
+ event = {
5387
+ id: this.generateId(),
5388
+ timestamp: /* @__PURE__ */ new Date(),
5389
+ type: "error",
5390
+ metadata: {
5391
+ error: `trace redaction failed: ${err instanceof Error ? err.message : String(err)}`,
5392
+ recommendation: decision.recommendation,
5393
+ purpose: context.purpose,
5394
+ action: context.action
5395
+ }
5396
+ };
5397
+ }
5398
+ this.write(event);
5399
+ }
5400
+ /**
5401
+ * Log a tool call the guard saw but did not govern. One line per skip
5402
+ * keeps coverage holes (unmapped platform tools) visible in the traces
5403
+ * instead of silently indistinguishable from ALLOW.
5404
+ */
5405
+ logSkipped(toolName, reason) {
5362
5406
  if (!this.enabled) return;
5363
5407
  this.write({
5364
5408
  id: this.generateId(),
5365
5409
  timestamp: /* @__PURE__ */ new Date(),
5366
- type: "evaluation",
5367
- context: redactDeep(context),
5368
- decision: redactDeep(decision)
5410
+ type: "skipped",
5411
+ metadata: { toolName, reason }
5369
5412
  });
5370
5413
  }
5371
5414
  /**
@@ -5394,17 +5437,34 @@ var TraceLogger = class {
5394
5437
  });
5395
5438
  }
5396
5439
  write(event) {
5440
+ let line;
5441
+ try {
5442
+ line = JSON.stringify(event) + "\n";
5443
+ } catch {
5444
+ return;
5445
+ }
5397
5446
  try {
5398
5447
  const filePath = this.getCurrentFilePath();
5399
- const line = JSON.stringify(event) + "\n";
5400
5448
  if ((0, import_fs3.existsSync)(filePath)) {
5401
5449
  const stats = (0, import_fs3.statSync)(filePath);
5402
5450
  if (stats.size > MAX_FILE_SIZE) {
5403
5451
  this.rotate();
5404
5452
  }
5405
5453
  }
5406
- (0, import_fs3.appendFileSync)(filePath, line);
5454
+ for (let attempt = 0; attempt < 3; attempt++) {
5455
+ try {
5456
+ (0, import_fs3.appendFileSync)(filePath, line);
5457
+ return;
5458
+ } catch {
5459
+ }
5460
+ }
5461
+ const fallbackPath = filePath.replace(/\.jsonl$/, `-r${process.pid}.jsonl`);
5462
+ (0, import_fs3.appendFileSync)(fallbackPath, line);
5407
5463
  } catch {
5464
+ try {
5465
+ console.error(`[AstraSync TraceLogger] failed to persist trace line: ${line.trim()}`);
5466
+ } catch {
5467
+ }
5408
5468
  }
5409
5469
  }
5410
5470
  getCurrentFilePath() {
@@ -5565,6 +5625,13 @@ var AstraSyncGateway = class {
5565
5625
  getSyncStatus() {
5566
5626
  return this.syncQueue?.getStatus() || null;
5567
5627
  }
5628
+ /**
5629
+ * Record a tool call the calling adapter saw but did not govern
5630
+ * (unmapped tool, skipTools, MCP with governMcp off). Trace-only.
5631
+ */
5632
+ logSkippedTool(toolName, reason) {
5633
+ this.traceLogger?.logSkipped(toolName, reason);
5634
+ }
5568
5635
  };
5569
5636
 
5570
5637
  // src/git-trigger/git-hooks.ts
@@ -5693,11 +5760,11 @@ async function evaluateChangedFiles(gateway, files, config = {}) {
5693
5760
  var import_fs5 = require("fs");
5694
5761
  var import_path4 = require("path");
5695
5762
  var ASTRASYNC_HOOK_MARKER = "astrasync-claude-hook";
5696
- var HOOK_MATCHER = "Bash|Write|Edit|MultiEdit|NotebookEdit|Read|Glob|Grep|WebFetch|WebSearch|Task|Agent|mcp__.*";
5763
+ var HOOK_MATCHER = "Bash|PowerShell|Write|Edit|MultiEdit|NotebookEdit|Read|Glob|Grep|WebFetch|WebSearch|Task|Agent|mcp__.*";
5697
5764
  var HOOK_TIMEOUT_SECONDS = 10;
5698
- function isGuardEntry(entry) {
5765
+ function isGuardEntry(entry, marker2) {
5699
5766
  return (entry.hooks ?? []).some(
5700
- (h) => typeof h.command === "string" && h.command.includes(ASTRASYNC_HOOK_MARKER)
5767
+ (h) => typeof h.command === "string" && h.command.includes(marker2)
5701
5768
  );
5702
5769
  }
5703
5770
  function readSettings(settingsPath) {
@@ -5711,24 +5778,26 @@ function writeSettings(settingsPath, settings) {
5711
5778
  (0, import_fs5.writeFileSync)(settingsPath, `${JSON.stringify(settings, null, 2)}
5712
5779
  `, "utf-8");
5713
5780
  }
5714
- function buildHookEntry(hookCommand) {
5781
+ function buildHookEntry(hookCommand, matcher = HOOK_MATCHER, extraHookFields = {}) {
5715
5782
  return {
5716
- matcher: HOOK_MATCHER,
5717
- hooks: [{ type: "command", command: hookCommand, timeout: HOOK_TIMEOUT_SECONDS }]
5783
+ matcher,
5784
+ hooks: [
5785
+ { type: "command", command: hookCommand, timeout: HOOK_TIMEOUT_SECONDS, ...extraHookFields }
5786
+ ]
5718
5787
  };
5719
5788
  }
5720
- function installClaudeHooks(settingsPath, hookCommand) {
5721
- if (!hookCommand.includes(ASTRASYNC_HOOK_MARKER)) {
5789
+ function installClaudeHooks(settingsPath, hookCommand, marker2 = ASTRASYNC_HOOK_MARKER, matcher = HOOK_MATCHER, extraHookFields = {}) {
5790
+ if (!hookCommand.includes(marker2)) {
5722
5791
  throw new Error(
5723
- `Hook command must reference ${ASTRASYNC_HOOK_MARKER} (got: ${hookCommand}) \u2014 the marker is how install/uninstall identify the guard entry.`
5792
+ `Hook command must reference ${marker2} (got: ${hookCommand}) \u2014 the marker is how install/uninstall identify the guard entry.`
5724
5793
  );
5725
5794
  }
5726
5795
  const settings = readSettings(settingsPath);
5727
5796
  settings.hooks = settings.hooks ?? {};
5728
5797
  const preToolUse = settings.hooks.PreToolUse = settings.hooks.PreToolUse ?? [];
5729
- const existing = preToolUse.find(isGuardEntry);
5798
+ const existing = preToolUse.find((entry) => isGuardEntry(entry, marker2));
5730
5799
  if (existing) {
5731
- const fresh = buildHookEntry(hookCommand);
5800
+ const fresh = buildHookEntry(hookCommand, matcher, extraHookFields);
5732
5801
  const changed = JSON.stringify(existing) !== JSON.stringify(fresh);
5733
5802
  if (changed) {
5734
5803
  preToolUse[preToolUse.indexOf(existing)] = fresh;
@@ -5736,11 +5805,11 @@ function installClaudeHooks(settingsPath, hookCommand) {
5736
5805
  }
5737
5806
  return { installed: false, alreadyPresent: true, updated: changed };
5738
5807
  }
5739
- preToolUse.push(buildHookEntry(hookCommand));
5808
+ preToolUse.push(buildHookEntry(hookCommand, matcher, extraHookFields));
5740
5809
  writeSettings(settingsPath, settings);
5741
5810
  return { installed: true, alreadyPresent: false, updated: false };
5742
5811
  }
5743
- function uninstallClaudeHooks(settingsPath) {
5812
+ function uninstallClaudeHooks(settingsPath, marker2 = ASTRASYNC_HOOK_MARKER) {
5744
5813
  if (!(0, import_fs5.existsSync)(settingsPath)) return false;
5745
5814
  let settings;
5746
5815
  try {
@@ -5750,7 +5819,7 @@ function uninstallClaudeHooks(settingsPath) {
5750
5819
  }
5751
5820
  const preToolUse = settings.hooks?.PreToolUse;
5752
5821
  if (!preToolUse) return false;
5753
- const remaining = preToolUse.filter((entry) => !isGuardEntry(entry));
5822
+ const remaining = preToolUse.filter((entry) => !isGuardEntry(entry, marker2));
5754
5823
  if (remaining.length === preToolUse.length) return false;
5755
5824
  if (remaining.length > 0) {
5756
5825
  settings.hooks.PreToolUse = remaining;
@@ -5764,7 +5833,9 @@ function uninstallClaudeHooks(settingsPath) {
5764
5833
  var GUARD_SELF_PROTECTION_PATTERNS = [
5765
5834
  "**/.claude/settings.json",
5766
5835
  "**/.claude/settings.local.json",
5767
- "**/.astrasync/**"
5836
+ "**/.astrasync/**",
5837
+ "**/.codex/hooks.json",
5838
+ "**/.codex/config.toml"
5768
5839
  ];
5769
5840
  function ensureGuardSelfProtection(policyPath) {
5770
5841
  const parsed = parseFile(policyPath);
@@ -5811,6 +5882,25 @@ function buildDefaultClaudeCodePolicy(name = "claude-code-local-guard") {
5811
5882
  };
5812
5883
  }
5813
5884
 
5885
+ // src/codex/settings-installer.ts
5886
+ var ASTRASYNC_CODEX_HOOK_MARKER = "astrasync-codex-hook";
5887
+ var CODEX_HOOK_MATCHER = "Bash|apply_patch|Edit|Write|web_search|mcp__.*";
5888
+ var CODEX_STATUS_MESSAGE = "AstraSync Local Guard";
5889
+ function installCodexHooks(hooksPath, hookCommand) {
5890
+ return installClaudeHooks(
5891
+ hooksPath,
5892
+ hookCommand,
5893
+ ASTRASYNC_CODEX_HOOK_MARKER,
5894
+ CODEX_HOOK_MATCHER,
5895
+ {
5896
+ statusMessage: CODEX_STATUS_MESSAGE
5897
+ }
5898
+ );
5899
+ }
5900
+ function uninstallCodexHooks(hooksPath) {
5901
+ return uninstallClaudeHooks(hooksPath, ASTRASYNC_CODEX_HOOK_MARKER);
5902
+ }
5903
+
5814
5904
  // src/cli/guard.ts
5815
5905
  var DEFAULT_POLICY_PATH = "./local-policy.yaml";
5816
5906
  var DEFAULT_CONFIG_DIR = ".astrasync";
@@ -5976,6 +6066,7 @@ async function cmdStatus() {
5976
6066
  console.log(' Policy: not found \u2014 run "astrasync guard setup"');
5977
6067
  }
5978
6068
  printClaudeCodeStatus();
6069
+ printCodexStatus();
5979
6070
  console.log("");
5980
6071
  }
5981
6072
  async function cmdCheck() {
@@ -6154,6 +6245,108 @@ async function cmdUninstallClaudeCode() {
6154
6245
  `[AstraSync] Policy and traces left in place at ${astrasyncDir} \u2014 delete manually if unwanted.`
6155
6246
  );
6156
6247
  }
6248
+ function resolveCodexHookCommand() {
6249
+ const sibling = path.join(__dirname, "astrasync-codex-hook.js");
6250
+ if (fs.existsSync(sibling)) {
6251
+ return `"${process.execPath}" "${sibling}"`;
6252
+ }
6253
+ return "astrasync-codex-hook";
6254
+ }
6255
+ function codexPaths(project) {
6256
+ const root = project ? process.cwd() : os.homedir();
6257
+ return {
6258
+ hooksPath: path.join(root, ".codex", "hooks.json"),
6259
+ astrasyncDir: path.join(root, ".astrasync")
6260
+ };
6261
+ }
6262
+ async function cmdInstallCodex() {
6263
+ const flags = parseClaudeCodeFlags();
6264
+ const { hooksPath, astrasyncDir } = codexPaths(flags.project);
6265
+ const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
6266
+ const guardJsonPath = path.join(astrasyncDir, "guard.json");
6267
+ console.log("\nAstraSync Local Guard for Codex CLI \u2014 install\n");
6268
+ if (fs.existsSync(policyPath)) {
6269
+ console.log(` Policy: using existing ${policyPath} (shared across adapters)`);
6270
+ if (ensureGuardSelfProtection(policyPath)) {
6271
+ console.log(" added guard self-protection patterns to file.write");
6272
+ }
6273
+ } else if (flags.wizard) {
6274
+ const prompt = createPrompt();
6275
+ try {
6276
+ fs.mkdirSync(path.dirname(policyPath), { recursive: true });
6277
+ const wizard = new SetupWizard(prompt);
6278
+ await wizard.runAndSave(policyPath);
6279
+ } finally {
6280
+ prompt.close();
6281
+ }
6282
+ ensureGuardSelfProtection(policyPath);
6283
+ console.log(` Policy: created via wizard at ${policyPath}`);
6284
+ } else {
6285
+ fs.mkdirSync(path.dirname(policyPath), { recursive: true });
6286
+ fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
6287
+ console.log(` Policy: default coding-agent policy written to ${policyPath}`);
6288
+ console.log(" (re-run with --wizard for the interactive setup instead)");
6289
+ }
6290
+ if (!fs.existsSync(guardJsonPath)) {
6291
+ const guardSettings = {
6292
+ posture: flags.active ? "active" : "passive",
6293
+ governMcp: false
6294
+ };
6295
+ fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
6296
+ `, "utf-8");
6297
+ console.log(
6298
+ ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
6299
+ );
6300
+ } else {
6301
+ console.log(` Guard: using existing ${guardJsonPath}`);
6302
+ }
6303
+ const hookCommand = resolveCodexHookCommand();
6304
+ const result = installCodexHooks(hooksPath, hookCommand);
6305
+ if (result.installed) {
6306
+ console.log(` Hook: installed in ${hooksPath}`);
6307
+ } else if (result.updated) {
6308
+ console.log(` Hook: updated in ${hooksPath}`);
6309
+ } else {
6310
+ console.log(` Hook: already present in ${hooksPath}`);
6311
+ }
6312
+ console.log("\n Next steps:");
6313
+ console.log(" 1. Start a NEW Codex session (hooks load at startup; /hooks to inspect).");
6314
+ console.log(` 2. Watch decisions: tail -f ${path.join(astrasyncDir, "traces")}/*.jsonl`);
6315
+ console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}.`);
6316
+ console.log(" Denies block; approvals fall through to Codex's own prompt \u2014");
6317
+ console.log(" except in dontAsk/bypassPermissions modes, where they deny.");
6318
+ console.log(" 4. Note: Codex fails OPEN if the hook itself times out; the guard");
6319
+ console.log(" fails closed on every failure it can see.\n");
6320
+ }
6321
+ async function cmdUninstallCodex() {
6322
+ const flags = parseClaudeCodeFlags();
6323
+ const { hooksPath, astrasyncDir } = codexPaths(flags.project);
6324
+ const removed = uninstallCodexHooks(hooksPath);
6325
+ if (removed) {
6326
+ console.log(`[AstraSync] Removed Codex hook from ${hooksPath}`);
6327
+ } else {
6328
+ console.log(`[AstraSync] No AstraSync hook found in ${hooksPath}`);
6329
+ }
6330
+ console.log(
6331
+ `[AstraSync] Policy and traces left in place at ${astrasyncDir} \u2014 delete manually if unwanted.`
6332
+ );
6333
+ }
6334
+ function printCodexStatus() {
6335
+ for (const scope of ["user", "project"]) {
6336
+ const { hooksPath, astrasyncDir } = codexPaths(scope === "project");
6337
+ let hookInstalled = false;
6338
+ try {
6339
+ const content = fs.readFileSync(hooksPath, "utf-8");
6340
+ hookInstalled = content.includes(ASTRASYNC_CODEX_HOOK_MARKER);
6341
+ } catch {
6342
+ }
6343
+ if (hookInstalled) {
6344
+ const policyPath = path.join(astrasyncDir, "policy.yaml");
6345
+ const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
6346
+ console.log(` Codex CLI (${scope}): hook installed, ${policy}`);
6347
+ }
6348
+ }
6349
+ }
6157
6350
  function printClaudeCodeStatus() {
6158
6351
  for (const scope of ["user", "project"]) {
6159
6352
  const { settingsPath, astrasyncDir } = claudeCodePaths(scope === "project");
@@ -6211,6 +6404,12 @@ async function main() {
6211
6404
  case "uninstall-claude-code":
6212
6405
  await cmdUninstallClaudeCode();
6213
6406
  break;
6407
+ case "install-codex":
6408
+ await cmdInstallCodex();
6409
+ break;
6410
+ case "uninstall-codex":
6411
+ await cmdUninstallCodex();
6412
+ break;
6214
6413
  default:
6215
6414
  console.log("AstraSync Local Guard\n");
6216
6415
  console.log("Usage: astrasync guard <command>\n");
@@ -6228,6 +6427,9 @@ async function main() {
6228
6427
  console.log(
6229
6428
  " uninstall-claude-code Remove the Claude Code hook (--project for project scope)"
6230
6429
  );
6430
+ console.log(" install-codex Install the Local Guard hook for OpenAI Codex CLI");
6431
+ console.log(" (same flags; policy shared with Claude Code)");
6432
+ console.log(" uninstall-codex Remove the Codex hook (--project for project scope)");
6231
6433
  console.log("");
6232
6434
  process.exit(command ? 1 : 0);
6233
6435
  }