agentapprove 0.1.11 → 0.1.12

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 (2) hide show
  1. package/dist/cli.js +206 -84
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2505,7 +2505,7 @@ function shouldDeleteCryptoMaterial(purgeConfirmed, firstKeyConfirmation, second
2505
2505
  }
2506
2506
 
2507
2507
  // src/cli.ts
2508
- var VERSION = "0.1.11";
2508
+ var VERSION = "0.1.12";
2509
2509
  function getApiUrl() {
2510
2510
  return process.env.AGENTAPPROVE_API || "https://api.agentapprove.com";
2511
2511
  }
@@ -2553,8 +2553,8 @@ function getCommand() {
2553
2553
  }
2554
2554
  return filtered[0] || "install";
2555
2555
  }
2556
- var OPENCODE_PLUGIN_VERSION = "0.1.7";
2557
- var OPENCLAW_PLUGIN_VERSION = "0.2.5";
2556
+ var OPENCODE_PLUGIN_VERSION = "0.1.8";
2557
+ var OPENCLAW_PLUGIN_VERSION = "0.2.7";
2558
2558
  var OPENCLAW_PLUGIN_SPEC = `@agentapprove/openclaw@${OPENCLAW_PLUGIN_VERSION}`;
2559
2559
  var AGENTS = {
2560
2560
  "claude-code": {
@@ -2667,6 +2667,53 @@ var AGENTS = {
2667
2667
  ]
2668
2668
  }
2669
2669
  };
2670
+ var SHARED_HOOK_FILES = ["common.sh"];
2671
+ var STATUS_FIELD_LABELS = {
2672
+ AGENTAPPROVE_API: "API URL",
2673
+ AGENTAPPROVE_TOKEN: "Token",
2674
+ AGENTAPPROVE_PRIVACY: "Privacy",
2675
+ AGENTAPPROVE_RETENTION_DAYS: "Retention",
2676
+ AGENTAPPROVE_CONFIG_SET_AT: "Configured at",
2677
+ AGENTAPPROVE_DEBUG_LOG: "Debug logging",
2678
+ AGENTAPPROVE_E2E_MODE: "Security mode",
2679
+ AGENTAPPROVE_E2E_ENABLED: "E2E encryption",
2680
+ AGENTAPPROVE_FAIL_BEHAVIOR: "If unreachable"
2681
+ };
2682
+ var HOOK_STATUS_LABELS = {
2683
+ PreToolUse: "Tool approval",
2684
+ PostToolUse: "Tool completed",
2685
+ PostToolUseFailure: "Tool failed",
2686
+ PermissionRequest: "Permission request",
2687
+ UserPromptSubmit: "Prompt submitted",
2688
+ SessionStart: "Session start",
2689
+ SessionEnd: "Session end",
2690
+ Stop: "Stop",
2691
+ sessionStart: "Session start",
2692
+ sessionEnd: "Session end",
2693
+ beforeShellExecution: "Shell approval",
2694
+ beforeMCPExecution: "MCP approval",
2695
+ preToolUse: "Tool approval",
2696
+ afterShellExecution: "Shell completed",
2697
+ afterMCPExecution: "MCP completed",
2698
+ postToolUse: "Tool completed",
2699
+ beforeSubmitPrompt: "Prompt submitted",
2700
+ subagentStart: "Subagent start",
2701
+ subagentStop: "Subagent stop",
2702
+ preCompact: "Pre-compact",
2703
+ afterAgentThought: "Agent thinking",
2704
+ afterAgentResponse: "Agent response",
2705
+ BeforeTool: "Tool approval",
2706
+ AfterTool: "Tool completed",
2707
+ BeforeAgent: "Prompt submitted",
2708
+ AfterAgent: "Stop",
2709
+ BeforeModel: "Model request",
2710
+ AfterModel: "Model response",
2711
+ Notification: "Notification",
2712
+ userPromptSubmitted: "Prompt submitted",
2713
+ errorOccurred: "Error",
2714
+ SubagentStart: "Subagent start",
2715
+ SubagentStop: "Subagent stop"
2716
+ };
2670
2717
  function findGitBash() {
2671
2718
  if (!isWindows())
2672
2719
  return null;
@@ -2864,6 +2911,93 @@ function detectInstalledAgents() {
2864
2911
  }
2865
2912
  return installed;
2866
2913
  }
2914
+ function getDownloadableHookFilesForAgent(agentId) {
2915
+ const agent = AGENTS[agentId];
2916
+ if (!agent)
2917
+ return [];
2918
+ return agent.hooks.filter((hook) => !hook.isPlugin).map((hook) => hook.file);
2919
+ }
2920
+ function buildHookDownloadPlan(agentIds) {
2921
+ const files = new Set;
2922
+ const perAgent = [];
2923
+ for (const agentId of agentIds) {
2924
+ const agent = AGENTS[agentId];
2925
+ if (!agent)
2926
+ continue;
2927
+ const agentFiles = [...new Set(getDownloadableHookFilesForAgent(agentId))];
2928
+ if (agentFiles.length === 0) {
2929
+ continue;
2930
+ }
2931
+ perAgent.push({
2932
+ agentId,
2933
+ agentName: agent.name,
2934
+ count: agentFiles.length
2935
+ });
2936
+ for (const file of agentFiles) {
2937
+ files.add(file);
2938
+ }
2939
+ }
2940
+ const sharedCount = perAgent.length > 0 ? SHARED_HOOK_FILES.length : 0;
2941
+ if (sharedCount > 0) {
2942
+ for (const file of SHARED_HOOK_FILES) {
2943
+ files.add(file);
2944
+ }
2945
+ }
2946
+ return {
2947
+ files: [...files],
2948
+ perAgent,
2949
+ sharedCount
2950
+ };
2951
+ }
2952
+ function formatHookDownloadSummary(plan) {
2953
+ const parts = plan.perAgent.map((entry) => `${entry.agentName}: ${entry.count}`);
2954
+ if (plan.sharedCount > 0) {
2955
+ parts.push(`shared: ${plan.sharedCount}`);
2956
+ }
2957
+ return parts.join(", ");
2958
+ }
2959
+ function parseEnvAssignment(line) {
2960
+ if (!line.includes("=")) {
2961
+ return null;
2962
+ }
2963
+ const [key, ...valueParts] = line.split("=");
2964
+ return {
2965
+ key: key.trim(),
2966
+ value: valueParts.join("=").trim()
2967
+ };
2968
+ }
2969
+ function formatStatusValue(key, value) {
2970
+ switch (key) {
2971
+ case "AGENTAPPROVE_TOKEN":
2972
+ return value.slice(0, 15) + "...";
2973
+ case "AGENTAPPROVE_RETENTION_DAYS":
2974
+ return `${value} days`;
2975
+ case "AGENTAPPROVE_CONFIG_SET_AT": {
2976
+ const timestamp = Number.parseInt(value, 10);
2977
+ if (Number.isFinite(timestamp)) {
2978
+ return new Date(timestamp * 1000).toLocaleString();
2979
+ }
2980
+ return value;
2981
+ }
2982
+ case "AGENTAPPROVE_DEBUG_LOG":
2983
+ case "AGENTAPPROVE_E2E_ENABLED":
2984
+ return value === "true" ? "Enabled" : "Disabled";
2985
+ case "AGENTAPPROVE_E2E_MODE":
2986
+ return value.charAt(0).toUpperCase() + value.slice(1);
2987
+ case "AGENTAPPROVE_FAIL_BEHAVIOR":
2988
+ case "AGENTAPPROVE_PRIVACY":
2989
+ return value.charAt(0).toUpperCase() + value.slice(1);
2990
+ default:
2991
+ return value;
2992
+ }
2993
+ }
2994
+ function formatHookStatusLabel(name) {
2995
+ const explicitLabel = HOOK_STATUS_LABELS[name];
2996
+ if (explicitLabel) {
2997
+ return explicitLabel;
2998
+ }
2999
+ return name.replace(/\*/g, "events").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[-_]/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (letter) => letter.toUpperCase());
3000
+ }
2867
3001
  function getAgentApproveDir() {
2868
3002
  return join(homedir(), ".agentapprove");
2869
3003
  }
@@ -2992,10 +3126,12 @@ function readExistingConfig() {
2992
3126
  const config = {};
2993
3127
  for (const line of content.split(`
2994
3128
  `)) {
2995
- if (line.startsWith("#") || !line.includes("="))
3129
+ if (line.startsWith("#"))
2996
3130
  continue;
2997
- const [key, ...valueParts] = line.split("=");
2998
- const value = valueParts.join("=").trim();
3131
+ const assignment = parseEnvAssignment(line);
3132
+ if (!assignment)
3133
+ continue;
3134
+ const { key, value } = assignment;
2999
3135
  switch (key.trim()) {
3000
3136
  case "AGENTAPPROVE_API":
3001
3137
  config.apiUrl = value;
@@ -3488,7 +3624,7 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3488
3624
  }
3489
3625
  hooks.internal.enabled = true;
3490
3626
  writeJsonConfig(agent.configPath, config);
3491
- installedHooks.push("openclaw");
3627
+ installedHooks.push(installResult.label);
3492
3628
  return { success: true, backupPath, hooks: installedHooks };
3493
3629
  }
3494
3630
  if (agentId === "opencode") {
@@ -3517,7 +3653,7 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3517
3653
  console.warn(`Warning: Could not update package.json: ${err.message}`);
3518
3654
  }
3519
3655
  }
3520
- installedHooks.push("agentapprove");
3656
+ installedHooks.push("Agent Approve plugin");
3521
3657
  return { success: true, backupPath, hooks: installedHooks };
3522
3658
  }
3523
3659
  const hooksToInstall = mode === "observe" ? agent.hooks.filter((h2) => !h2.isApprovalHook) : agent.hooks;
@@ -3944,64 +4080,10 @@ codex_hooks = true`;
3944
4080
  }
3945
4081
  return "";
3946
4082
  }
3947
- var HOOK_FILES = [
3948
- "common.sh",
3949
- "claude-pre-tool.sh",
3950
- "claude-post-tool.sh",
3951
- "claude-post-tool-failure.sh",
3952
- "claude-notification.sh",
3953
- "claude-user-prompt.sh",
3954
- "claude-prompt.sh",
3955
- "claude-session-start.sh",
3956
- "claude-session-end.sh",
3957
- "claude-stop.sh",
3958
- "notify-hook.sh",
3959
- "completion-hook.sh",
3960
- "cursor-session-start.sh",
3961
- "cursor-session-end.sh",
3962
- "cursor-approval.sh",
3963
- "cursor-mcp-approval.sh",
3964
- "cursor-pre-tool.sh",
3965
- "cursor-shell-complete.sh",
3966
- "cursor-mcp-complete.sh",
3967
- "cursor-post-tool.sh",
3968
- "cursor-start.sh",
3969
- "cursor-subagent-start.sh",
3970
- "cursor-subagent-stop.sh",
3971
- "cursor-precompact.sh",
3972
- "cursor-stop.sh",
3973
- "cursor-thought.sh",
3974
- "cursor-response.sh",
3975
- "gemini-before-tool.sh",
3976
- "gemini-after-tool.sh",
3977
- "gemini-before-agent.sh",
3978
- "gemini-after-agent.sh",
3979
- "gemini-before-model.sh",
3980
- "gemini-after-model.sh",
3981
- "gemini-notification.sh",
3982
- "gemini-session-start.sh",
3983
- "gemini-session-end.sh",
3984
- "gemini-stop.sh",
3985
- "codex-session-start.sh",
3986
- "codex-pre-tool.sh",
3987
- "codex-post-tool.sh",
3988
- "codex-user-prompt.sh",
3989
- "codex-stop.sh",
3990
- "github-session-start.sh",
3991
- "github-session-end.sh",
3992
- "github-user-prompt.sh",
3993
- "github-pre-tool.sh",
3994
- "github-post-tool.sh",
3995
- "github-error.sh",
3996
- "github-stop.sh",
3997
- "github-subagent-start.sh",
3998
- "github-subagent-stop.sh",
3999
- "github-precompact.sh"
4000
- ];
4001
- async function copyHookScripts(hooksDir, token) {
4083
+ async function copyHookScripts(hooksDir, token, files) {
4002
4084
  let downloaded = 0;
4003
4085
  const failed = [];
4004
- for (const file of HOOK_FILES) {
4086
+ for (const file of files) {
4005
4087
  try {
4006
4088
  const response = await fetch(`${API_URL}/${API_VERSION}/hooks/${file}?format=raw`, {
4007
4089
  headers: {
@@ -4028,13 +4110,43 @@ async function copyHookScripts(hooksDir, token) {
4028
4110
  }
4029
4111
  return { downloaded, failed };
4030
4112
  }
4113
+ function readOpenClawInstalledVersion() {
4114
+ const packagePath = join(homedir(), ".openclaw", "extensions", "openclaw", "package.json");
4115
+ if (!existsSync2(packagePath)) {
4116
+ return null;
4117
+ }
4118
+ try {
4119
+ const pkg = JSON.parse(readFileSync(packagePath, "utf-8"));
4120
+ return typeof pkg.version === "string" ? pkg.version : null;
4121
+ } catch {
4122
+ return null;
4123
+ }
4124
+ }
4031
4125
  function installOpenClawPluginViaCli() {
4032
4126
  try {
4033
4127
  execSync(`openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}`, { stdio: "pipe" });
4034
- return { success: true };
4128
+ const installedVersion = readOpenClawInstalledVersion();
4129
+ if (!installedVersion) {
4130
+ return {
4131
+ success: true,
4132
+ label: "Agent Approve plugin (version not verified)"
4133
+ };
4134
+ }
4135
+ if (installedVersion !== OPENCLAW_PLUGIN_VERSION) {
4136
+ return {
4137
+ success: false,
4138
+ error: `OpenClaw installed ${installedVersion}, expected ${OPENCLAW_PLUGIN_VERSION}. Re-run after updating the published installer package.`,
4139
+ label: "Agent Approve plugin"
4140
+ };
4141
+ }
4142
+ return {
4143
+ success: true,
4144
+ version: installedVersion,
4145
+ label: `Agent Approve plugin v${installedVersion}`
4146
+ };
4035
4147
  } catch (err) {
4036
4148
  const message = err instanceof Error ? err.message : "unknown error";
4037
- return { success: false, error: message };
4149
+ return { success: false, error: message, label: "Agent Approve plugin" };
4038
4150
  }
4039
4151
  }
4040
4152
  var SYSTEM_DEPS = [
@@ -4269,7 +4381,7 @@ E2E Key: ${keyId}`;
4269
4381
  Privacy: ${existingConfig.privacy || "unknown"}${e2eLine}`, "Existing configuration found");
4270
4382
  } else {
4271
4383
  me(`Approve AI agent actions from your iPhone or Apple Watch.
4272
- Installs hooks for Claude Code, Cursor, Gemini CLI, VS Code GitHub Copilot, and GitHub Copilot CLI.`, "About");
4384
+ Installs hooks and plugins for OpenClaw, OpenAI Codex, Claude Code, Cursor, Gemini CLI, and more.`, "About");
4273
4385
  }
4274
4386
  const installedAgents = detectInstalledAgents();
4275
4387
  const agentOptions = Object.entries(AGENTS).map(([id, agent]) => ({
@@ -4348,7 +4460,7 @@ Installs hooks for Claude Code, Cursor, Gemini CLI, VS Code GitHub Copilot, and
4348
4460
  }
4349
4461
  connectionOptions.push({ value: "qr", label: "Scan QR code", hint: hasExistingToken ? undefined : "Recommended" });
4350
4462
  const connectionMethod = await le({
4351
- message: "Connect to iOS app (required for hook download)",
4463
+ message: "Connect to iOS app (required for setup and any hook downloads)",
4352
4464
  options: connectionOptions
4353
4465
  });
4354
4466
  if (lD(connectionMethod)) {
@@ -4441,7 +4553,7 @@ Installs hooks for Claude Code, Cursor, Gemini CLI, VS Code GitHub Copilot, and
4441
4553
  });
4442
4554
  await new Promise((resolve) => setTimeout(resolve, 10));
4443
4555
  me(qrDisplay + `
4444
- Session: ${session.sessionCode}`, "Scan with Agent Approve iOS app");
4556
+ Session: ${session.sessionCode}`, "Scan in Agent Approve iOS app (Settings > Scan QR Code)");
4445
4557
  const pairingSpinner = _2();
4446
4558
  pairingSpinner.start("Waiting for iOS app...");
4447
4559
  const result = await waitForPairing(session.sessionCode, (expiresIn) => {
@@ -4511,14 +4623,20 @@ Session: ${session.sessionCode}`, "Scan with Agent Approve iOS app");
4511
4623
  failBehavior,
4512
4624
  configSetAt
4513
4625
  }).catch(() => {});
4514
- const downloadSpinner = _2();
4515
- downloadSpinner.start("Downloading hook scripts");
4516
- const downloadResult = await copyHookScripts(hooksDir, token);
4517
- if (downloadResult.failed.length > 0) {
4518
- downloadSpinner.stop(`Downloaded ${downloadResult.downloaded} hooks, ${downloadResult.failed.length} failed`);
4519
- v2.warn(`Failed to download: ${downloadResult.failed.join(", ")}`);
4626
+ const hookDownloadPlan = buildHookDownloadPlan(selectedAgents);
4627
+ if (hookDownloadPlan.files.length > 0) {
4628
+ const downloadSpinner = _2();
4629
+ downloadSpinner.start("Downloading hook scripts");
4630
+ const downloadResult = await copyHookScripts(hooksDir, token, hookDownloadPlan.files);
4631
+ const summary = formatHookDownloadSummary(hookDownloadPlan);
4632
+ if (downloadResult.failed.length > 0) {
4633
+ downloadSpinner.stop(`Downloaded ${downloadResult.downloaded} of ${hookDownloadPlan.files.length} hook files (${summary})`);
4634
+ v2.warn(`Failed to download: ${downloadResult.failed.join(", ")}`);
4635
+ } else {
4636
+ downloadSpinner.stop(`Hook scripts downloaded (${downloadResult.downloaded} files: ${summary})`);
4637
+ }
4520
4638
  } else {
4521
- downloadSpinner.stop(`Hook scripts downloaded (${downloadResult.downloaded} files)`);
4639
+ v2.success("No hook scripts needed for the selected agents");
4522
4640
  }
4523
4641
  const e2eKeyPath = join(homedir(), ".agentapprove", "e2e-key");
4524
4642
  const hasE2EKey = existsSync2(e2eKeyPath);
@@ -4670,9 +4788,13 @@ async function statusCommand() {
4670
4788
  `);
4671
4789
  for (const line of lines) {
4672
4790
  if (line.startsWith("AGENTAPPROVE_")) {
4673
- const [key, value] = line.split("=");
4674
- const displayKey = key.replace("AGENTAPPROVE_", "").toLowerCase();
4675
- const displayValue = key.includes("TOKEN") ? value.slice(0, 15) + "..." : value;
4791
+ const assignment = parseEnvAssignment(line);
4792
+ if (!assignment) {
4793
+ continue;
4794
+ }
4795
+ const { key, value } = assignment;
4796
+ const displayKey = STATUS_FIELD_LABELS[key] || key.replace("AGENTAPPROVE_", "");
4797
+ const displayValue = formatStatusValue(key, value);
4676
4798
  console.log(` ${source_default.dim(displayKey + ":")} ${displayValue}`);
4677
4799
  }
4678
4800
  }
@@ -4694,14 +4816,14 @@ async function statusCommand() {
4694
4816
  if (agentId === "opencode") {
4695
4817
  const pluginConfig = config.plugin;
4696
4818
  if (Array.isArray(pluginConfig) && pluginConfig.some((entry) => typeof entry === "string" && entry.includes("@agentapprove/opencode"))) {
4697
- console.log(` ${source_default.green("✓")} ${agent.name}: agentapprove plugin`);
4819
+ console.log(` ${source_default.green("✓")} ${agent.name}: Agent Approve plugin`);
4698
4820
  }
4699
4821
  continue;
4700
4822
  }
4701
4823
  if (agentId === "openclaw") {
4702
4824
  const entries = config.plugins?.entries;
4703
4825
  if (entries?.openclaw) {
4704
- console.log(` ${source_default.green("✓")} ${agent.name}: openclaw plugin`);
4826
+ console.log(` ${source_default.green("✓")} ${agent.name}: Agent Approve plugin`);
4705
4827
  }
4706
4828
  continue;
4707
4829
  }
@@ -4715,7 +4837,7 @@ async function statusCommand() {
4715
4837
  return str.includes("agentapprove");
4716
4838
  });
4717
4839
  if (installedHooks.length > 0) {
4718
- console.log(` ${source_default.green("✓")} ${agent.name}: ${installedHooks.map((h2) => h2.name).join(", ")}`);
4840
+ console.log(` ${source_default.green("✓")} ${agent.name}: ${installedHooks.map((h2) => formatHookStatusLabel(h2.name)).join(", ")}`);
4719
4841
  }
4720
4842
  }
4721
4843
  }
@@ -5059,7 +5181,7 @@ but if unused for 30 days they expire. Get a new one below.`, "Token Expired");
5059
5181
  });
5060
5182
  await new Promise((resolve) => setTimeout(resolve, 10));
5061
5183
  me(qrDisplay + `
5062
- Session: ${session.sessionCode}`, "Scan with Agent Approve iOS app");
5184
+ Session: ${session.sessionCode}`, "Scan in Agent Approve iOS app (Settings > Scan QR Code)");
5063
5185
  const pairingSpinner = _2();
5064
5186
  pairingSpinner.start("Waiting for iOS app...");
5065
5187
  const result = await waitForPairing(session.sessionCode, (expiresIn) => {
@@ -5223,7 +5345,7 @@ async function pairCommand() {
5223
5345
  const noteLabel = e2eKeyId ? `Session: ${session.sessionCode}
5224
5346
  Key ID: ${e2eKeyId}` : `Session: ${session.sessionCode}`;
5225
5347
  me(qrDisplay + `
5226
- ${noteLabel}`, "Scan with Agent Approve iOS app");
5348
+ ${noteLabel}`, "Scan in Agent Approve iOS app (Settings > Scan QR Code)");
5227
5349
  const pairingSpinner = _2();
5228
5350
  pairingSpinner.start("Waiting for iOS app...");
5229
5351
  const result = await waitForPairing(session.sessionCode, (expiresIn) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentapprove",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Approve AI agent actions from your iPhone or Apple Watch",
5
5
  "type": "module",
6
6
  "bin": {