@agentvalet/register 0.5.3 → 0.5.6
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.
- package/dist/index.js +520 -312
- package/package.json +59 -58
package/dist/index.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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 || !
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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
|
|
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(
|
|
1061
|
+
console.log(chalk5.dim(`
|
|
920
1062
|
Opening browser to authorize...`));
|
|
921
|
-
console.log(
|
|
1063
|
+
console.log(chalk5.dim(` URL: ${authUrl}
|
|
922
1064
|
`));
|
|
923
1065
|
await open(authUrl).catch(() => {
|
|
924
|
-
console.log(
|
|
925
|
-
console.log(
|
|
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
|
|
1121
|
+
import path3 from "path";
|
|
980
1122
|
async function writeLocalConfig(config) {
|
|
981
|
-
const dir =
|
|
1123
|
+
const dir = path3.join(process.cwd(), ".agentvalet");
|
|
982
1124
|
await mkdir2(dir, { recursive: true });
|
|
983
1125
|
await writeFile2(
|
|
984
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
1154
|
+
import chalk6 from "chalk";
|
|
1013
1155
|
import { appendFile as appendFile2, writeFile as writeFile3 } from "fs/promises";
|
|
1014
|
-
import { existsSync as
|
|
1015
|
-
import
|
|
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 ` ${
|
|
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
|
|
1026
|
-
import
|
|
1027
|
-
import
|
|
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 (
|
|
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
|
-
|
|
1097
|
-
|
|
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
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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");
|
|
1115
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");
|
|
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 (!
|
|
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(
|
|
1300
|
+
if (await stripAgentvaletFromMcpJson(path5.join(cwd, ".mcp.json"))) {
|
|
1138
1301
|
cleaned.push(".mcp.json (project entry removed)");
|
|
1139
1302
|
}
|
|
1140
|
-
if (await stripAgentvaletFromMcpJson(
|
|
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 =
|
|
1144
|
-
if (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1176
|
-
const cursorUserMcp =
|
|
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 =
|
|
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 =
|
|
1201
|
-
if (!
|
|
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 =
|
|
1214
|
-
const cursorMcpPath =
|
|
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 (
|
|
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 =
|
|
1246
|
-
if (!
|
|
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 =
|
|
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 =
|
|
1266
|
-
const projectDir =
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
1650
|
-
|
|
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
|
|
1804
|
-
import
|
|
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
|
|
1807
|
-
import { homedir
|
|
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
|
|
1866
|
-
|
|
1867
|
-
if (!existsSync7(path8)) return false;
|
|
1868
|
+
function stripCodexTomlAt(filePath) {
|
|
1869
|
+
if (!existsSync7(filePath)) return false;
|
|
1868
1870
|
try {
|
|
1869
|
-
const content = readFileSync4(
|
|
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(
|
|
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 =
|
|
1925
|
-
if (stripMcpJson(
|
|
1926
|
-
if (stripMcpJson(
|
|
1927
|
-
if (stripMcpJson(
|
|
1928
|
-
if (
|
|
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(
|
|
1934
|
-
for (const f of removed) console.log(
|
|
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(
|
|
1938
|
-
for (const f of stripped) console.log(
|
|
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
|
|
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(
|
|
2028
|
+
if (!creds) console.log(chalk9.dim(" No agent credentials found \u2014 cleaning local files only."));
|
|
2024
2029
|
cleanLocally();
|
|
2025
|
-
console.log(
|
|
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(
|
|
2035
|
-
console.log(
|
|
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(
|
|
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(
|
|
2055
|
+
console.log(chalk9.yellow(` \u26A0 Agent not found on server (already revoked?) \u2014 cleaning local files.`));
|
|
2051
2056
|
cleanLocally();
|
|
2052
|
-
console.log(
|
|
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(
|
|
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(
|
|
2068
|
+
console.log(chalk9.green(" \u2713 Revocation request submitted"));
|
|
2064
2069
|
console.log("");
|
|
2065
|
-
console.log(
|
|
2066
|
-
console.log(
|
|
2067
|
-
console.log(
|
|
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(
|
|
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(
|
|
2076
|
-
console.log(
|
|
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(
|
|
2084
|
+
console.log(chalk9.green("\n \u2713 Approved \u2014 cleaning local files...\n"));
|
|
2080
2085
|
cleanLocally();
|
|
2081
2086
|
console.log("");
|
|
2082
|
-
console.log(
|
|
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
|
|
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
|
|
2138
|
-
await
|
|
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
|
|
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)}`);
|