@agentvalet/register 1.3.0 → 1.5.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.
- package/README.md +21 -1
- package/dist/index.js +186 -103
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,9 +15,29 @@ No installation required. Authenticate with GitHub, and your agent is registered
|
|
|
15
15
|
1. Opens your browser to authenticate with GitHub
|
|
16
16
|
2. Creates your AgentValet account (or finds your existing one)
|
|
17
17
|
3. Registers this machine as an agent
|
|
18
|
-
4. Writes config to
|
|
18
|
+
4. Writes config and your private key to `~/.agentvalet/` in your **home**
|
|
19
|
+
directory (not the current directory)
|
|
19
20
|
5. Outputs the environment variables you need
|
|
20
21
|
|
|
22
|
+
## Already have an agent on this machine?
|
|
23
|
+
|
|
24
|
+
An agent's private key exists only on your machine — the proxy never sees it,
|
|
25
|
+
and there is no rotation endpoint. Overwriting it destroys that identity, along
|
|
26
|
+
with its grants and audit continuity.
|
|
27
|
+
|
|
28
|
+
So registering while `~/.agentvalet/agent.key` already belongs to a **different**
|
|
29
|
+
agent stops with an error rather than overwriting it. You have two options:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Keep both — store the new agent under ~/.agentvalet/<label>/
|
|
33
|
+
npx @agentvalet/register claim <bind_secret> --label work-laptop
|
|
34
|
+
|
|
35
|
+
# Replace the existing one — a timestamped backup is written first
|
|
36
|
+
npx @agentvalet/register --force
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Re-registering the *same* agent over its own key is always allowed.
|
|
40
|
+
|
|
21
41
|
## Requirements
|
|
22
42
|
|
|
23
43
|
- Node.js 18+
|
package/dist/index.js
CHANGED
|
@@ -180,8 +180,72 @@ function sleep(ms) {
|
|
|
180
180
|
|
|
181
181
|
// src/save.ts
|
|
182
182
|
import { writeFile, mkdir } from "fs/promises";
|
|
183
|
+
|
|
184
|
+
// src/config/key-guard.ts
|
|
185
|
+
import { existsSync, copyFileSync, readFileSync } from "fs";
|
|
186
|
+
import { dirname, join } from "path";
|
|
187
|
+
var KeyClobberError = class extends Error {
|
|
188
|
+
constructor(keyPath, existingAgentId, message) {
|
|
189
|
+
super(message);
|
|
190
|
+
this.keyPath = keyPath;
|
|
191
|
+
this.existingAgentId = existingAgentId;
|
|
192
|
+
this.name = "KeyClobberError";
|
|
193
|
+
}
|
|
194
|
+
keyPath;
|
|
195
|
+
existingAgentId;
|
|
196
|
+
};
|
|
197
|
+
function existingKeyOwner(keyPath) {
|
|
198
|
+
if (!existsSync(keyPath)) return null;
|
|
199
|
+
const configPath = join(dirname(keyPath), "config.json");
|
|
200
|
+
if (!existsSync(configPath)) return null;
|
|
201
|
+
try {
|
|
202
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
203
|
+
const id = parsed.agentId ?? parsed.lastAgentId;
|
|
204
|
+
return typeof id === "string" && id ? id : null;
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function backupExistingKey(keyPath) {
|
|
210
|
+
if (!existsSync(keyPath)) return null;
|
|
211
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
212
|
+
let backup = `${keyPath}.bak.${stamp}`;
|
|
213
|
+
let n = 1;
|
|
214
|
+
while (existsSync(backup)) backup = `${keyPath}.bak.${stamp}-${n++}`;
|
|
215
|
+
copyFileSync(keyPath, backup);
|
|
216
|
+
return backup;
|
|
217
|
+
}
|
|
218
|
+
function assertSafeToWriteKey(keyPath, incomingAgentId, opts = {}) {
|
|
219
|
+
if (!existsSync(keyPath)) return;
|
|
220
|
+
const owner = existingKeyOwner(keyPath);
|
|
221
|
+
if (owner && owner === incomingAgentId) return;
|
|
222
|
+
if (opts.force) {
|
|
223
|
+
backupExistingKey(keyPath);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const whose = owner ? `agent ${owner}` : "another agent (owner unknown)";
|
|
227
|
+
throw new KeyClobberError(
|
|
228
|
+
keyPath,
|
|
229
|
+
owner,
|
|
230
|
+
[
|
|
231
|
+
`${keyPath} already holds the private key for ${whose}.`,
|
|
232
|
+
"",
|
|
233
|
+
"Overwriting it would destroy that identity permanently: the key exists",
|
|
234
|
+
"only on this machine and there is no way to recover or rotate it.",
|
|
235
|
+
"",
|
|
236
|
+
"Options:",
|
|
237
|
+
` \u2022 Keep both \u2014 move ${keyPath} aside first, or use 'claim --label <name>'`,
|
|
238
|
+
" to store an additional agent under ~/.agentvalet/<name>/.",
|
|
239
|
+
" \u2022 Replace it \u2014 re-run with --force. The existing key is copied to",
|
|
240
|
+
` ${keyPath}.bak.<timestamp> before being replaced.`
|
|
241
|
+
].join("\n")
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/save.ts
|
|
183
246
|
async function saveOutput(opts) {
|
|
184
247
|
await mkdir(AGENT_DIR, { recursive: true });
|
|
248
|
+
assertSafeToWriteKey(AGENT_KEY_PATH, opts.agentId, { force: opts.force });
|
|
185
249
|
await writeFile(AGENT_KEY_PATH, opts.privateKey, { mode: 384 });
|
|
186
250
|
saveConfig({
|
|
187
251
|
agentId: opts.agentId,
|
|
@@ -196,8 +260,8 @@ async function saveOutput(opts) {
|
|
|
196
260
|
}
|
|
197
261
|
|
|
198
262
|
// src/commands/refresh.ts
|
|
199
|
-
import { readFileSync as
|
|
200
|
-
import { dirname, join, resolve as resolve2 } from "path";
|
|
263
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
264
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
201
265
|
|
|
202
266
|
// src/lib/jwt.ts
|
|
203
267
|
import { createSign, createPrivateKey } from "crypto";
|
|
@@ -215,13 +279,13 @@ function signJwt(agentId, ownerId, privateKeyPem) {
|
|
|
215
279
|
}
|
|
216
280
|
|
|
217
281
|
// src/lib/skill-writer.ts
|
|
218
|
-
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
282
|
+
import { existsSync as existsSync2, writeFileSync, mkdirSync } from "fs";
|
|
219
283
|
import { execSync } from "child_process";
|
|
220
284
|
import path from "path";
|
|
221
285
|
import chalk2 from "chalk";
|
|
222
286
|
function writeAgentsMd() {
|
|
223
287
|
const dest = path.join(process.cwd(), "AGENTS.md");
|
|
224
|
-
if (
|
|
288
|
+
if (existsSync2(dest)) {
|
|
225
289
|
console.log(chalk2.dim(" AGENTS.md already exists \u2014 skipping"));
|
|
226
290
|
return;
|
|
227
291
|
}
|
|
@@ -264,7 +328,7 @@ Do not call platform APIs directly. Tell the user:
|
|
|
264
328
|
}
|
|
265
329
|
function writeSkillMd() {
|
|
266
330
|
const dest = path.join(process.cwd(), "SKILL.md");
|
|
267
|
-
if (
|
|
331
|
+
if (existsSync2(dest)) {
|
|
268
332
|
console.log(chalk2.dim(" SKILL.md already exists \u2014 skipping"));
|
|
269
333
|
return;
|
|
270
334
|
}
|
|
@@ -302,7 +366,7 @@ Dashboard: https://app.agentvalet.ai
|
|
|
302
366
|
}
|
|
303
367
|
function writeQuickstartDoc(opts) {
|
|
304
368
|
const dest = path.join(process.cwd(), "AGENTVALET.md");
|
|
305
|
-
if (
|
|
369
|
+
if (existsSync2(dest)) {
|
|
306
370
|
console.log(chalk2.dim(" AGENTVALET.md already exists \u2014 skipping"));
|
|
307
371
|
return;
|
|
308
372
|
}
|
|
@@ -385,9 +449,9 @@ AGENT_PRIVATE_KEY_PATH=${keyPath}
|
|
|
385
449
|
}
|
|
386
450
|
function writeFactoryDroidDroid() {
|
|
387
451
|
const droidsDir = path.join(process.cwd(), ".factory", "droids");
|
|
388
|
-
if (!
|
|
452
|
+
if (!existsSync2(droidsDir)) mkdirSync(droidsDir, { recursive: true });
|
|
389
453
|
const dest = path.join(droidsDir, "agentvalet.yaml");
|
|
390
|
-
if (
|
|
454
|
+
if (existsSync2(dest)) {
|
|
391
455
|
console.log(chalk2.dim(" Factory Droid: .factory/droids/agentvalet.yaml already exists"));
|
|
392
456
|
return;
|
|
393
457
|
}
|
|
@@ -457,9 +521,9 @@ instructions: |
|
|
|
457
521
|
}
|
|
458
522
|
function writeFactoryDroidCommand() {
|
|
459
523
|
const commandsDir = path.join(process.cwd(), ".factory", "commands");
|
|
460
|
-
if (!
|
|
524
|
+
if (!existsSync2(commandsDir)) mkdirSync(commandsDir, { recursive: true });
|
|
461
525
|
const statusDest = path.join(commandsDir, "av-status.md");
|
|
462
|
-
if (!
|
|
526
|
+
if (!existsSync2(statusDest)) {
|
|
463
527
|
const statusContent = `# /av-status
|
|
464
528
|
|
|
465
529
|
Check your AgentValet agent status and approved platform connections.
|
|
@@ -482,7 +546,7 @@ Check your AgentValet agent status and approved platform connections.
|
|
|
482
546
|
console.log(chalk2.dim(" Factory Droid: .factory/commands/av-status.md already exists"));
|
|
483
547
|
}
|
|
484
548
|
const registerDest = path.join(commandsDir, "av-register.md");
|
|
485
|
-
if (!
|
|
549
|
+
if (!existsSync2(registerDest)) {
|
|
486
550
|
const registerContent = `# /av-register
|
|
487
551
|
|
|
488
552
|
Register or re-register this project with AgentValet.
|
|
@@ -509,9 +573,9 @@ After running, approve the agent at: https://app.agentvalet.ai/agents
|
|
|
509
573
|
}
|
|
510
574
|
function writeFactoryDroidReliabilityDroid() {
|
|
511
575
|
const droidsDir = path.join(process.cwd(), ".factory", "droids");
|
|
512
|
-
if (!
|
|
576
|
+
if (!existsSync2(droidsDir)) mkdirSync(droidsDir, { recursive: true });
|
|
513
577
|
const dest = path.join(droidsDir, "agentvalet-reliability.yaml");
|
|
514
|
-
if (
|
|
578
|
+
if (existsSync2(dest)) return;
|
|
515
579
|
const content = `# AgentValet Reliability Droid
|
|
516
580
|
# Monitors agent call patterns and surfaces governance issues
|
|
517
581
|
# Commit this file to your repo
|
|
@@ -597,8 +661,15 @@ function generatePermissionsFile(agentId, ownerId, permissions) {
|
|
|
597
661
|
const gated = new Set(p.approvalScopes ?? []);
|
|
598
662
|
const scopeList = p.scopes.map((s) => ` - \`${s}\`${gated.has(s) ? " \u2014 requires human approval" : ""}`).join("\n");
|
|
599
663
|
const approvalNote = p.requireApproval ? "\n > Requires human approval before executing.\n" : "";
|
|
664
|
+
const vendorBits = [
|
|
665
|
+
p.serverName ? `reports itself as \`${p.serverName}\`` : null,
|
|
666
|
+
p.serverHost ? `served from \`${p.serverHost}\`` : null
|
|
667
|
+
].filter(Boolean);
|
|
668
|
+
const vendorNote = vendorBits.length ? `
|
|
669
|
+
_MCP server \u2014 ${vendorBits.join(", ")}._
|
|
670
|
+
` : "";
|
|
600
671
|
return `### ${p.platformName} (\`${p.platformId}\`)
|
|
601
|
-
|
|
672
|
+
${vendorNote}
|
|
602
673
|
Approved scopes:
|
|
603
674
|
${scopeList}
|
|
604
675
|
${approvalNote}`;
|
|
@@ -677,7 +748,7 @@ To refresh this file: \`npx @agentvalet/register refresh\`
|
|
|
677
748
|
}
|
|
678
749
|
|
|
679
750
|
// src/lib/require-agent.ts
|
|
680
|
-
import { existsSync as
|
|
751
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
681
752
|
import { resolve } from "path";
|
|
682
753
|
import chalk3 from "chalk";
|
|
683
754
|
var NO_AGENT_MSG = chalk3.red("\n \u2717 No agent registered.\n") + chalk3.dim(
|
|
@@ -710,9 +781,9 @@ function tryResolveAgent(envFile = ".env.agentvalet") {
|
|
|
710
781
|
if (!keyPath) keyPath = AGENT_KEY_PATH;
|
|
711
782
|
if (!agentId || !ownerId) {
|
|
712
783
|
const envPath = resolve(envFile);
|
|
713
|
-
if (
|
|
784
|
+
if (existsSync3(envPath)) {
|
|
714
785
|
try {
|
|
715
|
-
const vars = parseEnvFile(
|
|
786
|
+
const vars = parseEnvFile(readFileSync3(envPath, "utf-8"));
|
|
716
787
|
agentId = agentId ?? vars["AGENTVALET_AGENT_ID"];
|
|
717
788
|
ownerId = ownerId ?? vars["AGENTVALET_OWNER_ID"];
|
|
718
789
|
apiUrl = vars["AGENTVALET_API_URL"] ?? apiUrl;
|
|
@@ -726,12 +797,12 @@ function tryResolveAgent(envFile = ".env.agentvalet") {
|
|
|
726
797
|
if (process.env["AGENTVALET_API_URL"]) apiUrl = process.env["AGENTVALET_API_URL"];
|
|
727
798
|
if (process.env["AGENTVALET_PRIVATE_KEY_PATH"]) keyPath = process.env["AGENTVALET_PRIVATE_KEY_PATH"];
|
|
728
799
|
if (!agentId || !ownerId || !keyPath) return null;
|
|
729
|
-
if (!
|
|
800
|
+
if (!existsSync3(resolve(keyPath))) return null;
|
|
730
801
|
return { agentId, ownerId, keyPath, apiUrl };
|
|
731
802
|
}
|
|
732
803
|
|
|
733
804
|
// src/config/frameworks.ts
|
|
734
|
-
import { existsSync as
|
|
805
|
+
import { existsSync as existsSync4 } from "fs";
|
|
735
806
|
import { execSync as execSync2 } from "child_process";
|
|
736
807
|
import os from "os";
|
|
737
808
|
import path2 from "path";
|
|
@@ -749,27 +820,27 @@ function codexDesktopInstalled() {
|
|
|
749
820
|
const home = os.homedir();
|
|
750
821
|
const platform = process.platform;
|
|
751
822
|
if (platform === "darwin") {
|
|
752
|
-
return
|
|
823
|
+
return existsSync4("/Applications/Codex.app") || existsSync4(path2.join(home, "Applications", "Codex.app"));
|
|
753
824
|
}
|
|
754
825
|
if (platform === "win32") {
|
|
755
826
|
const localAppData = process.env.LOCALAPPDATA ?? path2.join(home, "AppData", "Local");
|
|
756
827
|
const programFiles = process.env["ProgramFiles"] ?? "C:\\Program Files";
|
|
757
|
-
return
|
|
828
|
+
return existsSync4(path2.join(localAppData, "Programs", "codex")) || existsSync4(path2.join(localAppData, "Programs", "Codex")) || existsSync4(path2.join(programFiles, "Codex"));
|
|
758
829
|
}
|
|
759
|
-
return
|
|
830
|
+
return existsSync4("/usr/share/applications/codex.desktop") || existsSync4(path2.join(home, ".local", "share", "applications", "codex.desktop"));
|
|
760
831
|
}
|
|
761
832
|
function claudeDesktopInstalled() {
|
|
762
833
|
const home = os.homedir();
|
|
763
834
|
const platform = process.platform;
|
|
764
835
|
if (platform === "darwin") {
|
|
765
|
-
return
|
|
836
|
+
return existsSync4("/Applications/Claude.app") || existsSync4(path2.join(home, "Library", "Application Support", "Claude"));
|
|
766
837
|
}
|
|
767
838
|
if (platform === "win32") {
|
|
768
839
|
const appData = process.env.APPDATA ?? path2.join(home, "AppData", "Roaming");
|
|
769
840
|
const localAppData = process.env.LOCALAPPDATA ?? path2.join(home, "AppData", "Local");
|
|
770
|
-
return
|
|
841
|
+
return existsSync4(path2.join(appData, "Claude")) || existsSync4(path2.join(localAppData, "AnthropicClaude")) || existsSync4(path2.join(localAppData, "Programs", "claude"));
|
|
771
842
|
}
|
|
772
|
-
return
|
|
843
|
+
return existsSync4(path2.join(home, ".config", "Claude"));
|
|
773
844
|
}
|
|
774
845
|
function cursorInstalled() {
|
|
775
846
|
const home = os.homedir();
|
|
@@ -777,24 +848,24 @@ function cursorInstalled() {
|
|
|
777
848
|
if (platform === "win32") {
|
|
778
849
|
const appData = process.env.APPDATA ?? path2.join(home, "AppData", "Roaming");
|
|
779
850
|
const localAppData = process.env.LOCALAPPDATA ?? path2.join(home, "AppData", "Local");
|
|
780
|
-
return
|
|
851
|
+
return existsSync4(path2.join(appData, "Cursor")) || existsSync4(path2.join(localAppData, "Programs", "cursor"));
|
|
781
852
|
}
|
|
782
853
|
if (platform === "darwin") {
|
|
783
|
-
return
|
|
854
|
+
return existsSync4(path2.join(home, "Library", "Application Support", "Cursor")) || existsSync4("/Applications/Cursor.app");
|
|
784
855
|
}
|
|
785
|
-
return
|
|
856
|
+
return existsSync4(path2.join(home, ".config", "Cursor")) || existsSync4("/usr/share/applications/cursor.desktop");
|
|
786
857
|
}
|
|
787
858
|
function detectFrameworks() {
|
|
788
859
|
const home = os.homedir();
|
|
789
860
|
return {
|
|
790
|
-
codex:
|
|
861
|
+
codex: existsSync4(path2.join(home, ".codex")) || tryBinary("codex"),
|
|
791
862
|
codexDesktop: codexDesktopInstalled(),
|
|
792
|
-
claudeCode:
|
|
863
|
+
claudeCode: existsSync4(path2.join(home, ".claude")) || tryBinary("claude"),
|
|
793
864
|
claudeDesktop: claudeDesktopInstalled(),
|
|
794
|
-
cursor:
|
|
865
|
+
cursor: existsSync4(path2.join(process.cwd(), ".cursor")) || cursorInstalled(),
|
|
795
866
|
cursorCli: tryBinary("cursor-agent"),
|
|
796
|
-
openclaw:
|
|
797
|
-
factoryDroid:
|
|
867
|
+
openclaw: existsSync4(path2.join(home, ".openclaw")),
|
|
868
|
+
factoryDroid: existsSync4(path2.join(home, ".factory")) || existsSync4(path2.join(process.cwd(), ".factory"))
|
|
798
869
|
};
|
|
799
870
|
}
|
|
800
871
|
function resolveAgentType(detected) {
|
|
@@ -862,13 +933,13 @@ var CLAUDE_END = "<!-- agentvalet:end -->";
|
|
|
862
933
|
var HOOK_COMMAND = "npx @agentvalet/register refresh --if-stale --quiet 2>/dev/null || true";
|
|
863
934
|
var HOOK_MARKER = "agentvalet/register refresh";
|
|
864
935
|
function projectRootFor(outputPath) {
|
|
865
|
-
return
|
|
936
|
+
return dirname2(dirname2(resolve2(outputPath)));
|
|
866
937
|
}
|
|
867
938
|
function writePermissionsFromEvent(agentId, ownerId, platforms, outputPath = "./.claude/agentvalet-permissions.md") {
|
|
868
939
|
const content = generatePermissionsFile(agentId, ownerId, platforms);
|
|
869
940
|
const resolvedPath = resolve2(outputPath);
|
|
870
941
|
const projectRoot = projectRootFor(resolvedPath);
|
|
871
|
-
mkdirSync2(
|
|
942
|
+
mkdirSync2(dirname2(resolvedPath), { recursive: true });
|
|
872
943
|
writeFileSync2(resolvedPath, content, "utf-8");
|
|
873
944
|
embedInClaudeMd(content, projectRoot);
|
|
874
945
|
ensureHook(projectRoot);
|
|
@@ -908,8 +979,8 @@ async function runRefresh(options) {
|
|
|
908
979
|
try {
|
|
909
980
|
const stat = statSync(outputPath);
|
|
910
981
|
const fileIsFresh = Date.now() - stat.mtimeMs < 36e5;
|
|
911
|
-
const claudeMdPath =
|
|
912
|
-
const claudeMd =
|
|
982
|
+
const claudeMdPath = join2(projectRoot, "CLAUDE.md");
|
|
983
|
+
const claudeMd = existsSync5(claudeMdPath) ? readFileSync4(claudeMdPath, "utf-8") : "";
|
|
913
984
|
const blockStart = claudeMd.indexOf(CLAUDE_START);
|
|
914
985
|
const blockEnd = claudeMd.indexOf(CLAUDE_END);
|
|
915
986
|
const blockContent = blockStart !== -1 && blockEnd !== -1 ? claudeMd.slice(blockStart, blockEnd) : "";
|
|
@@ -919,14 +990,14 @@ async function runRefresh(options) {
|
|
|
919
990
|
}
|
|
920
991
|
}
|
|
921
992
|
const creds = tryResolveAgent(options.envFile);
|
|
922
|
-
if (!creds || !
|
|
993
|
+
if (!creds || !existsSync5(resolve2(creds.keyPath))) {
|
|
923
994
|
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");
|
|
924
995
|
process.exit(1);
|
|
925
996
|
}
|
|
926
997
|
const { agentId, ownerId, keyPath, apiUrl } = creds;
|
|
927
998
|
let privateKeyPem;
|
|
928
999
|
try {
|
|
929
|
-
privateKeyPem =
|
|
1000
|
+
privateKeyPem = readFileSync4(keyPath, "utf-8");
|
|
930
1001
|
} catch {
|
|
931
1002
|
throw new Error(`Cannot read private key: ${keyPath}`);
|
|
932
1003
|
}
|
|
@@ -944,17 +1015,17 @@ async function runRefresh(options) {
|
|
|
944
1015
|
const json = await res.json();
|
|
945
1016
|
const platforms = json.data?.platforms ?? [];
|
|
946
1017
|
const content = generatePermissionsFile(agentId, ownerId, platforms);
|
|
947
|
-
mkdirSync2(
|
|
1018
|
+
mkdirSync2(dirname2(outputPath), { recursive: true });
|
|
948
1019
|
writeFileSync2(outputPath, content, "utf-8");
|
|
949
1020
|
embedInClaudeMd(content, projectRoot);
|
|
950
1021
|
ensureHook(projectRoot);
|
|
951
1022
|
const detected = detectFrameworks();
|
|
952
1023
|
const refreshed = [];
|
|
953
1024
|
if (detected.codex || detected.codexDesktop) {
|
|
954
|
-
if (embedPermissionsBlock(
|
|
1025
|
+
if (embedPermissionsBlock(join2(projectRoot, "AGENTS.md"), content)) refreshed.push("AGENTS.md");
|
|
955
1026
|
}
|
|
956
1027
|
if (detected.openclaw) {
|
|
957
|
-
if (embedPermissionsBlock(
|
|
1028
|
+
if (embedPermissionsBlock(join2(projectRoot, "SKILL.md"), content)) refreshed.push("SKILL.md");
|
|
958
1029
|
}
|
|
959
1030
|
if (!options.quiet) {
|
|
960
1031
|
const extras = refreshed.length > 0 ? ` (also: ${refreshed.join(", ")})` : "";
|
|
@@ -962,9 +1033,9 @@ async function runRefresh(options) {
|
|
|
962
1033
|
}
|
|
963
1034
|
}
|
|
964
1035
|
function embedPermissionsBlock(filePath, permissionsContent) {
|
|
965
|
-
if (!
|
|
1036
|
+
if (!existsSync5(filePath)) return false;
|
|
966
1037
|
try {
|
|
967
|
-
const existing =
|
|
1038
|
+
const existing = readFileSync4(filePath, "utf-8");
|
|
968
1039
|
const block = `${PERMISSIONS_START}
|
|
969
1040
|
${permissionsContent.trimEnd()}
|
|
970
1041
|
${PERMISSIONS_END}`;
|
|
@@ -987,18 +1058,18 @@ ${block}
|
|
|
987
1058
|
}
|
|
988
1059
|
}
|
|
989
1060
|
function embedInClaudeMd(permissionsContent, projectRoot) {
|
|
990
|
-
const claudeMdPath =
|
|
1061
|
+
const claudeMdPath = join2(projectRoot, "CLAUDE.md");
|
|
991
1062
|
const block = `${CLAUDE_START}
|
|
992
1063
|
${permissionsContent.trimEnd()}
|
|
993
1064
|
${CLAUDE_END}`;
|
|
994
|
-
if (!
|
|
1065
|
+
if (!existsSync5(claudeMdPath)) {
|
|
995
1066
|
writeFileSync2(claudeMdPath, `# Agent Instructions
|
|
996
1067
|
|
|
997
1068
|
${block}
|
|
998
1069
|
`, "utf-8");
|
|
999
1070
|
return;
|
|
1000
1071
|
}
|
|
1001
|
-
const existing =
|
|
1072
|
+
const existing = readFileSync4(claudeMdPath, "utf-8");
|
|
1002
1073
|
if (existing.includes(CLAUDE_START)) {
|
|
1003
1074
|
const replaced = existing.replace(
|
|
1004
1075
|
new RegExp(`${CLAUDE_START}[\\s\\S]*?${CLAUDE_END}`, "m"),
|
|
@@ -1014,11 +1085,11 @@ ${block}
|
|
|
1014
1085
|
}
|
|
1015
1086
|
}
|
|
1016
1087
|
function ensureHook(projectRoot) {
|
|
1017
|
-
const settingsPath =
|
|
1088
|
+
const settingsPath = join2(projectRoot, ".claude", "settings.local.json");
|
|
1018
1089
|
let settings = {};
|
|
1019
|
-
if (
|
|
1090
|
+
if (existsSync5(settingsPath)) {
|
|
1020
1091
|
try {
|
|
1021
|
-
settings = JSON.parse(
|
|
1092
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
1022
1093
|
} catch {
|
|
1023
1094
|
return;
|
|
1024
1095
|
}
|
|
@@ -1046,16 +1117,16 @@ function ensureHook(projectRoot) {
|
|
|
1046
1117
|
perms.allow = [...allow, ...missing];
|
|
1047
1118
|
settings.permissions = perms;
|
|
1048
1119
|
}
|
|
1049
|
-
mkdirSync2(
|
|
1120
|
+
mkdirSync2(dirname2(settingsPath), { recursive: true });
|
|
1050
1121
|
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
1051
1122
|
ensureMcpJson(projectRoot);
|
|
1052
1123
|
}
|
|
1053
1124
|
function ensureMcpJson(projectRoot) {
|
|
1054
|
-
const mcpJsonPath =
|
|
1125
|
+
const mcpJsonPath = join2(projectRoot, ".mcp.json");
|
|
1055
1126
|
let mcpConfig = {};
|
|
1056
|
-
if (
|
|
1127
|
+
if (existsSync5(mcpJsonPath)) {
|
|
1057
1128
|
try {
|
|
1058
|
-
mcpConfig = JSON.parse(
|
|
1129
|
+
mcpConfig = JSON.parse(readFileSync4(mcpJsonPath, "utf-8"));
|
|
1059
1130
|
} catch {
|
|
1060
1131
|
return;
|
|
1061
1132
|
}
|
|
@@ -1254,7 +1325,7 @@ async function writeLocalConfig(config) {
|
|
|
1254
1325
|
// src/output/env.ts
|
|
1255
1326
|
import chalk6 from "chalk";
|
|
1256
1327
|
import { appendFile as appendFile2, writeFile as writeFile3 } from "fs/promises";
|
|
1257
|
-
import { existsSync as
|
|
1328
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1258
1329
|
import path4 from "path";
|
|
1259
1330
|
function formatEnvBlock(vars) {
|
|
1260
1331
|
return Object.entries(vars).map(([key, value]) => {
|
|
@@ -1265,7 +1336,7 @@ function formatEnvBlock(vars) {
|
|
|
1265
1336
|
|
|
1266
1337
|
// src/config/ide.ts
|
|
1267
1338
|
import { mkdir as mkdir3, writeFile as writeFile4, readFile as readFile2, appendFile as appendFile3, copyFile } from "fs/promises";
|
|
1268
|
-
import { existsSync as
|
|
1339
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1269
1340
|
import path5 from "path";
|
|
1270
1341
|
import os2 from "os";
|
|
1271
1342
|
function buildMcpEntry(params) {
|
|
@@ -1302,7 +1373,7 @@ function claudeDesktopFriendly() {
|
|
|
1302
1373
|
}
|
|
1303
1374
|
async function mergeJsonFile(filePath, key, value) {
|
|
1304
1375
|
let existing = {};
|
|
1305
|
-
if (
|
|
1376
|
+
if (existsSync7(filePath)) {
|
|
1306
1377
|
try {
|
|
1307
1378
|
const text = await readFile2(filePath, "utf8");
|
|
1308
1379
|
existing = JSON.parse(text);
|
|
@@ -1324,8 +1395,8 @@ env = { AGENT_ID = "${params.agentId}", OWNER_ID = "${params.ownerId}", PROXY_UR
|
|
|
1324
1395
|
}
|
|
1325
1396
|
async function appendCodexConfig(configPath) {
|
|
1326
1397
|
const dir = path5.dirname(configPath);
|
|
1327
|
-
if (!
|
|
1328
|
-
const existing =
|
|
1398
|
+
if (!existsSync7(dir)) await mkdir3(dir, { recursive: true });
|
|
1399
|
+
const existing = existsSync7(configPath) ? await readFile2(configPath, "utf8") : "";
|
|
1329
1400
|
if (existing.includes("[mcp_servers.agentvalet]")) return false;
|
|
1330
1401
|
return true;
|
|
1331
1402
|
}
|
|
@@ -1334,7 +1405,7 @@ async function writeGlobalFrameworkConfigs(params) {
|
|
|
1334
1405
|
const codexDir = path5.join(os2.homedir(), ".codex");
|
|
1335
1406
|
const codexConfigPath = path5.join(codexDir, "config.toml");
|
|
1336
1407
|
try {
|
|
1337
|
-
if (!
|
|
1408
|
+
if (!existsSync7(codexDir)) await mkdir3(codexDir, { recursive: true });
|
|
1338
1409
|
if (await appendCodexConfig(codexConfigPath)) {
|
|
1339
1410
|
await appendFile3(codexConfigPath, buildCodexTomlBlock(params), "utf8");
|
|
1340
1411
|
written.push("~/.codex/config.toml");
|
|
@@ -1359,7 +1430,7 @@ async function writeCodexProjectConfig(params) {
|
|
|
1359
1430
|
return written;
|
|
1360
1431
|
}
|
|
1361
1432
|
async function stripAgentvaletFromMcpJson(filePath) {
|
|
1362
|
-
if (!
|
|
1433
|
+
if (!existsSync7(filePath)) return false;
|
|
1363
1434
|
try {
|
|
1364
1435
|
const text = await readFile2(filePath, "utf8");
|
|
1365
1436
|
const config = JSON.parse(text);
|
|
@@ -1383,7 +1454,7 @@ async function cleanProjectScopedEntries() {
|
|
|
1383
1454
|
cleaned.push(".cursor/mcp.json (project entry removed)");
|
|
1384
1455
|
}
|
|
1385
1456
|
const factoryProjectMcp = path5.join(cwd, ".factory/mcp.json");
|
|
1386
|
-
if (
|
|
1457
|
+
if (existsSync7(factoryProjectMcp)) {
|
|
1387
1458
|
try {
|
|
1388
1459
|
const text = await readFile2(factoryProjectMcp, "utf8");
|
|
1389
1460
|
const config = JSON.parse(text);
|
|
@@ -1474,7 +1545,7 @@ async function writeIdeConfigs(params) {
|
|
|
1474
1545
|
}
|
|
1475
1546
|
async function mergeFactoryDroidMcpJson(filePath, key, value) {
|
|
1476
1547
|
let existing = {};
|
|
1477
|
-
if (
|
|
1548
|
+
if (existsSync7(filePath)) {
|
|
1478
1549
|
try {
|
|
1479
1550
|
const text = await readFile2(filePath, "utf8");
|
|
1480
1551
|
existing = JSON.parse(text);
|
|
@@ -1488,7 +1559,7 @@ async function mergeFactoryDroidMcpJson(filePath, key, value) {
|
|
|
1488
1559
|
}
|
|
1489
1560
|
async function updateGitignoreForFactoryDroid() {
|
|
1490
1561
|
const gitignorePath = path5.join(process.cwd(), ".gitignore");
|
|
1491
|
-
if (!
|
|
1562
|
+
if (!existsSync7(gitignorePath)) return;
|
|
1492
1563
|
const existing = await readFile2(gitignorePath, "utf8");
|
|
1493
1564
|
if (existing.includes(".factory/mcp.json")) return;
|
|
1494
1565
|
const additions = [
|
|
@@ -1870,8 +1941,8 @@ import chalk10 from "chalk";
|
|
|
1870
1941
|
// src/commands/uninstall.ts
|
|
1871
1942
|
import { Command as Command2 } from "commander";
|
|
1872
1943
|
import chalk9 from "chalk";
|
|
1873
|
-
import { existsSync as
|
|
1874
|
-
import { resolve as resolve3, join as
|
|
1944
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync3, rmSync } from "fs";
|
|
1945
|
+
import { resolve as resolve3, join as join3 } from "path";
|
|
1875
1946
|
import { homedir } from "os";
|
|
1876
1947
|
var CLAUDE_START2 = "<!-- agentvalet:start -->";
|
|
1877
1948
|
var CLAUDE_END2 = "<!-- agentvalet:end -->";
|
|
@@ -1879,9 +1950,9 @@ var PERMISSIONS_START2 = "<!-- agentvalet:permissions:start -->";
|
|
|
1879
1950
|
var PERMISSIONS_END2 = "<!-- agentvalet:permissions:end -->";
|
|
1880
1951
|
var HOOK_MARKER2 = "agentvalet/register refresh";
|
|
1881
1952
|
function stripPermissionsBlock(filePath) {
|
|
1882
|
-
if (!
|
|
1953
|
+
if (!existsSync8(filePath)) return false;
|
|
1883
1954
|
try {
|
|
1884
|
-
const content =
|
|
1955
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
1885
1956
|
if (!content.includes(PERMISSIONS_START2)) return false;
|
|
1886
1957
|
const stripped = content.replace(new RegExp(`${PERMISSIONS_START2}[\\s\\S]*?${PERMISSIONS_END2}`, "m"), "").replace(/(\r?\n){3,}/g, "\n\n").trimEnd();
|
|
1887
1958
|
writeFileSync3(filePath, stripped + "\n", "utf-8");
|
|
@@ -1892,8 +1963,8 @@ function stripPermissionsBlock(filePath) {
|
|
|
1892
1963
|
}
|
|
1893
1964
|
function stripClaudeMd() {
|
|
1894
1965
|
const path8 = resolve3("CLAUDE.md");
|
|
1895
|
-
if (!
|
|
1896
|
-
let content =
|
|
1966
|
+
if (!existsSync8(path8)) return;
|
|
1967
|
+
let content = readFileSync5(path8, "utf-8");
|
|
1897
1968
|
content = content.replace(new RegExp(`${CLAUDE_START2}[\\s\\S]*?${CLAUDE_END2}`, "m"), "");
|
|
1898
1969
|
content = content.replace(/^@\.claude\/agentvalet-permissions\.md\s*$/m, "");
|
|
1899
1970
|
content = content.replace(/(\r?\n){3,}/g, "\n\n").trim();
|
|
@@ -1901,9 +1972,9 @@ function stripClaudeMd() {
|
|
|
1901
1972
|
}
|
|
1902
1973
|
function stripSettingsJson() {
|
|
1903
1974
|
const path8 = resolve3(".claude/settings.local.json");
|
|
1904
|
-
if (!
|
|
1975
|
+
if (!existsSync8(path8)) return;
|
|
1905
1976
|
try {
|
|
1906
|
-
const settings = JSON.parse(
|
|
1977
|
+
const settings = JSON.parse(readFileSync5(path8, "utf-8"));
|
|
1907
1978
|
const hooks = settings.hooks ?? {};
|
|
1908
1979
|
const ups = hooks.UserPromptSubmit ?? [];
|
|
1909
1980
|
hooks.UserPromptSubmit = ups.filter(
|
|
@@ -1919,9 +1990,9 @@ function stripSettingsJson() {
|
|
|
1919
1990
|
}
|
|
1920
1991
|
}
|
|
1921
1992
|
function stripMcpJson(filePath, options = {}) {
|
|
1922
|
-
if (!
|
|
1993
|
+
if (!existsSync8(filePath)) return false;
|
|
1923
1994
|
try {
|
|
1924
|
-
const config = JSON.parse(
|
|
1995
|
+
const config = JSON.parse(readFileSync5(filePath, "utf-8"));
|
|
1925
1996
|
let removed = false;
|
|
1926
1997
|
if (options.wrapped === false) {
|
|
1927
1998
|
if (config["agentvalet"] !== void 0) {
|
|
@@ -1945,9 +2016,9 @@ function stripMcpJson(filePath, options = {}) {
|
|
|
1945
2016
|
}
|
|
1946
2017
|
}
|
|
1947
2018
|
function stripCodexTomlAt(filePath) {
|
|
1948
|
-
if (!
|
|
2019
|
+
if (!existsSync8(filePath)) return false;
|
|
1949
2020
|
try {
|
|
1950
|
-
const content =
|
|
2021
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
1951
2022
|
if (!content.includes("[mcp_servers.agentvalet]")) return false;
|
|
1952
2023
|
const stripped = content.replace(
|
|
1953
2024
|
/\r?\n?\[mcp_servers\.agentvalet\][\s\S]*?(?=\r?\n\[|\r?\n*$)/,
|
|
@@ -1960,9 +2031,9 @@ function stripCodexTomlAt(filePath) {
|
|
|
1960
2031
|
}
|
|
1961
2032
|
}
|
|
1962
2033
|
function isAgentValetGeneratedDoc(filePath, markers) {
|
|
1963
|
-
if (!
|
|
2034
|
+
if (!existsSync8(filePath)) return false;
|
|
1964
2035
|
try {
|
|
1965
|
-
const content =
|
|
2036
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
1966
2037
|
const firstLine = content.split(/\r?\n/).find((l) => l.trim().length > 0) ?? "";
|
|
1967
2038
|
return markers.some((m) => firstLine.startsWith(m));
|
|
1968
2039
|
} catch {
|
|
@@ -1974,13 +2045,13 @@ function cleanLocally() {
|
|
|
1974
2045
|
const stripped = [];
|
|
1975
2046
|
const r = (p) => {
|
|
1976
2047
|
const abs = resolve3(p);
|
|
1977
|
-
if (
|
|
2048
|
+
if (existsSync8(abs)) {
|
|
1978
2049
|
rmSync(abs, { recursive: true, force: true });
|
|
1979
2050
|
removed.push(abs);
|
|
1980
2051
|
}
|
|
1981
2052
|
};
|
|
1982
2053
|
const rAbs = (abs) => {
|
|
1983
|
-
if (
|
|
2054
|
+
if (existsSync8(abs)) {
|
|
1984
2055
|
rmSync(abs, { recursive: true, force: true });
|
|
1985
2056
|
removed.push(abs);
|
|
1986
2057
|
}
|
|
@@ -2003,12 +2074,12 @@ function cleanLocally() {
|
|
|
2003
2074
|
if (stripMcpJson(resolve3(".cursor/mcp.json"))) stripped.push(".cursor/mcp.json");
|
|
2004
2075
|
if (stripMcpJson(resolve3(".factory/mcp.json"), { wrapped: false })) stripped.push(".factory/mcp.json");
|
|
2005
2076
|
const home = homedir();
|
|
2006
|
-
if (stripMcpJson(
|
|
2007
|
-
const claudeDesktopConfig = process.platform === "darwin" ?
|
|
2077
|
+
if (stripMcpJson(join3(home, ".claude.json"))) stripped.push("~/.claude.json");
|
|
2078
|
+
const claudeDesktopConfig = process.platform === "darwin" ? join3(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join3(process.env.APPDATA ?? join3(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json") : join3(home, ".config", "Claude", "claude_desktop_config.json");
|
|
2008
2079
|
if (stripMcpJson(claudeDesktopConfig)) stripped.push("claude_desktop_config.json");
|
|
2009
|
-
if (stripMcpJson(
|
|
2010
|
-
if (stripMcpJson(
|
|
2011
|
-
if (stripCodexTomlAt(
|
|
2080
|
+
if (stripMcpJson(join3(home, ".cursor", "mcp.json"))) stripped.push("~/.cursor/mcp.json");
|
|
2081
|
+
if (stripMcpJson(join3(home, ".factory", "mcp.json"), { wrapped: false })) stripped.push("~/.factory/mcp.json");
|
|
2082
|
+
if (stripCodexTomlAt(join3(home, ".codex", "config.toml"))) stripped.push("~/.codex/config.toml");
|
|
2012
2083
|
if (stripCodexTomlAt(resolve3(".codex/config.toml"))) stripped.push(".codex/config.toml");
|
|
2013
2084
|
stripClaudeMd();
|
|
2014
2085
|
stripSettingsJson();
|
|
@@ -2113,7 +2184,7 @@ function uninstallCommand() {
|
|
|
2113
2184
|
}
|
|
2114
2185
|
let privateKeyPem;
|
|
2115
2186
|
try {
|
|
2116
|
-
privateKeyPem =
|
|
2187
|
+
privateKeyPem = readFileSync5(creds.keyPath, "utf-8").trim();
|
|
2117
2188
|
} catch {
|
|
2118
2189
|
}
|
|
2119
2190
|
if (!privateKeyPem) {
|
|
@@ -2230,13 +2301,13 @@ function revokeCommand() {
|
|
|
2230
2301
|
import { Command as Command4 } from "commander";
|
|
2231
2302
|
import chalk11 from "chalk";
|
|
2232
2303
|
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile8 } from "fs/promises";
|
|
2233
|
-
import { dirname as
|
|
2304
|
+
import { dirname as dirname3 } from "path";
|
|
2234
2305
|
|
|
2235
2306
|
// src/lib/claude-config.ts
|
|
2236
2307
|
import { homedir as homedir2 } from "os";
|
|
2237
|
-
import { join as
|
|
2308
|
+
import { join as join4 } from "path";
|
|
2238
2309
|
import { readFile as readFile3, writeFile as writeFile6, mkdir as mkdir4, access } from "fs/promises";
|
|
2239
|
-
var CLAUDE_CONFIG_PATH =
|
|
2310
|
+
var CLAUDE_CONFIG_PATH = join4(homedir2(), ".claude", "config");
|
|
2240
2311
|
async function writeKeyToClaudeConfig(agentId, privateKeyPem) {
|
|
2241
2312
|
let config = {};
|
|
2242
2313
|
try {
|
|
@@ -2247,7 +2318,7 @@ async function writeKeyToClaudeConfig(agentId, privateKeyPem) {
|
|
|
2247
2318
|
}
|
|
2248
2319
|
config.agent_keys = config.agent_keys ?? {};
|
|
2249
2320
|
config.agent_keys[agentId] = Buffer.from(privateKeyPem).toString("base64");
|
|
2250
|
-
await mkdir4(
|
|
2321
|
+
await mkdir4(join4(homedir2(), ".claude"), { recursive: true });
|
|
2251
2322
|
await writeFile6(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
2252
2323
|
}
|
|
2253
2324
|
|
|
@@ -2282,7 +2353,7 @@ function injectCommand() {
|
|
|
2282
2353
|
"--target <target>",
|
|
2283
2354
|
"Where to write the key: agentvalet (default \u2014 modern store + MCP configs), env-local (.env.local), claude-legacy (~/.claude/config, deprecated)",
|
|
2284
2355
|
"agentvalet"
|
|
2285
|
-
).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) => {
|
|
2356
|
+
).option("--no-frameworks", "Skip IDE/framework configuration prompts (only with --target agentvalet)").option("--project", "Write project-scoped framework configs instead of user-scoped").option("--force", "Replace an existing agent key (a timestamped backup is written first)").action(async (opts) => {
|
|
2286
2357
|
let privateKeyPem;
|
|
2287
2358
|
try {
|
|
2288
2359
|
privateKeyPem = await readFile5(opts.keyFile, "utf-8");
|
|
@@ -2302,7 +2373,8 @@ function injectCommand() {
|
|
|
2302
2373
|
console.error(chalk11.dim(" Find it at https://app.agentvalet.ai or in your existing ~/.agentvalet/config.json"));
|
|
2303
2374
|
process.exit(1);
|
|
2304
2375
|
}
|
|
2305
|
-
await mkdir5(
|
|
2376
|
+
await mkdir5(dirname3(AGENT_KEY_PATH), { recursive: true });
|
|
2377
|
+
assertSafeToWriteKey(AGENT_KEY_PATH, opts.agentId, { force: opts.force });
|
|
2306
2378
|
await writeFile8(AGENT_KEY_PATH, privateKeyPem, { mode: 384 });
|
|
2307
2379
|
saveConfig({
|
|
2308
2380
|
agentId: opts.agentId,
|
|
@@ -2357,7 +2429,7 @@ function injectCommand() {
|
|
|
2357
2429
|
process.exit(1);
|
|
2358
2430
|
}
|
|
2359
2431
|
} catch (err) {
|
|
2360
|
-
console.error(chalk11.red(String(err)));
|
|
2432
|
+
console.error(chalk11.red(err instanceof KeyClobberError ? err.message : String(err)));
|
|
2361
2433
|
process.exit(1);
|
|
2362
2434
|
}
|
|
2363
2435
|
console.log(chalk11.green("\n Done. Your agent is ready to use."));
|
|
@@ -2400,7 +2472,7 @@ async function bindInstallToken(proxyUrl, bootstrapToken) {
|
|
|
2400
2472
|
return { ...body, privateKeyPem: privateKey };
|
|
2401
2473
|
}
|
|
2402
2474
|
function installCommand() {
|
|
2403
|
-
return new Command5("install").description("Install an agent on this machine using a one-time install token (generates the keypair locally and binds it)").argument("<token>", "install token (itok_...) from the dashboard").option("--no-frameworks", "skip IDE/framework configuration prompts").option("--project", "write project-scoped configs (.mcp.json, .cursor/mcp.json) instead of user-scoped (~/.claude.json, ~/.cursor/mcp.json)").action(async (token, opts) => {
|
|
2475
|
+
return new Command5("install").description("Install an agent on this machine using a one-time install token (generates the keypair locally and binds it)").argument("<token>", "install token (itok_...) from the dashboard").option("--no-frameworks", "skip IDE/framework configuration prompts").option("--project", "write project-scoped configs (.mcp.json, .cursor/mcp.json) instead of user-scoped (~/.claude.json, ~/.cursor/mcp.json)").option("--force", "replace an existing agent key (a timestamped backup is written first)").action(async (token, opts) => {
|
|
2404
2476
|
const scope = opts.project ? "project" : "user";
|
|
2405
2477
|
if (!token.startsWith("itok_")) {
|
|
2406
2478
|
console.error(chalk12.red('Invalid install token. Tokens start with "itok_".'));
|
|
@@ -2426,8 +2498,14 @@ function installCommand() {
|
|
|
2426
2498
|
const agent_name = claim.agent_id;
|
|
2427
2499
|
try {
|
|
2428
2500
|
await mkdir6(AGENT_DIR, { recursive: true });
|
|
2501
|
+
assertSafeToWriteKey(AGENT_KEY_PATH, agent_id, { force: opts.force });
|
|
2429
2502
|
await writeFile9(AGENT_KEY_PATH, private_key_pem, { mode: 384 });
|
|
2430
2503
|
} catch (err) {
|
|
2504
|
+
if (err instanceof KeyClobberError) {
|
|
2505
|
+
console.error(chalk12.red(`
|
|
2506
|
+
\u2717 ${err.message}`));
|
|
2507
|
+
process.exit(3);
|
|
2508
|
+
}
|
|
2431
2509
|
console.error(chalk12.red(`
|
|
2432
2510
|
\u2717 Failed to write key: ${err instanceof Error ? err.message : err}`));
|
|
2433
2511
|
process.exit(3);
|
|
@@ -2563,14 +2641,14 @@ function installCommand() {
|
|
|
2563
2641
|
// src/commands/claim.ts
|
|
2564
2642
|
import { Command as Command6 } from "commander";
|
|
2565
2643
|
import { generateKeyPairSync as generateKeyPairSync2 } from "crypto";
|
|
2566
|
-
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, existsSync as
|
|
2644
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "fs";
|
|
2567
2645
|
import { homedir as homedir3 } from "os";
|
|
2568
|
-
import { join as
|
|
2646
|
+
import { join as join5 } from "path";
|
|
2569
2647
|
import chalk13 from "chalk";
|
|
2570
2648
|
var DEFAULT_PROXY_URL = "https://api.agentvalet.ai";
|
|
2571
2649
|
function resolveAgentDir(label) {
|
|
2572
|
-
const root =
|
|
2573
|
-
return label ?
|
|
2650
|
+
const root = join5(homedir3(), ".agentvalet");
|
|
2651
|
+
return label ? join5(root, label) : root;
|
|
2574
2652
|
}
|
|
2575
2653
|
function sanitiseLabel(label) {
|
|
2576
2654
|
return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -2614,9 +2692,9 @@ function claimCommand() {
|
|
|
2614
2692
|
process.exit(3);
|
|
2615
2693
|
}
|
|
2616
2694
|
const agentDir = resolveAgentDir(label);
|
|
2617
|
-
const keyPath =
|
|
2618
|
-
const identityPath =
|
|
2619
|
-
if (
|
|
2695
|
+
const keyPath = join5(agentDir, "agent.key");
|
|
2696
|
+
const identityPath = join5(agentDir, "agent.json");
|
|
2697
|
+
if (existsSync9(keyPath)) {
|
|
2620
2698
|
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.`;
|
|
2621
2699
|
console.error(isJson ? JSON.stringify({ error: msg }) : chalk13.red(msg));
|
|
2622
2700
|
process.exit(3);
|
|
@@ -2708,7 +2786,7 @@ function collectScope(value, previous) {
|
|
|
2708
2786
|
return [...previous, value];
|
|
2709
2787
|
}
|
|
2710
2788
|
var program = new Command7();
|
|
2711
|
-
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) => {
|
|
2789
|
+
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)").option("--force", "Replace an existing agent's key (a timestamped backup is written first)").action(async (opts) => {
|
|
2712
2790
|
const isJson = !!opts.json;
|
|
2713
2791
|
let ownerId = opts.owner ?? process.env.AGENTVALET_OWNER ?? "";
|
|
2714
2792
|
let agentName = opts.name ?? "";
|
|
@@ -2824,10 +2902,15 @@ Approve at: https://app.agentvalet.ai/agents
|
|
|
2824
2902
|
agentId,
|
|
2825
2903
|
ownerId: resolvedOwnerId,
|
|
2826
2904
|
privateKey,
|
|
2827
|
-
apiUrl: opts.apiUrl
|
|
2905
|
+
apiUrl: opts.apiUrl,
|
|
2906
|
+
force: !!opts.force
|
|
2828
2907
|
}));
|
|
2829
2908
|
} catch (err) {
|
|
2830
2909
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2910
|
+
if (err instanceof KeyClobberError) {
|
|
2911
|
+
console.error(isJson ? JSON.stringify({ error: msg, existing_agent_id: err.existingAgentId }) : chalk14.red(msg));
|
|
2912
|
+
process.exit(3);
|
|
2913
|
+
}
|
|
2831
2914
|
console.error(isJson ? JSON.stringify({ error: msg }) : chalk14.red(`Failed to save output: ${msg}`));
|
|
2832
2915
|
process.exit(3);
|
|
2833
2916
|
}
|