agentapprove 0.1.13 → 0.1.20

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 +195 -45
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2430,10 +2430,28 @@ function getOpenClawPluginTargets(openclawConfigPath) {
2430
2430
  legacyPath ? createRemovalTarget(legacyPath, "plugin_artifact", true) : null
2431
2431
  ].filter((target) => target !== null);
2432
2432
  }
2433
- function getOpenCodePluginTargets(opencodeConfigDir) {
2433
+ function getOpenCodePluginTargets(opencodeConfigDir, opencodeCacheDir) {
2434
2434
  const pluginPath = safeJoinWithinBase(opencodeConfigDir, "node_modules", "@agentapprove", "opencode");
2435
+ const cacheNodeModulesPath = opencodeCacheDir ? safeJoinWithinBase(opencodeCacheDir, "node_modules", "@agentapprove", "opencode") : null;
2436
+ const packageCacheTargets = [];
2437
+ if (opencodeCacheDir) {
2438
+ const scopedPackageCacheDir = safeJoinWithinBase(opencodeCacheDir, "packages", "@agentapprove");
2439
+ if (scopedPackageCacheDir && existsSync(scopedPackageCacheDir)) {
2440
+ for (const entry of readdirSync(scopedPackageCacheDir)) {
2441
+ if (!entry.startsWith("opencode@")) {
2442
+ continue;
2443
+ }
2444
+ const packageCachePath = safeJoinWithinBase(scopedPackageCacheDir, entry);
2445
+ if (packageCachePath) {
2446
+ packageCacheTargets.push(createRemovalTarget(packageCachePath, "plugin_artifact", true));
2447
+ }
2448
+ }
2449
+ }
2450
+ }
2435
2451
  return [
2436
- pluginPath ? createRemovalTarget(pluginPath, "plugin_artifact", true) : null
2452
+ pluginPath ? createRemovalTarget(pluginPath, "plugin_artifact", true) : null,
2453
+ cacheNodeModulesPath ? createRemovalTarget(cacheNodeModulesPath, "plugin_artifact", true) : null,
2454
+ ...packageCacheTargets
2437
2455
  ].filter((target) => target !== null);
2438
2456
  }
2439
2457
  function collectBackupTargets(configPaths) {
@@ -2613,7 +2631,7 @@ function shouldCreateFreshPairing(connectionMethod) {
2613
2631
  }
2614
2632
 
2615
2633
  // src/cli.ts
2616
- var VERSION = "0.1.13";
2634
+ var VERSION = "0.1.20";
2617
2635
  function getApiUrl() {
2618
2636
  return process.env.AGENTAPPROVE_API || "https://api.agentapprove.com";
2619
2637
  }
@@ -2626,6 +2644,12 @@ function getOpenCodeConfigDir() {
2626
2644
  function getOpenCodeConfigPath() {
2627
2645
  return join(getOpenCodeConfigDir(), "opencode.json");
2628
2646
  }
2647
+ function getXdgCacheHome() {
2648
+ return process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
2649
+ }
2650
+ function getOpenCodeCacheDir() {
2651
+ return join(getXdgCacheHome(), "opencode");
2652
+ }
2629
2653
  var API_URL = getApiUrl();
2630
2654
  var API_VERSION = process.env.AGENTAPPROVE_API_VERSION || "v001";
2631
2655
  function hasFlag2(flag) {
@@ -2633,18 +2657,36 @@ function hasFlag2(flag) {
2633
2657
  }
2634
2658
  function updateEnvValue(key, value) {
2635
2659
  const envPath = join(getAgentApproveDir(), "env");
2636
- if (!existsSync2(envPath))
2637
- return;
2638
- let content = readFileSync(envPath, "utf-8");
2639
- const pattern = new RegExp(`^${key}=.*$`, "m");
2640
- if (pattern.test(content)) {
2641
- content = content.replace(pattern, `${key}=${value}`);
2642
- } else {
2643
- content = content.trimEnd() + `
2644
- ${key}=${value}
2645
- `;
2660
+ let content;
2661
+ try {
2662
+ content = readFileSync(envPath, "utf-8");
2663
+ } catch (err) {
2664
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
2665
+ return;
2666
+ }
2667
+ throw err;
2646
2668
  }
2647
- writeFileSync(envPath, content, { mode: 384 });
2669
+ const lines = content.endsWith(`
2670
+ `) ? content.slice(0, -1).split(`
2671
+ `) : content.split(`
2672
+ `);
2673
+ let seenKey = false;
2674
+ const updatedLines = lines.flatMap((line) => {
2675
+ const trimmed = line.replace(/^export\s+/, "").trim();
2676
+ const eqIdx = trimmed.indexOf("=");
2677
+ if (eqIdx <= 0 || trimmed.slice(0, eqIdx) !== key)
2678
+ return [line];
2679
+ if (seenKey)
2680
+ return [];
2681
+ seenKey = true;
2682
+ return [`${key}=${value}`];
2683
+ });
2684
+ if (!seenKey) {
2685
+ updatedLines.push(`${key}=${value}`);
2686
+ }
2687
+ writeFileSync(envPath, `${updatedLines.join(`
2688
+ `)}
2689
+ `, { mode: 384 });
2648
2690
  }
2649
2691
  function getCommand() {
2650
2692
  const args = process.argv.slice(2);
@@ -2661,10 +2703,11 @@ function getCommand() {
2661
2703
  }
2662
2704
  return filtered[0] || "install";
2663
2705
  }
2664
- var OPENCODE_PLUGIN_VERSION = "0.1.9";
2665
- var OPENCLAW_PLUGIN_VERSION = "0.2.8";
2706
+ var OPENCODE_PLUGIN_VERSION = "0.1.16";
2707
+ var OPENCODE_PLUGIN_SPEC = `@agentapprove/opencode@${OPENCODE_PLUGIN_VERSION}`;
2708
+ var OPENCLAW_PLUGIN_VERSION = "0.2.10";
2666
2709
  var OPENCLAW_PLUGIN_SPEC = `@agentapprove/openclaw@${OPENCLAW_PLUGIN_VERSION}`;
2667
- var PI_PLUGIN_VERSION = "0.1.0";
2710
+ var PI_PLUGIN_VERSION = "0.1.5";
2668
2711
  var PI_PLUGIN_SPEC = `npm:@agentapprove/pi@${PI_PLUGIN_VERSION}`;
2669
2712
  var PI_STATUS_TIMEOUT_MS = 5000;
2670
2713
  var AGENTS = {
@@ -3208,7 +3251,7 @@ function buildUninstallPlan() {
3208
3251
  const configuredAgents = detectInstalledAgents();
3209
3252
  const pluginArtifactTargets = uniqueRemovalTargets([
3210
3253
  ...getOpenClawPluginTargets(AGENTS.openclaw.configPath),
3211
- ...getOpenCodePluginTargets(getOpenCodeConfigDir())
3254
+ ...getOpenCodePluginTargets(getOpenCodeConfigDir(), getOpenCodeCacheDir())
3212
3255
  ]);
3213
3256
  return {
3214
3257
  configuredAgents,
@@ -3335,6 +3378,13 @@ function readExistingConfig() {
3335
3378
  case "AGENTAPPROVE_RETENTION_DAYS":
3336
3379
  config.retentionDays = value === "forever" ? 365 : parseInt(value, 10);
3337
3380
  break;
3381
+ case "AGENTAPPROVE_CONFIG_SET_AT": {
3382
+ const parsed = parseInt(value, 10);
3383
+ if (Number.isFinite(parsed) && parsed > 0) {
3384
+ config.configSetAt = parsed;
3385
+ }
3386
+ break;
3387
+ }
3338
3388
  case "AGENTAPPROVE_DEBUG_LOG":
3339
3389
  config.debugLog = value === "true";
3340
3390
  break;
@@ -3904,32 +3954,41 @@ async function installHooksForAgent(agentId, hooksDir, mode = "approval") {
3904
3954
  return { success: true, backupPath, hooks: installedHooks };
3905
3955
  }
3906
3956
  if (agentId === "opencode") {
3907
- if (!Array.isArray(config.plugin)) {
3908
- config.plugin = [];
3957
+ const installResult = installOpenCodePluginViaCli();
3958
+ if (!installResult.success) {
3959
+ return { success: false, backupPath, hooks: [], error: installResult.error };
3909
3960
  }
3910
- const pluginArray = config.plugin;
3911
- if (!pluginArray.includes("@agentapprove/opencode")) {
3912
- pluginArray.push("@agentapprove/opencode");
3961
+ const opencodeConfig = readJsonConfig(agent.configPath);
3962
+ if (!Array.isArray(opencodeConfig.plugin)) {
3963
+ opencodeConfig.plugin = [];
3913
3964
  }
3914
- writeJsonConfig(agent.configPath, config);
3965
+ const pluginArray = opencodeConfig.plugin.filter((entry) => typeof entry === "string").filter((entry) => entry !== "@agentapprove/opencode" && !entry.startsWith("@agentapprove/opencode@"));
3966
+ if (!pluginArray.includes(OPENCODE_PLUGIN_SPEC)) {
3967
+ pluginArray.push(OPENCODE_PLUGIN_SPEC);
3968
+ }
3969
+ opencodeConfig.plugin = pluginArray;
3970
+ writeJsonConfig(agent.configPath, opencodeConfig);
3915
3971
  const opencodePkgPath = join(getOpenCodeConfigDir(), "package.json");
3916
3972
  try {
3917
- let pkgJson = {};
3918
- if (existsSync2(opencodePkgPath)) {
3919
- pkgJson = JSON.parse(readFileSync(opencodePkgPath, "utf-8"));
3920
- }
3921
- if (!pkgJson.dependencies) {
3922
- pkgJson.dependencies = {};
3923
- }
3924
- pkgJson.dependencies["@agentapprove/opencode"] = OPENCODE_PLUGIN_VERSION;
3925
- writeFileSync(opencodePkgPath, JSON.stringify(pkgJson, null, 2) + `
3973
+ const pkgJson = JSON.parse(readFileSync(opencodePkgPath, "utf-8"));
3974
+ const deps = pkgJson.dependencies;
3975
+ if (deps?.["@agentapprove/opencode"]) {
3976
+ delete deps["@agentapprove/opencode"];
3977
+ if (Object.keys(deps).length === 0) {
3978
+ delete pkgJson.dependencies;
3979
+ }
3980
+ writeFileSync(opencodePkgPath, JSON.stringify(pkgJson, null, 2) + `
3926
3981
  `);
3982
+ }
3927
3983
  } catch (err) {
3984
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
3985
+ return { success: true, backupPath, hooks: [...installedHooks, installResult.label] };
3986
+ }
3928
3987
  if (err instanceof Error) {
3929
3988
  console.warn(`Warning: Could not update package.json: ${err.message}`);
3930
3989
  }
3931
3990
  }
3932
- installedHooks.push("Agent Approve plugin");
3991
+ installedHooks.push(installResult.label);
3933
3992
  return { success: true, backupPath, hooks: installedHooks };
3934
3993
  }
3935
3994
  const hooksToInstall = mode === "observe" ? agent.hooks.filter((h2) => !h2.isApprovalHook) : agent.hooks;
@@ -4338,19 +4397,21 @@ codex_hooks = true`;
4338
4397
  `);
4339
4398
  } else if (agentId === "opencode") {
4340
4399
  const configJson = JSON.stringify({
4341
- plugin: ["@agentapprove/opencode"]
4400
+ plugin: [OPENCODE_PLUGIN_SPEC]
4342
4401
  }, null, 2);
4343
4402
  const opencodeConfigPath = process.env.XDG_CONFIG_HOME ? `${process.env.XDG_CONFIG_HOME}/opencode/opencode.json` : "~/.config/opencode/opencode.json";
4344
4403
  return [
4345
- `Add the Agent Approve plugin to your OpenCode config (${opencodeConfigPath}):`,
4404
+ "Install the Agent Approve plugin for OpenCode:",
4346
4405
  "",
4347
- configJson,
4406
+ ` opencode plugin ${OPENCODE_PLUGIN_SPEC} --global --force`,
4348
4407
  "",
4349
- "Then add the dependency to your OpenCode package.json (same directory):",
4408
+ `This command installs the npm plugin and updates your global OpenCode config (${opencodeConfigPath}).`,
4350
4409
  "",
4351
- ` { "dependencies": { "@agentapprove/opencode": "${OPENCODE_PLUGIN_VERSION}" } }`,
4410
+ "If you need to edit the config manually, ensure it contains:",
4352
4411
  "",
4353
- "OpenCode will auto-install the plugin on next start."
4412
+ configJson,
4413
+ "",
4414
+ "Restart OpenCode to activate the plugin."
4354
4415
  ].join(`
4355
4416
  `);
4356
4417
  } else if (agentId === "pi") {
@@ -4436,6 +4497,89 @@ function installOpenClawPluginViaCli() {
4436
4497
  return { success: false, error: message, label: "Agent Approve plugin" };
4437
4498
  }
4438
4499
  }
4500
+ function readJsonPackageVersion(packagePath) {
4501
+ if (!existsSync2(packagePath)) {
4502
+ return null;
4503
+ }
4504
+ try {
4505
+ const pkg = JSON.parse(readFileSync(packagePath, "utf-8"));
4506
+ return typeof pkg.version === "string" ? pkg.version : null;
4507
+ } catch {
4508
+ return null;
4509
+ }
4510
+ }
4511
+ function readOpenCodeInstalledVersion() {
4512
+ const cacheDir = getOpenCodeCacheDir();
4513
+ const candidates = [
4514
+ join(cacheDir, "packages", "@agentapprove", `opencode@${OPENCODE_PLUGIN_VERSION}`, "node_modules", "@agentapprove", "opencode", "package.json"),
4515
+ join(cacheDir, "packages", "@agentapprove", "opencode@latest", "node_modules", "@agentapprove", "opencode", "package.json"),
4516
+ join(cacheDir, "node_modules", "@agentapprove", "opencode", "package.json"),
4517
+ join(getOpenCodeConfigDir(), "node_modules", "@agentapprove", "opencode", "package.json")
4518
+ ];
4519
+ for (const candidate of candidates) {
4520
+ const version = readJsonPackageVersion(candidate);
4521
+ if (version === OPENCODE_PLUGIN_VERSION) {
4522
+ return version;
4523
+ }
4524
+ }
4525
+ for (const candidate of candidates) {
4526
+ const version = readJsonPackageVersion(candidate);
4527
+ if (version) {
4528
+ return version;
4529
+ }
4530
+ }
4531
+ return null;
4532
+ }
4533
+ function installOpenCodePluginViaCli() {
4534
+ try {
4535
+ const result = spawnSync("opencode", ["plugin", OPENCODE_PLUGIN_SPEC, "--global", "--force"], {
4536
+ encoding: "utf8",
4537
+ stdio: ["ignore", "pipe", "pipe"]
4538
+ });
4539
+ if (result.error) {
4540
+ return {
4541
+ success: false,
4542
+ label: "Agent Approve plugin",
4543
+ error: `Could not run opencode plugin installer: ${result.error.message}`
4544
+ };
4545
+ }
4546
+ if (result.status !== 0) {
4547
+ const detail = [result.stdout, result.stderr].filter(Boolean).join(`
4548
+ `).trim();
4549
+ return {
4550
+ success: false,
4551
+ label: "Agent Approve plugin",
4552
+ error: detail || `opencode plugin installer exited with code ${result.status}`
4553
+ };
4554
+ }
4555
+ const installedVersion = readOpenCodeInstalledVersion();
4556
+ if (!installedVersion) {
4557
+ return {
4558
+ success: false,
4559
+ label: "Agent Approve plugin",
4560
+ error: `OpenCode installed ${OPENCODE_PLUGIN_SPEC}, but the installer could not verify the package version. Re-run manually with: opencode plugin ${OPENCODE_PLUGIN_SPEC} --global --force`
4561
+ };
4562
+ }
4563
+ if (installedVersion !== OPENCODE_PLUGIN_VERSION) {
4564
+ return {
4565
+ success: false,
4566
+ label: "Agent Approve plugin",
4567
+ error: `OpenCode installed ${installedVersion}, expected ${OPENCODE_PLUGIN_VERSION}. Re-run after the npm registry exposes the latest package.`
4568
+ };
4569
+ }
4570
+ return {
4571
+ success: true,
4572
+ version: installedVersion,
4573
+ label: `Agent Approve plugin v${installedVersion}`
4574
+ };
4575
+ } catch (err) {
4576
+ return {
4577
+ success: false,
4578
+ label: "Agent Approve plugin",
4579
+ error: err instanceof Error ? err.message : String(err)
4580
+ };
4581
+ }
4582
+ }
4439
4583
  function installPiPluginViaCli() {
4440
4584
  try {
4441
4585
  execSync(`pi install ${PI_PLUGIN_SPEC}`, { stdio: "pipe" });
@@ -4730,7 +4874,7 @@ Installs hooks and extensions for Cursor, Claude, Gemini, Pi, OpenCode, OpenClaw
4730
4874
  let failBehavior = selectedInstallConfig.failBehavior;
4731
4875
  let installMode = selectedInstallConfig.installMode;
4732
4876
  let useE2E = selectedInstallConfig.e2eEnabled;
4733
- let configSetAt = Math.floor(Date.now() / 1000);
4877
+ let configSetAt = setupProfile === "existing-config" && existingConfig?.configSetAt ? existingConfig.configSetAt : Math.floor(Date.now() / 1000);
4734
4878
  if (setupProfile === "customize") {
4735
4879
  const modeChoice = await le({
4736
4880
  message: "Choose your security mode:",
@@ -4898,6 +5042,9 @@ Installs hooks and extensions for Cursor, Claude, Gemini, Pi, OpenCode, OpenClaw
4898
5042
  if (connectionMethod === "existing") {
4899
5043
  token = existingConfig.token;
4900
5044
  apiUrl = existingConfig.apiUrl || API_URL;
5045
+ if (setupProfile === "existing-config" && existingConfig?.configSetAt) {
5046
+ configSetAt = existingConfig.configSetAt;
5047
+ }
4901
5048
  const e2eKeyExists = existsSync2(existingKeyPath);
4902
5049
  useE2E = e2eKeyExists;
4903
5050
  e2eUserKey = null;
@@ -5062,7 +5209,10 @@ AGENTAPPROVE_E2E_MODE=${installMode}
5062
5209
  filesToModify.push(join(homedir(), ".codex", "config.toml"));
5063
5210
  }
5064
5211
  if (agentId === "opencode") {
5065
- filesToModify.push(join(getOpenCodeConfigDir(), "package.json"));
5212
+ filesToModify.push(`OpenCode plugin cache (via \`opencode plugin ${OPENCODE_PLUGIN_SPEC} --global --force\`)`);
5213
+ }
5214
+ if (agentId === "openclaw") {
5215
+ filesToModify.push(`OpenClaw plugin registry (via \`openclaw plugins install ${OPENCLAW_PLUGIN_SPEC}\`)`);
5066
5216
  }
5067
5217
  if (agentId === "pi") {
5068
5218
  filesToModify.push("Pi package registry (via `pi install`)");
@@ -5094,8 +5244,8 @@ Backups will be created with timestamp`, "Files to be modified");
5094
5244
  if (result.success) {
5095
5245
  const installedHookNames = result.hooks.join(", ");
5096
5246
  const backupMsg = result.backupPath ? source_default.dim(` (backup created)`) : "";
5097
- const modeLabel = installMode === "observe" ? source_default.cyan(" [observe]") : source_default.green(" [approval]");
5098
- spinner.stop(`${agent.name}${modeLabel}: ${installedHookNames}${backupMsg}`);
5247
+ const agentName = source_default.cyan(agent.name);
5248
+ spinner.stop(`${agentName}: ${installedHookNames}${backupMsg}`);
5099
5249
  if (agentId === "vscode-agent") {
5100
5250
  const vsCodeSpinner = _2();
5101
5251
  vsCodeSpinner.start("Registering hook path in VS Code settings");
@@ -5566,7 +5716,7 @@ but if unused for 30 days they expire. Get a new one below.`, "Token Expired");
5566
5716
  } else {
5567
5717
  v2.info('Refresh updates the token only. Use "npx agentapprove pair" if you need to repair or change E2E pairing.');
5568
5718
  }
5569
- const configSetAt = Math.floor(Date.now() / 1000);
5719
+ const configSetAt = existingConfig.configSetAt || Math.floor(Date.now() / 1000);
5570
5720
  const privacy = existingConfig.privacy || "full";
5571
5721
  const retentionDays = existingConfig.retentionDays ?? 30;
5572
5722
  const failBehavior = existingConfig.failBehavior || "ask";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentapprove",
3
- "version": "0.1.13",
3
+ "version": "0.1.20",
4
4
  "description": "Approve AI agent actions from your iPhone or Apple Watch",
5
5
  "type": "module",
6
6
  "bin": {