@agentvalet/register 0.5.3 → 0.6.0

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/index.js +681 -331
  2. package/package.json +59 -58
package/dist/index.js CHANGED
@@ -10,8 +10,8 @@ import {
10
10
 
11
11
  // src/index.ts
12
12
  import { createRequire as createRequire2 } from "module";
13
- import { Command as Command6 } from "commander";
14
- import chalk13 from "chalk";
13
+ import { Command as Command7 } from "commander";
14
+ import chalk14 from "chalk";
15
15
 
16
16
  // src/wizard.ts
17
17
  import prompts from "prompts";
@@ -196,7 +196,7 @@ async function saveOutput(opts) {
196
196
  }
197
197
 
198
198
  // src/commands/refresh.ts
199
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, statSync } from "fs";
199
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, statSync } from "fs";
200
200
  import { dirname, resolve as resolve2 } from "path";
201
201
 
202
202
  // src/lib/jwt.ts
@@ -685,7 +685,115 @@ function tryResolveAgent(envFile = ".env.agentvalet") {
685
685
  return { agentId, ownerId, keyPath, apiUrl };
686
686
  }
687
687
 
688
+ // src/config/frameworks.ts
689
+ import { existsSync as existsSync3 } from "fs";
690
+ import { execSync as execSync2 } from "child_process";
691
+ import os from "os";
692
+ import path2 from "path";
693
+ import prompts2 from "prompts";
694
+ import chalk4 from "chalk";
695
+ function tryBinary(cmd) {
696
+ try {
697
+ execSync2(`${cmd} --version`, { stdio: "ignore", timeout: 3e3 });
698
+ return true;
699
+ } catch {
700
+ return false;
701
+ }
702
+ }
703
+ function codexDesktopInstalled() {
704
+ const home = os.homedir();
705
+ const platform = process.platform;
706
+ if (platform === "darwin") {
707
+ return existsSync3("/Applications/Codex.app") || existsSync3(path2.join(home, "Applications", "Codex.app"));
708
+ }
709
+ if (platform === "win32") {
710
+ const localAppData = process.env.LOCALAPPDATA ?? path2.join(home, "AppData", "Local");
711
+ const programFiles = process.env["ProgramFiles"] ?? "C:\\Program Files";
712
+ return existsSync3(path2.join(localAppData, "Programs", "codex")) || existsSync3(path2.join(localAppData, "Programs", "Codex")) || existsSync3(path2.join(programFiles, "Codex"));
713
+ }
714
+ return existsSync3("/usr/share/applications/codex.desktop") || existsSync3(path2.join(home, ".local", "share", "applications", "codex.desktop"));
715
+ }
716
+ function cursorInstalled() {
717
+ const home = os.homedir();
718
+ const platform = process.platform;
719
+ if (platform === "win32") {
720
+ const appData = process.env.APPDATA ?? path2.join(home, "AppData", "Roaming");
721
+ const localAppData = process.env.LOCALAPPDATA ?? path2.join(home, "AppData", "Local");
722
+ return existsSync3(path2.join(appData, "Cursor")) || existsSync3(path2.join(localAppData, "Programs", "cursor"));
723
+ }
724
+ if (platform === "darwin") {
725
+ return existsSync3(path2.join(home, "Library", "Application Support", "Cursor")) || existsSync3("/Applications/Cursor.app");
726
+ }
727
+ return existsSync3(path2.join(home, ".config", "Cursor")) || existsSync3("/usr/share/applications/cursor.desktop");
728
+ }
729
+ function detectFrameworks() {
730
+ const home = os.homedir();
731
+ return {
732
+ codex: existsSync3(path2.join(home, ".codex")) || tryBinary("codex"),
733
+ codexDesktop: codexDesktopInstalled(),
734
+ claudeCode: existsSync3(path2.join(home, ".claude")) || tryBinary("claude"),
735
+ cursor: existsSync3(path2.join(process.cwd(), ".cursor")) || cursorInstalled(),
736
+ cursorCli: tryBinary("cursor-agent"),
737
+ openclaw: existsSync3(path2.join(home, ".openclaw")),
738
+ factoryDroid: existsSync3(path2.join(home, ".factory")) || existsSync3(path2.join(process.cwd(), ".factory"))
739
+ };
740
+ }
741
+ function resolveAgentType(detected) {
742
+ if (detected.claudeCode) return "claude-code";
743
+ if (detected.codexDesktop) return "codex-desktop";
744
+ if (detected.codex) return "codex-cli";
745
+ if (detected.cursor) return "cursor-ide";
746
+ if (detected.cursorCli) return "cursor-agent-cli";
747
+ if (detected.factoryDroid) return "factory-droid";
748
+ if (detected.openclaw) return "openclaw";
749
+ return "unknown";
750
+ }
751
+ async function frameworkSetupConsent(detected) {
752
+ const items = [];
753
+ if (detected.claudeCode) {
754
+ items.push("Claude Code \u2192 .mcp.json (MCP server)");
755
+ items.push(" \u2192 CLAUDE.md (usage rules)");
756
+ }
757
+ if (detected.cursor || detected.cursorCli) {
758
+ const surfaces = [];
759
+ if (detected.cursor) surfaces.push("IDE");
760
+ if (detected.cursorCli) surfaces.push("cursor-agent CLI");
761
+ items.push(`Cursor (${surfaces.join(" + ")}) \u2192 .cursor/mcp.json (MCP server)`);
762
+ }
763
+ if (detected.codex || detected.codexDesktop) {
764
+ const surfaces = [];
765
+ if (detected.codex) surfaces.push("Codex CLI");
766
+ if (detected.codexDesktop) surfaces.push("Desktop app");
767
+ items.push(`Codex (${surfaces.join(" + ")}) \u2192 ~/.codex/config.toml (MCP server)`);
768
+ items.push(" \u2192 AGENTS.md (usage rules)");
769
+ }
770
+ if (detected.openclaw) {
771
+ items.push("OpenClaw \u2192 SKILL.md (usage rules)");
772
+ }
773
+ if (detected.factoryDroid) {
774
+ items.push("Factory Droid \u2192 add MCP server to ~/.factory/mcp.json");
775
+ items.push(" \u2192 create .factory/droids/agentvalet.yaml");
776
+ items.push(" \u2192 create .factory/commands/av-status.md");
777
+ }
778
+ if (items.length === 0) return false;
779
+ console.log("");
780
+ console.log(chalk4.bold(" Detected agent frameworks"));
781
+ for (const item of items) {
782
+ console.log(chalk4.dim(` ${item}`));
783
+ }
784
+ console.log("");
785
+ const { confirm } = await prompts2({
786
+ type: "confirm",
787
+ name: "confirm",
788
+ message: "Configure these automatically?",
789
+ initial: true
790
+ });
791
+ return confirm ?? false;
792
+ }
793
+
688
794
  // src/commands/refresh.ts
795
+ var PERMISSIONS_START = "<!-- agentvalet:permissions:start -->";
796
+ var PERMISSIONS_END = "<!-- agentvalet:permissions:end -->";
689
797
  var CLAUDE_START = "<!-- agentvalet:start -->";
690
798
  var CLAUDE_END = "<!-- agentvalet:end -->";
691
799
  var HOOK_COMMAND = "npx @agentvalet/register refresh --if-stale --quiet 2>/dev/null || true";
@@ -705,7 +813,7 @@ async function runRefresh(options) {
705
813
  const stat = statSync(outputPath);
706
814
  const fileIsFresh = Date.now() - stat.mtimeMs < 36e5;
707
815
  const claudeMdPath = resolve2("CLAUDE.md");
708
- const claudeMd = existsSync3(claudeMdPath) ? readFileSync3(claudeMdPath, "utf-8") : "";
816
+ const claudeMd = existsSync4(claudeMdPath) ? readFileSync3(claudeMdPath, "utf-8") : "";
709
817
  const blockStart = claudeMd.indexOf(CLAUDE_START);
710
818
  const blockEnd = claudeMd.indexOf(CLAUDE_END);
711
819
  const blockContent = blockStart !== -1 && blockEnd !== -1 ? claudeMd.slice(blockStart, blockEnd) : "";
@@ -715,7 +823,7 @@ async function runRefresh(options) {
715
823
  }
716
824
  }
717
825
  const creds = tryResolveAgent(options.envFile);
718
- if (!creds || !existsSync3(resolve2(creds.keyPath))) {
826
+ if (!creds || !existsSync4(resolve2(creds.keyPath))) {
719
827
  console.error(" \u2717 No agent registered.\n Run the following command first to register an agent in this directory:\n\n npx @agentvalet/register\n");
720
828
  process.exit(1);
721
829
  }
@@ -744,8 +852,42 @@ async function runRefresh(options) {
744
852
  writeFileSync2(outputPath, content, "utf-8");
745
853
  embedInClaudeMd(content);
746
854
  ensureHook();
855
+ const detected = detectFrameworks();
856
+ const refreshed = [];
857
+ if (detected.codex || detected.codexDesktop) {
858
+ if (embedPermissionsBlock(resolve2("AGENTS.md"), content)) refreshed.push("AGENTS.md");
859
+ }
860
+ if (detected.openclaw) {
861
+ if (embedPermissionsBlock(resolve2("SKILL.md"), content)) refreshed.push("SKILL.md");
862
+ }
747
863
  if (!options.quiet) {
748
- console.log(`Permissions refreshed \u2192 ${outputPath} (${platforms.length} platform${platforms.length !== 1 ? "s" : ""})`);
864
+ const extras = refreshed.length > 0 ? ` (also: ${refreshed.join(", ")})` : "";
865
+ console.log(`Permissions refreshed \u2192 ${outputPath} (${platforms.length} platform${platforms.length !== 1 ? "s" : ""})${extras}`);
866
+ }
867
+ }
868
+ function embedPermissionsBlock(filePath, permissionsContent) {
869
+ if (!existsSync4(filePath)) return false;
870
+ try {
871
+ const existing = readFileSync3(filePath, "utf-8");
872
+ const block = `${PERMISSIONS_START}
873
+ ${permissionsContent.trimEnd()}
874
+ ${PERMISSIONS_END}`;
875
+ let next;
876
+ if (existing.includes(PERMISSIONS_START)) {
877
+ next = existing.replace(
878
+ new RegExp(`${PERMISSIONS_START}[\\s\\S]*?${PERMISSIONS_END}`, "m"),
879
+ block
880
+ );
881
+ } else {
882
+ next = `${existing.trimEnd()}
883
+
884
+ ${block}
885
+ `;
886
+ }
887
+ writeFileSync2(filePath, next, "utf-8");
888
+ return true;
889
+ } catch {
890
+ return false;
749
891
  }
750
892
  }
751
893
  function embedInClaudeMd(permissionsContent) {
@@ -753,7 +895,7 @@ function embedInClaudeMd(permissionsContent) {
753
895
  const block = `${CLAUDE_START}
754
896
  ${permissionsContent.trimEnd()}
755
897
  ${CLAUDE_END}`;
756
- if (!existsSync3(claudeMdPath)) {
898
+ if (!existsSync4(claudeMdPath)) {
757
899
  writeFileSync2(claudeMdPath, `# Agent Instructions
758
900
 
759
901
  ${block}
@@ -778,7 +920,7 @@ ${block}
778
920
  function ensureHook() {
779
921
  const settingsPath = resolve2(".claude/settings.local.json");
780
922
  let settings = {};
781
- if (existsSync3(settingsPath)) {
923
+ if (existsSync4(settingsPath)) {
782
924
  try {
783
925
  settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
784
926
  } catch {
@@ -809,7 +951,7 @@ function ensureHook() {
809
951
  function ensureMcpJson() {
810
952
  const mcpJsonPath = resolve2(".mcp.json");
811
953
  let mcpConfig = {};
812
- if (existsSync3(mcpJsonPath)) {
954
+ if (existsSync4(mcpJsonPath)) {
813
955
  try {
814
956
  mcpConfig = JSON.parse(readFileSync3(mcpJsonPath, "utf-8"));
815
957
  } catch {
@@ -844,7 +986,7 @@ import prompts3 from "prompts";
844
986
  import { createServer } from "http";
845
987
  import { createRequire } from "module";
846
988
  import open from "open";
847
- import chalk4 from "chalk";
989
+ import chalk5 from "chalk";
848
990
  var _require = createRequire(import.meta.url);
849
991
  var FUNCTIONS_URL = "https://jmodblrolfoavrxqgbps.supabase.co/functions/v1";
850
992
  var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imptb2RibHJvbGZvYXZyeHFnYnBzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQwODIzNzYsImV4cCI6MjA4OTY1ODM3Nn0.xQQiYHXbDsHyiTd5TsAfXtNWBUGkT4eO7Ew9UH8n6UE";
@@ -916,13 +1058,13 @@ async function browserLogin() {
916
1058
  }, CALLBACK_TIMEOUT_MS);
917
1059
  });
918
1060
  const authUrl = `${APP_URL}/cli-auth?state=${state}&port=${port}`;
919
- console.log(chalk4.dim(`
1061
+ console.log(chalk5.dim(`
920
1062
  Opening browser to authorize...`));
921
- console.log(chalk4.dim(` URL: ${authUrl}
1063
+ console.log(chalk5.dim(` URL: ${authUrl}
922
1064
  `));
923
1065
  await open(authUrl).catch(() => {
924
- console.log(chalk4.yellow(` Could not open browser automatically.`));
925
- console.log(chalk4.yellow(` Please open this URL in your browser:
1066
+ console.log(chalk5.yellow(` Could not open browser automatically.`));
1067
+ console.log(chalk5.yellow(` Please open this URL in your browser:
926
1068
  `));
927
1069
  console.log(` ${authUrl}
928
1070
  `);
@@ -976,12 +1118,12 @@ async function registerAgent2(params, apiKey) {
976
1118
 
977
1119
  // src/config/local.ts
978
1120
  import { mkdir as mkdir2, writeFile as writeFile2, appendFile, readFile } from "fs/promises";
979
- import path2 from "path";
1121
+ import path3 from "path";
980
1122
  async function writeLocalConfig(config) {
981
- const dir = path2.join(process.cwd(), ".agentvalet");
1123
+ const dir = path3.join(process.cwd(), ".agentvalet");
982
1124
  await mkdir2(dir, { recursive: true });
983
1125
  await writeFile2(
984
- path2.join(dir, "config.json"),
1126
+ path3.join(dir, "config.json"),
985
1127
  JSON.stringify({
986
1128
  agent_id: config.agentId,
987
1129
  agent_name: config.agentName,
@@ -991,14 +1133,14 @@ async function writeLocalConfig(config) {
991
1133
  { encoding: "utf8", mode: 384 }
992
1134
  );
993
1135
  await writeFile2(
994
- path2.join(dir, "agent.key"),
1136
+ path3.join(dir, "agent.key"),
995
1137
  config.privateKeyPem,
996
1138
  { encoding: "utf8", mode: 384 }
997
1139
  );
998
1140
  if (process.platform === "win32") {
999
1141
  console.warn(" Note: file permissions (0o600) are not enforced on Windows. Keep .agentvalet/ in a secure location.");
1000
1142
  }
1001
- const gitignorePath = path2.join(process.cwd(), ".gitignore");
1143
+ const gitignorePath = path3.join(process.cwd(), ".gitignore");
1002
1144
  const gitignoreContent = await readFile(gitignorePath, "utf8").catch(() => null);
1003
1145
  if (gitignoreContent !== null) {
1004
1146
  const lines = gitignoreContent.split("\n").map((l) => l.trim());
@@ -1009,22 +1151,22 @@ async function writeLocalConfig(config) {
1009
1151
  }
1010
1152
 
1011
1153
  // src/output/env.ts
1012
- import chalk5 from "chalk";
1154
+ import chalk6 from "chalk";
1013
1155
  import { appendFile as appendFile2, writeFile as writeFile3 } from "fs/promises";
1014
- import { existsSync as existsSync4 } from "fs";
1015
- import path3 from "path";
1156
+ import { existsSync as existsSync5 } from "fs";
1157
+ import path4 from "path";
1016
1158
  function formatEnvBlock(vars) {
1017
1159
  return Object.entries(vars).map(([key, value]) => {
1018
1160
  const truncated = value.length > 60 ? value.substring(0, 40) + "..." + value.substring(value.length - 10) : value;
1019
- return ` ${chalk5.dim(key + "=")}${chalk5.white(truncated)}`;
1161
+ return ` ${chalk6.dim(key + "=")}${chalk6.white(truncated)}`;
1020
1162
  }).join("\n");
1021
1163
  }
1022
1164
 
1023
1165
  // src/config/ide.ts
1024
1166
  import { mkdir as mkdir3, writeFile as writeFile4, readFile as readFile2, appendFile as appendFile3, copyFile } from "fs/promises";
1025
- import { existsSync as existsSync5 } from "fs";
1026
- import path4 from "path";
1027
- import os from "os";
1167
+ import { existsSync as existsSync6 } from "fs";
1168
+ import path5 from "path";
1169
+ import os2 from "os";
1028
1170
  function buildMcpEntry(params) {
1029
1171
  return {
1030
1172
  command: "npx",
@@ -1039,7 +1181,7 @@ function buildMcpEntry(params) {
1039
1181
  }
1040
1182
  async function mergeJsonFile(filePath, key, value) {
1041
1183
  let existing = {};
1042
- if (existsSync5(filePath)) {
1184
+ if (existsSync6(filePath)) {
1043
1185
  try {
1044
1186
  const text = await readFile2(filePath, "utf8");
1045
1187
  existing = JSON.parse(text);
@@ -1093,31 +1235,52 @@ Tell the user: "AgentValet MCP is not connected. Run: npx @agentvalet/register"
1093
1235
  Dashboard: ${proxyUrl.replace("api.", "app.")}
1094
1236
  `;
1095
1237
  }
1096
- async function writeGlobalFrameworkConfigs(params) {
1097
- const written = [];
1098
- const codexDir = path4.join(os.homedir(), ".codex");
1099
- const codexConfigPath = path4.join(codexDir, "config.toml");
1100
- if (existsSync5(codexDir)) {
1101
- try {
1102
- const existing = existsSync5(codexConfigPath) ? await readFile2(codexConfigPath, "utf8") : "";
1103
- if (!existing.includes("[mcp_servers.agentvalet]")) {
1104
- const addition = `
1238
+ function buildCodexTomlBlock(params) {
1239
+ return `
1105
1240
  [mcp_servers.agentvalet]
1106
1241
  command = "npx"
1107
1242
  args = ["-y", "@agentvalet/register", "mcp-server"]
1108
1243
  env = { AGENT_ID = "${params.agentId}", OWNER_ID = "${params.ownerId}", PROXY_URL = "${params.proxyUrl}", AGENT_PRIVATE_KEY_PATH = "${params.keyPath.replace(/\\/g, "/")}" }
1109
1244
  `;
1110
- await appendFile3(codexConfigPath, addition, "utf8");
1111
- written.push("~/.codex/config.toml");
1112
- }
1113
- } catch (err) {
1114
- console.warn(` Warning: could not write Codex CLI config: ${err.message}`);
1245
+ }
1246
+ async function appendCodexConfig(configPath) {
1247
+ const dir = path5.dirname(configPath);
1248
+ if (!existsSync6(dir)) await mkdir3(dir, { recursive: true });
1249
+ const existing = existsSync6(configPath) ? await readFile2(configPath, "utf8") : "";
1250
+ if (existing.includes("[mcp_servers.agentvalet]")) return false;
1251
+ return true;
1252
+ }
1253
+ async function writeGlobalFrameworkConfigs(params) {
1254
+ const written = [];
1255
+ const codexDir = path5.join(os2.homedir(), ".codex");
1256
+ const codexConfigPath = path5.join(codexDir, "config.toml");
1257
+ try {
1258
+ if (!existsSync6(codexDir)) await mkdir3(codexDir, { recursive: true });
1259
+ if (await appendCodexConfig(codexConfigPath)) {
1260
+ await appendFile3(codexConfigPath, buildCodexTomlBlock(params), "utf8");
1261
+ written.push("~/.codex/config.toml");
1262
+ }
1263
+ } catch (err) {
1264
+ console.warn(` Warning: could not write Codex config: ${err.message}`);
1265
+ }
1266
+ return written;
1267
+ }
1268
+ async function writeCodexProjectConfig(params) {
1269
+ const cwd = process.cwd();
1270
+ const configPath = path5.join(cwd, ".codex", "config.toml");
1271
+ const written = [];
1272
+ try {
1273
+ if (await appendCodexConfig(configPath)) {
1274
+ await appendFile3(configPath, buildCodexTomlBlock(params), "utf8");
1275
+ written.push(".codex/config.toml");
1115
1276
  }
1277
+ } catch (err) {
1278
+ console.warn(` Warning: could not write .codex/config.toml: ${err.message}`);
1116
1279
  }
1117
1280
  return written;
1118
1281
  }
1119
1282
  async function stripAgentvaletFromMcpJson(filePath) {
1120
- if (!existsSync5(filePath)) return false;
1283
+ if (!existsSync6(filePath)) return false;
1121
1284
  try {
1122
1285
  const text = await readFile2(filePath, "utf8");
1123
1286
  const config = JSON.parse(text);
@@ -1134,14 +1297,14 @@ async function stripAgentvaletFromMcpJson(filePath) {
1134
1297
  async function cleanProjectScopedEntries() {
1135
1298
  const cleaned = [];
1136
1299
  const cwd = process.cwd();
1137
- if (await stripAgentvaletFromMcpJson(path4.join(cwd, ".mcp.json"))) {
1300
+ if (await stripAgentvaletFromMcpJson(path5.join(cwd, ".mcp.json"))) {
1138
1301
  cleaned.push(".mcp.json (project entry removed)");
1139
1302
  }
1140
- if (await stripAgentvaletFromMcpJson(path4.join(cwd, ".cursor/mcp.json"))) {
1303
+ if (await stripAgentvaletFromMcpJson(path5.join(cwd, ".cursor/mcp.json"))) {
1141
1304
  cleaned.push(".cursor/mcp.json (project entry removed)");
1142
1305
  }
1143
- const factoryProjectMcp = path4.join(cwd, ".factory/mcp.json");
1144
- if (existsSync5(factoryProjectMcp)) {
1306
+ const factoryProjectMcp = path5.join(cwd, ".factory/mcp.json");
1307
+ if (existsSync6(factoryProjectMcp)) {
1145
1308
  try {
1146
1309
  const text = await readFile2(factoryProjectMcp, "utf8");
1147
1310
  const config = JSON.parse(text);
@@ -1158,12 +1321,12 @@ async function cleanProjectScopedEntries() {
1158
1321
  async function writeIdeConfigs(params) {
1159
1322
  const { detected, scope = "user", ...mcpParams } = params;
1160
1323
  const cwd = process.cwd();
1161
- const home = os.homedir();
1324
+ const home = os2.homedir();
1162
1325
  const written = [];
1163
1326
  const entry = buildMcpEntry(mcpParams);
1164
1327
  if (scope === "user") {
1165
1328
  if (detected.claudeCode) {
1166
- const claudeUserConfig = path4.join(home, ".claude.json");
1329
+ const claudeUserConfig = path5.join(home, ".claude.json");
1167
1330
  try {
1168
1331
  await mergeJsonFile(claudeUserConfig, "agentvalet", entry);
1169
1332
  written.push("~/.claude.json");
@@ -1171,9 +1334,9 @@ async function writeIdeConfigs(params) {
1171
1334
  console.warn(` Warning: could not write ~/.claude.json: ${err.message}`);
1172
1335
  }
1173
1336
  }
1174
- if (detected.cursor) {
1175
- const cursorUserDir = path4.join(home, ".cursor");
1176
- const cursorUserMcp = path4.join(cursorUserDir, "mcp.json");
1337
+ if (detected.cursor || detected.cursorCli) {
1338
+ const cursorUserDir = path5.join(home, ".cursor");
1339
+ const cursorUserMcp = path5.join(cursorUserDir, "mcp.json");
1177
1340
  try {
1178
1341
  await mkdir3(cursorUserDir, { recursive: true });
1179
1342
  await mergeJsonFile(cursorUserMcp, "agentvalet", entry);
@@ -1190,15 +1353,15 @@ async function writeIdeConfigs(params) {
1190
1353
  written.push(...cleaned);
1191
1354
  } else {
1192
1355
  if (detected.claudeCode) {
1193
- const claudeMcpPath = path4.join(cwd, ".mcp.json");
1356
+ const claudeMcpPath = path5.join(cwd, ".mcp.json");
1194
1357
  try {
1195
1358
  await mergeJsonFile(claudeMcpPath, "agentvalet", entry);
1196
1359
  written.push(".mcp.json");
1197
1360
  } catch (err) {
1198
1361
  console.warn(` Warning: could not write .mcp.json: ${err.message}`);
1199
1362
  }
1200
- const claudeMdPath = path4.join(cwd, "CLAUDE.md");
1201
- if (!existsSync5(claudeMdPath)) {
1363
+ const claudeMdPath = path5.join(cwd, "CLAUDE.md");
1364
+ if (!existsSync6(claudeMdPath)) {
1202
1365
  try {
1203
1366
  await writeFile4(claudeMdPath, buildClaudeMd(params.agentId, params.ownerId, params.proxyUrl), "utf8");
1204
1367
  written.push("CLAUDE.md");
@@ -1209,9 +1372,9 @@ async function writeIdeConfigs(params) {
1209
1372
  written.push("CLAUDE.md (already exists \u2014 skipped)");
1210
1373
  }
1211
1374
  }
1212
- if (detected.cursor) {
1213
- const cursorDir = path4.join(cwd, ".cursor");
1214
- const cursorMcpPath = path4.join(cursorDir, "mcp.json");
1375
+ if (detected.cursor || detected.cursorCli) {
1376
+ const cursorDir = path5.join(cwd, ".cursor");
1377
+ const cursorMcpPath = path5.join(cursorDir, "mcp.json");
1215
1378
  try {
1216
1379
  await mkdir3(cursorDir, { recursive: true });
1217
1380
  await mergeJsonFile(cursorMcpPath, "agentvalet", entry);
@@ -1224,12 +1387,16 @@ async function writeIdeConfigs(params) {
1224
1387
  const fdResults = await writeFactoryDroidConfig({ ...mcpParams, scope: "project" });
1225
1388
  written.push(...fdResults);
1226
1389
  }
1390
+ if (detected.codex || detected.codexDesktop) {
1391
+ const codexResults = await writeCodexProjectConfig(mcpParams);
1392
+ written.push(...codexResults);
1393
+ }
1227
1394
  }
1228
1395
  return written;
1229
1396
  }
1230
1397
  async function mergeFactoryDroidMcpJson(filePath, key, value) {
1231
1398
  let existing = {};
1232
- if (existsSync5(filePath)) {
1399
+ if (existsSync6(filePath)) {
1233
1400
  try {
1234
1401
  const text = await readFile2(filePath, "utf8");
1235
1402
  existing = JSON.parse(text);
@@ -1242,8 +1409,8 @@ async function mergeFactoryDroidMcpJson(filePath, key, value) {
1242
1409
  await writeFile4(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
1243
1410
  }
1244
1411
  async function updateGitignoreForFactoryDroid() {
1245
- const gitignorePath = path4.join(process.cwd(), ".gitignore");
1246
- if (!existsSync5(gitignorePath)) return;
1412
+ const gitignorePath = path5.join(process.cwd(), ".gitignore");
1413
+ if (!existsSync6(gitignorePath)) return;
1247
1414
  const existing = await readFile2(gitignorePath, "utf8");
1248
1415
  if (existing.includes(".factory/mcp.json")) return;
1249
1416
  const additions = [
@@ -1258,16 +1425,16 @@ async function updateGitignoreForFactoryDroid() {
1258
1425
  await appendFile3(gitignorePath, additions + "\n", "utf8");
1259
1426
  }
1260
1427
  async function writeFactoryDroidConfig(params) {
1261
- const home = os.homedir();
1428
+ const home = os2.homedir();
1262
1429
  const written = [];
1263
1430
  const entry = buildMcpEntry(params);
1264
1431
  const scope = params.scope ?? "user";
1265
- const globalDir = path4.join(home, ".factory");
1266
- const projectDir = path4.join(process.cwd(), ".factory");
1432
+ const globalDir = path5.join(home, ".factory");
1433
+ const projectDir = path5.join(process.cwd(), ".factory");
1267
1434
  if (scope === "user") {
1268
1435
  try {
1269
1436
  await mkdir3(globalDir, { recursive: true });
1270
- await mergeFactoryDroidMcpJson(path4.join(globalDir, "mcp.json"), "agentvalet", entry);
1437
+ await mergeFactoryDroidMcpJson(path5.join(globalDir, "mcp.json"), "agentvalet", entry);
1271
1438
  written.push("~/.factory/mcp.json");
1272
1439
  } catch (err) {
1273
1440
  console.warn(` Warning: could not write ~/.factory/mcp.json: ${err.message}`);
@@ -1275,7 +1442,7 @@ async function writeFactoryDroidConfig(params) {
1275
1442
  } else {
1276
1443
  try {
1277
1444
  await mkdir3(projectDir, { recursive: true });
1278
- await mergeFactoryDroidMcpJson(path4.join(projectDir, "mcp.json"), "agentvalet", entry);
1445
+ await mergeFactoryDroidMcpJson(path5.join(projectDir, "mcp.json"), "agentvalet", entry);
1279
1446
  written.push(".factory/mcp.json");
1280
1447
  } catch (err) {
1281
1448
  console.warn(` Warning: could not write .factory/mcp.json: ${err.message}`);
@@ -1288,81 +1455,6 @@ async function writeFactoryDroidConfig(params) {
1288
1455
  return written;
1289
1456
  }
1290
1457
 
1291
- // src/config/frameworks.ts
1292
- import { existsSync as existsSync6 } from "fs";
1293
- import { execSync as execSync2 } from "child_process";
1294
- import os2 from "os";
1295
- import path5 from "path";
1296
- import prompts2 from "prompts";
1297
- import chalk6 from "chalk";
1298
- function tryBinary(cmd) {
1299
- try {
1300
- execSync2(`${cmd} --version`, { stdio: "ignore", timeout: 3e3 });
1301
- return true;
1302
- } catch {
1303
- return false;
1304
- }
1305
- }
1306
- function cursorInstalled() {
1307
- const home = os2.homedir();
1308
- const platform = process.platform;
1309
- if (platform === "win32") {
1310
- const appData = process.env.APPDATA ?? path5.join(home, "AppData", "Roaming");
1311
- const localAppData = process.env.LOCALAPPDATA ?? path5.join(home, "AppData", "Local");
1312
- return existsSync6(path5.join(appData, "Cursor")) || existsSync6(path5.join(localAppData, "Programs", "cursor"));
1313
- }
1314
- if (platform === "darwin") {
1315
- return existsSync6(path5.join(home, "Library", "Application Support", "Cursor")) || existsSync6("/Applications/Cursor.app");
1316
- }
1317
- return existsSync6(path5.join(home, ".config", "Cursor")) || existsSync6("/usr/share/applications/cursor.desktop");
1318
- }
1319
- function detectFrameworks() {
1320
- const home = os2.homedir();
1321
- return {
1322
- codex: existsSync6(path5.join(home, ".codex")) || tryBinary("codex"),
1323
- claudeCode: existsSync6(path5.join(home, ".claude")) || tryBinary("claude"),
1324
- cursor: existsSync6(path5.join(process.cwd(), ".cursor")) || cursorInstalled(),
1325
- openclaw: existsSync6(path5.join(home, ".openclaw")),
1326
- factoryDroid: existsSync6(path5.join(home, ".factory")) || existsSync6(path5.join(process.cwd(), ".factory"))
1327
- };
1328
- }
1329
- async function frameworkSetupConsent(detected) {
1330
- const items = [];
1331
- if (detected.claudeCode) {
1332
- items.push("Claude Code \u2192 .mcp.json (MCP server)");
1333
- items.push(" \u2192 CLAUDE.md (usage rules)");
1334
- }
1335
- if (detected.cursor) {
1336
- items.push("Cursor \u2192 .cursor/mcp.json (MCP server)");
1337
- }
1338
- if (detected.codex) {
1339
- items.push("Codex CLI \u2192 ~/.codex/config.toml (MCP server)");
1340
- items.push(" \u2192 AGENTS.md (usage rules)");
1341
- }
1342
- if (detected.openclaw) {
1343
- items.push("OpenClaw \u2192 SKILL.md (usage rules)");
1344
- }
1345
- if (detected.factoryDroid) {
1346
- items.push("Factory Droid \u2192 add MCP server to ~/.factory/mcp.json");
1347
- items.push(" \u2192 create .factory/droids/agentvalet.yaml");
1348
- items.push(" \u2192 create .factory/commands/av-status.md");
1349
- }
1350
- if (items.length === 0) return false;
1351
- console.log("");
1352
- console.log(chalk6.bold(" Detected agent frameworks"));
1353
- for (const item of items) {
1354
- console.log(chalk6.dim(` ${item}`));
1355
- }
1356
- console.log("");
1357
- const { confirm } = await prompts2({
1358
- type: "confirm",
1359
- name: "confirm",
1360
- message: "Configure these automatically?",
1361
- initial: true
1362
- });
1363
- return confirm ?? false;
1364
- }
1365
-
1366
1458
  // src/commands/onboard.ts
1367
1459
  import os3 from "os";
1368
1460
  import path6 from "path";
@@ -1425,7 +1517,7 @@ async function runOnboard(opts = {}) {
1425
1517
  }
1426
1518
  async function registerAgentFlow(apiKey, ownerId, scope = "user", presetName) {
1427
1519
  const hostname = os3.hostname().split(".")[0];
1428
- const dirName = process.cwd().split("/").pop() ?? process.cwd().split("\\").pop() ?? "agent";
1520
+ const dirName = path6.basename(process.cwd()) || "agent";
1429
1521
  let agentName;
1430
1522
  if (presetName) {
1431
1523
  agentName = presetName;
@@ -1443,13 +1535,16 @@ async function registerAgentFlow(apiKey, ownerId, scope = "user", presetName) {
1443
1535
  }
1444
1536
  agentName = answer.agentName;
1445
1537
  }
1538
+ const detectedAtRegister = detectFrameworks();
1539
+ const agentType = resolveAgentType(detectedAtRegister);
1446
1540
  const spinner = ora(" Registering agent...").start();
1447
1541
  let agent;
1448
1542
  try {
1449
1543
  agent = await registerAgent2({
1450
1544
  owner_id: ownerId,
1451
1545
  name: agentName.trim(),
1452
- source: "cli"
1546
+ source: "cli",
1547
+ agent_type: agentType
1453
1548
  }, apiKey);
1454
1549
  spinner.succeed(` Agent registered: ${chalk7.bold(agent.agent_id)}`);
1455
1550
  } catch (err) {
@@ -1519,7 +1614,7 @@ async function registerAgentFlow(apiKey, ownerId, scope = "user", presetName) {
1519
1614
  console.log(chalk7.green(` \u2713 ${r}`));
1520
1615
  }
1521
1616
  if (scope === "project") {
1522
- if (detected.codex) writeAgentsMd();
1617
+ if (detected.codex || detected.codexDesktop) writeAgentsMd();
1523
1618
  if (detected.openclaw) writeSkillMd();
1524
1619
  if (detected.factoryDroid) {
1525
1620
  writeFactoryDroidDroid();
@@ -1564,9 +1659,11 @@ async function registerAgentFlow(apiKey, ownerId, scope = "user", presetName) {
1564
1659
  if (scope === "user") {
1565
1660
  const restartList = [];
1566
1661
  if (detected.claudeCode) restartList.push("Claude Code");
1567
- if (detected.cursor) restartList.push("Cursor");
1662
+ if (detected.cursor) restartList.push("Cursor IDE");
1663
+ if (detected.cursorCli) restartList.push("cursor-agent CLI (next session picks it up automatically)");
1568
1664
  if (detected.factoryDroid) restartList.push("Factory Droid");
1569
1665
  if (detected.codex) restartList.push("Codex CLI");
1666
+ if (detected.codexDesktop) restartList.push('Codex Desktop app (approve "agentvalet" in Settings \u2192 MCP)');
1570
1667
  if (restartList.length > 0) {
1571
1668
  console.log(chalk7.dim(" Restart these to pick up the new MCP server (no trust prompt \u2014 user-scoped):"));
1572
1669
  for (const r of restartList) console.log(chalk7.dim(` ${r}`));
@@ -1580,7 +1677,10 @@ async function registerAgentFlow(apiKey, ownerId, scope = "user", presetName) {
1580
1677
  console.log("");
1581
1678
  }
1582
1679
  if (detected.cursor) {
1583
- console.log(chalk7.dim(" Cursor: restart Cursor (or reopen the workspace) to pick up the MCP."));
1680
+ console.log(chalk7.dim(" Cursor IDE: restart Cursor (or reopen the workspace) to pick up the MCP."));
1681
+ }
1682
+ if (detected.cursorCli) {
1683
+ console.log(chalk7.dim(" cursor-agent CLI: next `cursor-agent` invocation will pick up the MCP \u2014 verify with `cursor-agent mcp list`."));
1584
1684
  }
1585
1685
  if (detected.factoryDroid) {
1586
1686
  console.log(chalk7.dim(" Factory Droid: restart Factory to pick up the MCP."));
@@ -1588,6 +1688,9 @@ async function registerAgentFlow(apiKey, ownerId, scope = "user", presetName) {
1588
1688
  if (detected.codex) {
1589
1689
  console.log(chalk7.dim(" Codex CLI: start a new codex session to pick up the MCP."));
1590
1690
  }
1691
+ if (detected.codexDesktop) {
1692
+ console.log(chalk7.dim(' Codex Desktop: trust this project, then approve "agentvalet" in Settings \u2192 MCP.'));
1693
+ }
1591
1694
  console.log("");
1592
1695
  }
1593
1696
  }
@@ -1646,9 +1749,18 @@ function listCommand() {
1646
1749
  return new Command("list").description("List all registered agents").action(async () => {
1647
1750
  const config = getStoredConfig();
1648
1751
  if (!config.cliToken) {
1649
- process.stderr.write(
1650
- chalk8.red("\n \u2717 No agent registered.\n") + chalk8.dim(" Run the following command first:\n\n npx @agentvalet/register\n\n")
1651
- );
1752
+ const creds = tryResolveAgent();
1753
+ if (creds) {
1754
+ process.stderr.write(
1755
+ chalk8.red("\n \u2717 CLI session not authenticated.\n") + chalk8.dim(
1756
+ " An agent is registered (" + creds.agentId + "), but `list` needs an\n authenticated CLI session. Run `npx @agentvalet/register` to sign in,\n or view your agents at:\n\n " + creds.apiUrl.replace("api.", "app.") + "/agents\n\n"
1757
+ )
1758
+ );
1759
+ } else {
1760
+ process.stderr.write(
1761
+ chalk8.red("\n \u2717 No agent registered.\n") + chalk8.dim(" Run the following command first:\n\n npx @agentvalet/register\n\n")
1762
+ );
1763
+ }
1652
1764
  process.exit(1);
1653
1765
  }
1654
1766
  try {
@@ -1673,141 +1785,32 @@ function listCommand() {
1673
1785
  }
1674
1786
 
1675
1787
  // src/commands/revoke.ts
1676
- import { Command as Command2 } from "commander";
1677
- import chalk9 from "chalk";
1678
- import * as readline from "readline";
1679
- function revokeCommand() {
1680
- return new Command2("revoke").description("Revoke an agent (cascades to all child agents)").requiredOption("--agent-id <agentId>", "Agent ID to revoke (agt_...)").action(async (opts) => {
1681
- const config = getStoredConfig();
1682
- if (!config.cliToken) {
1683
- process.stderr.write(
1684
- chalk9.red("\n \u2717 No agent registered.\n") + chalk9.dim(" Run the following command first:\n\n npx @agentvalet/register\n\n")
1685
- );
1686
- process.exit(1);
1687
- }
1688
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1689
- const confirmed = await new Promise((resolve4) => {
1690
- rl.question(
1691
- chalk9.yellow(`Revoke ${opts.agentId}? This cascades to all child agents and cannot be undone. (y/N) `),
1692
- (ans) => {
1693
- rl.close();
1694
- resolve4(ans.trim().toLowerCase() === "y");
1695
- }
1696
- );
1697
- });
1698
- if (!confirmed) {
1699
- console.log("Cancelled.");
1700
- return;
1701
- }
1702
- try {
1703
- const result = await revokeAgent(opts.agentId);
1704
- console.log(chalk9.green(`Revoked ${result.revokedAgentIds.length} agent(s)`));
1705
- if (result.revokedAgentIds.length > 1) {
1706
- console.log("Revoked:", result.revokedAgentIds.join(", "));
1707
- }
1708
- } catch (err) {
1709
- console.error(chalk9.red(String(err)));
1710
- process.exit(1);
1711
- }
1712
- });
1713
- }
1714
-
1715
- // src/commands/inject.ts
1716
1788
  import { Command as Command3 } from "commander";
1717
1789
  import chalk10 from "chalk";
1718
- import { readFile as readFile5 } from "fs/promises";
1719
-
1720
- // src/lib/claude-config.ts
1721
- import { homedir } from "os";
1722
- import { join } from "path";
1723
- import { readFile as readFile3, writeFile as writeFile6, mkdir as mkdir4, access } from "fs/promises";
1724
- var CLAUDE_CONFIG_PATH = join(homedir(), ".claude", "config");
1725
- async function writeKeyToClaudeConfig(agentId, privateKeyPem) {
1726
- let config = {};
1727
- try {
1728
- await access(CLAUDE_CONFIG_PATH);
1729
- const raw = await readFile3(CLAUDE_CONFIG_PATH, "utf-8");
1730
- config = JSON.parse(raw);
1731
- } catch {
1732
- }
1733
- config.agent_keys = config.agent_keys ?? {};
1734
- config.agent_keys[agentId] = Buffer.from(privateKeyPem).toString("base64");
1735
- await mkdir4(join(homedir(), ".claude"), { recursive: true });
1736
- await writeFile6(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
1737
- }
1738
-
1739
- // src/lib/env-writer.ts
1740
- import { readFile as readFile4, writeFile as writeFile7, access as access2 } from "fs/promises";
1741
- var ENV_VAR_NAME = (agentId) => `AGENTVALET_PRIVATE_KEY_${agentId.replace(/-/g, "_").toUpperCase()}`;
1742
- async function writeKeyToEnvLocal(agentId, privateKeyPem) {
1743
- const varName = ENV_VAR_NAME(agentId);
1744
- const b64Key = Buffer.from(privateKeyPem).toString("base64");
1745
- const line = `
1746
- # AgentValet private key for ${agentId} (base64-encoded PEM)
1747
- ${varName}=${b64Key}
1748
- `;
1749
- let existing = "";
1750
- try {
1751
- await access2(".env.local");
1752
- existing = await readFile4(".env.local", "utf-8");
1753
- } catch {
1754
- }
1755
- if (existing.includes(varName)) {
1756
- const lines = existing.split("\n");
1757
- const filtered = lines.filter((l) => !l.startsWith(`${varName}=`));
1758
- await writeFile7(".env.local", filtered.join("\n") + line, "utf-8");
1759
- } else {
1760
- await writeFile7(".env.local", existing + line, "utf-8");
1761
- }
1762
- }
1763
-
1764
- // src/commands/inject.ts
1765
- function injectCommand() {
1766
- return new Command3("inject").description("Inject agent private key into Claude Code config or .env.local").requiredOption("--agent-id <agentId>", "Agent ID (agt_...)").requiredOption("--key-file <path>", "Path to the private key PEM file").option("--target <target>", "Injection target: claude-code or env-local", "claude-code").action(async (opts) => {
1767
- let privateKeyPem;
1768
- try {
1769
- privateKeyPem = await readFile5(opts.keyFile, "utf-8");
1770
- } catch {
1771
- console.error(chalk10.red(`Could not read key file: ${opts.keyFile}`));
1772
- process.exit(1);
1773
- }
1774
- if (!privateKeyPem.includes("BEGIN PRIVATE KEY")) {
1775
- console.error(chalk10.red("File does not appear to be a PEM private key"));
1776
- process.exit(1);
1777
- }
1778
- try {
1779
- if (opts.target === "claude-code") {
1780
- await writeKeyToClaudeConfig(opts.agentId, privateKeyPem);
1781
- console.log(chalk10.green("Key injected into ~/.claude/config"));
1782
- } else if (opts.target === "env-local") {
1783
- await writeKeyToEnvLocal(opts.agentId, privateKeyPem);
1784
- console.log(chalk10.green("Key appended to .env.local"));
1785
- } else {
1786
- console.error(chalk10.red(`Unknown target: ${opts.target}. Use 'claude-code' or 'env-local'`));
1787
- process.exit(1);
1788
- }
1789
- } catch (err) {
1790
- console.error(chalk10.red(String(err)));
1791
- process.exit(1);
1792
- }
1793
- if (opts.target === "env-local") {
1794
- console.log(chalk10.dim("Add .env.local to .gitignore \u2014 do not commit private keys to git."));
1795
- } else {
1796
- console.log(chalk10.dim("The key is stored in ~/.claude/config \u2014 it is NOT committed to git."));
1797
- }
1798
- console.log(chalk10.green("Done. Your agent is ready to use."));
1799
- });
1800
- }
1801
1790
 
1802
1791
  // src/commands/uninstall.ts
1803
- import { Command as Command4 } from "commander";
1804
- import chalk11 from "chalk";
1792
+ import { Command as Command2 } from "commander";
1793
+ import chalk9 from "chalk";
1805
1794
  import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync3, rmSync } from "fs";
1806
- import { resolve as resolve3, join as join2 } from "path";
1807
- import { homedir as homedir2 } from "os";
1795
+ import { resolve as resolve3, join } from "path";
1796
+ import { homedir } from "os";
1808
1797
  var CLAUDE_START2 = "<!-- agentvalet:start -->";
1809
1798
  var CLAUDE_END2 = "<!-- agentvalet:end -->";
1799
+ var PERMISSIONS_START2 = "<!-- agentvalet:permissions:start -->";
1800
+ var PERMISSIONS_END2 = "<!-- agentvalet:permissions:end -->";
1810
1801
  var HOOK_MARKER2 = "agentvalet/register refresh";
1802
+ function stripPermissionsBlock(filePath) {
1803
+ if (!existsSync7(filePath)) return false;
1804
+ try {
1805
+ const content = readFileSync4(filePath, "utf-8");
1806
+ if (!content.includes(PERMISSIONS_START2)) return false;
1807
+ const stripped = content.replace(new RegExp(`${PERMISSIONS_START2}[\\s\\S]*?${PERMISSIONS_END2}`, "m"), "").replace(/(\r?\n){3,}/g, "\n\n").trimEnd();
1808
+ writeFileSync3(filePath, stripped + "\n", "utf-8");
1809
+ return true;
1810
+ } catch {
1811
+ return false;
1812
+ }
1813
+ }
1811
1814
  function stripClaudeMd() {
1812
1815
  const path8 = resolve3("CLAUDE.md");
1813
1816
  if (!existsSync7(path8)) return;
@@ -1862,17 +1865,16 @@ function stripMcpJson(filePath, options = {}) {
1862
1865
  return false;
1863
1866
  }
1864
1867
  }
1865
- function stripCodexToml() {
1866
- const path8 = join2(homedir2(), ".codex", "config.toml");
1867
- if (!existsSync7(path8)) return false;
1868
+ function stripCodexTomlAt(filePath) {
1869
+ if (!existsSync7(filePath)) return false;
1868
1870
  try {
1869
- const content = readFileSync4(path8, "utf-8");
1871
+ const content = readFileSync4(filePath, "utf-8");
1870
1872
  if (!content.includes("[mcp_servers.agentvalet]")) return false;
1871
1873
  const stripped = content.replace(
1872
1874
  /\r?\n?\[mcp_servers\.agentvalet\][\s\S]*?(?=\r?\n\[|\r?\n*$)/,
1873
1875
  ""
1874
1876
  );
1875
- writeFileSync3(path8, stripped.replace(/(\r?\n){3,}/g, "\n\n").trimEnd() + "\n", "utf-8");
1877
+ writeFileSync3(filePath, stripped.replace(/(\r?\n){3,}/g, "\n\n").trimEnd() + "\n", "utf-8");
1876
1878
  return true;
1877
1879
  } catch {
1878
1880
  return false;
@@ -1921,21 +1923,24 @@ function cleanLocally() {
1921
1923
  if (stripMcpJson(resolve3(".mcp.json"))) stripped.push(".mcp.json");
1922
1924
  if (stripMcpJson(resolve3(".cursor/mcp.json"))) stripped.push(".cursor/mcp.json");
1923
1925
  if (stripMcpJson(resolve3(".factory/mcp.json"), { wrapped: false })) stripped.push(".factory/mcp.json");
1924
- const home = homedir2();
1925
- if (stripMcpJson(join2(home, ".claude.json"))) stripped.push("~/.claude.json");
1926
- if (stripMcpJson(join2(home, ".cursor", "mcp.json"))) stripped.push("~/.cursor/mcp.json");
1927
- if (stripMcpJson(join2(home, ".factory", "mcp.json"), { wrapped: false })) stripped.push("~/.factory/mcp.json");
1928
- if (stripCodexToml()) stripped.push("~/.codex/config.toml");
1926
+ const home = homedir();
1927
+ if (stripMcpJson(join(home, ".claude.json"))) stripped.push("~/.claude.json");
1928
+ if (stripMcpJson(join(home, ".cursor", "mcp.json"))) stripped.push("~/.cursor/mcp.json");
1929
+ if (stripMcpJson(join(home, ".factory", "mcp.json"), { wrapped: false })) stripped.push("~/.factory/mcp.json");
1930
+ if (stripCodexTomlAt(join(home, ".codex", "config.toml"))) stripped.push("~/.codex/config.toml");
1931
+ if (stripCodexTomlAt(resolve3(".codex/config.toml"))) stripped.push(".codex/config.toml");
1929
1932
  stripClaudeMd();
1930
1933
  stripSettingsJson();
1931
1934
  stripped.push("CLAUDE.md", ".claude/settings.local.json");
1935
+ if (stripPermissionsBlock(resolve3("AGENTS.md"))) stripped.push("AGENTS.md");
1936
+ if (stripPermissionsBlock(resolve3("SKILL.md"))) stripped.push("SKILL.md");
1932
1937
  if (removed.length > 0) {
1933
- console.log(chalk11.green(" \u2713 Removed files:"));
1934
- for (const f of removed) console.log(chalk11.dim(` ${f}`));
1938
+ console.log(chalk9.green(" \u2713 Removed files:"));
1939
+ for (const f of removed) console.log(chalk9.dim(` ${f}`));
1935
1940
  }
1936
1941
  if (stripped.length > 0) {
1937
- console.log(chalk11.green(" \u2713 Stripped agentvalet entries from:"));
1938
- for (const f of stripped) console.log(chalk11.dim(` ${f}`));
1942
+ console.log(chalk9.green(" \u2713 Stripped agentvalet entries from:"));
1943
+ for (const f of stripped) console.log(chalk9.dim(` ${f}`));
1939
1944
  }
1940
1945
  }
1941
1946
  var SSE_READ_TIMEOUT_MS = 25e3;
@@ -2016,13 +2021,13 @@ async function pollForApproval(apiUrl, revocationToken, timeoutMs) {
2016
2021
  });
2017
2022
  }
2018
2023
  function uninstallCommand() {
2019
- return new Command4("uninstall").description("Request agent revocation (requires owner approval) and remove all local files").option("--local-only", "Skip the server-side revoke request \u2014 just clean local files").option("--timeout <seconds>", "Seconds to wait for owner approval", "300").action(async (opts) => {
2024
+ return new Command2("uninstall").description("Request agent revocation (requires owner approval) and remove all local files").option("--local-only", "Skip the server-side revoke request \u2014 just clean local files").option("--timeout <seconds>", "Seconds to wait for owner approval", "300").action(async (opts) => {
2020
2025
  const timeoutMs = parseInt(opts.timeout, 10) * 1e3;
2021
2026
  const creds = tryResolveAgent();
2022
2027
  if (opts.localOnly || !creds) {
2023
- if (!creds) console.log(chalk11.dim(" No agent credentials found \u2014 cleaning local files only."));
2028
+ if (!creds) console.log(chalk9.dim(" No agent credentials found \u2014 cleaning local files only."));
2024
2029
  cleanLocally();
2025
- console.log(chalk11.dim("\n To register a new agent: npx @agentvalet/register"));
2030
+ console.log(chalk9.dim("\n To register a new agent: npx @agentvalet/register"));
2026
2031
  return;
2027
2032
  }
2028
2033
  let privateKeyPem;
@@ -2031,11 +2036,11 @@ function uninstallCommand() {
2031
2036
  } catch {
2032
2037
  }
2033
2038
  if (!privateKeyPem) {
2034
- console.log(chalk11.yellow(" \u26A0 Private key not found \u2014 cannot submit revocation request."));
2035
- console.log(chalk11.dim(" Use --local-only to clean local files without revoking."));
2039
+ console.log(chalk9.yellow(" \u26A0 Private key not found \u2014 cannot submit revocation request."));
2040
+ console.log(chalk9.dim(" Use --local-only to clean local files without revoking."));
2036
2041
  process.exit(1);
2037
2042
  }
2038
- console.log(chalk11.dim(`
2043
+ console.log(chalk9.dim(`
2039
2044
  Submitting revocation request for ${creds.agentId}...`));
2040
2045
  const jwt = signJwt(creds.agentId, creds.ownerId, privateKeyPem);
2041
2046
  let revocationToken;
@@ -2047,9 +2052,9 @@ function uninstallCommand() {
2047
2052
  });
2048
2053
  const body = await res.json();
2049
2054
  if (res.status === 404 || res.status === 409) {
2050
- console.log(chalk11.yellow(` \u26A0 Agent not found on server (already revoked?) \u2014 cleaning local files.`));
2055
+ console.log(chalk9.yellow(` \u26A0 Agent not found on server (already revoked?) \u2014 cleaning local files.`));
2051
2056
  cleanLocally();
2052
- console.log(chalk11.dim("\n To register a new agent: npx @agentvalet/register"));
2057
+ console.log(chalk9.dim("\n To register a new agent: npx @agentvalet/register"));
2053
2058
  return;
2054
2059
  }
2055
2060
  if (!res.ok || !body.revocation_token) {
@@ -2057,34 +2062,229 @@ function uninstallCommand() {
2057
2062
  }
2058
2063
  revocationToken = body.revocation_token;
2059
2064
  } catch (err) {
2060
- console.error(chalk11.red(` \u2717 Failed to submit revocation request: ${err instanceof Error ? err.message : err}`));
2065
+ console.error(chalk9.red(` \u2717 Failed to submit revocation request: ${err instanceof Error ? err.message : err}`));
2061
2066
  process.exit(1);
2062
2067
  }
2063
- console.log(chalk11.green(" \u2713 Revocation request submitted"));
2068
+ console.log(chalk9.green(" \u2713 Revocation request submitted"));
2064
2069
  console.log("");
2065
- console.log(chalk11.yellow(" ACTION REQUIRED:"));
2066
- console.log(chalk11.yellow(` Open ${creds.apiUrl.replace("api.", "app.")}/agents and approve the uninstall request.`));
2067
- console.log(chalk11.dim(` Waiting up to ${opts.timeout}s for approval...
2070
+ console.log(chalk9.yellow(" ACTION REQUIRED:"));
2071
+ console.log(chalk9.yellow(` Open ${creds.apiUrl.replace("api.", "app.")}/agents and approve the uninstall request.`));
2072
+ console.log(chalk9.dim(` Waiting up to ${opts.timeout}s for approval...
2068
2073
  `));
2069
2074
  const result = await pollForApproval(creds.apiUrl, revocationToken, timeoutMs);
2070
2075
  if (result === "rejected") {
2071
- console.log(chalk11.red("\n \u2717 Owner rejected the uninstall request. Agent remains active."));
2076
+ console.log(chalk9.red("\n \u2717 Owner rejected the uninstall request. Agent remains active."));
2072
2077
  process.exit(1);
2073
2078
  }
2074
2079
  if (result === "timeout") {
2075
- console.log(chalk11.yellow("\n \u26A0 Timed out waiting for owner approval."));
2076
- console.log(chalk11.dim(" The request remains pending in your dashboard. Run again to resume."));
2080
+ console.log(chalk9.yellow("\n \u26A0 Timed out waiting for owner approval."));
2081
+ console.log(chalk9.dim(" The request remains pending in your dashboard. Run again to resume."));
2077
2082
  process.exit(1);
2078
2083
  }
2079
- console.log(chalk11.green("\n \u2713 Approved \u2014 cleaning local files...\n"));
2084
+ console.log(chalk9.green("\n \u2713 Approved \u2014 cleaning local files...\n"));
2080
2085
  cleanLocally();
2081
2086
  console.log("");
2082
- console.log(chalk11.dim(" Agent revoked. To register a new agent: npx @agentvalet/register"));
2087
+ console.log(chalk9.dim(" Agent revoked. To register a new agent: npx @agentvalet/register"));
2088
+ });
2089
+ }
2090
+
2091
+ // src/commands/revoke.ts
2092
+ import * as readline from "readline";
2093
+ function revokeCommand() {
2094
+ return new Command3("revoke").description("Revoke an agent (cascades to all child agents) and clean local config").requiredOption("--agent-id <agentId>", "Agent ID to revoke (agt_...)").option("--keep-local", "Skip cleaning local MCP configs and key files after revoke").action(async (opts) => {
2095
+ const config = getStoredConfig();
2096
+ if (!config.cliToken) {
2097
+ const creds = tryResolveAgent();
2098
+ if (creds) {
2099
+ process.stderr.write(
2100
+ chalk10.red("\n \u2717 CLI session not authenticated.\n") + chalk10.dim(
2101
+ " An agent is registered (" + creds.agentId + "), but `revoke` needs an\n authenticated CLI session. Run `npx @agentvalet/register` to sign in,\n or revoke from the dashboard:\n\n " + creds.apiUrl.replace("api.", "app.") + "/agents\n\n"
2102
+ )
2103
+ );
2104
+ } else {
2105
+ process.stderr.write(
2106
+ chalk10.red("\n \u2717 No agent registered.\n") + chalk10.dim(" Run the following command first:\n\n npx @agentvalet/register\n\n")
2107
+ );
2108
+ }
2109
+ process.exit(1);
2110
+ }
2111
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2112
+ const confirmed = await new Promise((resolve4) => {
2113
+ rl.question(
2114
+ chalk10.yellow(`Revoke ${opts.agentId}? This cascades to all child agents and cannot be undone. (y/N) `),
2115
+ (ans) => {
2116
+ rl.close();
2117
+ resolve4(ans.trim().toLowerCase() === "y");
2118
+ }
2119
+ );
2120
+ });
2121
+ if (!confirmed) {
2122
+ console.log("Cancelled.");
2123
+ return;
2124
+ }
2125
+ let revokedAgentIds = [];
2126
+ try {
2127
+ const result = await revokeAgent(opts.agentId);
2128
+ revokedAgentIds = result.revokedAgentIds;
2129
+ console.log(chalk10.green(`Revoked ${revokedAgentIds.length} agent(s)`));
2130
+ if (revokedAgentIds.length > 1) {
2131
+ console.log("Revoked:", revokedAgentIds.join(", "));
2132
+ }
2133
+ } catch (err) {
2134
+ console.error(chalk10.red(String(err)));
2135
+ process.exit(1);
2136
+ }
2137
+ if (!opts.keepLocal) {
2138
+ const creds = tryResolveAgent();
2139
+ if (creds && revokedAgentIds.includes(creds.agentId)) {
2140
+ console.log("");
2141
+ console.log(chalk10.dim(" Cleaning local config for revoked agent..."));
2142
+ cleanLocally();
2143
+ }
2144
+ }
2145
+ });
2146
+ }
2147
+
2148
+ // src/commands/inject.ts
2149
+ import { Command as Command4 } from "commander";
2150
+ import chalk11 from "chalk";
2151
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile8 } from "fs/promises";
2152
+ import { dirname as dirname2 } from "path";
2153
+
2154
+ // src/lib/claude-config.ts
2155
+ import { homedir as homedir2 } from "os";
2156
+ import { join as join2 } from "path";
2157
+ import { readFile as readFile3, writeFile as writeFile6, mkdir as mkdir4, access } from "fs/promises";
2158
+ var CLAUDE_CONFIG_PATH = join2(homedir2(), ".claude", "config");
2159
+ async function writeKeyToClaudeConfig(agentId, privateKeyPem) {
2160
+ let config = {};
2161
+ try {
2162
+ await access(CLAUDE_CONFIG_PATH);
2163
+ const raw = await readFile3(CLAUDE_CONFIG_PATH, "utf-8");
2164
+ config = JSON.parse(raw);
2165
+ } catch {
2166
+ }
2167
+ config.agent_keys = config.agent_keys ?? {};
2168
+ config.agent_keys[agentId] = Buffer.from(privateKeyPem).toString("base64");
2169
+ await mkdir4(join2(homedir2(), ".claude"), { recursive: true });
2170
+ await writeFile6(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
2171
+ }
2172
+
2173
+ // src/lib/env-writer.ts
2174
+ import { readFile as readFile4, writeFile as writeFile7, access as access2 } from "fs/promises";
2175
+ var ENV_VAR_NAME = (agentId) => `AGENTVALET_PRIVATE_KEY_${agentId.replace(/-/g, "_").toUpperCase()}`;
2176
+ async function writeKeyToEnvLocal(agentId, privateKeyPem) {
2177
+ const varName = ENV_VAR_NAME(agentId);
2178
+ const b64Key = Buffer.from(privateKeyPem).toString("base64");
2179
+ const line = `
2180
+ # AgentValet private key for ${agentId} (base64-encoded PEM)
2181
+ ${varName}=${b64Key}
2182
+ `;
2183
+ let existing = "";
2184
+ try {
2185
+ await access2(".env.local");
2186
+ existing = await readFile4(".env.local", "utf-8");
2187
+ } catch {
2188
+ }
2189
+ if (existing.includes(varName)) {
2190
+ const lines = existing.split("\n");
2191
+ const filtered = lines.filter((l) => !l.startsWith(`${varName}=`));
2192
+ await writeFile7(".env.local", filtered.join("\n") + line, "utf-8");
2193
+ } else {
2194
+ await writeFile7(".env.local", existing + line, "utf-8");
2195
+ }
2196
+ }
2197
+
2198
+ // src/commands/inject.ts
2199
+ function injectCommand() {
2200
+ return new Command4("inject").description("Import an existing PEM private key for an agent into the local store + framework configs").requiredOption("--agent-id <agentId>", "Agent ID (agt_...)").requiredOption("--key-file <path>", "Path to the private key PEM file").option("--owner-id <ownerId>", "Owner ID (required for --target agentvalet so the MCP server can authenticate)").option("--proxy-url <url>", "Proxy base URL", "https://api.agentvalet.ai").option(
2201
+ "--target <target>",
2202
+ "Where to write the key: agentvalet (default \u2014 modern store + MCP configs), env-local (.env.local), claude-legacy (~/.claude/config, deprecated)",
2203
+ "agentvalet"
2204
+ ).option("--no-frameworks", "Skip IDE/framework configuration prompts (only with --target agentvalet)").option("--project", "Write project-scoped framework configs instead of user-scoped").action(async (opts) => {
2205
+ let privateKeyPem;
2206
+ try {
2207
+ privateKeyPem = await readFile5(opts.keyFile, "utf-8");
2208
+ } catch {
2209
+ console.error(chalk11.red(`Could not read key file: ${opts.keyFile}`));
2210
+ process.exit(1);
2211
+ }
2212
+ if (!privateKeyPem.includes("BEGIN PRIVATE KEY")) {
2213
+ console.error(chalk11.red("File does not appear to be a PEM private key"));
2214
+ process.exit(1);
2215
+ }
2216
+ const target = opts.target === "claude-code" ? "claude-legacy" : opts.target;
2217
+ try {
2218
+ if (target === "agentvalet") {
2219
+ if (!opts.ownerId) {
2220
+ console.error(chalk11.red("--owner-id is required when target is 'agentvalet' (the MCP server needs it to authenticate)"));
2221
+ console.error(chalk11.dim(" Find it at https://app.agentvalet.ai or in your existing ~/.agentvalet/config.json"));
2222
+ process.exit(1);
2223
+ }
2224
+ await mkdir5(dirname2(AGENT_KEY_PATH), { recursive: true });
2225
+ await writeFile8(AGENT_KEY_PATH, privateKeyPem, { mode: 384 });
2226
+ saveConfig({
2227
+ agentId: opts.agentId,
2228
+ ownerId: opts.ownerId,
2229
+ apiUrl: opts.proxyUrl,
2230
+ proxyUrl: opts.proxyUrl,
2231
+ keyPath: AGENT_KEY_PATH,
2232
+ lastAgentId: opts.agentId,
2233
+ lastKeyPath: AGENT_KEY_PATH
2234
+ });
2235
+ console.log(chalk11.green(` \u2713 Key imported \u2192 ${AGENT_KEY_PATH}`));
2236
+ console.log(chalk11.green(` \u2713 Agent ID saved to ~/.agentvalet/config.json`));
2237
+ if (opts.frameworks !== false) {
2238
+ const detected = detectFrameworks();
2239
+ const shouldSetup = await frameworkSetupConsent(detected);
2240
+ if (shouldSetup) {
2241
+ console.log("");
2242
+ console.log(chalk11.bold(" Configuring agent frameworks..."));
2243
+ console.log("");
2244
+ const ideResults = await writeIdeConfigs({
2245
+ agentId: opts.agentId,
2246
+ ownerId: opts.ownerId,
2247
+ proxyUrl: opts.proxyUrl,
2248
+ keyPath: AGENT_KEY_PATH,
2249
+ detected,
2250
+ scope: opts.project ? "project" : "user"
2251
+ });
2252
+ for (const r of ideResults) console.log(chalk11.green(` \u2713 ${r}`));
2253
+ const globalResults = await writeGlobalFrameworkConfigs({
2254
+ agentId: opts.agentId,
2255
+ ownerId: opts.ownerId,
2256
+ proxyUrl: opts.proxyUrl,
2257
+ keyPath: AGENT_KEY_PATH
2258
+ });
2259
+ for (const r of globalResults) console.log(chalk11.green(` \u2713 ${r}`));
2260
+ }
2261
+ }
2262
+ console.log("");
2263
+ console.log(chalk11.dim(` ${AGENT_DIR}/ is excluded from git by convention \u2014 do not commit it.`));
2264
+ } else if (target === "env-local") {
2265
+ await writeKeyToEnvLocal(opts.agentId, privateKeyPem);
2266
+ console.log(chalk11.green(" \u2713 Key appended to .env.local"));
2267
+ console.log(chalk11.dim(" Add .env.local to .gitignore \u2014 do not commit private keys to git."));
2268
+ } else if (target === "claude-legacy") {
2269
+ console.warn(chalk11.yellow(" \u26A0 --target claude-legacy is deprecated."));
2270
+ console.warn(chalk11.dim(" Modern Claude Code reads MCP configs from ~/.claude.json, not ~/.claude/config."));
2271
+ console.warn(chalk11.dim(" Use --target agentvalet (default) instead."));
2272
+ await writeKeyToClaudeConfig(opts.agentId, privateKeyPem);
2273
+ console.log(chalk11.green(" \u2713 Key written to ~/.claude/config (legacy location)"));
2274
+ } else {
2275
+ console.error(chalk11.red(`Unknown target: ${opts.target}. Use 'agentvalet', 'env-local', or 'claude-legacy'.`));
2276
+ process.exit(1);
2277
+ }
2278
+ } catch (err) {
2279
+ console.error(chalk11.red(String(err)));
2280
+ process.exit(1);
2281
+ }
2282
+ console.log(chalk11.green("\n Done. Your agent is ready to use."));
2083
2283
  });
2084
2284
  }
2085
2285
 
2086
2286
  // src/commands/install.ts
2087
- import { writeFile as writeFile8, mkdir as mkdir5 } from "fs/promises";
2287
+ import { writeFile as writeFile9, mkdir as mkdir6 } from "fs/promises";
2088
2288
  import path7 from "path";
2089
2289
  import { Command as Command5 } from "commander";
2090
2290
  import chalk12 from "chalk";
@@ -2134,8 +2334,8 @@ function installCommand() {
2134
2334
  }
2135
2335
  const { agent_id, agent_name, owner_id, private_key_pem, proxy_url } = claim;
2136
2336
  try {
2137
- await mkdir5(AGENT_DIR, { recursive: true });
2138
- await writeFile8(AGENT_KEY_PATH, private_key_pem, { mode: 384 });
2337
+ await mkdir6(AGENT_DIR, { recursive: true });
2338
+ await writeFile9(AGENT_KEY_PATH, private_key_pem, { mode: 384 });
2139
2339
  } catch (err) {
2140
2340
  console.error(chalk12.red(`
2141
2341
  \u2717 Failed to write key: ${err instanceof Error ? err.message : err}`));
@@ -2186,7 +2386,7 @@ function installCommand() {
2186
2386
  });
2187
2387
  for (const r of globalResults) console.log(chalk12.green(` \u2713 ${r}`));
2188
2388
  if (scope === "project") {
2189
- if (detected.codex) writeAgentsMd();
2389
+ if (detected.codex || detected.codexDesktop) writeAgentsMd();
2190
2390
  if (detected.openclaw) writeSkillMd();
2191
2391
  if (detected.factoryDroid) {
2192
2392
  writeFactoryDroidDroid();
@@ -2213,7 +2413,7 @@ function installCommand() {
2213
2413
  `AGENTVALET_PRIVATE_KEY_PATH=${AGENT_KEY_PATH}`,
2214
2414
  ""
2215
2415
  ].join("\n");
2216
- await writeFile8(path7.join(process.cwd(), ".env.agentvalet"), envContent, { encoding: "utf8", mode: 384 });
2416
+ await writeFile9(path7.join(process.cwd(), ".env.agentvalet"), envContent, { encoding: "utf8", mode: 384 });
2217
2417
  } catch {
2218
2418
  }
2219
2419
  console.log("");
@@ -2228,9 +2428,11 @@ function installCommand() {
2228
2428
  if (scope === "user") {
2229
2429
  const restartList = [];
2230
2430
  if (detected.claudeCode) restartList.push("Claude Code");
2231
- if (detected.cursor) restartList.push("Cursor");
2431
+ if (detected.cursor) restartList.push("Cursor IDE");
2432
+ if (detected.cursorCli) restartList.push("cursor-agent CLI (next session picks it up automatically)");
2232
2433
  if (detected.factoryDroid) restartList.push("Factory Droid");
2233
2434
  if (detected.codex) restartList.push("Codex CLI");
2435
+ if (detected.codexDesktop) restartList.push('Codex Desktop app (approve "agentvalet" in Settings \u2192 MCP)');
2234
2436
  if (restartList.length > 0) {
2235
2437
  console.log(chalk12.dim(" Restart these to pick up the new MCP server (no trust prompt \u2014 user-scoped):"));
2236
2438
  for (const r of restartList) console.log(chalk12.dim(` ${r}`));
@@ -2244,7 +2446,10 @@ function installCommand() {
2244
2446
  console.log("");
2245
2447
  }
2246
2448
  if (detected.cursor) {
2247
- console.log(chalk12.dim(" Cursor: restart Cursor (or reopen the workspace) to pick up the MCP."));
2449
+ console.log(chalk12.dim(" Cursor IDE: restart Cursor (or reopen the workspace) to pick up the MCP."));
2450
+ }
2451
+ if (detected.cursorCli) {
2452
+ console.log(chalk12.dim(" cursor-agent CLI: next `cursor-agent` invocation will pick up the MCP \u2014 verify with `cursor-agent mcp list`."));
2248
2453
  }
2249
2454
  if (detected.factoryDroid) {
2250
2455
  console.log(chalk12.dim(" Factory Droid: restart Factory to pick up the MCP."));
@@ -2252,6 +2457,9 @@ function installCommand() {
2252
2457
  if (detected.codex) {
2253
2458
  console.log(chalk12.dim(" Codex CLI: start a new codex session to pick up the MCP."));
2254
2459
  }
2460
+ if (detected.codexDesktop) {
2461
+ console.log(chalk12.dim(' Codex Desktop: trust this project, then approve "agentvalet" in Settings \u2192 MCP.'));
2462
+ }
2255
2463
  }
2256
2464
  }
2257
2465
  console.log(` Dashboard: ${chalk12.cyan.underline("https://app.agentvalet.ai/agents/" + agent_id)}`);
@@ -2260,13 +2468,154 @@ function installCommand() {
2260
2468
  });
2261
2469
  }
2262
2470
 
2471
+ // src/commands/claim.ts
2472
+ import { Command as Command6 } from "commander";
2473
+ import { generateKeyPairSync } from "crypto";
2474
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, existsSync as existsSync8 } from "fs";
2475
+ import { homedir as homedir3 } from "os";
2476
+ import { join as join3 } from "path";
2477
+ import chalk13 from "chalk";
2478
+ var DEFAULT_PROXY_URL = "https://api.agentvalet.ai";
2479
+ function resolveAgentDir(label) {
2480
+ const root = join3(homedir3(), ".agentvalet");
2481
+ return label ? join3(root, label) : root;
2482
+ }
2483
+ function sanitiseLabel(label) {
2484
+ return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2485
+ }
2486
+ async function postBind(proxyUrl, bindSecret, publicKeyPem) {
2487
+ const url = `${proxyUrl.replace(/\/$/, "")}/v1/invites/bind`;
2488
+ const res = await fetch(url, {
2489
+ method: "POST",
2490
+ headers: { "Content-Type": "application/json" },
2491
+ body: JSON.stringify({ bind_secret: bindSecret, public_key_pem: publicKeyPem })
2492
+ });
2493
+ if (!res.ok) {
2494
+ let detail = `HTTP ${res.status}`;
2495
+ try {
2496
+ const body = await res.json();
2497
+ if (body.error) detail = body.error;
2498
+ } catch {
2499
+ }
2500
+ if (res.status === 410) {
2501
+ throw new Error(
2502
+ `Bind secret rejected (${detail}). It was either already used or expired \u2014 ask the inviting manager to re-issue.`
2503
+ );
2504
+ }
2505
+ throw new Error(`Invite bind failed: ${detail}`);
2506
+ }
2507
+ return await res.json();
2508
+ }
2509
+ function claimCommand() {
2510
+ return new Command6("claim").description("Bind an AgentValet agent from a one-shot invite secret").argument("<bind_secret>", "The bsec_\u2026 value shown on the AgentValet claim page").option("--label <name>", "Save under ~/.agentvalet/<label>/ for multi-agent machines").option("--api-url <url>", "AgentValet proxy URL", DEFAULT_PROXY_URL).option("--json", "Machine-readable JSON output").action(async (bindSecret, opts) => {
2511
+ const isJson = !!opts.json;
2512
+ const rawLabel = opts.label?.trim() ?? "";
2513
+ const label = rawLabel ? sanitiseLabel(rawLabel) : null;
2514
+ if (rawLabel && label !== rawLabel.toLowerCase()) {
2515
+ if (!isJson) {
2516
+ console.error(chalk13.yellow(`Note: label sanitised to "${label}"`));
2517
+ }
2518
+ }
2519
+ if (!bindSecret.startsWith("bsec_")) {
2520
+ const msg = "bind_secret should start with 'bsec_'";
2521
+ console.error(isJson ? JSON.stringify({ error: msg }) : chalk13.red(msg));
2522
+ process.exit(3);
2523
+ }
2524
+ const agentDir = resolveAgentDir(label);
2525
+ const keyPath = join3(agentDir, "agent.key");
2526
+ const identityPath = join3(agentDir, "agent.json");
2527
+ if (existsSync8(keyPath)) {
2528
+ const msg = label ? `An agent already exists at ${keyPath}. Pick a different --label or remove the directory first.` : `A default agent already exists at ${keyPath}. Pass --label <name> to claim alongside it without overwriting.`;
2529
+ console.error(isJson ? JSON.stringify({ error: msg }) : chalk13.red(msg));
2530
+ process.exit(3);
2531
+ }
2532
+ const proxyUrl = opts.apiUrl.replace(/\/$/, "");
2533
+ const { publicKey, privateKey } = generateKeyPairSync("rsa", {
2534
+ modulusLength: 2048,
2535
+ publicKeyEncoding: { type: "spki", format: "pem" },
2536
+ privateKeyEncoding: { type: "pkcs8", format: "pem" }
2537
+ });
2538
+ let payload;
2539
+ try {
2540
+ payload = await postBind(proxyUrl, bindSecret, publicKey);
2541
+ } catch (err) {
2542
+ const msg = err instanceof Error ? err.message : String(err);
2543
+ console.error(isJson ? JSON.stringify({ error: msg }) : chalk13.red(msg));
2544
+ process.exit(3);
2545
+ }
2546
+ if (!payload.agent_id || !payload.owner_id || !payload.proxy_url) {
2547
+ const msg = "Bind succeeded but response was missing required fields";
2548
+ console.error(isJson ? JSON.stringify({ error: msg }) : chalk13.red(msg));
2549
+ process.exit(3);
2550
+ }
2551
+ const identity = {
2552
+ agent_id: payload.agent_id,
2553
+ owner_id: payload.owner_id,
2554
+ proxy_url: payload.proxy_url,
2555
+ label,
2556
+ bound_at: (/* @__PURE__ */ new Date()).toISOString()
2557
+ };
2558
+ mkdirSync3(agentDir, { recursive: true, mode: 448 });
2559
+ writeFileSync4(keyPath, privateKey, { mode: 384 });
2560
+ writeFileSync4(identityPath, JSON.stringify(identity, null, 2));
2561
+ const mcpServerName = label ? `agentvalet-${label}` : "agentvalet";
2562
+ const mcpEnv = label ? {
2563
+ AGENT_ID: payload.agent_id,
2564
+ OWNER_ID: payload.owner_id,
2565
+ PROXY_URL: payload.proxy_url,
2566
+ AGENT_PRIVATE_KEY_PATH: keyPath
2567
+ } : {
2568
+ // For the default install, mcp-server picks up agent.key from
2569
+ // ~/.agentvalet/ automatically — only PROXY_URL is needed.
2570
+ PROXY_URL: payload.proxy_url
2571
+ };
2572
+ if (isJson) {
2573
+ console.log(JSON.stringify({
2574
+ status: "bound",
2575
+ agent_id: payload.agent_id,
2576
+ owner_id: payload.owner_id,
2577
+ proxy_url: payload.proxy_url,
2578
+ key_path: keyPath,
2579
+ identity_path: identityPath,
2580
+ label,
2581
+ mcp_server_name: mcpServerName
2582
+ }));
2583
+ return;
2584
+ }
2585
+ console.log(chalk13.green(`
2586
+ \u2713 Bound as ${payload.agent_id}`));
2587
+ console.log(chalk13.gray(` owner: ${payload.owner_id}`));
2588
+ console.log(chalk13.gray(` proxy: ${payload.proxy_url}`));
2589
+ console.log(chalk13.gray(` key: ${keyPath}`));
2590
+ console.log(chalk13.gray(` identity: ${identityPath}`));
2591
+ console.log(chalk13.cyan("\nAdd this to your Claude Desktop / Claude Code MCP config:"));
2592
+ const snippet = {
2593
+ mcpServers: {
2594
+ [mcpServerName]: {
2595
+ command: "npx",
2596
+ args: ["-y", "@agentvalet/mcp-server"],
2597
+ env: mcpEnv
2598
+ }
2599
+ }
2600
+ };
2601
+ console.log(JSON.stringify(snippet, null, 2));
2602
+ if (label) {
2603
+ console.log(chalk13.gray(`
2604
+ Keep the named entry (${mcpServerName}) so it lives alongside any existing AgentValet agents on this machine.`));
2605
+ } else {
2606
+ console.log(chalk13.gray(`
2607
+ Or run 'agentvalet-register inject' to write this into ~/.claude.json automatically.`));
2608
+ }
2609
+ });
2610
+ }
2611
+
2263
2612
  // src/index.ts
2264
2613
  var require2 = createRequire2(import.meta.url);
2265
2614
  var { version } = require2("../package.json");
2266
2615
  function collectScope(value, previous) {
2267
2616
  return [...previous, value];
2268
2617
  }
2269
- var program = new Command6();
2618
+ var program = new Command7();
2270
2619
  program.name("agentvalet-register").description("Self-register an AI agent with AgentValet").version(version).option("--owner <uuid>", "Owner ID (or set AGENTVALET_OWNER env var)").option("--name <name>", "Agent name").option("--description <desc>", "Agent description").option("--scope <platform:scope>", "Requested scope, repeatable", collectScope, []).option("--api-url <url>", "AgentValet API base URL", "https://api.agentvalet.ai").option("--json", "Machine-readable JSON output").option("--no-wait", "Register only, do not wait for approval").option("--timeout <seconds>", "Approval wait timeout in seconds", "300").option("--project", "Write project-scoped MCP configs (.mcp.json, .cursor/mcp.json) instead of user-scoped (~/.claude.json, ~/.cursor/mcp.json)").action(async (opts) => {
2271
2620
  const isJson = !!opts.json;
2272
2621
  let ownerId = opts.owner ?? process.env.AGENTVALET_OWNER ?? "";
@@ -2300,7 +2649,7 @@ program.name("agentvalet-register").description("Self-register an AI agent with
2300
2649
  console.error(JSON.stringify({ error: "--owner or AGENTVALET_OWNER is required" }));
2301
2650
  process.exit(3);
2302
2651
  }
2303
- console.error(chalk13.red("Error: --owner or AGENTVALET_OWNER environment variable is required"));
2652
+ console.error(chalk14.red("Error: --owner or AGENTVALET_OWNER environment variable is required"));
2304
2653
  process.exit(3);
2305
2654
  }
2306
2655
  const scopeMap = /* @__PURE__ */ new Map();
@@ -2308,7 +2657,7 @@ program.name("agentvalet-register").description("Self-register an AI agent with
2308
2657
  const colon = s.indexOf(":");
2309
2658
  if (colon === -1) {
2310
2659
  console.error(
2311
- isJson ? JSON.stringify({ error: `Invalid scope format: ${s} (expected platform:scope)` }) : chalk13.red(`Invalid scope format: ${s} (expected platform:scope)`)
2660
+ isJson ? JSON.stringify({ error: `Invalid scope format: ${s} (expected platform:scope)` }) : chalk14.red(`Invalid scope format: ${s} (expected platform:scope)`)
2312
2661
  );
2313
2662
  process.exit(3);
2314
2663
  }
@@ -2331,11 +2680,11 @@ program.name("agentvalet-register").description("Self-register an AI agent with
2331
2680
  });
2332
2681
  } catch (err) {
2333
2682
  const msg = err instanceof Error ? err.message : String(err);
2334
- console.error(isJson ? JSON.stringify({ error: msg }) : chalk13.red(`Registration failed: ${msg}`));
2683
+ console.error(isJson ? JSON.stringify({ error: msg }) : chalk14.red(`Registration failed: ${msg}`));
2335
2684
  process.exit(3);
2336
2685
  }
2337
2686
  if (!isJson) {
2338
- console.log(chalk13.cyan(`
2687
+ console.log(chalk14.cyan(`
2339
2688
  Approve at: https://app.agentvalet.ai/agents
2340
2689
  `));
2341
2690
  }
@@ -2343,7 +2692,7 @@ Approve at: https://app.agentvalet.ai/agents
2343
2692
  if (isJson) {
2344
2693
  console.log(JSON.stringify({ status: "pending", token }));
2345
2694
  } else {
2346
- console.log(chalk13.yellow("Registration submitted (--no-wait). Token:"), token);
2695
+ console.log(chalk14.yellow("Registration submitted (--no-wait). Token:"), token);
2347
2696
  }
2348
2697
  process.exit(0);
2349
2698
  }
@@ -2354,14 +2703,14 @@ Approve at: https://app.agentvalet.ai/agents
2354
2703
  });
2355
2704
  } catch (err) {
2356
2705
  const msg = err instanceof Error ? err.message : String(err);
2357
- console.error(isJson ? JSON.stringify({ error: msg }) : chalk13.red(`Wait failed: ${msg}`));
2706
+ console.error(isJson ? JSON.stringify({ error: msg }) : chalk14.red(`Wait failed: ${msg}`));
2358
2707
  process.exit(3);
2359
2708
  }
2360
2709
  if (result.status === "rejected") {
2361
2710
  if (isJson) {
2362
2711
  console.log(JSON.stringify({ status: "rejected" }));
2363
2712
  } else {
2364
- console.log(chalk13.red("\nRegistration denied by owner."));
2713
+ console.log(chalk14.red("\nRegistration denied by owner."));
2365
2714
  }
2366
2715
  process.exit(1);
2367
2716
  }
@@ -2369,7 +2718,7 @@ Approve at: https://app.agentvalet.ai/agents
2369
2718
  if (isJson) {
2370
2719
  console.log(JSON.stringify({ status: "timeout" }));
2371
2720
  } else {
2372
- console.log(chalk13.yellow("\nTimed out waiting for approval."));
2721
+ console.log(chalk14.yellow("\nTimed out waiting for approval."));
2373
2722
  }
2374
2723
  process.exit(2);
2375
2724
  }
@@ -2387,7 +2736,7 @@ Approve at: https://app.agentvalet.ai/agents
2387
2736
  }));
2388
2737
  } catch (err) {
2389
2738
  const msg = err instanceof Error ? err.message : String(err);
2390
- console.error(isJson ? JSON.stringify({ error: msg }) : chalk13.red(`Failed to save output: ${msg}`));
2739
+ console.error(isJson ? JSON.stringify({ error: msg }) : chalk14.red(`Failed to save output: ${msg}`));
2391
2740
  process.exit(3);
2392
2741
  }
2393
2742
  } else {
@@ -2397,25 +2746,25 @@ Approve at: https://app.agentvalet.ai/agents
2397
2746
  if (isJson) {
2398
2747
  console.log(JSON.stringify({ status: "approved", agentId, keyPath: keyWritten ? keyPath : null }));
2399
2748
  } else {
2400
- console.log(chalk13.green(`
2749
+ console.log(chalk14.green(`
2401
2750
  Approved! Agent ID: ${agentId}`));
2402
2751
  if (keyWritten) {
2403
- console.log(chalk13.gray(`Key: ${keyPath}`));
2752
+ console.log(chalk14.gray(`Key: ${keyPath}`));
2404
2753
  } else {
2405
- console.log(chalk13.yellow(`Key: not written \u2014 copy it from the dashboard and paste into ${keyPath}`));
2754
+ console.log(chalk14.yellow(`Key: not written \u2014 copy it from the dashboard and paste into ${keyPath}`));
2406
2755
  }
2407
2756
  }
2408
2757
  if (keyWritten) {
2409
2758
  try {
2410
2759
  if (grantedPlatforms && grantedPlatforms.length > 0) {
2411
2760
  writePermissionsFromEvent(agentId, resolvedOwnerId, grantedPlatforms);
2412
- if (!isJson) console.log(chalk13.gray(`Permissions: .claude/agentvalet-permissions.md`));
2761
+ if (!isJson) console.log(chalk14.gray(`Permissions: .claude/agentvalet-permissions.md`));
2413
2762
  } else {
2414
2763
  await runRefresh({ envFile: ".env.agentvalet", outputPath: ".claude/agentvalet-permissions.md", ifStale: false, quiet: isJson });
2415
- if (!isJson) console.log(chalk13.gray(`Permissions: .claude/agentvalet-permissions.md`));
2764
+ if (!isJson) console.log(chalk14.gray(`Permissions: .claude/agentvalet-permissions.md`));
2416
2765
  }
2417
2766
  } catch {
2418
- if (!isJson) console.log(chalk13.yellow(`Permissions: run 'agentvalet-register refresh' to generate`));
2767
+ if (!isJson) console.log(chalk14.yellow(`Permissions: run 'agentvalet-register refresh' to generate`));
2419
2768
  }
2420
2769
  }
2421
2770
  process.exit(0);
@@ -2430,7 +2779,7 @@ program.command("refresh").description("Refresh approved permissions and update
2430
2779
  });
2431
2780
  } catch (err) {
2432
2781
  const msg = err instanceof Error ? err.message : String(err);
2433
- console.error(chalk13.red(`Refresh failed: ${msg}`));
2782
+ console.error(chalk14.red(`Refresh failed: ${msg}`));
2434
2783
  process.exit(3);
2435
2784
  }
2436
2785
  });
@@ -2443,6 +2792,7 @@ program.addCommand(revokeCommand());
2443
2792
  program.addCommand(injectCommand());
2444
2793
  program.addCommand(uninstallCommand());
2445
2794
  program.addCommand(installCommand());
2795
+ program.addCommand(claimCommand());
2446
2796
  program.parseAsync().catch((err) => {
2447
2797
  console.error(err);
2448
2798
  process.exit(3);