agentapprove 0.1.0 → 0.1.2

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 +2 -1
  2. package/dist/cli.js +202 -111
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -48,6 +48,7 @@ agentapprove --no-e2e # Skip end-to-end encryption setup
48
48
  - **Gemini CLI** - Google's CLI
49
49
  - **VS Code Agent** - VS Code agent hooks
50
50
  - **GitHub Copilot CLI** - GitHub's coding agent
51
+ - **OpenCode** - Open-source AI coding agent
51
52
  - **OpenClaw** - Open-source AI agent platform
52
53
  - **OpenAI Codex** - coming soon
53
54
  - And more
@@ -58,7 +59,7 @@ agentapprove --no-e2e # Skip end-to-end encryption setup
58
59
  - **Push notifications** - Never miss an approval request. Avoid wasted idle time with your agents
59
60
  - **Apple Watch** - One-tap approvals from your wrist. Review context and respond without reaching for your phone
60
61
  - **Voice commands** - Send follow-up commands to your agents using the microphone when they finish a task
61
- - **Agent observability** - Full visibility into what all your agents are doing, especially autonomous agents like OpenClaw or long-running loops
62
+ - **Agent observability** - Full visibility into what all your agents are doing, especially autonomous agents like OpenClaw or long-running loops (Ralph loops)
62
63
  - **Centralized policies** - Set auto-approve and deny rules across all your agents from a single policy
63
64
  - **End-to-end encryption** - You own the keys. Approval requests are encrypted on your machine and decrypted only on your device
64
65
  - **Privacy tiers** - Choose minimal, summary, or full data logging. Control retention from 1 to 365 days
package/dist/cli.js CHANGED
@@ -2318,10 +2318,19 @@ import { homedir, hostname, platform } from "os";
2318
2318
  import { join, dirname, basename } from "path";
2319
2319
  import { execSync, spawnSync } from "child_process";
2320
2320
  import { randomBytes, createHash } from "crypto";
2321
- var VERSION = "0.1.0";
2321
+ var VERSION = "0.1.1";
2322
2322
  function getApiUrl() {
2323
2323
  return process.env.AGENTAPPROVE_API || "https://api.agentapprove.com";
2324
2324
  }
2325
+ function getXdgConfigHome() {
2326
+ return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
2327
+ }
2328
+ function getOpenCodeConfigDir() {
2329
+ return join(getXdgConfigHome(), "opencode");
2330
+ }
2331
+ function getOpenCodeConfigPath() {
2332
+ return join(getOpenCodeConfigDir(), "opencode.json");
2333
+ }
2325
2334
  var API_URL = getApiUrl();
2326
2335
  var API_VERSION = process.env.AGENTAPPROVE_API_VERSION || "v001";
2327
2336
  function hasFlag2(flag) {
@@ -2444,7 +2453,15 @@ var AGENTS = {
2444
2453
  configPath: join(homedir(), ".openclaw", "openclaw.json"),
2445
2454
  hooksKey: "plugins",
2446
2455
  hooks: [
2447
- { name: "agentapprove", file: "openclaw-plugin.js", description: "Tool approval + event monitoring", isApprovalHook: true, isPlugin: true }
2456
+ { name: "agentapprove", file: "@agentapprove/openclaw", description: "Tool approval + event monitoring", isApprovalHook: true, isPlugin: true }
2457
+ ]
2458
+ },
2459
+ opencode: {
2460
+ name: "OpenCode",
2461
+ configPath: getOpenCodeConfigPath(),
2462
+ hooksKey: "plugin",
2463
+ hooks: [
2464
+ { name: "agentapprove", file: "@agentapprove/opencode", description: "Tool approval + event monitoring", isApprovalHook: true, isPlugin: true }
2448
2465
  ]
2449
2466
  }
2450
2467
  };
@@ -2619,6 +2636,10 @@ function detectInstalledAgents() {
2619
2636
  if (existsSync(join(homedir(), ".openclaw"))) {
2620
2637
  installed.push(id);
2621
2638
  }
2639
+ } else if (id === "opencode") {
2640
+ if (existsSync(getOpenCodeConfigDir())) {
2641
+ installed.push(id);
2642
+ }
2622
2643
  } else {
2623
2644
  const configDir = dirname(agent.configPath);
2624
2645
  if (existsSync(configDir)) {
@@ -3009,7 +3030,7 @@ async function pushConfigToCloud(config) {
3009
3030
  return false;
3010
3031
  }
3011
3032
  }
3012
- function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3033
+ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3013
3034
  const agent = AGENTS[agentId];
3014
3035
  if (!agent) {
3015
3036
  return { success: false, backupPath: null, hooks: [] };
@@ -3021,6 +3042,73 @@ function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3021
3042
  }
3022
3043
  const hooksConfig = config[agent.hooksKey];
3023
3044
  const installedHooks = [];
3045
+ if (agentId === "openclaw") {
3046
+ const installResult = installOpenClawPluginViaCli();
3047
+ if (!installResult.success) {
3048
+ return { success: false, backupPath, hooks: [], error: installResult.error };
3049
+ }
3050
+ if (!config.plugins) {
3051
+ config.plugins = {};
3052
+ }
3053
+ const plugins = config.plugins;
3054
+ if (!plugins.entries) {
3055
+ plugins.entries = {};
3056
+ }
3057
+ const entries = plugins.entries;
3058
+ entries.agentapprove = {
3059
+ enabled: mode === "approval",
3060
+ config: {
3061
+ apiUrl: API_URL,
3062
+ timeout: 300,
3063
+ failBehavior: "ask",
3064
+ privacyTier: "full"
3065
+ }
3066
+ };
3067
+ if (!config.hooks) {
3068
+ config.hooks = {};
3069
+ }
3070
+ const hooks = config.hooks;
3071
+ if (!hooks.internal) {
3072
+ hooks.internal = {};
3073
+ }
3074
+ hooks.internal.enabled = true;
3075
+ writeJsonConfig(agent.configPath, config);
3076
+ installedHooks.push("agentapprove");
3077
+ return { success: true, backupPath, hooks: installedHooks };
3078
+ }
3079
+ if (agentId === "opencode") {
3080
+ if (!config.plugin) {
3081
+ config.plugin = [];
3082
+ }
3083
+ const pluginArray = config.plugin;
3084
+ if (!pluginArray.includes("@agentapprove/opencode")) {
3085
+ pluginArray.push("@agentapprove/opencode");
3086
+ }
3087
+ writeJsonConfig(agent.configPath, config);
3088
+ const opencodePkgDir = join(getOpenCodeConfigDir(), ".opencode");
3089
+ const opencodePkgPath = join(opencodePkgDir, "package.json");
3090
+ try {
3091
+ if (!existsSync(opencodePkgDir)) {
3092
+ mkdirSync(opencodePkgDir, { recursive: true });
3093
+ }
3094
+ let pkgJson = {};
3095
+ if (existsSync(opencodePkgPath)) {
3096
+ pkgJson = JSON.parse(readFileSync(opencodePkgPath, "utf-8"));
3097
+ }
3098
+ if (!pkgJson.dependencies) {
3099
+ pkgJson.dependencies = {};
3100
+ }
3101
+ pkgJson.dependencies["@agentapprove/opencode"] = "latest";
3102
+ writeFileSync(opencodePkgPath, JSON.stringify(pkgJson, null, 2) + `
3103
+ `);
3104
+ } catch (err) {
3105
+ if (err instanceof Error) {
3106
+ console.warn(`Warning: Could not update .opencode/package.json: ${err.message}`);
3107
+ }
3108
+ }
3109
+ installedHooks.push("agentapprove");
3110
+ return { success: true, backupPath, hooks: installedHooks };
3111
+ }
3024
3112
  const hooksToInstall = mode === "observe" ? agent.hooks.filter((h2) => !h2.isApprovalHook) : agent.hooks;
3025
3113
  for (const hook of hooksToInstall) {
3026
3114
  const hookPath = join(hooksDir, hook.file);
@@ -3152,65 +3240,6 @@ function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3152
3240
  cleanedArray.push(hookEntry);
3153
3241
  hooksConfig[hook.name] = cleanedArray;
3154
3242
  installedHooks.push(hook.name);
3155
- } else if (agentId === "openclaw") {
3156
- const extensionsDir = join(homedir(), ".openclaw", "extensions", "agentapprove");
3157
- mkdirSync(extensionsDir, { recursive: true });
3158
- const pluginSrc = join(hooksDir, "openclaw-plugin.js");
3159
- if (existsSync(pluginSrc)) {
3160
- copyFileSync(pluginSrc, join(extensionsDir, "index.js"));
3161
- }
3162
- const manifest = {
3163
- id: "agentapprove",
3164
- name: "Agent Approve",
3165
- description: "Mobile approval for AI agent tool execution",
3166
- version: VERSION,
3167
- homepage: "https://agentapprove.com",
3168
- configSchema: {
3169
- type: "object",
3170
- additionalProperties: false,
3171
- properties: {
3172
- apiUrl: { type: "string", default: API_URL },
3173
- timeout: { type: "number", default: 300 },
3174
- failBehavior: { type: "string", enum: ["allow", "deny", "ask"], default: "ask" },
3175
- privacyTier: { type: "string", enum: ["minimal", "summary", "full"], default: "full" },
3176
- debug: { type: "boolean", default: false }
3177
- }
3178
- }
3179
- };
3180
- writeFileSync(join(extensionsDir, "openclaw.plugin.json"), JSON.stringify(manifest, null, 2));
3181
- const pkgJson = {
3182
- name: "@agentapprove/openclaw",
3183
- version: VERSION,
3184
- main: "./index.js",
3185
- openclaw: { extensions: ["./index.js"] }
3186
- };
3187
- writeFileSync(join(extensionsDir, "package.json"), JSON.stringify(pkgJson, null, 2));
3188
- if (!config.plugins) {
3189
- config.plugins = {};
3190
- }
3191
- const plugins = config.plugins;
3192
- if (!plugins.entries) {
3193
- plugins.entries = {};
3194
- }
3195
- const entries = plugins.entries;
3196
- entries.agentapprove = {
3197
- enabled: mode === "approval",
3198
- config: {
3199
- apiUrl: API_URL,
3200
- timeout: 300,
3201
- failBehavior: "ask",
3202
- privacyTier: "full"
3203
- }
3204
- };
3205
- if (!config.hooks) {
3206
- config.hooks = {};
3207
- }
3208
- const hooks = config.hooks;
3209
- if (!hooks.internal) {
3210
- hooks.internal = {};
3211
- }
3212
- hooks.internal.enabled = true;
3213
- installedHooks.push("agentapprove");
3214
3243
  }
3215
3244
  }
3216
3245
  if (mode === "observe") {
@@ -3360,34 +3389,51 @@ function getManualInstructions(agentId, hooksDir) {
3360
3389
  }
3361
3390
  return JSON.stringify({ version: 1, hooks: hooksObj }, null, 2);
3362
3391
  } else if (agentId === "openclaw") {
3363
- return [
3364
- "# Install the Agent Approve plugin for OpenClaw:",
3365
- "#",
3366
- "# Option 1: Via npm (when published)",
3367
- "# openclaw plugins install @agentapprove/openclaw",
3368
- "#",
3369
- "# Option 2: Manual install",
3370
- "# 1. Copy the plugin bundle to ~/.openclaw/extensions/agentapprove/",
3371
- "# 2. Add to your OpenClaw config (~/.openclaw/openclaw.json):",
3372
- JSON.stringify({
3373
- plugins: {
3374
- entries: {
3375
- agentapprove: {
3376
- enabled: true,
3377
- config: {
3378
- apiUrl: API_URL,
3379
- timeout: 300,
3380
- failBehavior: "ask",
3381
- privacyTier: "full"
3382
- }
3392
+ const configJson = JSON.stringify({
3393
+ plugins: {
3394
+ entries: {
3395
+ agentapprove: {
3396
+ enabled: true,
3397
+ config: {
3398
+ apiUrl: API_URL,
3399
+ timeout: 300,
3400
+ failBehavior: "ask",
3401
+ privacyTier: "full"
3383
3402
  }
3384
3403
  }
3385
- },
3386
- hooks: {
3387
- internal: { enabled: true }
3388
3404
  }
3389
- }, null, 2),
3390
- "# 3. Restart the OpenClaw gateway"
3405
+ },
3406
+ hooks: {
3407
+ internal: { enabled: true }
3408
+ }
3409
+ }, null, 2);
3410
+ return [
3411
+ "Install the Agent Approve plugin for OpenClaw:",
3412
+ "",
3413
+ " openclaw plugins install @agentapprove/openclaw",
3414
+ "",
3415
+ "Then add to your OpenClaw config (~/.openclaw/openclaw.json):",
3416
+ "",
3417
+ configJson,
3418
+ "",
3419
+ "Restart the OpenClaw gateway to activate."
3420
+ ].join(`
3421
+ `);
3422
+ } else if (agentId === "opencode") {
3423
+ const configJson = JSON.stringify({
3424
+ plugin: ["@agentapprove/opencode"]
3425
+ }, null, 2);
3426
+ const opencodeConfigPath = process.env.XDG_CONFIG_HOME ? `${process.env.XDG_CONFIG_HOME}/opencode/opencode.json` : "~/.config/opencode/opencode.json";
3427
+ return [
3428
+ `Add the Agent Approve plugin to your OpenCode config (${opencodeConfigPath}):`,
3429
+ "",
3430
+ configJson,
3431
+ "",
3432
+ "Then add the dependency to .opencode/package.json:",
3433
+ "",
3434
+ ' { "dependencies": { "@agentapprove/opencode": "latest" } }',
3435
+ "",
3436
+ "OpenCode will auto-install the plugin on next start."
3391
3437
  ].join(`
3392
3438
  `);
3393
3439
  }
@@ -3440,8 +3486,7 @@ var HOOK_FILES = [
3440
3486
  "github-stop.sh",
3441
3487
  "github-subagent-start.sh",
3442
3488
  "github-subagent-stop.sh",
3443
- "github-precompact.sh",
3444
- "openclaw-plugin.js"
3489
+ "github-precompact.sh"
3445
3490
  ];
3446
3491
  async function copyHookScripts(hooksDir, token) {
3447
3492
  let downloaded = 0;
@@ -3473,6 +3518,15 @@ async function copyHookScripts(hooksDir, token) {
3473
3518
  }
3474
3519
  return { downloaded, failed };
3475
3520
  }
3521
+ function installOpenClawPluginViaCli() {
3522
+ try {
3523
+ execSync("openclaw plugins install @agentapprove/openclaw", { stdio: "pipe" });
3524
+ return { success: true };
3525
+ } catch (err) {
3526
+ const message = err instanceof Error ? err.message : "unknown error";
3527
+ return { success: false, error: message };
3528
+ }
3529
+ }
3476
3530
  var SYSTEM_DEPS = [
3477
3531
  {
3478
3532
  name: "curl",
@@ -4025,8 +4079,9 @@ Backups will be created with timestamp`, "Files to be modified");
4025
4079
  for (const agentId of selectedAgents) {
4026
4080
  const agent = AGENTS[agentId];
4027
4081
  const spinner = _2();
4028
- spinner.start(`Configuring ${agent.name}`);
4029
- const result = installHooksForAgent(agentId, hooksDir, installMode);
4082
+ const spinnerMsg = agentId === "openclaw" ? `Installing ${agent.name} plugin` : `Configuring ${agent.name}`;
4083
+ spinner.start(spinnerMsg);
4084
+ const result = await installHooksForAgent(agentId, hooksDir, installMode);
4030
4085
  if (result.success) {
4031
4086
  const installedHookNames = result.hooks.join(", ");
4032
4087
  const backupMsg = result.backupPath ? source_default.dim(` (backup created)`) : "";
@@ -4053,6 +4108,12 @@ Backups will be created with timestamp`, "Files to be modified");
4053
4108
  }
4054
4109
  } else {
4055
4110
  spinner.stop(`${agent.name} configuration failed`);
4111
+ if (agentId === "openclaw") {
4112
+ v2.warn(`Could not install @agentapprove/openclaw via OpenClaw CLI.
4113
+ ` + ` Error: ${result.error || "unknown"}
4114
+ ` + ` Install manually: openclaw plugins install @agentapprove/openclaw
4115
+ ` + ` Then re-run: npx agentapprove`);
4116
+ }
4056
4117
  }
4057
4118
  }
4058
4119
  } else {
@@ -4178,32 +4239,62 @@ async function uninstallCommand() {
4178
4239
  if (!hooksConfig)
4179
4240
  continue;
4180
4241
  let modified = false;
4181
- for (const hook of agent.hooks) {
4182
- const hookEntry = hooksConfig[hook.name];
4183
- if (!hookEntry)
4184
- continue;
4185
- if (Array.isArray(hookEntry)) {
4186
- const filtered = hookEntry.filter((e2) => {
4187
- const str = JSON.stringify(e2);
4188
- return !str.includes("agentapprove");
4189
- });
4190
- if (filtered.length !== hookEntry.length) {
4191
- if (filtered.length === 0) {
4192
- delete hooksConfig[hook.name];
4193
- } else {
4194
- hooksConfig[hook.name] = filtered;
4242
+ if (agentId === "opencode") {
4243
+ const pluginArray = config.plugin;
4244
+ if (Array.isArray(pluginArray)) {
4245
+ const filtered = pluginArray.filter((p) => !p.includes("agentapprove"));
4246
+ if (filtered.length !== pluginArray.length) {
4247
+ config.plugin = filtered;
4248
+ modified = true;
4249
+ }
4250
+ }
4251
+ const opencodePkgDir = join(getOpenCodeConfigDir(), ".opencode");
4252
+ const opencodePkgPath = join(opencodePkgDir, "package.json");
4253
+ try {
4254
+ if (existsSync(opencodePkgPath)) {
4255
+ const pkgJson = JSON.parse(readFileSync(opencodePkgPath, "utf-8"));
4256
+ const deps = pkgJson.dependencies;
4257
+ if (deps && deps["@agentapprove/opencode"]) {
4258
+ delete deps["@agentapprove/opencode"];
4259
+ writeFileSync(opencodePkgPath, JSON.stringify(pkgJson, null, 2) + `
4260
+ `);
4261
+ }
4262
+ }
4263
+ } catch {}
4264
+ } else if (agentId === "openclaw") {
4265
+ const entries = hooksConfig.entries;
4266
+ if (entries && entries.agentapprove) {
4267
+ delete entries.agentapprove;
4268
+ modified = true;
4269
+ }
4270
+ } else {
4271
+ for (const hook of agent.hooks) {
4272
+ const hookEntry = hooksConfig[hook.name];
4273
+ if (!hookEntry)
4274
+ continue;
4275
+ if (Array.isArray(hookEntry)) {
4276
+ const filtered = hookEntry.filter((e2) => {
4277
+ const str = JSON.stringify(e2);
4278
+ return !str.includes("agentapprove");
4279
+ });
4280
+ if (filtered.length !== hookEntry.length) {
4281
+ if (filtered.length === 0) {
4282
+ delete hooksConfig[hook.name];
4283
+ } else {
4284
+ hooksConfig[hook.name] = filtered;
4285
+ }
4286
+ modified = true;
4195
4287
  }
4288
+ } else if (typeof hookEntry === "string" && hookEntry.includes("agentapprove")) {
4289
+ delete hooksConfig[hook.name];
4196
4290
  modified = true;
4197
4291
  }
4198
- } else if (typeof hookEntry === "string" && hookEntry.includes("agentapprove")) {
4199
- delete hooksConfig[hook.name];
4292
+ }
4293
+ if (hooksConfig["Prompt"]) {
4294
+ delete hooksConfig["Prompt"];
4200
4295
  modified = true;
4201
4296
  }
4202
4297
  }
4203
- if (hooksConfig["Prompt"]) {
4204
- delete hooksConfig["Prompt"];
4205
- modified = true;
4206
- }
4207
4298
  if (modified) {
4208
4299
  writeJsonConfig(agent.configPath, config);
4209
4300
  console.log(` ${source_default.green("✓")} Removed hooks from ${agent.name}`);
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "agentapprove",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Approve AI agent actions from your iPhone or Apple Watch",
5
5
  "type": "module",
6
6
  "bin": {
7
- "agentapprove": "./dist/cli.js"
7
+ "agentapprove": "dist/cli.js"
8
8
  },
9
9
  "scripts": {
10
10
  "dev": "bun --watch src/cli.ts",
@@ -22,6 +22,7 @@
22
22
  "cursor",
23
23
  "gemini",
24
24
  "openclaw",
25
+ "opencode",
25
26
  "ai",
26
27
  "agent",
27
28
  "approval",