agentapprove 0.1.7 → 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 +258 -42
  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
@@ -5,21 +5,35 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
34
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
21
35
 
22
- // node_modules/sisteransi/src/index.js
36
+ // ../../node_modules/.bun/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
23
37
  var require_src = __commonJS((exports, module) => {
24
38
  var ESC = "\x1B";
25
39
  var CSI = `${ESC}[`;
@@ -77,7 +91,7 @@ var require_src = __commonJS((exports, module) => {
77
91
  module.exports = { cursor, scroll, erase, beep };
78
92
  });
79
93
 
80
- // node_modules/picocolors/picocolors.js
94
+ // ../../node_modules/.bun/picocolors@1.1.1/node_modules/picocolors/picocolors.js
81
95
  var require_picocolors = __commonJS((exports, module) => {
82
96
  var p = process || {};
83
97
  var argv = p.argv || [];
@@ -147,7 +161,7 @@ var require_picocolors = __commonJS((exports, module) => {
147
161
  module.exports.createColors = createColors;
148
162
  });
149
163
 
150
- // 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
151
165
  var require_QRMode = __commonJS((exports, module) => {
152
166
  module.exports = {
153
167
  MODE_NUMBER: 1 << 0,
@@ -157,7 +171,7 @@ var require_QRMode = __commonJS((exports, module) => {
157
171
  };
158
172
  });
159
173
 
160
- // 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
161
175
  var require_QR8bitByte = __commonJS((exports, module) => {
162
176
  var QRMode = require_QRMode();
163
177
  function QR8bitByte(data) {
@@ -177,7 +191,7 @@ var require_QR8bitByte = __commonJS((exports, module) => {
177
191
  module.exports = QR8bitByte;
178
192
  });
179
193
 
180
- // 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
181
195
  var require_QRMath = __commonJS((exports, module) => {
182
196
  var QRMath = {
183
197
  glog: function(n) {
@@ -213,7 +227,7 @@ var require_QRMath = __commonJS((exports, module) => {
213
227
  module.exports = QRMath;
214
228
  });
215
229
 
216
- // 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
217
231
  var require_QRPolynomial = __commonJS((exports, module) => {
218
232
  var QRMath = require_QRMath();
219
233
  function QRPolynomial(num, shift) {
@@ -263,7 +277,7 @@ var require_QRPolynomial = __commonJS((exports, module) => {
263
277
  module.exports = QRPolynomial;
264
278
  });
265
279
 
266
- // 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
267
281
  var require_QRMaskPattern = __commonJS((exports, module) => {
268
282
  module.exports = {
269
283
  PATTERN000: 0,
@@ -277,7 +291,7 @@ var require_QRMaskPattern = __commonJS((exports, module) => {
277
291
  };
278
292
  });
279
293
 
280
- // 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
281
295
  var require_QRUtil = __commonJS((exports, module) => {
282
296
  var QRMode = require_QRMode();
283
297
  var QRPolynomial = require_QRPolynomial();
@@ -503,7 +517,7 @@ var require_QRUtil = __commonJS((exports, module) => {
503
517
  module.exports = QRUtil;
504
518
  });
505
519
 
506
- // 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
507
521
  var require_QRErrorCorrectLevel = __commonJS((exports, module) => {
508
522
  module.exports = {
509
523
  L: 1,
@@ -513,7 +527,7 @@ var require_QRErrorCorrectLevel = __commonJS((exports, module) => {
513
527
  };
514
528
  });
515
529
 
516
- // 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
517
531
  var require_QRRSBlock = __commonJS((exports, module) => {
518
532
  var QRErrorCorrectLevel = require_QRErrorCorrectLevel();
519
533
  function QRRSBlock(totalCount, dataCount) {
@@ -716,7 +730,7 @@ var require_QRRSBlock = __commonJS((exports, module) => {
716
730
  module.exports = QRRSBlock;
717
731
  });
718
732
 
719
- // 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
720
734
  var require_QRBitBuffer = __commonJS((exports, module) => {
721
735
  function QRBitBuffer() {
722
736
  this.buffer = [];
@@ -749,7 +763,7 @@ var require_QRBitBuffer = __commonJS((exports, module) => {
749
763
  module.exports = QRBitBuffer;
750
764
  });
751
765
 
752
- // 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
753
767
  var require_QRCode = __commonJS((exports, module) => {
754
768
  var QR8bitByte = require_QR8bitByte();
755
769
  var QRUtil = require_QRUtil();
@@ -1070,7 +1084,7 @@ var require_QRCode = __commonJS((exports, module) => {
1070
1084
  module.exports = QRCode;
1071
1085
  });
1072
1086
 
1073
- // node_modules/qrcode-terminal/lib/main.js
1087
+ // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/lib/main.js
1074
1088
  var require_main = __commonJS((exports, module) => {
1075
1089
  var QRCode = require_QRCode();
1076
1090
  var QRErrorCorrectLevel = require_QRErrorCorrectLevel();
@@ -1164,7 +1178,7 @@ var require_main = __commonJS((exports, module) => {
1164
1178
  };
1165
1179
  });
1166
1180
 
1167
- // node_modules/@clack/core/dist/index.mjs
1181
+ // ../../node_modules/.bun/@clack+core@0.3.5/node_modules/@clack/core/dist/index.mjs
1168
1182
  var import_sisteransi = __toESM(require_src(), 1);
1169
1183
  import { stdin as $, stdout as k } from "node:process";
1170
1184
  import * as f from "node:readline";
@@ -1578,7 +1592,7 @@ function OD({ input: e = $, output: u = k, overwrite: F = true, hideCursor: t =
1578
1592
  };
1579
1593
  }
1580
1594
 
1581
- // node_modules/@clack/prompts/dist/index.mjs
1595
+ // ../../node_modules/.bun/@clack+prompts@0.8.2/node_modules/@clack/prompts/dist/index.mjs
1582
1596
  var import_picocolors = __toESM(require_picocolors(), 1);
1583
1597
  var import_sisteransi2 = __toESM(require_src(), 1);
1584
1598
  import h from "node:process";
@@ -1822,7 +1836,7 @@ var ve = async (s, n) => {
1822
1836
  return t;
1823
1837
  };
1824
1838
 
1825
- // 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
1826
1840
  var ANSI_BACKGROUND_OFFSET = 10;
1827
1841
  var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
1828
1842
  var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
@@ -1999,7 +2013,7 @@ function assembleStyles() {
1999
2013
  var ansiStyles = assembleStyles();
2000
2014
  var ansi_styles_default = ansiStyles;
2001
2015
 
2002
- // 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
2003
2017
  import process2 from "node:process";
2004
2018
  import os from "node:os";
2005
2019
  import tty from "node:tty";
@@ -2131,7 +2145,7 @@ var supportsColor = {
2131
2145
  };
2132
2146
  var supports_color_default = supportsColor;
2133
2147
 
2134
- // node_modules/chalk/source/utilities.js
2148
+ // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/utilities.js
2135
2149
  function stringReplaceAll(string, substring, replacer) {
2136
2150
  let index = string.indexOf(substring);
2137
2151
  if (index === -1) {
@@ -2164,7 +2178,7 @@ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
2164
2178
  return returnValue;
2165
2179
  }
2166
2180
 
2167
- // node_modules/chalk/source/index.js
2181
+ // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/index.js
2168
2182
  var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
2169
2183
  var GENERATOR = Symbol("GENERATOR");
2170
2184
  var STYLER = Symbol("STYLER");
@@ -2318,7 +2332,7 @@ import { homedir, hostname, platform } from "os";
2318
2332
  import { join, dirname, basename } from "path";
2319
2333
  import { execSync, spawnSync } from "child_process";
2320
2334
  import { randomBytes, createHash } from "crypto";
2321
- var VERSION = "0.1.7";
2335
+ var VERSION = "0.1.9";
2322
2336
  function getApiUrl() {
2323
2337
  return process.env.AGENTAPPROVE_API || "https://api.agentapprove.com";
2324
2338
  }
@@ -2366,6 +2380,9 @@ function getCommand() {
2366
2380
  }
2367
2381
  return filtered[0] || "install";
2368
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}`;
2369
2386
  var AGENTS = {
2370
2387
  "claude-code": {
2371
2388
  name: "Claude Code",
@@ -2421,7 +2438,7 @@ var AGENTS = {
2421
2438
  ]
2422
2439
  },
2423
2440
  "vscode-agent": {
2424
- name: "VS Code Agent Hooks",
2441
+ name: "VS Code GitHub Copilot",
2425
2442
  configPath: join(homedir(), ".agentapprove", "hooks", "vscode-hooks.json"),
2426
2443
  hooksKey: "hooks",
2427
2444
  hooks: [
@@ -2436,7 +2453,7 @@ var AGENTS = {
2436
2453
  ]
2437
2454
  },
2438
2455
  "copilot-cli": {
2439
- name: "Copilot CLI",
2456
+ name: "GitHub Copilot CLI",
2440
2457
  configPath: join(homedir(), ".agentapprove", "copilot-cli-hooks.json"),
2441
2458
  hooksKey: "hooks",
2442
2459
  hooks: [
@@ -2456,6 +2473,18 @@ var AGENTS = {
2456
2473
  { name: "agentapprove", file: "@agentapprove/openclaw", description: "Tool approval + event monitoring", isApprovalHook: true, isPlugin: true }
2457
2474
  ]
2458
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
+ },
2459
2488
  opencode: {
2460
2489
  name: "OpenCode",
2461
2490
  configPath: getOpenCodeConfigPath(),
@@ -2580,6 +2609,15 @@ function readJsoncConfig(configPath) {
2580
2609
  return {};
2581
2610
  }
2582
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
+ }
2583
2621
  function addToVSCodeHookLocations() {
2584
2622
  const hookLocation = "~/.agentapprove/hooks";
2585
2623
  const modified = [];
@@ -2636,6 +2674,10 @@ function detectInstalledAgents() {
2636
2674
  if (existsSync(join(homedir(), ".openclaw"))) {
2637
2675
  installed.push(id);
2638
2676
  }
2677
+ } else if (id === "codex") {
2678
+ if (existsSync(join(homedir(), ".codex"))) {
2679
+ installed.push(id);
2680
+ }
2639
2681
  } else if (id === "opencode") {
2640
2682
  if (existsSync(getOpenCodeConfigDir())) {
2641
2683
  installed.push(id);
@@ -2800,6 +2842,87 @@ function writeJsonConfig(configPath, config) {
2800
2842
  writeFileSync(configPath, JSON.stringify(config, null, 2) + `
2801
2843
  `);
2802
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
+ }
2803
2926
  async function createPairingSession(configuredAgents, e2eKeyId) {
2804
2927
  try {
2805
2928
  const machineHostname = hostname();
@@ -3133,7 +3256,38 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3133
3256
  hookArray.push(hookEntry);
3134
3257
  }
3135
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);
3136
3287
  } else if (agentId === "cursor") {
3288
+ if (typeof config["version"] !== "number") {
3289
+ config["version"] = 1;
3290
+ }
3137
3291
  const existing = hooksConfig[hook.name];
3138
3292
  const needsStopTimeout = hook.name === "stop" || hook.name === "subagentStop";
3139
3293
  const isApproval = hook.isApprovalHook;
@@ -3214,6 +3368,10 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3214
3368
  type: "command",
3215
3369
  command: hookCommand
3216
3370
  };
3371
+ const hookEnv = getGithubHookEnv(agentId);
3372
+ if (hookEnv) {
3373
+ hookEntry.env = hookEnv;
3374
+ }
3217
3375
  if (hook.isApprovalHook) {
3218
3376
  hookEntry.timeout = 300;
3219
3377
  }
@@ -3224,7 +3382,9 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3224
3382
  hooksConfig[hook.name] = cleanedArray;
3225
3383
  installedHooks.push(hook.name);
3226
3384
  } else if (agentId === "copilot-cli") {
3227
- config["version"] = 1;
3385
+ if (typeof config["version"] !== "number") {
3386
+ config["version"] = 1;
3387
+ }
3228
3388
  if (!hooksConfig[hook.name]) {
3229
3389
  hooksConfig[hook.name] = [];
3230
3390
  }
@@ -3237,6 +3397,10 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3237
3397
  type: "command",
3238
3398
  bash: isWindows() ? toGitBashPath(hookPath) : hookPath
3239
3399
  };
3400
+ const hookEnv = getGithubHookEnv(agentId);
3401
+ if (hookEnv) {
3402
+ hookEntry.env = hookEnv;
3403
+ }
3240
3404
  if (hook.isApprovalHook) {
3241
3405
  hookEntry.timeoutSec = 300;
3242
3406
  }
@@ -3249,7 +3413,7 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3249
3413
  const approvalHooks = agent.hooks.filter((h2) => h2.isApprovalHook);
3250
3414
  for (const hook of approvalHooks) {
3251
3415
  if (hooksConfig[hook.name]) {
3252
- if (agentId === "claude-code" || agentId === "gemini-cli") {
3416
+ if (agentId === "claude-code" || agentId === "gemini-cli" || agentId === "codex") {
3253
3417
  const hookArray = hooksConfig[hook.name];
3254
3418
  if (Array.isArray(hookArray)) {
3255
3419
  const cleaned = hookArray.filter((h2) => {
@@ -3321,6 +3485,9 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3321
3485
  }
3322
3486
  }
3323
3487
  writeJsonConfig(agent.configPath, config);
3488
+ if (agentId === "codex") {
3489
+ ensureCodexFeatureFlag();
3490
+ }
3324
3491
  return { success: true, backupPath, hooks: installedHooks };
3325
3492
  }
3326
3493
  function getManualInstructions(agentId, hooksDir) {
@@ -3334,13 +3501,39 @@ function getManualInstructions(agentId, hooksDir) {
3334
3501
  const hookTimeout = hook.timeout;
3335
3502
  const hookPath = join(hooksDir, hook.file);
3336
3503
  const hookCmd = buildHookCommand(hookPath, agentId);
3337
- const entry = hasMatcher ? { matcher: "*", hooks: [{ type: "command", command: hookCmd }] } : { hooks: [{ type: "command", command: hookCmd }] };
3504
+ const hookObject = {
3505
+ type: "command",
3506
+ command: hookCmd
3507
+ };
3338
3508
  if (hookTimeout) {
3339
- entry.timeout = hookTimeout;
3509
+ hookObject.timeout = hookTimeout;
3340
3510
  }
3511
+ const entry = hasMatcher ? { matcher: "*", hooks: [hookObject] } : { hooks: [hookObject] };
3341
3512
  hooksObj[hook.name] = [entry];
3342
3513
  }
3343
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`;
3344
3537
  } else if (agentId === "cursor") {
3345
3538
  const hooksObj = {};
3346
3539
  for (const hook of agent.hooks) {
@@ -3357,7 +3550,7 @@ function getManualInstructions(agentId, hooksDir) {
3357
3550
  }
3358
3551
  hooksObj[hook.name] = [entry];
3359
3552
  }
3360
- return JSON.stringify({ hooks: hooksObj }, null, 2);
3553
+ return JSON.stringify({ version: 1, hooks: hooksObj }, null, 2);
3361
3554
  } else if (agentId === "gemini-cli") {
3362
3555
  const hooksObj = {};
3363
3556
  for (const hook of agent.hooks) {
@@ -3378,6 +3571,9 @@ function getManualInstructions(agentId, hooksDir) {
3378
3571
  type: "command",
3379
3572
  command: hookCmd
3380
3573
  };
3574
+ const hookEnv = getGithubHookEnv(agentId);
3575
+ if (hookEnv)
3576
+ entry.env = hookEnv;
3381
3577
  if (isApproval)
3382
3578
  entry.timeout = 300;
3383
3579
  if (hookTimeout)
@@ -3394,6 +3590,9 @@ function getManualInstructions(agentId, hooksDir) {
3394
3590
  type: "command",
3395
3591
  bash: isWindows() ? toGitBashPath(hookPath) : hookPath
3396
3592
  };
3593
+ const hookEnv = getGithubHookEnv(agentId);
3594
+ if (hookEnv)
3595
+ entry.env = hookEnv;
3397
3596
  if (isApproval)
3398
3597
  entry.timeoutSec = 300;
3399
3598
  hooksObj[hook.name] = [entry];
@@ -3421,7 +3620,7 @@ function getManualInstructions(agentId, hooksDir) {
3421
3620
  return [
3422
3621
  "Install the Agent Approve plugin for OpenClaw:",
3423
3622
  "",
3424
- " openclaw plugins install @agentapprove/openclaw",
3623
+ ` openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}`,
3425
3624
  "",
3426
3625
  "Then add to your OpenClaw config (~/.openclaw/openclaw.json):",
3427
3626
  "",
@@ -3442,7 +3641,7 @@ function getManualInstructions(agentId, hooksDir) {
3442
3641
  "",
3443
3642
  "Then add the dependency to your OpenCode package.json (same directory):",
3444
3643
  "",
3445
- ' { "dependencies": { "@agentapprove/opencode": "latest" } }',
3644
+ ` { "dependencies": { "@agentapprove/opencode": "${OPENCODE_PLUGIN_VERSION}" } }`,
3446
3645
  "",
3447
3646
  "OpenCode will auto-install the plugin on next start."
3448
3647
  ].join(`
@@ -3488,6 +3687,11 @@ var HOOK_FILES = [
3488
3687
  "gemini-session-start.sh",
3489
3688
  "gemini-session-end.sh",
3490
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",
3491
3695
  "github-session-start.sh",
3492
3696
  "github-session-end.sh",
3493
3697
  "github-user-prompt.sh",
@@ -3531,7 +3735,7 @@ async function copyHookScripts(hooksDir, token) {
3531
3735
  }
3532
3736
  function installOpenClawPluginViaCli() {
3533
3737
  try {
3534
- execSync("openclaw plugins install @agentapprove/openclaw", { stdio: "pipe" });
3738
+ execSync(`openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}`, { stdio: "pipe" });
3535
3739
  return { success: true };
3536
3740
  } catch (err) {
3537
3741
  const message = err instanceof Error ? err.message : "unknown error";
@@ -3770,7 +3974,7 @@ E2E Key: ${keyId}`;
3770
3974
  Privacy: ${existingConfig.privacy || "unknown"}${e2eLine}`, "Existing configuration found");
3771
3975
  } else {
3772
3976
  me(`Approve AI agent actions from your iPhone or Apple Watch.
3773
- 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");
3774
3978
  }
3775
3979
  const installedAgents = detectInstalledAgents();
3776
3980
  const agentOptions = Object.entries(AGENTS).map(([id, agent]) => ({
@@ -4069,6 +4273,9 @@ AGENTAPPROVE_E2E_MODE=${installMode}
4069
4273
  for (const agentId of selectedAgents) {
4070
4274
  const agent = AGENTS[agentId];
4071
4275
  filesToModify.push(agent.configPath);
4276
+ if (agentId === "codex") {
4277
+ filesToModify.push(join(homedir(), ".codex", "config.toml"));
4278
+ }
4072
4279
  if (agentId === "opencode") {
4073
4280
  filesToModify.push(join(getOpenCodeConfigDir(), "package.json"));
4074
4281
  }
@@ -4116,16 +4323,19 @@ Backups will be created with timestamp`, "Files to be modified");
4116
4323
  }
4117
4324
  }
4118
4325
  }
4326
+ if (agentId === "codex") {
4327
+ v2.info(source_default.dim(" Updated ~/.codex/config.toml to enable codex_hooks"));
4328
+ }
4119
4329
  if (agentId === "copilot-cli") {
4120
- 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:
4121
4331
  ` + ` cp ${agent.configPath} .github/hooks/agentapprove.json`));
4122
4332
  }
4123
4333
  } else {
4124
4334
  spinner.stop(`${agent.name} configuration failed`);
4125
4335
  if (agentId === "openclaw") {
4126
- v2.warn(`Could not install @agentapprove/openclaw via OpenClaw CLI.
4336
+ v2.warn(`Could not install ${OPENCLAW_PLUGIN_SPEC} via OpenClaw CLI.
4127
4337
  ` + ` Error: ${result.error || "unknown"}
4128
- ` + ` Install manually: openclaw plugins install @agentapprove/openclaw
4338
+ ` + ` Install manually: openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}
4129
4339
  ` + ` Then re-run: npx agentapprove`);
4130
4340
  }
4131
4341
  }
@@ -4263,9 +4473,7 @@ async function uninstallCommand() {
4263
4473
  if (!existsSync(agent.configPath))
4264
4474
  continue;
4265
4475
  const config = readJsonConfig(agent.configPath);
4266
- const hooksConfig = config[agent.hooksKey];
4267
- if (!hooksConfig && agentId !== "opencode")
4268
- continue;
4476
+ const hooksConfig = config[agent.hooksKey] ?? {};
4269
4477
  let modified = false;
4270
4478
  if (agentId === "opencode") {
4271
4479
  const pluginArray = config.plugin;
@@ -4330,6 +4538,12 @@ async function uninstallCommand() {
4330
4538
  writeJsonConfig(agent.configPath, config);
4331
4539
  console.log(` ${source_default.green("✓")} Removed hooks from ${agent.name}`);
4332
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
+ }
4333
4547
  }
4334
4548
  const envPath = join(getAgentApproveDir(), "env");
4335
4549
  if (existsSync(envPath)) {
@@ -4388,7 +4602,7 @@ async function initRepoCommand() {
4388
4602
  const cliHooksPath = join(getAgentApproveDir(), "copilot-cli-hooks.json");
4389
4603
  if (!existsSync(cliHooksPath)) {
4390
4604
  console.log(source_default.yellow(`
4391
- 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.`));
4392
4606
  console.log(source_default.dim(` npx agentapprove install
4393
4607
  `));
4394
4608
  process.exit(1);
@@ -4411,7 +4625,7 @@ async function initRepoCommand() {
4411
4625
  const targetDir = join(repoRoot, ".github", "hooks");
4412
4626
  const targetFile = join(targetDir, "agentapprove.json");
4413
4627
  console.log(source_default.cyan(`
4414
- Installing Copilot CLI hooks to ${targetFile}
4628
+ Installing GitHub Copilot CLI hooks to ${targetFile}
4415
4629
  `));
4416
4630
  if (existsSync(targetFile)) {
4417
4631
  console.log(source_default.yellow(" agentapprove.json already exists in .github/hooks/"));
@@ -4428,7 +4642,7 @@ async function initRepoCommand() {
4428
4642
  mkdirSync(targetDir, { recursive: true });
4429
4643
  }
4430
4644
  copyFileSync(cliHooksPath, targetFile);
4431
- 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`));
4432
4646
  console.log(source_default.dim(` Commit this file so Copilot coding agent uses your hooks.
4433
4647
  `));
4434
4648
  }
@@ -4444,7 +4658,7 @@ ${source_default.yellow("Commands:")}
4444
4658
  ${source_default.green("install")} Run the installation wizard (default)
4445
4659
  ${source_default.green("pair")} Link a new iOS device with existing E2E keys
4446
4660
  ${source_default.green("refresh")} Generate a new token (when expired)
4447
- ${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/)
4448
4662
  ${source_default.green("status")} Show current configuration and installed hooks
4449
4663
  ${source_default.green("disable")} Temporarily disable hooks
4450
4664
  ${source_default.green("enable")} Re-enable hooks after disabling
@@ -4520,6 +4734,7 @@ Session: ${session.sessionCode}`, "Scan with Agent Approve iOS app");
4520
4734
  failBehavior,
4521
4735
  configSetAt
4522
4736
  });
4737
+ await storeTokenInKeychain(token);
4523
4738
  pushConfigToCloud({
4524
4739
  apiUrl,
4525
4740
  token,
@@ -4692,6 +4907,7 @@ ${noteLabel}`, "Scan with Agent Approve iOS app");
4692
4907
  failBehavior: existingConfig?.failBehavior || "ask",
4693
4908
  configSetAt
4694
4909
  });
4910
+ await storeTokenInKeychain(result.token);
4695
4911
  const hooksDir = join(getAgentApproveDir(), "hooks");
4696
4912
  if (existsSync(hooksDir)) {
4697
4913
  const updateSpinner = _2();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentapprove",
3
- "version": "0.1.7",
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
  }