@astrasyncai/verification-gateway 3.10.1 → 3.11.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 (57) hide show
  1. package/dist/adapters/express.js +1 -1
  2. package/dist/adapters/express.js.map +1 -1
  3. package/dist/adapters/express.mjs +1 -1
  4. package/dist/adapters/express.mjs.map +1 -1
  5. package/dist/adapters/mcp.js +1 -1
  6. package/dist/adapters/mcp.js.map +1 -1
  7. package/dist/adapters/mcp.mjs +1 -1
  8. package/dist/adapters/mcp.mjs.map +1 -1
  9. package/dist/adapters/nextjs.js +1 -1
  10. package/dist/adapters/nextjs.js.map +1 -1
  11. package/dist/adapters/nextjs.mjs +1 -1
  12. package/dist/adapters/nextjs.mjs.map +1 -1
  13. package/dist/adapters/sdk.js +1 -1
  14. package/dist/adapters/sdk.js.map +1 -1
  15. package/dist/adapters/sdk.mjs +1 -1
  16. package/dist/adapters/sdk.mjs.map +1 -1
  17. package/dist/bin/astrasync-claude-hook.js +47 -13
  18. package/dist/bin/astrasync-codex-hook.js +5107 -0
  19. package/dist/bin/astrasync-guard.js +176 -18
  20. package/dist/bin/astrasync.js +184 -18
  21. package/dist/browser/background.js +26 -3
  22. package/dist/browser/background.js.map +1 -1
  23. package/dist/browser/background.mjs +26 -3
  24. package/dist/browser/background.mjs.map +1 -1
  25. package/dist/claude-code/claude-code-adapter.js +9 -1
  26. package/dist/claude-code/claude-code-adapter.js.map +1 -1
  27. package/dist/claude-code/claude-code-adapter.mjs +9 -1
  28. package/dist/claude-code/claude-code-adapter.mjs.map +1 -1
  29. package/dist/codex/index.d.mts +230 -0
  30. package/dist/codex/index.d.ts +230 -0
  31. package/dist/codex/index.js +5225 -0
  32. package/dist/codex/index.js.map +1 -0
  33. package/dist/codex/index.mjs +5197 -0
  34. package/dist/codex/index.mjs.map +1 -0
  35. package/dist/cursor/extension.js +26 -3
  36. package/dist/cursor/extension.js.map +1 -1
  37. package/dist/cursor/extension.mjs +26 -3
  38. package/dist/cursor/extension.mjs.map +1 -1
  39. package/dist/gateway/gateway.d.mts +5 -0
  40. package/dist/gateway/gateway.d.ts +5 -0
  41. package/dist/gateway/gateway.js +26 -3
  42. package/dist/gateway/gateway.js.map +1 -1
  43. package/dist/gateway/gateway.mjs +26 -3
  44. package/dist/gateway/gateway.mjs.map +1 -1
  45. package/dist/index.d.mts +1 -1
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.mjs +1 -1
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/local-evaluator/evaluator.js.map +1 -1
  52. package/dist/local-evaluator/evaluator.mjs.map +1 -1
  53. package/dist/verify.js +1 -1
  54. package/dist/verify.js.map +1 -1
  55. package/dist/verify.mjs +1 -1
  56. package/dist/verify.mjs.map +1 -1
  57. package/package.json +8 -2
@@ -4598,7 +4598,7 @@ function getTrustLevel(score) {
4598
4598
  }
4599
4599
 
4600
4600
  // src/version.ts
4601
- var SDK_VERSION = "3.10.1";
4601
+ var SDK_VERSION = "3.11.0";
4602
4602
 
4603
4603
  // src/well-known.ts
4604
4604
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -5113,10 +5113,12 @@ function toGatewayConfig(config) {
5113
5113
  };
5114
5114
  }
5115
5115
  function toVerificationRequest(context, astraId) {
5116
+ const dotIndex = context.purpose.indexOf(".");
5117
+ const dotted = dotIndex > 0;
5116
5118
  return {
5117
5119
  credentials: { astraId },
5118
- purpose: context.purpose,
5119
- action: context.action,
5120
+ purpose: dotted ? context.purpose.slice(0, dotIndex) : context.purpose,
5121
+ action: dotted ? context.purpose : context.action,
5120
5122
  resourceType: context.resourceType,
5121
5123
  resource: context.target,
5122
5124
  transactionValue: context.transactionValue,
@@ -5368,6 +5370,20 @@ var TraceLogger = class {
5368
5370
  decision: redactDeep(decision)
5369
5371
  });
5370
5372
  }
5373
+ /**
5374
+ * Log a tool call the guard saw but did not govern. One line per skip
5375
+ * keeps coverage holes (unmapped platform tools) visible in the traces
5376
+ * instead of silently indistinguishable from ALLOW.
5377
+ */
5378
+ logSkipped(toolName, reason) {
5379
+ if (!this.enabled) return;
5380
+ this.write({
5381
+ id: this.generateId(),
5382
+ timestamp: /* @__PURE__ */ new Date(),
5383
+ type: "skipped",
5384
+ metadata: { toolName, reason }
5385
+ });
5386
+ }
5371
5387
  /**
5372
5388
  * Log a mode switch event.
5373
5389
  */
@@ -5565,6 +5581,13 @@ var AstraSyncGateway = class {
5565
5581
  getSyncStatus() {
5566
5582
  return this.syncQueue?.getStatus() || null;
5567
5583
  }
5584
+ /**
5585
+ * Record a tool call the calling adapter saw but did not govern
5586
+ * (unmapped tool, skipTools, MCP with governMcp off). Trace-only.
5587
+ */
5588
+ logSkippedTool(toolName, reason) {
5589
+ this.traceLogger?.logSkipped(toolName, reason);
5590
+ }
5568
5591
  };
5569
5592
 
5570
5593
  // src/git-trigger/git-hooks.ts
@@ -5693,11 +5716,11 @@ async function evaluateChangedFiles(gateway, files, config = {}) {
5693
5716
  var import_fs5 = require("fs");
5694
5717
  var import_path4 = require("path");
5695
5718
  var ASTRASYNC_HOOK_MARKER = "astrasync-claude-hook";
5696
- var HOOK_MATCHER = "Bash|Write|Edit|MultiEdit|NotebookEdit|Read|Glob|Grep|WebFetch|WebSearch|Task|Agent|mcp__.*";
5719
+ var HOOK_MATCHER = "Bash|PowerShell|Write|Edit|MultiEdit|NotebookEdit|Read|Glob|Grep|WebFetch|WebSearch|Task|Agent|mcp__.*";
5697
5720
  var HOOK_TIMEOUT_SECONDS = 10;
5698
- function isGuardEntry(entry) {
5721
+ function isGuardEntry(entry, marker2) {
5699
5722
  return (entry.hooks ?? []).some(
5700
- (h) => typeof h.command === "string" && h.command.includes(ASTRASYNC_HOOK_MARKER)
5723
+ (h) => typeof h.command === "string" && h.command.includes(marker2)
5701
5724
  );
5702
5725
  }
5703
5726
  function readSettings(settingsPath) {
@@ -5711,24 +5734,26 @@ function writeSettings(settingsPath, settings) {
5711
5734
  (0, import_fs5.writeFileSync)(settingsPath, `${JSON.stringify(settings, null, 2)}
5712
5735
  `, "utf-8");
5713
5736
  }
5714
- function buildHookEntry(hookCommand) {
5737
+ function buildHookEntry(hookCommand, matcher = HOOK_MATCHER, extraHookFields = {}) {
5715
5738
  return {
5716
- matcher: HOOK_MATCHER,
5717
- hooks: [{ type: "command", command: hookCommand, timeout: HOOK_TIMEOUT_SECONDS }]
5739
+ matcher,
5740
+ hooks: [
5741
+ { type: "command", command: hookCommand, timeout: HOOK_TIMEOUT_SECONDS, ...extraHookFields }
5742
+ ]
5718
5743
  };
5719
5744
  }
5720
- function installClaudeHooks(settingsPath, hookCommand) {
5721
- if (!hookCommand.includes(ASTRASYNC_HOOK_MARKER)) {
5745
+ function installClaudeHooks(settingsPath, hookCommand, marker2 = ASTRASYNC_HOOK_MARKER, matcher = HOOK_MATCHER, extraHookFields = {}) {
5746
+ if (!hookCommand.includes(marker2)) {
5722
5747
  throw new Error(
5723
- `Hook command must reference ${ASTRASYNC_HOOK_MARKER} (got: ${hookCommand}) \u2014 the marker is how install/uninstall identify the guard entry.`
5748
+ `Hook command must reference ${marker2} (got: ${hookCommand}) \u2014 the marker is how install/uninstall identify the guard entry.`
5724
5749
  );
5725
5750
  }
5726
5751
  const settings = readSettings(settingsPath);
5727
5752
  settings.hooks = settings.hooks ?? {};
5728
5753
  const preToolUse = settings.hooks.PreToolUse = settings.hooks.PreToolUse ?? [];
5729
- const existing = preToolUse.find(isGuardEntry);
5754
+ const existing = preToolUse.find((entry) => isGuardEntry(entry, marker2));
5730
5755
  if (existing) {
5731
- const fresh = buildHookEntry(hookCommand);
5756
+ const fresh = buildHookEntry(hookCommand, matcher, extraHookFields);
5732
5757
  const changed = JSON.stringify(existing) !== JSON.stringify(fresh);
5733
5758
  if (changed) {
5734
5759
  preToolUse[preToolUse.indexOf(existing)] = fresh;
@@ -5736,11 +5761,11 @@ function installClaudeHooks(settingsPath, hookCommand) {
5736
5761
  }
5737
5762
  return { installed: false, alreadyPresent: true, updated: changed };
5738
5763
  }
5739
- preToolUse.push(buildHookEntry(hookCommand));
5764
+ preToolUse.push(buildHookEntry(hookCommand, matcher, extraHookFields));
5740
5765
  writeSettings(settingsPath, settings);
5741
5766
  return { installed: true, alreadyPresent: false, updated: false };
5742
5767
  }
5743
- function uninstallClaudeHooks(settingsPath) {
5768
+ function uninstallClaudeHooks(settingsPath, marker2 = ASTRASYNC_HOOK_MARKER) {
5744
5769
  if (!(0, import_fs5.existsSync)(settingsPath)) return false;
5745
5770
  let settings;
5746
5771
  try {
@@ -5750,7 +5775,7 @@ function uninstallClaudeHooks(settingsPath) {
5750
5775
  }
5751
5776
  const preToolUse = settings.hooks?.PreToolUse;
5752
5777
  if (!preToolUse) return false;
5753
- const remaining = preToolUse.filter((entry) => !isGuardEntry(entry));
5778
+ const remaining = preToolUse.filter((entry) => !isGuardEntry(entry, marker2));
5754
5779
  if (remaining.length === preToolUse.length) return false;
5755
5780
  if (remaining.length > 0) {
5756
5781
  settings.hooks.PreToolUse = remaining;
@@ -5764,7 +5789,9 @@ function uninstallClaudeHooks(settingsPath) {
5764
5789
  var GUARD_SELF_PROTECTION_PATTERNS = [
5765
5790
  "**/.claude/settings.json",
5766
5791
  "**/.claude/settings.local.json",
5767
- "**/.astrasync/**"
5792
+ "**/.astrasync/**",
5793
+ "**/.codex/hooks.json",
5794
+ "**/.codex/config.toml"
5768
5795
  ];
5769
5796
  function ensureGuardSelfProtection(policyPath) {
5770
5797
  const parsed = parseFile(policyPath);
@@ -5811,6 +5838,25 @@ function buildDefaultClaudeCodePolicy(name = "claude-code-local-guard") {
5811
5838
  };
5812
5839
  }
5813
5840
 
5841
+ // src/codex/settings-installer.ts
5842
+ var ASTRASYNC_CODEX_HOOK_MARKER = "astrasync-codex-hook";
5843
+ var CODEX_HOOK_MATCHER = "Bash|apply_patch|Edit|Write|web_search|mcp__.*";
5844
+ var CODEX_STATUS_MESSAGE = "AstraSync Local Guard";
5845
+ function installCodexHooks(hooksPath, hookCommand) {
5846
+ return installClaudeHooks(
5847
+ hooksPath,
5848
+ hookCommand,
5849
+ ASTRASYNC_CODEX_HOOK_MARKER,
5850
+ CODEX_HOOK_MATCHER,
5851
+ {
5852
+ statusMessage: CODEX_STATUS_MESSAGE
5853
+ }
5854
+ );
5855
+ }
5856
+ function uninstallCodexHooks(hooksPath) {
5857
+ return uninstallClaudeHooks(hooksPath, ASTRASYNC_CODEX_HOOK_MARKER);
5858
+ }
5859
+
5814
5860
  // src/cli/guard.ts
5815
5861
  var DEFAULT_POLICY_PATH = "./local-policy.yaml";
5816
5862
  var DEFAULT_CONFIG_DIR = ".astrasync";
@@ -5976,6 +6022,7 @@ async function cmdStatus() {
5976
6022
  console.log(' Policy: not found \u2014 run "astrasync guard setup"');
5977
6023
  }
5978
6024
  printClaudeCodeStatus();
6025
+ printCodexStatus();
5979
6026
  console.log("");
5980
6027
  }
5981
6028
  async function cmdCheck() {
@@ -6154,6 +6201,108 @@ async function cmdUninstallClaudeCode() {
6154
6201
  `[AstraSync] Policy and traces left in place at ${astrasyncDir} \u2014 delete manually if unwanted.`
6155
6202
  );
6156
6203
  }
6204
+ function resolveCodexHookCommand() {
6205
+ const sibling = path.join(__dirname, "astrasync-codex-hook.js");
6206
+ if (fs.existsSync(sibling)) {
6207
+ return `"${process.execPath}" "${sibling}"`;
6208
+ }
6209
+ return "astrasync-codex-hook";
6210
+ }
6211
+ function codexPaths(project) {
6212
+ const root = project ? process.cwd() : os.homedir();
6213
+ return {
6214
+ hooksPath: path.join(root, ".codex", "hooks.json"),
6215
+ astrasyncDir: path.join(root, ".astrasync")
6216
+ };
6217
+ }
6218
+ async function cmdInstallCodex() {
6219
+ const flags = parseClaudeCodeFlags();
6220
+ const { hooksPath, astrasyncDir } = codexPaths(flags.project);
6221
+ const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
6222
+ const guardJsonPath = path.join(astrasyncDir, "guard.json");
6223
+ console.log("\nAstraSync Local Guard for Codex CLI \u2014 install\n");
6224
+ if (fs.existsSync(policyPath)) {
6225
+ console.log(` Policy: using existing ${policyPath} (shared across adapters)`);
6226
+ if (ensureGuardSelfProtection(policyPath)) {
6227
+ console.log(" added guard self-protection patterns to file.write");
6228
+ }
6229
+ } else if (flags.wizard) {
6230
+ const prompt = createPrompt();
6231
+ try {
6232
+ fs.mkdirSync(path.dirname(policyPath), { recursive: true });
6233
+ const wizard = new SetupWizard(prompt);
6234
+ await wizard.runAndSave(policyPath);
6235
+ } finally {
6236
+ prompt.close();
6237
+ }
6238
+ ensureGuardSelfProtection(policyPath);
6239
+ console.log(` Policy: created via wizard at ${policyPath}`);
6240
+ } else {
6241
+ fs.mkdirSync(path.dirname(policyPath), { recursive: true });
6242
+ fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
6243
+ console.log(` Policy: default coding-agent policy written to ${policyPath}`);
6244
+ console.log(" (re-run with --wizard for the interactive setup instead)");
6245
+ }
6246
+ if (!fs.existsSync(guardJsonPath)) {
6247
+ const guardSettings = {
6248
+ posture: flags.active ? "active" : "passive",
6249
+ governMcp: false
6250
+ };
6251
+ fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
6252
+ `, "utf-8");
6253
+ console.log(
6254
+ ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
6255
+ );
6256
+ } else {
6257
+ console.log(` Guard: using existing ${guardJsonPath}`);
6258
+ }
6259
+ const hookCommand = resolveCodexHookCommand();
6260
+ const result = installCodexHooks(hooksPath, hookCommand);
6261
+ if (result.installed) {
6262
+ console.log(` Hook: installed in ${hooksPath}`);
6263
+ } else if (result.updated) {
6264
+ console.log(` Hook: updated in ${hooksPath}`);
6265
+ } else {
6266
+ console.log(` Hook: already present in ${hooksPath}`);
6267
+ }
6268
+ console.log("\n Next steps:");
6269
+ console.log(" 1. Start a NEW Codex session (hooks load at startup; /hooks to inspect).");
6270
+ console.log(` 2. Watch decisions: tail -f ${path.join(astrasyncDir, "traces")}/*.jsonl`);
6271
+ console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}.`);
6272
+ console.log(" Denies block; approvals fall through to Codex's own prompt \u2014");
6273
+ console.log(" except in dontAsk/bypassPermissions modes, where they deny.");
6274
+ console.log(" 4. Note: Codex fails OPEN if the hook itself times out; the guard");
6275
+ console.log(" fails closed on every failure it can see.\n");
6276
+ }
6277
+ async function cmdUninstallCodex() {
6278
+ const flags = parseClaudeCodeFlags();
6279
+ const { hooksPath, astrasyncDir } = codexPaths(flags.project);
6280
+ const removed = uninstallCodexHooks(hooksPath);
6281
+ if (removed) {
6282
+ console.log(`[AstraSync] Removed Codex hook from ${hooksPath}`);
6283
+ } else {
6284
+ console.log(`[AstraSync] No AstraSync hook found in ${hooksPath}`);
6285
+ }
6286
+ console.log(
6287
+ `[AstraSync] Policy and traces left in place at ${astrasyncDir} \u2014 delete manually if unwanted.`
6288
+ );
6289
+ }
6290
+ function printCodexStatus() {
6291
+ for (const scope of ["user", "project"]) {
6292
+ const { hooksPath, astrasyncDir } = codexPaths(scope === "project");
6293
+ let hookInstalled = false;
6294
+ try {
6295
+ const content = fs.readFileSync(hooksPath, "utf-8");
6296
+ hookInstalled = content.includes(ASTRASYNC_CODEX_HOOK_MARKER);
6297
+ } catch {
6298
+ }
6299
+ if (hookInstalled) {
6300
+ const policyPath = path.join(astrasyncDir, "policy.yaml");
6301
+ const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
6302
+ console.log(` Codex CLI (${scope}): hook installed, ${policy}`);
6303
+ }
6304
+ }
6305
+ }
6157
6306
  function printClaudeCodeStatus() {
6158
6307
  for (const scope of ["user", "project"]) {
6159
6308
  const { settingsPath, astrasyncDir } = claudeCodePaths(scope === "project");
@@ -6211,6 +6360,12 @@ async function main() {
6211
6360
  case "uninstall-claude-code":
6212
6361
  await cmdUninstallClaudeCode();
6213
6362
  break;
6363
+ case "install-codex":
6364
+ await cmdInstallCodex();
6365
+ break;
6366
+ case "uninstall-codex":
6367
+ await cmdUninstallCodex();
6368
+ break;
6214
6369
  default:
6215
6370
  console.log("AstraSync Local Guard\n");
6216
6371
  console.log("Usage: astrasync guard <command>\n");
@@ -6228,6 +6383,9 @@ async function main() {
6228
6383
  console.log(
6229
6384
  " uninstall-claude-code Remove the Claude Code hook (--project for project scope)"
6230
6385
  );
6386
+ console.log(" install-codex Install the Local Guard hook for OpenAI Codex CLI");
6387
+ console.log(" (same flags; policy shared with Claude Code)");
6388
+ console.log(" uninstall-codex Remove the Codex hook (--project for project scope)");
6231
6389
  console.log("");
6232
6390
  process.exit(command ? 1 : 0);
6233
6391
  }
@@ -4989,7 +4989,7 @@ var SDK_VERSION;
4989
4989
  var init_version = __esm({
4990
4990
  "src/version.ts"() {
4991
4991
  "use strict";
4992
- SDK_VERSION = "3.10.1";
4992
+ SDK_VERSION = "3.11.0";
4993
4993
  }
4994
4994
  });
4995
4995
 
@@ -5959,10 +5959,12 @@ function toGatewayConfig(config) {
5959
5959
  };
5960
5960
  }
5961
5961
  function toVerificationRequest(context, astraId) {
5962
+ const dotIndex = context.purpose.indexOf(".");
5963
+ const dotted = dotIndex > 0;
5962
5964
  return {
5963
5965
  credentials: { astraId },
5964
- purpose: context.purpose,
5965
- action: context.action,
5966
+ purpose: dotted ? context.purpose.slice(0, dotIndex) : context.purpose,
5967
+ action: dotted ? context.purpose : context.action,
5966
5968
  resourceType: context.resourceType,
5967
5969
  resource: context.target,
5968
5970
  transactionValue: context.transactionValue,
@@ -6237,6 +6239,20 @@ var init_trace_logger = __esm({
6237
6239
  decision: redactDeep(decision)
6238
6240
  });
6239
6241
  }
6242
+ /**
6243
+ * Log a tool call the guard saw but did not govern. One line per skip
6244
+ * keeps coverage holes (unmapped platform tools) visible in the traces
6245
+ * instead of silently indistinguishable from ALLOW.
6246
+ */
6247
+ logSkipped(toolName, reason) {
6248
+ if (!this.enabled) return;
6249
+ this.write({
6250
+ id: this.generateId(),
6251
+ timestamp: /* @__PURE__ */ new Date(),
6252
+ type: "skipped",
6253
+ metadata: { toolName, reason }
6254
+ });
6255
+ }
6240
6256
  /**
6241
6257
  * Log a mode switch event.
6242
6258
  */
@@ -6445,6 +6461,13 @@ var init_gateway = __esm({
6445
6461
  getSyncStatus() {
6446
6462
  return this.syncQueue?.getStatus() || null;
6447
6463
  }
6464
+ /**
6465
+ * Record a tool call the calling adapter saw but did not govern
6466
+ * (unmapped tool, skipTools, MCP with governMcp off). Trace-only.
6467
+ */
6468
+ logSkippedTool(toolName, reason) {
6469
+ this.traceLogger?.logSkipped(toolName, reason);
6470
+ }
6448
6471
  };
6449
6472
  }
6450
6473
  });
@@ -6579,9 +6602,9 @@ var init_git_hooks = __esm({
6579
6602
  });
6580
6603
 
6581
6604
  // src/claude-code/settings-installer.ts
6582
- function isGuardEntry(entry) {
6605
+ function isGuardEntry(entry, marker2) {
6583
6606
  return (entry.hooks ?? []).some(
6584
- (h) => typeof h.command === "string" && h.command.includes(ASTRASYNC_HOOK_MARKER)
6607
+ (h) => typeof h.command === "string" && h.command.includes(marker2)
6585
6608
  );
6586
6609
  }
6587
6610
  function readSettings(settingsPath) {
@@ -6595,24 +6618,26 @@ function writeSettings(settingsPath, settings) {
6595
6618
  (0, import_fs5.writeFileSync)(settingsPath, `${JSON.stringify(settings, null, 2)}
6596
6619
  `, "utf-8");
6597
6620
  }
6598
- function buildHookEntry(hookCommand) {
6621
+ function buildHookEntry(hookCommand, matcher = HOOK_MATCHER, extraHookFields = {}) {
6599
6622
  return {
6600
- matcher: HOOK_MATCHER,
6601
- hooks: [{ type: "command", command: hookCommand, timeout: HOOK_TIMEOUT_SECONDS }]
6623
+ matcher,
6624
+ hooks: [
6625
+ { type: "command", command: hookCommand, timeout: HOOK_TIMEOUT_SECONDS, ...extraHookFields }
6626
+ ]
6602
6627
  };
6603
6628
  }
6604
- function installClaudeHooks(settingsPath, hookCommand) {
6605
- if (!hookCommand.includes(ASTRASYNC_HOOK_MARKER)) {
6629
+ function installClaudeHooks(settingsPath, hookCommand, marker2 = ASTRASYNC_HOOK_MARKER, matcher = HOOK_MATCHER, extraHookFields = {}) {
6630
+ if (!hookCommand.includes(marker2)) {
6606
6631
  throw new Error(
6607
- `Hook command must reference ${ASTRASYNC_HOOK_MARKER} (got: ${hookCommand}) \u2014 the marker is how install/uninstall identify the guard entry.`
6632
+ `Hook command must reference ${marker2} (got: ${hookCommand}) \u2014 the marker is how install/uninstall identify the guard entry.`
6608
6633
  );
6609
6634
  }
6610
6635
  const settings = readSettings(settingsPath);
6611
6636
  settings.hooks = settings.hooks ?? {};
6612
6637
  const preToolUse = settings.hooks.PreToolUse = settings.hooks.PreToolUse ?? [];
6613
- const existing = preToolUse.find(isGuardEntry);
6638
+ const existing = preToolUse.find((entry) => isGuardEntry(entry, marker2));
6614
6639
  if (existing) {
6615
- const fresh = buildHookEntry(hookCommand);
6640
+ const fresh = buildHookEntry(hookCommand, matcher, extraHookFields);
6616
6641
  const changed = JSON.stringify(existing) !== JSON.stringify(fresh);
6617
6642
  if (changed) {
6618
6643
  preToolUse[preToolUse.indexOf(existing)] = fresh;
@@ -6620,11 +6645,11 @@ function installClaudeHooks(settingsPath, hookCommand) {
6620
6645
  }
6621
6646
  return { installed: false, alreadyPresent: true, updated: changed };
6622
6647
  }
6623
- preToolUse.push(buildHookEntry(hookCommand));
6648
+ preToolUse.push(buildHookEntry(hookCommand, matcher, extraHookFields));
6624
6649
  writeSettings(settingsPath, settings);
6625
6650
  return { installed: true, alreadyPresent: false, updated: false };
6626
6651
  }
6627
- function uninstallClaudeHooks(settingsPath) {
6652
+ function uninstallClaudeHooks(settingsPath, marker2 = ASTRASYNC_HOOK_MARKER) {
6628
6653
  if (!(0, import_fs5.existsSync)(settingsPath)) return false;
6629
6654
  let settings;
6630
6655
  try {
@@ -6634,7 +6659,7 @@ function uninstallClaudeHooks(settingsPath) {
6634
6659
  }
6635
6660
  const preToolUse = settings.hooks?.PreToolUse;
6636
6661
  if (!preToolUse) return false;
6637
- const remaining = preToolUse.filter((entry) => !isGuardEntry(entry));
6662
+ const remaining = preToolUse.filter((entry) => !isGuardEntry(entry, marker2));
6638
6663
  if (remaining.length === preToolUse.length) return false;
6639
6664
  if (remaining.length > 0) {
6640
6665
  settings.hooks.PreToolUse = remaining;
@@ -6698,16 +6723,44 @@ var init_settings_installer = __esm({
6698
6723
  init_wizard();
6699
6724
  init_pdlss_parser();
6700
6725
  ASTRASYNC_HOOK_MARKER = "astrasync-claude-hook";
6701
- HOOK_MATCHER = "Bash|Write|Edit|MultiEdit|NotebookEdit|Read|Glob|Grep|WebFetch|WebSearch|Task|Agent|mcp__.*";
6726
+ HOOK_MATCHER = "Bash|PowerShell|Write|Edit|MultiEdit|NotebookEdit|Read|Glob|Grep|WebFetch|WebSearch|Task|Agent|mcp__.*";
6702
6727
  HOOK_TIMEOUT_SECONDS = 10;
6703
6728
  GUARD_SELF_PROTECTION_PATTERNS = [
6704
6729
  "**/.claude/settings.json",
6705
6730
  "**/.claude/settings.local.json",
6706
- "**/.astrasync/**"
6731
+ "**/.astrasync/**",
6732
+ "**/.codex/hooks.json",
6733
+ "**/.codex/config.toml"
6707
6734
  ];
6708
6735
  }
6709
6736
  });
6710
6737
 
6738
+ // src/codex/settings-installer.ts
6739
+ function installCodexHooks(hooksPath, hookCommand) {
6740
+ return installClaudeHooks(
6741
+ hooksPath,
6742
+ hookCommand,
6743
+ ASTRASYNC_CODEX_HOOK_MARKER,
6744
+ CODEX_HOOK_MATCHER,
6745
+ {
6746
+ statusMessage: CODEX_STATUS_MESSAGE
6747
+ }
6748
+ );
6749
+ }
6750
+ function uninstallCodexHooks(hooksPath) {
6751
+ return uninstallClaudeHooks(hooksPath, ASTRASYNC_CODEX_HOOK_MARKER);
6752
+ }
6753
+ var ASTRASYNC_CODEX_HOOK_MARKER, CODEX_HOOK_MATCHER, CODEX_STATUS_MESSAGE;
6754
+ var init_settings_installer2 = __esm({
6755
+ "src/codex/settings-installer.ts"() {
6756
+ "use strict";
6757
+ init_settings_installer();
6758
+ ASTRASYNC_CODEX_HOOK_MARKER = "astrasync-codex-hook";
6759
+ CODEX_HOOK_MATCHER = "Bash|apply_patch|Edit|Write|web_search|mcp__.*";
6760
+ CODEX_STATUS_MESSAGE = "AstraSync Local Guard";
6761
+ }
6762
+ });
6763
+
6711
6764
  // src/cli/guard.ts
6712
6765
  var guard_exports = {};
6713
6766
  function createPrompt() {
@@ -6872,6 +6925,7 @@ async function cmdStatus() {
6872
6925
  console.log(' Policy: not found \u2014 run "astrasync guard setup"');
6873
6926
  }
6874
6927
  printClaudeCodeStatus();
6928
+ printCodexStatus();
6875
6929
  console.log("");
6876
6930
  }
6877
6931
  async function cmdCheck() {
@@ -7050,6 +7104,108 @@ async function cmdUninstallClaudeCode() {
7050
7104
  `[AstraSync] Policy and traces left in place at ${astrasyncDir} \u2014 delete manually if unwanted.`
7051
7105
  );
7052
7106
  }
7107
+ function resolveCodexHookCommand() {
7108
+ const sibling = path.join(__dirname, "astrasync-codex-hook.js");
7109
+ if (fs.existsSync(sibling)) {
7110
+ return `"${process.execPath}" "${sibling}"`;
7111
+ }
7112
+ return "astrasync-codex-hook";
7113
+ }
7114
+ function codexPaths(project) {
7115
+ const root = project ? process.cwd() : os.homedir();
7116
+ return {
7117
+ hooksPath: path.join(root, ".codex", "hooks.json"),
7118
+ astrasyncDir: path.join(root, ".astrasync")
7119
+ };
7120
+ }
7121
+ async function cmdInstallCodex() {
7122
+ const flags = parseClaudeCodeFlags();
7123
+ const { hooksPath, astrasyncDir } = codexPaths(flags.project);
7124
+ const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
7125
+ const guardJsonPath = path.join(astrasyncDir, "guard.json");
7126
+ console.log("\nAstraSync Local Guard for Codex CLI \u2014 install\n");
7127
+ if (fs.existsSync(policyPath)) {
7128
+ console.log(` Policy: using existing ${policyPath} (shared across adapters)`);
7129
+ if (ensureGuardSelfProtection(policyPath)) {
7130
+ console.log(" added guard self-protection patterns to file.write");
7131
+ }
7132
+ } else if (flags.wizard) {
7133
+ const prompt = createPrompt();
7134
+ try {
7135
+ fs.mkdirSync(path.dirname(policyPath), { recursive: true });
7136
+ const wizard = new SetupWizard(prompt);
7137
+ await wizard.runAndSave(policyPath);
7138
+ } finally {
7139
+ prompt.close();
7140
+ }
7141
+ ensureGuardSelfProtection(policyPath);
7142
+ console.log(` Policy: created via wizard at ${policyPath}`);
7143
+ } else {
7144
+ fs.mkdirSync(path.dirname(policyPath), { recursive: true });
7145
+ fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
7146
+ console.log(` Policy: default coding-agent policy written to ${policyPath}`);
7147
+ console.log(" (re-run with --wizard for the interactive setup instead)");
7148
+ }
7149
+ if (!fs.existsSync(guardJsonPath)) {
7150
+ const guardSettings = {
7151
+ posture: flags.active ? "active" : "passive",
7152
+ governMcp: false
7153
+ };
7154
+ fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
7155
+ `, "utf-8");
7156
+ console.log(
7157
+ ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
7158
+ );
7159
+ } else {
7160
+ console.log(` Guard: using existing ${guardJsonPath}`);
7161
+ }
7162
+ const hookCommand = resolveCodexHookCommand();
7163
+ const result = installCodexHooks(hooksPath, hookCommand);
7164
+ if (result.installed) {
7165
+ console.log(` Hook: installed in ${hooksPath}`);
7166
+ } else if (result.updated) {
7167
+ console.log(` Hook: updated in ${hooksPath}`);
7168
+ } else {
7169
+ console.log(` Hook: already present in ${hooksPath}`);
7170
+ }
7171
+ console.log("\n Next steps:");
7172
+ console.log(" 1. Start a NEW Codex session (hooks load at startup; /hooks to inspect).");
7173
+ console.log(` 2. Watch decisions: tail -f ${path.join(astrasyncDir, "traces")}/*.jsonl`);
7174
+ console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}.`);
7175
+ console.log(" Denies block; approvals fall through to Codex's own prompt \u2014");
7176
+ console.log(" except in dontAsk/bypassPermissions modes, where they deny.");
7177
+ console.log(" 4. Note: Codex fails OPEN if the hook itself times out; the guard");
7178
+ console.log(" fails closed on every failure it can see.\n");
7179
+ }
7180
+ async function cmdUninstallCodex() {
7181
+ const flags = parseClaudeCodeFlags();
7182
+ const { hooksPath, astrasyncDir } = codexPaths(flags.project);
7183
+ const removed = uninstallCodexHooks(hooksPath);
7184
+ if (removed) {
7185
+ console.log(`[AstraSync] Removed Codex hook from ${hooksPath}`);
7186
+ } else {
7187
+ console.log(`[AstraSync] No AstraSync hook found in ${hooksPath}`);
7188
+ }
7189
+ console.log(
7190
+ `[AstraSync] Policy and traces left in place at ${astrasyncDir} \u2014 delete manually if unwanted.`
7191
+ );
7192
+ }
7193
+ function printCodexStatus() {
7194
+ for (const scope of ["user", "project"]) {
7195
+ const { hooksPath, astrasyncDir } = codexPaths(scope === "project");
7196
+ let hookInstalled = false;
7197
+ try {
7198
+ const content = fs.readFileSync(hooksPath, "utf-8");
7199
+ hookInstalled = content.includes(ASTRASYNC_CODEX_HOOK_MARKER);
7200
+ } catch {
7201
+ }
7202
+ if (hookInstalled) {
7203
+ const policyPath = path.join(astrasyncDir, "policy.yaml");
7204
+ const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
7205
+ console.log(` Codex CLI (${scope}): hook installed, ${policy}`);
7206
+ }
7207
+ }
7208
+ }
7053
7209
  function printClaudeCodeStatus() {
7054
7210
  for (const scope of ["user", "project"]) {
7055
7211
  const { settingsPath, astrasyncDir } = claudeCodePaths(scope === "project");
@@ -7107,6 +7263,12 @@ async function main() {
7107
7263
  case "uninstall-claude-code":
7108
7264
  await cmdUninstallClaudeCode();
7109
7265
  break;
7266
+ case "install-codex":
7267
+ await cmdInstallCodex();
7268
+ break;
7269
+ case "uninstall-codex":
7270
+ await cmdUninstallCodex();
7271
+ break;
7110
7272
  default:
7111
7273
  console.log("AstraSync Local Guard\n");
7112
7274
  console.log("Usage: astrasync guard <command>\n");
@@ -7124,6 +7286,9 @@ async function main() {
7124
7286
  console.log(
7125
7287
  " uninstall-claude-code Remove the Claude Code hook (--project for project scope)"
7126
7288
  );
7289
+ console.log(" install-codex Install the Local Guard hook for OpenAI Codex CLI");
7290
+ console.log(" (same flags; policy shared with Claude Code)");
7291
+ console.log(" uninstall-codex Remove the Codex hook (--project for project scope)");
7127
7292
  console.log("");
7128
7293
  process.exit(command ? 1 : 0);
7129
7294
  }
@@ -7143,6 +7308,7 @@ var init_guard = __esm({
7143
7308
  init_pdlss_parser();
7144
7309
  init_git_hooks();
7145
7310
  init_settings_installer();
7311
+ init_settings_installer2();
7146
7312
  DEFAULT_POLICY_PATH = "./local-policy.yaml";
7147
7313
  DEFAULT_CONFIG_DIR = ".astrasync";
7148
7314
  main().catch((err) => {
@@ -3927,7 +3927,7 @@ function getTrustLevel(score) {
3927
3927
  }
3928
3928
 
3929
3929
  // src/version.ts
3930
- var SDK_VERSION = "3.10.1";
3930
+ var SDK_VERSION = "3.11.0";
3931
3931
 
3932
3932
  // src/well-known.ts
3933
3933
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -4442,10 +4442,12 @@ function toGatewayConfig(config) {
4442
4442
  };
4443
4443
  }
4444
4444
  function toVerificationRequest(context, astraId) {
4445
+ const dotIndex = context.purpose.indexOf(".");
4446
+ const dotted = dotIndex > 0;
4445
4447
  return {
4446
4448
  credentials: { astraId },
4447
- purpose: context.purpose,
4448
- action: context.action,
4449
+ purpose: dotted ? context.purpose.slice(0, dotIndex) : context.purpose,
4450
+ action: dotted ? context.purpose : context.action,
4449
4451
  resourceType: context.resourceType,
4450
4452
  resource: context.target,
4451
4453
  transactionValue: context.transactionValue,
@@ -4697,6 +4699,20 @@ var TraceLogger = class {
4697
4699
  decision: redactDeep(decision)
4698
4700
  });
4699
4701
  }
4702
+ /**
4703
+ * Log a tool call the guard saw but did not govern. One line per skip
4704
+ * keeps coverage holes (unmapped platform tools) visible in the traces
4705
+ * instead of silently indistinguishable from ALLOW.
4706
+ */
4707
+ logSkipped(toolName, reason) {
4708
+ if (!this.enabled) return;
4709
+ this.write({
4710
+ id: this.generateId(),
4711
+ timestamp: /* @__PURE__ */ new Date(),
4712
+ type: "skipped",
4713
+ metadata: { toolName, reason }
4714
+ });
4715
+ }
4700
4716
  /**
4701
4717
  * Log a mode switch event.
4702
4718
  */
@@ -4894,6 +4910,13 @@ var AstraSyncGateway = class {
4894
4910
  getSyncStatus() {
4895
4911
  return this.syncQueue?.getStatus() || null;
4896
4912
  }
4913
+ /**
4914
+ * Record a tool call the calling adapter saw but did not govern
4915
+ * (unmapped tool, skipTools, MCP with governMcp off). Trace-only.
4916
+ */
4917
+ logSkippedTool(toolName, reason) {
4918
+ this.traceLogger?.logSkipped(toolName, reason);
4919
+ }
4897
4920
  };
4898
4921
 
4899
4922
  // src/browser/background.ts