agentapprove 0.1.8 → 0.1.10

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 +256 -49
  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.10";
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.5";
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();
@@ -3069,10 +3178,11 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3069
3178
  plugins.entries = {};
3070
3179
  }
3071
3180
  const entries = plugins.entries;
3072
- entries.agentapprove = {
3181
+ entries.openclaw = {
3073
3182
  enabled: mode === "approval",
3074
3183
  config: {
3075
3184
  apiUrl: API_URL,
3185
+ apiVersion: "v001",
3076
3186
  timeout: 300,
3077
3187
  failBehavior: "ask",
3078
3188
  privacyTier: "full"
@@ -3087,7 +3197,7 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3087
3197
  }
3088
3198
  hooks.internal.enabled = true;
3089
3199
  writeJsonConfig(agent.configPath, config);
3090
- installedHooks.push("agentapprove");
3200
+ installedHooks.push("openclaw");
3091
3201
  return { success: true, backupPath, hooks: installedHooks };
3092
3202
  }
3093
3203
  if (agentId === "opencode") {
@@ -3147,6 +3257,34 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3147
3257
  hookArray.push(hookEntry);
3148
3258
  }
3149
3259
  installedHooks.push(hook.name);
3260
+ } else if (agentId === "codex") {
3261
+ if (!hooksConfig[hook.name]) {
3262
+ hooksConfig[hook.name] = [];
3263
+ }
3264
+ const hookArray = hooksConfig[hook.name];
3265
+ const hasMatcher = hook.hasMatcher ?? true;
3266
+ const hookTimeout = hook.timeout;
3267
+ const existingIdx = hookArray.findIndex((h2) => h2.hooks?.some((hookScript) => {
3268
+ if (typeof hookScript === "string")
3269
+ return hookScript.includes("agentapprove");
3270
+ if (typeof hookScript === "object" && hookScript.command)
3271
+ return hookScript.command.includes("agentapprove");
3272
+ return false;
3273
+ }));
3274
+ const hookObject = {
3275
+ type: "command",
3276
+ command: hookCommand
3277
+ };
3278
+ if (hookTimeout) {
3279
+ hookObject.timeout = hookTimeout;
3280
+ }
3281
+ const hookEntry = hasMatcher ? { matcher: "*", hooks: [hookObject] } : { hooks: [hookObject] };
3282
+ if (existingIdx >= 0) {
3283
+ hookArray[existingIdx] = hookEntry;
3284
+ } else {
3285
+ hookArray.push(hookEntry);
3286
+ }
3287
+ installedHooks.push(hook.name);
3150
3288
  } else if (agentId === "cursor") {
3151
3289
  if (typeof config["version"] !== "number") {
3152
3290
  config["version"] = 1;
@@ -3231,6 +3369,10 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3231
3369
  type: "command",
3232
3370
  command: hookCommand
3233
3371
  };
3372
+ const hookEnv = getGithubHookEnv(agentId);
3373
+ if (hookEnv) {
3374
+ hookEntry.env = hookEnv;
3375
+ }
3234
3376
  if (hook.isApprovalHook) {
3235
3377
  hookEntry.timeout = 300;
3236
3378
  }
@@ -3256,6 +3398,10 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3256
3398
  type: "command",
3257
3399
  bash: isWindows() ? toGitBashPath(hookPath) : hookPath
3258
3400
  };
3401
+ const hookEnv = getGithubHookEnv(agentId);
3402
+ if (hookEnv) {
3403
+ hookEntry.env = hookEnv;
3404
+ }
3259
3405
  if (hook.isApprovalHook) {
3260
3406
  hookEntry.timeoutSec = 300;
3261
3407
  }
@@ -3268,7 +3414,7 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3268
3414
  const approvalHooks = agent.hooks.filter((h2) => h2.isApprovalHook);
3269
3415
  for (const hook of approvalHooks) {
3270
3416
  if (hooksConfig[hook.name]) {
3271
- if (agentId === "claude-code" || agentId === "gemini-cli") {
3417
+ if (agentId === "claude-code" || agentId === "gemini-cli" || agentId === "codex") {
3272
3418
  const hookArray = hooksConfig[hook.name];
3273
3419
  if (Array.isArray(hookArray)) {
3274
3420
  const cleaned = hookArray.filter((h2) => {
@@ -3332,14 +3478,17 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3332
3478
  } else if (agentId === "openclaw") {
3333
3479
  const pluginsObj = hooksConfig;
3334
3480
  const entries = pluginsObj?.entries;
3335
- if (entries?.agentapprove && typeof entries.agentapprove === "object") {
3336
- entries.agentapprove.enabled = false;
3481
+ if (entries?.openclaw && typeof entries.openclaw === "object") {
3482
+ entries.openclaw.enabled = false;
3337
3483
  }
3338
3484
  }
3339
3485
  }
3340
3486
  }
3341
3487
  }
3342
3488
  writeJsonConfig(agent.configPath, config);
3489
+ if (agentId === "codex") {
3490
+ ensureCodexFeatureFlag();
3491
+ }
3343
3492
  return { success: true, backupPath, hooks: installedHooks };
3344
3493
  }
3345
3494
  function getManualInstructions(agentId, hooksDir) {
@@ -3353,13 +3502,39 @@ function getManualInstructions(agentId, hooksDir) {
3353
3502
  const hookTimeout = hook.timeout;
3354
3503
  const hookPath = join(hooksDir, hook.file);
3355
3504
  const hookCmd = buildHookCommand(hookPath, agentId);
3356
- const entry = hasMatcher ? { matcher: "*", hooks: [{ type: "command", command: hookCmd }] } : { hooks: [{ type: "command", command: hookCmd }] };
3505
+ const hookObject = {
3506
+ type: "command",
3507
+ command: hookCmd
3508
+ };
3357
3509
  if (hookTimeout) {
3358
- entry.timeout = hookTimeout;
3510
+ hookObject.timeout = hookTimeout;
3359
3511
  }
3512
+ const entry = hasMatcher ? { matcher: "*", hooks: [hookObject] } : { hooks: [hookObject] };
3360
3513
  hooksObj[hook.name] = [entry];
3361
3514
  }
3362
3515
  return JSON.stringify({ hooks: hooksObj }, null, 2);
3516
+ } else if (agentId === "codex") {
3517
+ const hooksObj = {};
3518
+ for (const hook of agent.hooks) {
3519
+ const hasMatcher = hook.hasMatcher ?? true;
3520
+ const hookTimeout = hook.timeout;
3521
+ const hookPath = join(hooksDir, hook.file);
3522
+ const hookCmd = buildHookCommand(hookPath, agentId);
3523
+ const hookObject = {
3524
+ type: "command",
3525
+ command: hookCmd
3526
+ };
3527
+ if (hookTimeout) {
3528
+ hookObject.timeout = hookTimeout;
3529
+ }
3530
+ const entry = hasMatcher ? { matcher: "*", hooks: [hookObject] } : { hooks: [hookObject] };
3531
+ hooksObj[hook.name] = [entry];
3532
+ }
3533
+ return `${JSON.stringify({ hooks: hooksObj }, null, 2)}
3534
+
3535
+ Also ensure ~/.codex/config.toml contains:
3536
+ [features]
3537
+ codex_hooks = true`;
3363
3538
  } else if (agentId === "cursor") {
3364
3539
  const hooksObj = {};
3365
3540
  for (const hook of agent.hooks) {
@@ -3397,6 +3572,9 @@ function getManualInstructions(agentId, hooksDir) {
3397
3572
  type: "command",
3398
3573
  command: hookCmd
3399
3574
  };
3575
+ const hookEnv = getGithubHookEnv(agentId);
3576
+ if (hookEnv)
3577
+ entry.env = hookEnv;
3400
3578
  if (isApproval)
3401
3579
  entry.timeout = 300;
3402
3580
  if (hookTimeout)
@@ -3413,6 +3591,9 @@ function getManualInstructions(agentId, hooksDir) {
3413
3591
  type: "command",
3414
3592
  bash: isWindows() ? toGitBashPath(hookPath) : hookPath
3415
3593
  };
3594
+ const hookEnv = getGithubHookEnv(agentId);
3595
+ if (hookEnv)
3596
+ entry.env = hookEnv;
3416
3597
  if (isApproval)
3417
3598
  entry.timeoutSec = 300;
3418
3599
  hooksObj[hook.name] = [entry];
@@ -3422,10 +3603,11 @@ function getManualInstructions(agentId, hooksDir) {
3422
3603
  const configJson = JSON.stringify({
3423
3604
  plugins: {
3424
3605
  entries: {
3425
- agentapprove: {
3606
+ openclaw: {
3426
3607
  enabled: true,
3427
3608
  config: {
3428
3609
  apiUrl: API_URL,
3610
+ apiVersion: "v001",
3429
3611
  timeout: 300,
3430
3612
  failBehavior: "ask",
3431
3613
  privacyTier: "full"
@@ -3440,12 +3622,14 @@ function getManualInstructions(agentId, hooksDir) {
3440
3622
  return [
3441
3623
  "Install the Agent Approve plugin for OpenClaw:",
3442
3624
  "",
3443
- " openclaw plugins install @agentapprove/openclaw",
3625
+ ` openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}`,
3444
3626
  "",
3445
3627
  "Then add to your OpenClaw config (~/.openclaw/openclaw.json):",
3446
3628
  "",
3447
3629
  configJson,
3448
3630
  "",
3631
+ 'Hosted installs can also set config.token/config.apiUrl/etc. with OpenClaw env substitution such as "${AGENTAPPROVE_TOKEN}".',
3632
+ "",
3449
3633
  "Restart the OpenClaw gateway to activate."
3450
3634
  ].join(`
3451
3635
  `);
@@ -3461,7 +3645,7 @@ function getManualInstructions(agentId, hooksDir) {
3461
3645
  "",
3462
3646
  "Then add the dependency to your OpenCode package.json (same directory):",
3463
3647
  "",
3464
- ' { "dependencies": { "@agentapprove/opencode": "latest" } }',
3648
+ ` { "dependencies": { "@agentapprove/opencode": "${OPENCODE_PLUGIN_VERSION}" } }`,
3465
3649
  "",
3466
3650
  "OpenCode will auto-install the plugin on next start."
3467
3651
  ].join(`
@@ -3507,6 +3691,11 @@ var HOOK_FILES = [
3507
3691
  "gemini-session-start.sh",
3508
3692
  "gemini-session-end.sh",
3509
3693
  "gemini-stop.sh",
3694
+ "codex-session-start.sh",
3695
+ "codex-pre-tool.sh",
3696
+ "codex-post-tool.sh",
3697
+ "codex-user-prompt.sh",
3698
+ "codex-stop.sh",
3510
3699
  "github-session-start.sh",
3511
3700
  "github-session-end.sh",
3512
3701
  "github-user-prompt.sh",
@@ -3550,7 +3739,7 @@ async function copyHookScripts(hooksDir, token) {
3550
3739
  }
3551
3740
  function installOpenClawPluginViaCli() {
3552
3741
  try {
3553
- execSync("openclaw plugins install @agentapprove/openclaw", { stdio: "pipe" });
3742
+ execSync(`openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}`, { stdio: "pipe" });
3554
3743
  return { success: true };
3555
3744
  } catch (err) {
3556
3745
  const message = err instanceof Error ? err.message : "unknown error";
@@ -3789,7 +3978,7 @@ E2E Key: ${keyId}`;
3789
3978
  Privacy: ${existingConfig.privacy || "unknown"}${e2eLine}`, "Existing configuration found");
3790
3979
  } else {
3791
3980
  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");
3981
+ Installs hooks for Claude Code, Cursor, Gemini CLI, VS Code GitHub Copilot, and GitHub Copilot CLI.`, "About");
3793
3982
  }
3794
3983
  const installedAgents = detectInstalledAgents();
3795
3984
  const agentOptions = Object.entries(AGENTS).map(([id, agent]) => ({
@@ -4088,6 +4277,9 @@ AGENTAPPROVE_E2E_MODE=${installMode}
4088
4277
  for (const agentId of selectedAgents) {
4089
4278
  const agent = AGENTS[agentId];
4090
4279
  filesToModify.push(agent.configPath);
4280
+ if (agentId === "codex") {
4281
+ filesToModify.push(join(homedir(), ".codex", "config.toml"));
4282
+ }
4091
4283
  if (agentId === "opencode") {
4092
4284
  filesToModify.push(join(getOpenCodeConfigDir(), "package.json"));
4093
4285
  }
@@ -4135,16 +4327,19 @@ Backups will be created with timestamp`, "Files to be modified");
4135
4327
  }
4136
4328
  }
4137
4329
  }
4330
+ if (agentId === "codex") {
4331
+ v2.info(source_default.dim(" Updated ~/.codex/config.toml to enable codex_hooks"));
4332
+ }
4138
4333
  if (agentId === "copilot-cli") {
4139
- v2.info(source_default.dim(` To use with Copilot CLI, copy to your repo:
4334
+ v2.info(source_default.dim(` To use with GitHub Copilot CLI, copy to your repo:
4140
4335
  ` + ` cp ${agent.configPath} .github/hooks/agentapprove.json`));
4141
4336
  }
4142
4337
  } else {
4143
4338
  spinner.stop(`${agent.name} configuration failed`);
4144
4339
  if (agentId === "openclaw") {
4145
- v2.warn(`Could not install @agentapprove/openclaw via OpenClaw CLI.
4340
+ v2.warn(`Could not install ${OPENCLAW_PLUGIN_SPEC} via OpenClaw CLI.
4146
4341
  ` + ` Error: ${result.error || "unknown"}
4147
- ` + ` Install manually: openclaw plugins install @agentapprove/openclaw
4342
+ ` + ` Install manually: openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}
4148
4343
  ` + ` Then re-run: npx agentapprove`);
4149
4344
  }
4150
4345
  }
@@ -4213,8 +4408,8 @@ async function statusCommand() {
4213
4408
  }
4214
4409
  if (agentId === "openclaw") {
4215
4410
  const entries = config.plugins?.entries;
4216
- if (entries?.agentapprove) {
4217
- console.log(` ${source_default.green("✓")} ${agent.name}: agentapprove plugin`);
4411
+ if (entries?.openclaw) {
4412
+ console.log(` ${source_default.green("✓")} ${agent.name}: openclaw plugin`);
4218
4413
  }
4219
4414
  continue;
4220
4415
  }
@@ -4282,9 +4477,7 @@ async function uninstallCommand() {
4282
4477
  if (!existsSync(agent.configPath))
4283
4478
  continue;
4284
4479
  const config = readJsonConfig(agent.configPath);
4285
- const hooksConfig = config[agent.hooksKey];
4286
- if (!hooksConfig && agentId !== "opencode")
4287
- continue;
4480
+ const hooksConfig = config[agent.hooksKey] ?? {};
4288
4481
  let modified = false;
4289
4482
  if (agentId === "opencode") {
4290
4483
  const pluginArray = config.plugin;
@@ -4312,9 +4505,15 @@ async function uninstallCommand() {
4312
4505
  }
4313
4506
  } catch {}
4314
4507
  } else if (agentId === "openclaw") {
4315
- const entries = hooksConfig.entries;
4316
- if (entries && entries.agentapprove) {
4317
- delete entries.agentapprove;
4508
+ const pluginsConfig = hooksConfig;
4509
+ const entries = pluginsConfig.entries;
4510
+ const installs = pluginsConfig.installs;
4511
+ if (entries && entries.openclaw) {
4512
+ delete entries.openclaw;
4513
+ modified = true;
4514
+ }
4515
+ if (installs && installs.openclaw) {
4516
+ delete installs.openclaw;
4318
4517
  modified = true;
4319
4518
  }
4320
4519
  } else {
@@ -4349,6 +4548,12 @@ async function uninstallCommand() {
4349
4548
  writeJsonConfig(agent.configPath, config);
4350
4549
  console.log(` ${source_default.green("✓")} Removed hooks from ${agent.name}`);
4351
4550
  }
4551
+ if (agentId === "codex" && modified && Object.keys(hooksConfig).length === 0) {
4552
+ const codexFlag = disableCodexFeatureFlag();
4553
+ if (codexFlag.updated) {
4554
+ console.log(` ${source_default.green("✓")} Disabled codex_hooks in ~/.codex/config.toml`);
4555
+ }
4556
+ }
4352
4557
  }
4353
4558
  const envPath = join(getAgentApproveDir(), "env");
4354
4559
  if (existsSync(envPath)) {
@@ -4407,7 +4612,7 @@ async function initRepoCommand() {
4407
4612
  const cliHooksPath = join(getAgentApproveDir(), "copilot-cli-hooks.json");
4408
4613
  if (!existsSync(cliHooksPath)) {
4409
4614
  console.log(source_default.yellow(`
4410
- Copilot CLI hooks not found. Run the installer first with Copilot CLI selected.`));
4615
+ GitHub Copilot CLI hooks not found. Run the installer first with GitHub Copilot CLI selected.`));
4411
4616
  console.log(source_default.dim(` npx agentapprove install
4412
4617
  `));
4413
4618
  process.exit(1);
@@ -4430,7 +4635,7 @@ async function initRepoCommand() {
4430
4635
  const targetDir = join(repoRoot, ".github", "hooks");
4431
4636
  const targetFile = join(targetDir, "agentapprove.json");
4432
4637
  console.log(source_default.cyan(`
4433
- Installing Copilot CLI hooks to ${targetFile}
4638
+ Installing GitHub Copilot CLI hooks to ${targetFile}
4434
4639
  `));
4435
4640
  if (existsSync(targetFile)) {
4436
4641
  console.log(source_default.yellow(" agentapprove.json already exists in .github/hooks/"));
@@ -4447,7 +4652,7 @@ async function initRepoCommand() {
4447
4652
  mkdirSync(targetDir, { recursive: true });
4448
4653
  }
4449
4654
  copyFileSync(cliHooksPath, targetFile);
4450
- console.log(source_default.green(` ✓ Copied Copilot CLI hooks to .github/hooks/agentapprove.json`));
4655
+ console.log(source_default.green(` ✓ Copied GitHub Copilot CLI hooks to .github/hooks/agentapprove.json`));
4451
4656
  console.log(source_default.dim(` Commit this file so Copilot coding agent uses your hooks.
4452
4657
  `));
4453
4658
  }
@@ -4463,7 +4668,7 @@ ${source_default.yellow("Commands:")}
4463
4668
  ${source_default.green("install")} Run the installation wizard (default)
4464
4669
  ${source_default.green("pair")} Link a new iOS device with existing E2E keys
4465
4670
  ${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/)
4671
+ ${source_default.green("init-repo")} Add GitHub Copilot CLI hooks to current repo (.github/hooks/)
4467
4672
  ${source_default.green("status")} Show current configuration and installed hooks
4468
4673
  ${source_default.green("disable")} Temporarily disable hooks
4469
4674
  ${source_default.green("enable")} Re-enable hooks after disabling
@@ -4539,6 +4744,7 @@ Session: ${session.sessionCode}`, "Scan with Agent Approve iOS app");
4539
4744
  failBehavior,
4540
4745
  configSetAt
4541
4746
  });
4747
+ await storeTokenInKeychain(token);
4542
4748
  pushConfigToCloud({
4543
4749
  apiUrl,
4544
4750
  token,
@@ -4711,6 +4917,7 @@ ${noteLabel}`, "Scan with Agent Approve iOS app");
4711
4917
  failBehavior: existingConfig?.failBehavior || "ask",
4712
4918
  configSetAt
4713
4919
  });
4920
+ await storeTokenInKeychain(result.token);
4714
4921
  const hooksDir = join(getAgentApproveDir(), "hooks");
4715
4922
  if (existsSync(hooksDir)) {
4716
4923
  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.10",
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
  }