agentapprove 0.1.8 → 0.1.9

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +236 -39
  3. package/package.json +10 -8
package/README.md CHANGED
@@ -95,7 +95,7 @@ agentapprove --no-e2e # Skip end-to-end encryption setup
95
95
  - Node.js 18+
96
96
  - macOS, Linux, or Windows (with Git Bash / Git for Windows)
97
97
  - System tools: `curl`, `jq`, `openssl` (included with Git Bash on Windows)
98
- - Agent Approve iOS app and active subscription ($9.99/month or $99/year, 7-day free trial)
98
+ - Agent Approve iOS app and active subscription ($14.99/month, 7-day free trial)
99
99
 
100
100
  ## Links
101
101
 
package/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ var __toESM = (mod, isNodeMode, target) => {
33
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
34
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
35
35
 
36
- // node_modules/sisteransi/src/index.js
36
+ // ../../node_modules/.bun/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
37
37
  var require_src = __commonJS((exports, module) => {
38
38
  var ESC = "\x1B";
39
39
  var CSI = `${ESC}[`;
@@ -91,7 +91,7 @@ var require_src = __commonJS((exports, module) => {
91
91
  module.exports = { cursor, scroll, erase, beep };
92
92
  });
93
93
 
94
- // node_modules/picocolors/picocolors.js
94
+ // ../../node_modules/.bun/picocolors@1.1.1/node_modules/picocolors/picocolors.js
95
95
  var require_picocolors = __commonJS((exports, module) => {
96
96
  var p = process || {};
97
97
  var argv = p.argv || [];
@@ -161,7 +161,7 @@ var require_picocolors = __commonJS((exports, module) => {
161
161
  module.exports.createColors = createColors;
162
162
  });
163
163
 
164
- // node_modules/qrcode-terminal/vendor/QRCode/QRMode.js
164
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMode.js
165
165
  var require_QRMode = __commonJS((exports, module) => {
166
166
  module.exports = {
167
167
  MODE_NUMBER: 1 << 0,
@@ -171,7 +171,7 @@ var require_QRMode = __commonJS((exports, module) => {
171
171
  };
172
172
  });
173
173
 
174
- // node_modules/qrcode-terminal/vendor/QRCode/QR8bitByte.js
174
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QR8bitByte.js
175
175
  var require_QR8bitByte = __commonJS((exports, module) => {
176
176
  var QRMode = require_QRMode();
177
177
  function QR8bitByte(data) {
@@ -191,7 +191,7 @@ var require_QR8bitByte = __commonJS((exports, module) => {
191
191
  module.exports = QR8bitByte;
192
192
  });
193
193
 
194
- // node_modules/qrcode-terminal/vendor/QRCode/QRMath.js
194
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMath.js
195
195
  var require_QRMath = __commonJS((exports, module) => {
196
196
  var QRMath = {
197
197
  glog: function(n) {
@@ -227,7 +227,7 @@ var require_QRMath = __commonJS((exports, module) => {
227
227
  module.exports = QRMath;
228
228
  });
229
229
 
230
- // node_modules/qrcode-terminal/vendor/QRCode/QRPolynomial.js
230
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRPolynomial.js
231
231
  var require_QRPolynomial = __commonJS((exports, module) => {
232
232
  var QRMath = require_QRMath();
233
233
  function QRPolynomial(num, shift) {
@@ -277,7 +277,7 @@ var require_QRPolynomial = __commonJS((exports, module) => {
277
277
  module.exports = QRPolynomial;
278
278
  });
279
279
 
280
- // node_modules/qrcode-terminal/vendor/QRCode/QRMaskPattern.js
280
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMaskPattern.js
281
281
  var require_QRMaskPattern = __commonJS((exports, module) => {
282
282
  module.exports = {
283
283
  PATTERN000: 0,
@@ -291,7 +291,7 @@ var require_QRMaskPattern = __commonJS((exports, module) => {
291
291
  };
292
292
  });
293
293
 
294
- // node_modules/qrcode-terminal/vendor/QRCode/QRUtil.js
294
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRUtil.js
295
295
  var require_QRUtil = __commonJS((exports, module) => {
296
296
  var QRMode = require_QRMode();
297
297
  var QRPolynomial = require_QRPolynomial();
@@ -517,7 +517,7 @@ var require_QRUtil = __commonJS((exports, module) => {
517
517
  module.exports = QRUtil;
518
518
  });
519
519
 
520
- // node_modules/qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js
520
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js
521
521
  var require_QRErrorCorrectLevel = __commonJS((exports, module) => {
522
522
  module.exports = {
523
523
  L: 1,
@@ -527,7 +527,7 @@ var require_QRErrorCorrectLevel = __commonJS((exports, module) => {
527
527
  };
528
528
  });
529
529
 
530
- // node_modules/qrcode-terminal/vendor/QRCode/QRRSBlock.js
530
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRRSBlock.js
531
531
  var require_QRRSBlock = __commonJS((exports, module) => {
532
532
  var QRErrorCorrectLevel = require_QRErrorCorrectLevel();
533
533
  function QRRSBlock(totalCount, dataCount) {
@@ -730,7 +730,7 @@ var require_QRRSBlock = __commonJS((exports, module) => {
730
730
  module.exports = QRRSBlock;
731
731
  });
732
732
 
733
- // node_modules/qrcode-terminal/vendor/QRCode/QRBitBuffer.js
733
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRBitBuffer.js
734
734
  var require_QRBitBuffer = __commonJS((exports, module) => {
735
735
  function QRBitBuffer() {
736
736
  this.buffer = [];
@@ -763,7 +763,7 @@ var require_QRBitBuffer = __commonJS((exports, module) => {
763
763
  module.exports = QRBitBuffer;
764
764
  });
765
765
 
766
- // node_modules/qrcode-terminal/vendor/QRCode/index.js
766
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/index.js
767
767
  var require_QRCode = __commonJS((exports, module) => {
768
768
  var QR8bitByte = require_QR8bitByte();
769
769
  var QRUtil = require_QRUtil();
@@ -1084,7 +1084,7 @@ var require_QRCode = __commonJS((exports, module) => {
1084
1084
  module.exports = QRCode;
1085
1085
  });
1086
1086
 
1087
- // node_modules/qrcode-terminal/lib/main.js
1087
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/lib/main.js
1088
1088
  var require_main = __commonJS((exports, module) => {
1089
1089
  var QRCode = require_QRCode();
1090
1090
  var QRErrorCorrectLevel = require_QRErrorCorrectLevel();
@@ -1178,7 +1178,7 @@ var require_main = __commonJS((exports, module) => {
1178
1178
  };
1179
1179
  });
1180
1180
 
1181
- // node_modules/@clack/core/dist/index.mjs
1181
+ // ../../node_modules/.bun/@clack+core@0.3.5/node_modules/@clack/core/dist/index.mjs
1182
1182
  var import_sisteransi = __toESM(require_src(), 1);
1183
1183
  import { stdin as $, stdout as k } from "node:process";
1184
1184
  import * as f from "node:readline";
@@ -1592,7 +1592,7 @@ function OD({ input: e = $, output: u = k, overwrite: F = true, hideCursor: t =
1592
1592
  };
1593
1593
  }
1594
1594
 
1595
- // node_modules/@clack/prompts/dist/index.mjs
1595
+ // ../../node_modules/.bun/@clack+prompts@0.8.2/node_modules/@clack/prompts/dist/index.mjs
1596
1596
  var import_picocolors = __toESM(require_picocolors(), 1);
1597
1597
  var import_sisteransi2 = __toESM(require_src(), 1);
1598
1598
  import h from "node:process";
@@ -1836,7 +1836,7 @@ var ve = async (s, n) => {
1836
1836
  return t;
1837
1837
  };
1838
1838
 
1839
- // node_modules/chalk/source/vendor/ansi-styles/index.js
1839
+ // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
1840
1840
  var ANSI_BACKGROUND_OFFSET = 10;
1841
1841
  var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
1842
1842
  var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
@@ -2013,7 +2013,7 @@ function assembleStyles() {
2013
2013
  var ansiStyles = assembleStyles();
2014
2014
  var ansi_styles_default = ansiStyles;
2015
2015
 
2016
- // node_modules/chalk/source/vendor/supports-color/index.js
2016
+ // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
2017
2017
  import process2 from "node:process";
2018
2018
  import os from "node:os";
2019
2019
  import tty from "node:tty";
@@ -2145,7 +2145,7 @@ var supportsColor = {
2145
2145
  };
2146
2146
  var supports_color_default = supportsColor;
2147
2147
 
2148
- // node_modules/chalk/source/utilities.js
2148
+ // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/utilities.js
2149
2149
  function stringReplaceAll(string, substring, replacer) {
2150
2150
  let index = string.indexOf(substring);
2151
2151
  if (index === -1) {
@@ -2178,7 +2178,7 @@ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
2178
2178
  return returnValue;
2179
2179
  }
2180
2180
 
2181
- // node_modules/chalk/source/index.js
2181
+ // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/index.js
2182
2182
  var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
2183
2183
  var GENERATOR = Symbol("GENERATOR");
2184
2184
  var STYLER = Symbol("STYLER");
@@ -2332,7 +2332,7 @@ import { homedir, hostname, platform } from "os";
2332
2332
  import { join, dirname, basename } from "path";
2333
2333
  import { execSync, spawnSync } from "child_process";
2334
2334
  import { randomBytes, createHash } from "crypto";
2335
- var VERSION = "0.1.7";
2335
+ var VERSION = "0.1.9";
2336
2336
  function getApiUrl() {
2337
2337
  return process.env.AGENTAPPROVE_API || "https://api.agentapprove.com";
2338
2338
  }
@@ -2380,6 +2380,9 @@ function getCommand() {
2380
2380
  }
2381
2381
  return filtered[0] || "install";
2382
2382
  }
2383
+ var OPENCODE_PLUGIN_VERSION = "0.1.6";
2384
+ var OPENCLAW_PLUGIN_VERSION = "0.2.3";
2385
+ var OPENCLAW_PLUGIN_SPEC = `@agentapprove/openclaw@${OPENCLAW_PLUGIN_VERSION}`;
2383
2386
  var AGENTS = {
2384
2387
  "claude-code": {
2385
2388
  name: "Claude Code",
@@ -2435,7 +2438,7 @@ var AGENTS = {
2435
2438
  ]
2436
2439
  },
2437
2440
  "vscode-agent": {
2438
- name: "VS Code Agent Hooks",
2441
+ name: "VS Code GitHub Copilot",
2439
2442
  configPath: join(homedir(), ".agentapprove", "hooks", "vscode-hooks.json"),
2440
2443
  hooksKey: "hooks",
2441
2444
  hooks: [
@@ -2450,7 +2453,7 @@ var AGENTS = {
2450
2453
  ]
2451
2454
  },
2452
2455
  "copilot-cli": {
2453
- name: "Copilot CLI",
2456
+ name: "GitHub Copilot CLI",
2454
2457
  configPath: join(homedir(), ".agentapprove", "copilot-cli-hooks.json"),
2455
2458
  hooksKey: "hooks",
2456
2459
  hooks: [
@@ -2470,6 +2473,18 @@ var AGENTS = {
2470
2473
  { name: "agentapprove", file: "@agentapprove/openclaw", description: "Tool approval + event monitoring", isApprovalHook: true, isPlugin: true }
2471
2474
  ]
2472
2475
  },
2476
+ codex: {
2477
+ name: "OpenAI Codex",
2478
+ configPath: join(homedir(), ".codex", "hooks.json"),
2479
+ hooksKey: "hooks",
2480
+ hooks: [
2481
+ { name: "SessionStart", file: "codex-session-start.sh", description: "Session started", hasMatcher: true },
2482
+ { name: "PreToolUse", file: "codex-pre-tool.sh", description: "Tool approval", hasMatcher: true, isApprovalHook: true, timeout: 300 },
2483
+ { name: "PostToolUse", file: "codex-post-tool.sh", description: "Tool completion logging", hasMatcher: true },
2484
+ { name: "UserPromptSubmit", file: "codex-user-prompt.sh", description: "User prompt submitted", hasMatcher: false },
2485
+ { name: "Stop", file: "codex-stop.sh", description: "Agent stopped (iOS input)", hasMatcher: false, timeout: 600 }
2486
+ ]
2487
+ },
2473
2488
  opencode: {
2474
2489
  name: "OpenCode",
2475
2490
  configPath: getOpenCodeConfigPath(),
@@ -2594,6 +2609,15 @@ function readJsoncConfig(configPath) {
2594
2609
  return {};
2595
2610
  }
2596
2611
  }
2612
+ function getGithubHookEnv(agentId) {
2613
+ if (agentId === "vscode-agent") {
2614
+ return { AGENTAPPROVE_GITHUB_VARIANT: "vscode" };
2615
+ }
2616
+ if (agentId === "copilot-cli") {
2617
+ return { AGENTAPPROVE_GITHUB_VARIANT: "github" };
2618
+ }
2619
+ return;
2620
+ }
2597
2621
  function addToVSCodeHookLocations() {
2598
2622
  const hookLocation = "~/.agentapprove/hooks";
2599
2623
  const modified = [];
@@ -2650,6 +2674,10 @@ function detectInstalledAgents() {
2650
2674
  if (existsSync(join(homedir(), ".openclaw"))) {
2651
2675
  installed.push(id);
2652
2676
  }
2677
+ } else if (id === "codex") {
2678
+ if (existsSync(join(homedir(), ".codex"))) {
2679
+ installed.push(id);
2680
+ }
2653
2681
  } else if (id === "opencode") {
2654
2682
  if (existsSync(getOpenCodeConfigDir())) {
2655
2683
  installed.push(id);
@@ -2814,6 +2842,87 @@ function writeJsonConfig(configPath, config) {
2814
2842
  writeFileSync(configPath, JSON.stringify(config, null, 2) + `
2815
2843
  `);
2816
2844
  }
2845
+ function ensureCodexFeatureFlag(configPath = join(homedir(), ".codex", "config.toml")) {
2846
+ const dir = dirname(configPath);
2847
+ if (!existsSync(dir)) {
2848
+ mkdirSync(dir, { recursive: true, mode: 448 });
2849
+ }
2850
+ const sectionHeader = "[features]";
2851
+ const desiredLine = "codex_hooks = true";
2852
+ if (!existsSync(configPath)) {
2853
+ writeFileSync(configPath, `${sectionHeader}
2854
+ ${desiredLine}
2855
+ `, { mode: 384 });
2856
+ return { updated: true, backupPath: null };
2857
+ }
2858
+ const original = readFileSync(configPath, "utf-8");
2859
+ const lines = original.split(/\r?\n/);
2860
+ const updatedLines = [];
2861
+ let inFeatures = false;
2862
+ let foundFeatures = false;
2863
+ let foundKey = false;
2864
+ let changed = false;
2865
+ for (const line of lines) {
2866
+ const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
2867
+ if (sectionMatch) {
2868
+ if (inFeatures && !foundKey) {
2869
+ updatedLines.push(desiredLine);
2870
+ foundKey = true;
2871
+ changed = true;
2872
+ }
2873
+ inFeatures = sectionMatch[1] === "features";
2874
+ foundFeatures ||= inFeatures;
2875
+ updatedLines.push(line);
2876
+ continue;
2877
+ }
2878
+ if (inFeatures && /^\s*codex_hooks\s*=/.test(line)) {
2879
+ if (line.trim() !== desiredLine) {
2880
+ updatedLines.push(desiredLine);
2881
+ changed = true;
2882
+ } else {
2883
+ updatedLines.push(line);
2884
+ }
2885
+ foundKey = true;
2886
+ continue;
2887
+ }
2888
+ updatedLines.push(line);
2889
+ }
2890
+ if (inFeatures && !foundKey) {
2891
+ updatedLines.push(desiredLine);
2892
+ changed = true;
2893
+ }
2894
+ if (!foundFeatures) {
2895
+ const hasContent = updatedLines.some((line) => line.trim() !== "");
2896
+ if (hasContent) {
2897
+ updatedLines.push("");
2898
+ }
2899
+ updatedLines.push(sectionHeader, desiredLine);
2900
+ changed = true;
2901
+ }
2902
+ if (!changed) {
2903
+ return { updated: false, backupPath: null };
2904
+ }
2905
+ const backupPath = backupConfig(configPath);
2906
+ writeFileSync(configPath, `${updatedLines.join(`
2907
+ `).replace(/\n*$/, `
2908
+ `)}`, { mode: 384 });
2909
+ return { updated: true, backupPath };
2910
+ }
2911
+ function disableCodexFeatureFlag(configPath = join(homedir(), ".codex", "config.toml")) {
2912
+ if (!existsSync(configPath)) {
2913
+ return { updated: false, backupPath: null };
2914
+ }
2915
+ const original = readFileSync(configPath, "utf-8");
2916
+ const updated = original.split(/\r?\n/).filter((line) => !/^\s*codex_hooks\s*=/.test(line)).join(`
2917
+ `).replace(/\n*$/, `
2918
+ `);
2919
+ if (updated === original) {
2920
+ return { updated: false, backupPath: null };
2921
+ }
2922
+ const backupPath = backupConfig(configPath);
2923
+ writeFileSync(configPath, updated, { mode: 384 });
2924
+ return { updated: true, backupPath };
2925
+ }
2817
2926
  async function createPairingSession(configuredAgents, e2eKeyId) {
2818
2927
  try {
2819
2928
  const machineHostname = hostname();
@@ -3147,6 +3256,34 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3147
3256
  hookArray.push(hookEntry);
3148
3257
  }
3149
3258
  installedHooks.push(hook.name);
3259
+ } else if (agentId === "codex") {
3260
+ if (!hooksConfig[hook.name]) {
3261
+ hooksConfig[hook.name] = [];
3262
+ }
3263
+ const hookArray = hooksConfig[hook.name];
3264
+ const hasMatcher = hook.hasMatcher ?? true;
3265
+ const hookTimeout = hook.timeout;
3266
+ const existingIdx = hookArray.findIndex((h2) => h2.hooks?.some((hookScript) => {
3267
+ if (typeof hookScript === "string")
3268
+ return hookScript.includes("agentapprove");
3269
+ if (typeof hookScript === "object" && hookScript.command)
3270
+ return hookScript.command.includes("agentapprove");
3271
+ return false;
3272
+ }));
3273
+ const hookObject = {
3274
+ type: "command",
3275
+ command: hookCommand
3276
+ };
3277
+ if (hookTimeout) {
3278
+ hookObject.timeout = hookTimeout;
3279
+ }
3280
+ const hookEntry = hasMatcher ? { matcher: "*", hooks: [hookObject] } : { hooks: [hookObject] };
3281
+ if (existingIdx >= 0) {
3282
+ hookArray[existingIdx] = hookEntry;
3283
+ } else {
3284
+ hookArray.push(hookEntry);
3285
+ }
3286
+ installedHooks.push(hook.name);
3150
3287
  } else if (agentId === "cursor") {
3151
3288
  if (typeof config["version"] !== "number") {
3152
3289
  config["version"] = 1;
@@ -3231,6 +3368,10 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3231
3368
  type: "command",
3232
3369
  command: hookCommand
3233
3370
  };
3371
+ const hookEnv = getGithubHookEnv(agentId);
3372
+ if (hookEnv) {
3373
+ hookEntry.env = hookEnv;
3374
+ }
3234
3375
  if (hook.isApprovalHook) {
3235
3376
  hookEntry.timeout = 300;
3236
3377
  }
@@ -3256,6 +3397,10 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3256
3397
  type: "command",
3257
3398
  bash: isWindows() ? toGitBashPath(hookPath) : hookPath
3258
3399
  };
3400
+ const hookEnv = getGithubHookEnv(agentId);
3401
+ if (hookEnv) {
3402
+ hookEntry.env = hookEnv;
3403
+ }
3259
3404
  if (hook.isApprovalHook) {
3260
3405
  hookEntry.timeoutSec = 300;
3261
3406
  }
@@ -3268,7 +3413,7 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3268
3413
  const approvalHooks = agent.hooks.filter((h2) => h2.isApprovalHook);
3269
3414
  for (const hook of approvalHooks) {
3270
3415
  if (hooksConfig[hook.name]) {
3271
- if (agentId === "claude-code" || agentId === "gemini-cli") {
3416
+ if (agentId === "claude-code" || agentId === "gemini-cli" || agentId === "codex") {
3272
3417
  const hookArray = hooksConfig[hook.name];
3273
3418
  if (Array.isArray(hookArray)) {
3274
3419
  const cleaned = hookArray.filter((h2) => {
@@ -3340,6 +3485,9 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3340
3485
  }
3341
3486
  }
3342
3487
  writeJsonConfig(agent.configPath, config);
3488
+ if (agentId === "codex") {
3489
+ ensureCodexFeatureFlag();
3490
+ }
3343
3491
  return { success: true, backupPath, hooks: installedHooks };
3344
3492
  }
3345
3493
  function getManualInstructions(agentId, hooksDir) {
@@ -3353,13 +3501,39 @@ function getManualInstructions(agentId, hooksDir) {
3353
3501
  const hookTimeout = hook.timeout;
3354
3502
  const hookPath = join(hooksDir, hook.file);
3355
3503
  const hookCmd = buildHookCommand(hookPath, agentId);
3356
- const entry = hasMatcher ? { matcher: "*", hooks: [{ type: "command", command: hookCmd }] } : { hooks: [{ type: "command", command: hookCmd }] };
3504
+ const hookObject = {
3505
+ type: "command",
3506
+ command: hookCmd
3507
+ };
3357
3508
  if (hookTimeout) {
3358
- entry.timeout = hookTimeout;
3509
+ hookObject.timeout = hookTimeout;
3359
3510
  }
3511
+ const entry = hasMatcher ? { matcher: "*", hooks: [hookObject] } : { hooks: [hookObject] };
3360
3512
  hooksObj[hook.name] = [entry];
3361
3513
  }
3362
3514
  return JSON.stringify({ hooks: hooksObj }, null, 2);
3515
+ } else if (agentId === "codex") {
3516
+ const hooksObj = {};
3517
+ for (const hook of agent.hooks) {
3518
+ const hasMatcher = hook.hasMatcher ?? true;
3519
+ const hookTimeout = hook.timeout;
3520
+ const hookPath = join(hooksDir, hook.file);
3521
+ const hookCmd = buildHookCommand(hookPath, agentId);
3522
+ const hookObject = {
3523
+ type: "command",
3524
+ command: hookCmd
3525
+ };
3526
+ if (hookTimeout) {
3527
+ hookObject.timeout = hookTimeout;
3528
+ }
3529
+ const entry = hasMatcher ? { matcher: "*", hooks: [hookObject] } : { hooks: [hookObject] };
3530
+ hooksObj[hook.name] = [entry];
3531
+ }
3532
+ return `${JSON.stringify({ hooks: hooksObj }, null, 2)}
3533
+
3534
+ Also ensure ~/.codex/config.toml contains:
3535
+ [features]
3536
+ codex_hooks = true`;
3363
3537
  } else if (agentId === "cursor") {
3364
3538
  const hooksObj = {};
3365
3539
  for (const hook of agent.hooks) {
@@ -3397,6 +3571,9 @@ function getManualInstructions(agentId, hooksDir) {
3397
3571
  type: "command",
3398
3572
  command: hookCmd
3399
3573
  };
3574
+ const hookEnv = getGithubHookEnv(agentId);
3575
+ if (hookEnv)
3576
+ entry.env = hookEnv;
3400
3577
  if (isApproval)
3401
3578
  entry.timeout = 300;
3402
3579
  if (hookTimeout)
@@ -3413,6 +3590,9 @@ function getManualInstructions(agentId, hooksDir) {
3413
3590
  type: "command",
3414
3591
  bash: isWindows() ? toGitBashPath(hookPath) : hookPath
3415
3592
  };
3593
+ const hookEnv = getGithubHookEnv(agentId);
3594
+ if (hookEnv)
3595
+ entry.env = hookEnv;
3416
3596
  if (isApproval)
3417
3597
  entry.timeoutSec = 300;
3418
3598
  hooksObj[hook.name] = [entry];
@@ -3440,7 +3620,7 @@ function getManualInstructions(agentId, hooksDir) {
3440
3620
  return [
3441
3621
  "Install the Agent Approve plugin for OpenClaw:",
3442
3622
  "",
3443
- " openclaw plugins install @agentapprove/openclaw",
3623
+ ` openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}`,
3444
3624
  "",
3445
3625
  "Then add to your OpenClaw config (~/.openclaw/openclaw.json):",
3446
3626
  "",
@@ -3461,7 +3641,7 @@ function getManualInstructions(agentId, hooksDir) {
3461
3641
  "",
3462
3642
  "Then add the dependency to your OpenCode package.json (same directory):",
3463
3643
  "",
3464
- ' { "dependencies": { "@agentapprove/opencode": "latest" } }',
3644
+ ` { "dependencies": { "@agentapprove/opencode": "${OPENCODE_PLUGIN_VERSION}" } }`,
3465
3645
  "",
3466
3646
  "OpenCode will auto-install the plugin on next start."
3467
3647
  ].join(`
@@ -3507,6 +3687,11 @@ var HOOK_FILES = [
3507
3687
  "gemini-session-start.sh",
3508
3688
  "gemini-session-end.sh",
3509
3689
  "gemini-stop.sh",
3690
+ "codex-session-start.sh",
3691
+ "codex-pre-tool.sh",
3692
+ "codex-post-tool.sh",
3693
+ "codex-user-prompt.sh",
3694
+ "codex-stop.sh",
3510
3695
  "github-session-start.sh",
3511
3696
  "github-session-end.sh",
3512
3697
  "github-user-prompt.sh",
@@ -3550,7 +3735,7 @@ async function copyHookScripts(hooksDir, token) {
3550
3735
  }
3551
3736
  function installOpenClawPluginViaCli() {
3552
3737
  try {
3553
- execSync("openclaw plugins install @agentapprove/openclaw", { stdio: "pipe" });
3738
+ execSync(`openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}`, { stdio: "pipe" });
3554
3739
  return { success: true };
3555
3740
  } catch (err) {
3556
3741
  const message = err instanceof Error ? err.message : "unknown error";
@@ -3789,7 +3974,7 @@ E2E Key: ${keyId}`;
3789
3974
  Privacy: ${existingConfig.privacy || "unknown"}${e2eLine}`, "Existing configuration found");
3790
3975
  } else {
3791
3976
  me(`Approve AI agent actions from your iPhone or Apple Watch.
3792
- Installs hooks for Claude Code, Cursor, Gemini CLI, VS Code, and Copilot CLI.`, "About");
3977
+ Installs hooks for Claude Code, Cursor, Gemini CLI, VS Code GitHub Copilot, and GitHub Copilot CLI.`, "About");
3793
3978
  }
3794
3979
  const installedAgents = detectInstalledAgents();
3795
3980
  const agentOptions = Object.entries(AGENTS).map(([id, agent]) => ({
@@ -4088,6 +4273,9 @@ AGENTAPPROVE_E2E_MODE=${installMode}
4088
4273
  for (const agentId of selectedAgents) {
4089
4274
  const agent = AGENTS[agentId];
4090
4275
  filesToModify.push(agent.configPath);
4276
+ if (agentId === "codex") {
4277
+ filesToModify.push(join(homedir(), ".codex", "config.toml"));
4278
+ }
4091
4279
  if (agentId === "opencode") {
4092
4280
  filesToModify.push(join(getOpenCodeConfigDir(), "package.json"));
4093
4281
  }
@@ -4135,16 +4323,19 @@ Backups will be created with timestamp`, "Files to be modified");
4135
4323
  }
4136
4324
  }
4137
4325
  }
4326
+ if (agentId === "codex") {
4327
+ v2.info(source_default.dim(" Updated ~/.codex/config.toml to enable codex_hooks"));
4328
+ }
4138
4329
  if (agentId === "copilot-cli") {
4139
- v2.info(source_default.dim(` To use with Copilot CLI, copy to your repo:
4330
+ v2.info(source_default.dim(` To use with GitHub Copilot CLI, copy to your repo:
4140
4331
  ` + ` cp ${agent.configPath} .github/hooks/agentapprove.json`));
4141
4332
  }
4142
4333
  } else {
4143
4334
  spinner.stop(`${agent.name} configuration failed`);
4144
4335
  if (agentId === "openclaw") {
4145
- v2.warn(`Could not install @agentapprove/openclaw via OpenClaw CLI.
4336
+ v2.warn(`Could not install ${OPENCLAW_PLUGIN_SPEC} via OpenClaw CLI.
4146
4337
  ` + ` Error: ${result.error || "unknown"}
4147
- ` + ` Install manually: openclaw plugins install @agentapprove/openclaw
4338
+ ` + ` Install manually: openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}
4148
4339
  ` + ` Then re-run: npx agentapprove`);
4149
4340
  }
4150
4341
  }
@@ -4282,9 +4473,7 @@ async function uninstallCommand() {
4282
4473
  if (!existsSync(agent.configPath))
4283
4474
  continue;
4284
4475
  const config = readJsonConfig(agent.configPath);
4285
- const hooksConfig = config[agent.hooksKey];
4286
- if (!hooksConfig && agentId !== "opencode")
4287
- continue;
4476
+ const hooksConfig = config[agent.hooksKey] ?? {};
4288
4477
  let modified = false;
4289
4478
  if (agentId === "opencode") {
4290
4479
  const pluginArray = config.plugin;
@@ -4349,6 +4538,12 @@ async function uninstallCommand() {
4349
4538
  writeJsonConfig(agent.configPath, config);
4350
4539
  console.log(` ${source_default.green("✓")} Removed hooks from ${agent.name}`);
4351
4540
  }
4541
+ if (agentId === "codex" && modified && Object.keys(hooksConfig).length === 0) {
4542
+ const codexFlag = disableCodexFeatureFlag();
4543
+ if (codexFlag.updated) {
4544
+ console.log(` ${source_default.green("✓")} Disabled codex_hooks in ~/.codex/config.toml`);
4545
+ }
4546
+ }
4352
4547
  }
4353
4548
  const envPath = join(getAgentApproveDir(), "env");
4354
4549
  if (existsSync(envPath)) {
@@ -4407,7 +4602,7 @@ async function initRepoCommand() {
4407
4602
  const cliHooksPath = join(getAgentApproveDir(), "copilot-cli-hooks.json");
4408
4603
  if (!existsSync(cliHooksPath)) {
4409
4604
  console.log(source_default.yellow(`
4410
- Copilot CLI hooks not found. Run the installer first with Copilot CLI selected.`));
4605
+ GitHub Copilot CLI hooks not found. Run the installer first with GitHub Copilot CLI selected.`));
4411
4606
  console.log(source_default.dim(` npx agentapprove install
4412
4607
  `));
4413
4608
  process.exit(1);
@@ -4430,7 +4625,7 @@ async function initRepoCommand() {
4430
4625
  const targetDir = join(repoRoot, ".github", "hooks");
4431
4626
  const targetFile = join(targetDir, "agentapprove.json");
4432
4627
  console.log(source_default.cyan(`
4433
- Installing Copilot CLI hooks to ${targetFile}
4628
+ Installing GitHub Copilot CLI hooks to ${targetFile}
4434
4629
  `));
4435
4630
  if (existsSync(targetFile)) {
4436
4631
  console.log(source_default.yellow(" agentapprove.json already exists in .github/hooks/"));
@@ -4447,7 +4642,7 @@ async function initRepoCommand() {
4447
4642
  mkdirSync(targetDir, { recursive: true });
4448
4643
  }
4449
4644
  copyFileSync(cliHooksPath, targetFile);
4450
- console.log(source_default.green(` ✓ Copied Copilot CLI hooks to .github/hooks/agentapprove.json`));
4645
+ console.log(source_default.green(` ✓ Copied GitHub Copilot CLI hooks to .github/hooks/agentapprove.json`));
4451
4646
  console.log(source_default.dim(` Commit this file so Copilot coding agent uses your hooks.
4452
4647
  `));
4453
4648
  }
@@ -4463,7 +4658,7 @@ ${source_default.yellow("Commands:")}
4463
4658
  ${source_default.green("install")} Run the installation wizard (default)
4464
4659
  ${source_default.green("pair")} Link a new iOS device with existing E2E keys
4465
4660
  ${source_default.green("refresh")} Generate a new token (when expired)
4466
- ${source_default.green("init-repo")} Add Copilot CLI hooks to current repo (.github/hooks/)
4661
+ ${source_default.green("init-repo")} Add GitHub Copilot CLI hooks to current repo (.github/hooks/)
4467
4662
  ${source_default.green("status")} Show current configuration and installed hooks
4468
4663
  ${source_default.green("disable")} Temporarily disable hooks
4469
4664
  ${source_default.green("enable")} Re-enable hooks after disabling
@@ -4539,6 +4734,7 @@ Session: ${session.sessionCode}`, "Scan with Agent Approve iOS app");
4539
4734
  failBehavior,
4540
4735
  configSetAt
4541
4736
  });
4737
+ await storeTokenInKeychain(token);
4542
4738
  pushConfigToCloud({
4543
4739
  apiUrl,
4544
4740
  token,
@@ -4711,6 +4907,7 @@ ${noteLabel}`, "Scan with Agent Approve iOS app");
4711
4907
  failBehavior: existingConfig?.failBehavior || "ask",
4712
4908
  configSetAt
4713
4909
  });
4910
+ await storeTokenInKeychain(result.token);
4714
4911
  const hooksDir = join(getAgentApproveDir(), "hooks");
4715
4912
  if (existsSync(hooksDir)) {
4716
4913
  const updateSpinner = _2();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentapprove",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Approve AI agent actions from your iPhone or Apple Watch",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,9 +9,10 @@
9
9
  "scripts": {
10
10
  "dev": "bun --watch src/cli.ts",
11
11
  "build": "bun build src/cli.ts --outdir dist --target node",
12
+ "pack:check": "bun run build && node ../../scripts/check-publish-packlist.mjs",
12
13
  "start": "bun src/cli.ts",
13
14
  "test": "bun test",
14
- "prepublishOnly": "bun run build"
15
+ "prepublishOnly": "bun run pack:check"
15
16
  },
16
17
  "files": [
17
18
  "dist/**/*"
@@ -43,13 +44,14 @@
43
44
  "node": ">=18"
44
45
  },
45
46
  "dependencies": {
46
- "@clack/prompts": "^0.8.2",
47
- "chalk": "^5.4.1",
48
- "qrcode-terminal": "^0.12.0"
47
+ "@clack/prompts": "0.8.2",
48
+ "chalk": "5.6.2",
49
+ "qrcode-terminal": "0.12.0"
49
50
  },
50
51
  "devDependencies": {
51
- "@types/node": "^22.14.0",
52
- "@types/qrcode-terminal": "^0.12.2",
53
- "typescript": "^5.7.3"
52
+ "@socketsecurity/bun-security-scanner": "1.1.2",
53
+ "@types/node": "22.19.3",
54
+ "@types/qrcode-terminal": "0.12.2",
55
+ "typescript": "5.9.3"
54
56
  }
55
57
  }