@agentvalet/register 0.4.11 → 0.4.13
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 +320 -35
- package/dist/mcp-server-R3LG4PNM.js +46 -0
- package/package.json +10 -10
- package/dist/mcp-server-SBGWYBP7.js +0 -139
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
4
5
|
import { Command as Command5 } from "commander";
|
|
5
6
|
import chalk12 from "chalk";
|
|
6
7
|
|
|
@@ -191,12 +192,26 @@ async function saveOutput(opts) {
|
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
// src/commands/refresh.ts
|
|
194
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync, statSync } from "fs";
|
|
195
|
-
import { createSign, createPrivateKey } from "crypto";
|
|
195
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
196
196
|
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
197
197
|
|
|
198
|
+
// src/lib/jwt.ts
|
|
199
|
+
import { createSign, createPrivateKey } from "crypto";
|
|
200
|
+
function signJwt(agentId, ownerId, privateKeyPem) {
|
|
201
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
202
|
+
const b64 = (s) => Buffer.from(s).toString("base64url");
|
|
203
|
+
const header = b64(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
204
|
+
const payload = b64(
|
|
205
|
+
JSON.stringify({ agent_id: agentId, owner_id: ownerId, iat: now, exp: now + 300 })
|
|
206
|
+
);
|
|
207
|
+
const signingInput = `${header}.${payload}`;
|
|
208
|
+
const sign = createSign("RSA-SHA256");
|
|
209
|
+
sign.update(signingInput);
|
|
210
|
+
return `${signingInput}.${sign.sign(createPrivateKey(privateKeyPem), "base64url")}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
198
213
|
// src/lib/skill-writer.ts
|
|
199
|
-
import { existsSync, writeFileSync } from "fs";
|
|
214
|
+
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
200
215
|
import { execSync } from "child_process";
|
|
201
216
|
import path from "path";
|
|
202
217
|
import chalk2 from "chalk";
|
|
@@ -301,6 +316,11 @@ The \`.mcp.json\` in this directory has been configured. Run \`/mcp\` in Claude
|
|
|
301
316
|
if (detected.openclaw) {
|
|
302
317
|
lines.push(`### OpenClaw
|
|
303
318
|
See \`SKILL.md\` for platform call instructions.`);
|
|
319
|
+
}
|
|
320
|
+
if (detected.factoryDroid) {
|
|
321
|
+
lines.push(`### Factory Droid
|
|
322
|
+
\`~/.factory/mcp.json\` has been updated with the AgentValet MCP server block.
|
|
323
|
+
Type \`@AgentValet\` in Factory Droid to activate governance. Use \`/av-status\` to verify connections.`);
|
|
304
324
|
}
|
|
305
325
|
if (lines.length === 0) {
|
|
306
326
|
lines.push(`Add the MCP server to your IDE manually. See: https://docs.agentvalet.ai/setup`);
|
|
@@ -355,6 +375,203 @@ AGENT_PRIVATE_KEY_PATH=${keyPath}
|
|
|
355
375
|
console.log(chalk2.green(" \u2713 AGENTVALET.md created \u2014 quick reference for this agent"));
|
|
356
376
|
console.log(chalk2.dim(" Add to .gitignore if it contains sensitive paths"));
|
|
357
377
|
}
|
|
378
|
+
function writeFactoryDroidDroid() {
|
|
379
|
+
const droidsDir = path.join(process.cwd(), ".factory", "droids");
|
|
380
|
+
if (!existsSync(droidsDir)) mkdirSync(droidsDir, { recursive: true });
|
|
381
|
+
const dest = path.join(droidsDir, "agentvalet.yaml");
|
|
382
|
+
if (existsSync(dest)) {
|
|
383
|
+
console.log(chalk2.dim(" Factory Droid: .factory/droids/agentvalet.yaml already exists"));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const content = `# AgentValet Governance Droid
|
|
387
|
+
# Automatically created by @agentvalet/register
|
|
388
|
+
# Commit this file to your repo
|
|
389
|
+
|
|
390
|
+
name: AgentValet
|
|
391
|
+
description: >
|
|
392
|
+
Routes all platform API calls through AgentValet's credential
|
|
393
|
+
proxy. Enforces per-agent permissions, rate limits, and human
|
|
394
|
+
approval for high-risk operations. Every call is logged.
|
|
395
|
+
|
|
396
|
+
model: inherit
|
|
397
|
+
|
|
398
|
+
mcp_servers:
|
|
399
|
+
- agentvalet
|
|
400
|
+
|
|
401
|
+
autonomy: Low
|
|
402
|
+
|
|
403
|
+
instructions: |
|
|
404
|
+
You are operating under AgentValet credential governance.
|
|
405
|
+
|
|
406
|
+
## Rules \u2014 read before every platform call
|
|
407
|
+
|
|
408
|
+
1. NEVER call platform APIs (Slack, Stripe, Gmail, GitHub, etc.) directly.
|
|
409
|
+
Always use the \`use_platform\` MCP tool.
|
|
410
|
+
|
|
411
|
+
2. Call \`list_platforms\` at the start of any session that involves
|
|
412
|
+
platform calls. Do not assume platforms are available.
|
|
413
|
+
|
|
414
|
+
3. Call \`authzen_evaluate\` before any destructive operation
|
|
415
|
+
(delete, send, charge, publish). Do not proceed if decision=false.
|
|
416
|
+
|
|
417
|
+
4. If \`use_platform\` returns \`{ status: "pending_approval" }\`,
|
|
418
|
+
wait for human approval. Do not retry the call.
|
|
419
|
+
|
|
420
|
+
5. If AgentValet MCP tools are unavailable, stop and tell the user:
|
|
421
|
+
"AgentValet MCP is not connected. Run: npx @agentvalet/register"
|
|
422
|
+
Do not attempt platform calls without governance.
|
|
423
|
+
|
|
424
|
+
## Making a platform call
|
|
425
|
+
|
|
426
|
+
\`\`\`
|
|
427
|
+
use_platform({
|
|
428
|
+
platform: "slack",
|
|
429
|
+
endpoint: "/api/chat.postMessage",
|
|
430
|
+
method: "POST",
|
|
431
|
+
scope: "slack:write",
|
|
432
|
+
data: { channel: "#general", text: "Hello" }
|
|
433
|
+
})
|
|
434
|
+
\`\`\`
|
|
435
|
+
|
|
436
|
+
## Scope categories
|
|
437
|
+
|
|
438
|
+
- Read scopes (*.read, *.list) \u2014 lower risk, may auto-approve
|
|
439
|
+
- Write scopes (*.write, *.create) \u2014 check authzen_evaluate first
|
|
440
|
+
- Destructive scopes (*.delete, stripe:charge, mail:send) \u2014 always
|
|
441
|
+
call authzen_evaluate, always inform user before proceeding
|
|
442
|
+
|
|
443
|
+
Dashboard: https://app.agentvalet.ai
|
|
444
|
+
Docs: https://docs.agentvalet.ai
|
|
445
|
+
`;
|
|
446
|
+
writeFileSync(dest, content, "utf8");
|
|
447
|
+
console.log(chalk2.green(" \u2713 Factory Droid: created .factory/droids/agentvalet.yaml"));
|
|
448
|
+
console.log(chalk2.dim(" Commit this file to your repo"));
|
|
449
|
+
}
|
|
450
|
+
function writeFactoryDroidCommand() {
|
|
451
|
+
const commandsDir = path.join(process.cwd(), ".factory", "commands");
|
|
452
|
+
if (!existsSync(commandsDir)) mkdirSync(commandsDir, { recursive: true });
|
|
453
|
+
const statusDest = path.join(commandsDir, "av-status.md");
|
|
454
|
+
if (!existsSync(statusDest)) {
|
|
455
|
+
const statusContent = `# /av-status
|
|
456
|
+
|
|
457
|
+
Check your AgentValet agent status and approved platform connections.
|
|
458
|
+
|
|
459
|
+
## Instructions for the droid
|
|
460
|
+
|
|
461
|
+
1. Call \`list_platforms\` using the AgentValet MCP server
|
|
462
|
+
2. Display the results in a clean table:
|
|
463
|
+
- Platform name
|
|
464
|
+
- Approved scopes
|
|
465
|
+
- Whether any scope requires human approval
|
|
466
|
+
3. If no platforms are approved yet, tell the user to visit
|
|
467
|
+
https://app.agentvalet.ai/agents to configure permissions
|
|
468
|
+
4. If AgentValet MCP is not connected, explain how to set it up:
|
|
469
|
+
Run: npx @agentvalet/register
|
|
470
|
+
`;
|
|
471
|
+
writeFileSync(statusDest, statusContent, "utf8");
|
|
472
|
+
console.log(chalk2.green(" \u2713 Factory Droid: created .factory/commands/av-status.md"));
|
|
473
|
+
} else {
|
|
474
|
+
console.log(chalk2.dim(" Factory Droid: .factory/commands/av-status.md already exists"));
|
|
475
|
+
}
|
|
476
|
+
const registerDest = path.join(commandsDir, "av-register.md");
|
|
477
|
+
if (!existsSync(registerDest)) {
|
|
478
|
+
const registerContent = `# /av-register
|
|
479
|
+
|
|
480
|
+
Register or re-register this project with AgentValet.
|
|
481
|
+
|
|
482
|
+
## Instructions for the droid
|
|
483
|
+
|
|
484
|
+
Tell the user to run the following command in their terminal:
|
|
485
|
+
|
|
486
|
+
\`\`\`bash
|
|
487
|
+
npx @agentvalet/register
|
|
488
|
+
\`\`\`
|
|
489
|
+
|
|
490
|
+
This will:
|
|
491
|
+
1. Authenticate with GitHub (opens browser)
|
|
492
|
+
2. Create or find their AgentValet account
|
|
493
|
+
3. Register this machine as an agent
|
|
494
|
+
4. Configure Factory Droid MCP integration automatically
|
|
495
|
+
|
|
496
|
+
After running, approve the agent at: https://app.agentvalet.ai/agents
|
|
497
|
+
`;
|
|
498
|
+
writeFileSync(registerDest, registerContent, "utf8");
|
|
499
|
+
console.log(chalk2.green(" \u2713 Factory Droid: created .factory/commands/av-register.md"));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function writeFactoryDroidReliabilityDroid() {
|
|
503
|
+
const droidsDir = path.join(process.cwd(), ".factory", "droids");
|
|
504
|
+
if (!existsSync(droidsDir)) mkdirSync(droidsDir, { recursive: true });
|
|
505
|
+
const dest = path.join(droidsDir, "agentvalet-reliability.yaml");
|
|
506
|
+
if (existsSync(dest)) return;
|
|
507
|
+
const content = `# AgentValet Reliability Droid
|
|
508
|
+
# Monitors agent call patterns and surfaces governance issues
|
|
509
|
+
# Commit this file to your repo
|
|
510
|
+
|
|
511
|
+
name: AgentValet Reliability
|
|
512
|
+
description: >
|
|
513
|
+
Monitors AI agent behaviour against AgentValet governance rules.
|
|
514
|
+
Surfaces approval-required actions, scope violations, rate limit
|
|
515
|
+
warnings, and audit anomalies directly in Factory Droid.
|
|
516
|
+
|
|
517
|
+
type: reliability
|
|
518
|
+
model: inherit
|
|
519
|
+
mcp_servers:
|
|
520
|
+
- agentvalet
|
|
521
|
+
autonomy: Low
|
|
522
|
+
|
|
523
|
+
triggers:
|
|
524
|
+
- on_agent_action: true
|
|
525
|
+
- on_approval_required: true
|
|
526
|
+
- on_scope_violation: true
|
|
527
|
+
|
|
528
|
+
instructions: |
|
|
529
|
+
You are a governance monitor. You observe what the other droids
|
|
530
|
+
are doing and flag any actions that require human oversight.
|
|
531
|
+
|
|
532
|
+
## Your responsibilities
|
|
533
|
+
|
|
534
|
+
1. BEFORE any droid makes a platform API call, check if the scope
|
|
535
|
+
requires approval using \`authzen_evaluate\`:
|
|
536
|
+
- If decision=false: block the call, explain why, suggest alternatives
|
|
537
|
+
- If decision=true: allow, but log it
|
|
538
|
+
|
|
539
|
+
2. WHEN a droid calls \`use_platform\`, verify:
|
|
540
|
+
- The platform is in the approved list from \`list_platforms\`
|
|
541
|
+
- The scope matches what was approved
|
|
542
|
+
- The call hasn't exceeded rate limits
|
|
543
|
+
|
|
544
|
+
3. SURFACE approval-required actions clearly:
|
|
545
|
+
"\u26A0 This action requires human approval: [action description]
|
|
546
|
+
Waiting for approval at app.agentvalet.ai/approvals"
|
|
547
|
+
|
|
548
|
+
4. MONITOR for unusual patterns:
|
|
549
|
+
- Multiple calls to the same destructive scope in quick succession
|
|
550
|
+
- Calls to platforms not in the approved list
|
|
551
|
+
- Call volume spikes beyond normal operation
|
|
552
|
+
|
|
553
|
+
5. REPORT governance status when asked:
|
|
554
|
+
- Current approved platforms and scopes
|
|
555
|
+
- Pending approvals
|
|
556
|
+
- Recent audit log entries (call list_platforms + agent_status)
|
|
557
|
+
|
|
558
|
+
## What you do NOT do
|
|
559
|
+
|
|
560
|
+
- You do not make platform API calls yourself
|
|
561
|
+
- You do not approve actions on behalf of the human
|
|
562
|
+
- You do not bypass governance even if another droid requests it
|
|
563
|
+
|
|
564
|
+
## Escalation
|
|
565
|
+
|
|
566
|
+
If you detect a potential security concern (unexpected scope,
|
|
567
|
+
unusual call pattern), immediately tell the human:
|
|
568
|
+
"\u{1F534} Governance alert: [description]. Recommend reviewing at
|
|
569
|
+
app.agentvalet.ai/audit"
|
|
570
|
+
`;
|
|
571
|
+
writeFileSync(dest, content, "utf8");
|
|
572
|
+
console.log(chalk2.green(" \u2713 Factory Droid: created .factory/droids/agentvalet-reliability.yaml"));
|
|
573
|
+
console.log(chalk2.dim(" Commit this file to your repo"));
|
|
574
|
+
}
|
|
358
575
|
function ensureMcpServer() {
|
|
359
576
|
try {
|
|
360
577
|
execSync("npx --yes @agentvalet/mcp-server --version", {
|
|
@@ -500,23 +717,10 @@ var CLAUDE_START = "<!-- agentvalet:start -->";
|
|
|
500
717
|
var CLAUDE_END = "<!-- agentvalet:end -->";
|
|
501
718
|
var HOOK_COMMAND = "npx @agentvalet/register refresh --if-stale --quiet 2>/dev/null || true";
|
|
502
719
|
var HOOK_MARKER = "agentvalet/register refresh";
|
|
503
|
-
function base64url(input) {
|
|
504
|
-
return Buffer.from(input).toString("base64url");
|
|
505
|
-
}
|
|
506
|
-
function signJwt(agentId, ownerId, privateKeyPem) {
|
|
507
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
508
|
-
const header = base64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
509
|
-
const payload = base64url(JSON.stringify({ agent_id: agentId, owner_id: ownerId, iat: now, exp: now + 300 }));
|
|
510
|
-
const signingInput = `${header}.${payload}`;
|
|
511
|
-
const sign = createSign("RSA-SHA256");
|
|
512
|
-
sign.update(signingInput);
|
|
513
|
-
const signature = sign.sign(createPrivateKey(privateKeyPem), "base64url");
|
|
514
|
-
return `${signingInput}.${signature}`;
|
|
515
|
-
}
|
|
516
720
|
function writePermissionsFromEvent(agentId, ownerId, platforms, outputPath = "./.claude/agentvalet-permissions.md") {
|
|
517
721
|
const content = generatePermissionsFile(agentId, ownerId, platforms);
|
|
518
722
|
const resolvedPath = resolve3(outputPath);
|
|
519
|
-
|
|
723
|
+
mkdirSync2(dirname2(resolvedPath), { recursive: true });
|
|
520
724
|
writeFileSync2(resolvedPath, content, "utf-8");
|
|
521
725
|
embedInClaudeMd(content);
|
|
522
726
|
ensureHook();
|
|
@@ -563,7 +767,7 @@ async function runRefresh(options) {
|
|
|
563
767
|
const json = await res.json();
|
|
564
768
|
const platforms = json.data?.platforms ?? [];
|
|
565
769
|
const content = generatePermissionsFile(agentId, ownerId, platforms);
|
|
566
|
-
|
|
770
|
+
mkdirSync2(dirname2(outputPath), { recursive: true });
|
|
567
771
|
writeFileSync2(outputPath, content, "utf-8");
|
|
568
772
|
embedInClaudeMd(content);
|
|
569
773
|
ensureHook();
|
|
@@ -625,7 +829,7 @@ function ensureHook() {
|
|
|
625
829
|
perms.allow = [...allow, ...missing];
|
|
626
830
|
settings.permissions = perms;
|
|
627
831
|
}
|
|
628
|
-
|
|
832
|
+
mkdirSync2(dirname2(settingsPath), { recursive: true });
|
|
629
833
|
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
630
834
|
ensureMcpJson();
|
|
631
835
|
}
|
|
@@ -847,7 +1051,7 @@ function formatEnvBlock(vars) {
|
|
|
847
1051
|
}
|
|
848
1052
|
|
|
849
1053
|
// src/config/ide.ts
|
|
850
|
-
import { mkdir as mkdir3, writeFile as writeFile4, readFile as readFile2, appendFile as appendFile3 } from "fs/promises";
|
|
1054
|
+
import { mkdir as mkdir3, writeFile as writeFile4, readFile as readFile2, appendFile as appendFile3, copyFile } from "fs/promises";
|
|
851
1055
|
import { existsSync as existsSync5 } from "fs";
|
|
852
1056
|
import path4 from "path";
|
|
853
1057
|
import os from "os";
|
|
@@ -978,6 +1182,72 @@ async function writeIdeConfigs(params) {
|
|
|
978
1182
|
console.warn(` Warning: could not write .cursor/mcp.json: ${err.message}`);
|
|
979
1183
|
}
|
|
980
1184
|
}
|
|
1185
|
+
if (detected.factoryDroid) {
|
|
1186
|
+
const fdResults = await writeFactoryDroidConfig(mcpParams);
|
|
1187
|
+
written.push(...fdResults);
|
|
1188
|
+
}
|
|
1189
|
+
return written;
|
|
1190
|
+
}
|
|
1191
|
+
async function mergeFactoryDroidMcpJson(filePath, key, value) {
|
|
1192
|
+
let existing = {};
|
|
1193
|
+
if (existsSync5(filePath)) {
|
|
1194
|
+
try {
|
|
1195
|
+
const text = await readFile2(filePath, "utf8");
|
|
1196
|
+
existing = JSON.parse(text);
|
|
1197
|
+
} catch {
|
|
1198
|
+
await copyFile(filePath, filePath + ".backup");
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
if (existing[key]) return;
|
|
1202
|
+
existing[key] = value;
|
|
1203
|
+
await writeFile4(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
1204
|
+
}
|
|
1205
|
+
async function updateGitignoreForFactoryDroid() {
|
|
1206
|
+
const gitignorePath = path4.join(process.cwd(), ".gitignore");
|
|
1207
|
+
if (!existsSync5(gitignorePath)) return;
|
|
1208
|
+
const existing = await readFile2(gitignorePath, "utf8");
|
|
1209
|
+
if (existing.includes(".factory/mcp.json")) return;
|
|
1210
|
+
const additions = [
|
|
1211
|
+
"",
|
|
1212
|
+
"# Factory Droid \u2014 private key in MCP config",
|
|
1213
|
+
".factory/mcp.json",
|
|
1214
|
+
"",
|
|
1215
|
+
"# Factory Droid \u2014 commit these:",
|
|
1216
|
+
"# .factory/droids/",
|
|
1217
|
+
"# .factory/commands/"
|
|
1218
|
+
].join("\n");
|
|
1219
|
+
await appendFile3(gitignorePath, additions + "\n", "utf8");
|
|
1220
|
+
}
|
|
1221
|
+
async function writeFactoryDroidConfig(params) {
|
|
1222
|
+
const home = os.homedir();
|
|
1223
|
+
const written = [];
|
|
1224
|
+
const entry = buildMcpEntry(params);
|
|
1225
|
+
const globalDir = path4.join(home, ".factory");
|
|
1226
|
+
const projectDir = path4.join(process.cwd(), ".factory");
|
|
1227
|
+
const globalDirExists = existsSync5(globalDir);
|
|
1228
|
+
const projectDirExists = existsSync5(projectDir);
|
|
1229
|
+
if (globalDirExists) {
|
|
1230
|
+
try {
|
|
1231
|
+
await mergeFactoryDroidMcpJson(path4.join(globalDir, "mcp.json"), "agentvalet", entry);
|
|
1232
|
+
written.push("~/.factory/mcp.json");
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
console.warn(` Warning: could not write ~/.factory/mcp.json: ${err.message}`);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
if (projectDirExists && !globalDirExists) {
|
|
1238
|
+
try {
|
|
1239
|
+
await mergeFactoryDroidMcpJson(path4.join(projectDir, "mcp.json"), "agentvalet", entry);
|
|
1240
|
+
written.push(".factory/mcp.json");
|
|
1241
|
+
} catch (err) {
|
|
1242
|
+
console.warn(` Warning: could not write .factory/mcp.json: ${err.message}`);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (written.length > 0) {
|
|
1246
|
+
try {
|
|
1247
|
+
await updateGitignoreForFactoryDroid();
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
981
1251
|
return written;
|
|
982
1252
|
}
|
|
983
1253
|
|
|
@@ -1015,7 +1285,8 @@ function detectFrameworks() {
|
|
|
1015
1285
|
codex: existsSync6(path5.join(home, ".codex")) || tryBinary("codex"),
|
|
1016
1286
|
claudeCode: existsSync6(path5.join(home, ".claude")) || tryBinary("claude"),
|
|
1017
1287
|
cursor: existsSync6(path5.join(process.cwd(), ".cursor")) || cursorInstalled(),
|
|
1018
|
-
openclaw: existsSync6(path5.join(home, ".openclaw"))
|
|
1288
|
+
openclaw: existsSync6(path5.join(home, ".openclaw")),
|
|
1289
|
+
factoryDroid: existsSync6(path5.join(home, ".factory")) || existsSync6(path5.join(process.cwd(), ".factory"))
|
|
1019
1290
|
};
|
|
1020
1291
|
}
|
|
1021
1292
|
async function frameworkSetupConsent(detected) {
|
|
@@ -1034,6 +1305,11 @@ async function frameworkSetupConsent(detected) {
|
|
|
1034
1305
|
if (detected.openclaw) {
|
|
1035
1306
|
items.push("OpenClaw \u2192 SKILL.md (usage rules)");
|
|
1036
1307
|
}
|
|
1308
|
+
if (detected.factoryDroid) {
|
|
1309
|
+
items.push("Factory Droid \u2192 add MCP server to ~/.factory/mcp.json");
|
|
1310
|
+
items.push(" \u2192 create .factory/droids/agentvalet.yaml");
|
|
1311
|
+
items.push(" \u2192 create .factory/commands/av-status.md");
|
|
1312
|
+
}
|
|
1037
1313
|
if (items.length === 0) return false;
|
|
1038
1314
|
console.log("");
|
|
1039
1315
|
console.log(chalk6.bold(" Detected agent frameworks"));
|
|
@@ -1253,6 +1529,16 @@ async function registerAgentFlow(apiKey, ownerId) {
|
|
|
1253
1529
|
}
|
|
1254
1530
|
const detected = detectFrameworks();
|
|
1255
1531
|
const shouldSetup = await frameworkSetupConsent(detected);
|
|
1532
|
+
let installReliabilityDroid = false;
|
|
1533
|
+
if (detected.factoryDroid && shouldSetup) {
|
|
1534
|
+
const { reliability } = await prompts3({
|
|
1535
|
+
type: "confirm",
|
|
1536
|
+
name: "reliability",
|
|
1537
|
+
message: "Also install AgentValet Reliability Droid for Factory Droid? (monitors agent governance)",
|
|
1538
|
+
initial: false
|
|
1539
|
+
});
|
|
1540
|
+
installReliabilityDroid = reliability ?? false;
|
|
1541
|
+
}
|
|
1256
1542
|
if (shouldSetup) {
|
|
1257
1543
|
console.log("");
|
|
1258
1544
|
console.log(chalk7.bold(" Configuring agent frameworks..."));
|
|
@@ -1282,6 +1568,13 @@ async function registerAgentFlow(apiKey, ownerId) {
|
|
|
1282
1568
|
if (detected.openclaw) {
|
|
1283
1569
|
writeSkillMd();
|
|
1284
1570
|
}
|
|
1571
|
+
if (detected.factoryDroid) {
|
|
1572
|
+
writeFactoryDroidDroid();
|
|
1573
|
+
writeFactoryDroidCommand();
|
|
1574
|
+
if (installReliabilityDroid) {
|
|
1575
|
+
writeFactoryDroidReliabilityDroid();
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1285
1578
|
writeQuickstartDoc({
|
|
1286
1579
|
agentId: agent.agent_id,
|
|
1287
1580
|
ownerId,
|
|
@@ -1529,20 +1822,9 @@ import chalk11 from "chalk";
|
|
|
1529
1822
|
import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync3, rmSync } from "fs";
|
|
1530
1823
|
import { resolve as resolve4, join as join3 } from "path";
|
|
1531
1824
|
import { homedir as homedir2 } from "os";
|
|
1532
|
-
import { createSign as createSign2, createPrivateKey as createPrivateKey2 } from "crypto";
|
|
1533
1825
|
var CLAUDE_START2 = "<!-- agentvalet:start -->";
|
|
1534
1826
|
var CLAUDE_END2 = "<!-- agentvalet:end -->";
|
|
1535
1827
|
var HOOK_MARKER2 = "agentvalet/register refresh";
|
|
1536
|
-
function signJwt2(agentId, ownerId, privateKeyPem) {
|
|
1537
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
1538
|
-
const b64 = (s) => Buffer.from(s).toString("base64url");
|
|
1539
|
-
const header = b64(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
1540
|
-
const payload = b64(JSON.stringify({ agent_id: agentId, owner_id: ownerId, iat: now, exp: now + 300 }));
|
|
1541
|
-
const signingInput = `${header}.${payload}`;
|
|
1542
|
-
const sign = createSign2("RSA-SHA256");
|
|
1543
|
-
sign.update(signingInput);
|
|
1544
|
-
return `${signingInput}.${sign.sign(createPrivateKey2(privateKeyPem), "base64url")}`;
|
|
1545
|
-
}
|
|
1546
1828
|
function stripClaudeMd() {
|
|
1547
1829
|
const path7 = resolve4("CLAUDE.md");
|
|
1548
1830
|
if (!existsSync7(path7)) return;
|
|
@@ -1718,7 +2000,7 @@ function uninstallCommand() {
|
|
|
1718
2000
|
}
|
|
1719
2001
|
console.log(chalk11.dim(`
|
|
1720
2002
|
Submitting revocation request for ${creds.agentId}...`));
|
|
1721
|
-
const jwt =
|
|
2003
|
+
const jwt = signJwt(creds.agentId, creds.ownerId, privateKeyPem);
|
|
1722
2004
|
let revocationToken;
|
|
1723
2005
|
try {
|
|
1724
2006
|
const res = await fetch(`${creds.apiUrl}/v1/revoke-request`, {
|
|
@@ -1765,11 +2047,13 @@ function uninstallCommand() {
|
|
|
1765
2047
|
}
|
|
1766
2048
|
|
|
1767
2049
|
// src/index.ts
|
|
2050
|
+
var require2 = createRequire(import.meta.url);
|
|
2051
|
+
var { version } = require2("../package.json");
|
|
1768
2052
|
function collectScope(value, previous) {
|
|
1769
2053
|
return [...previous, value];
|
|
1770
2054
|
}
|
|
1771
2055
|
var program = new Command5();
|
|
1772
|
-
program.name("agentvalet-register").description("Self-register an AI agent with AgentValet").version(
|
|
2056
|
+
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("--output <path>", "Path to write .env file", "./.env.agentvalet").option("--key-path <path>", "Path to write private key", "./agentvalet.key").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").action(async (opts) => {
|
|
1773
2057
|
const isJson = !!opts.json;
|
|
1774
2058
|
let ownerId = opts.owner ?? process.env.AGENTVALET_OWNER ?? "";
|
|
1775
2059
|
let agentName = opts.name ?? "";
|
|
@@ -1940,7 +2224,8 @@ program.command("refresh").description("Refresh approved permissions and update
|
|
|
1940
2224
|
}
|
|
1941
2225
|
});
|
|
1942
2226
|
program.command("mcp-server").description("Start the AgentValet MCP server (stdio transport)").action(async () => {
|
|
1943
|
-
await import("./mcp-server-
|
|
2227
|
+
const { mcpServerCommand } = await import("./mcp-server-R3LG4PNM.js");
|
|
2228
|
+
await mcpServerCommand();
|
|
1944
2229
|
});
|
|
1945
2230
|
program.addCommand(listCommand());
|
|
1946
2231
|
program.addCommand(revokeCommand());
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/commands/mcp-server.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
function resolveMcpServerBin() {
|
|
5
|
+
try {
|
|
6
|
+
const require2 = createRequire(import.meta.url);
|
|
7
|
+
return require2.resolve("@agentvalet/mcp-server/dist/index.js");
|
|
8
|
+
} catch {
|
|
9
|
+
try {
|
|
10
|
+
const require2 = createRequire(import.meta.url);
|
|
11
|
+
const pkgRoot = require2.resolve("@agentvalet/mcp-server/package.json");
|
|
12
|
+
return pkgRoot.replace("package.json", "dist/index.js");
|
|
13
|
+
} catch {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"@agentvalet/mcp-server is not installed. Run: npx @agentvalet/register"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function mcpServerCommand() {
|
|
21
|
+
let binPath;
|
|
22
|
+
try {
|
|
23
|
+
binPath = resolveMcpServerBin();
|
|
24
|
+
} catch (err) {
|
|
25
|
+
process.stderr.write(
|
|
26
|
+
`[agentvalet] ${err instanceof Error ? err.message : String(err)}
|
|
27
|
+
`
|
|
28
|
+
);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const child = spawn(process.execPath, [binPath], {
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
env: process.env
|
|
34
|
+
});
|
|
35
|
+
child.on("error", (err) => {
|
|
36
|
+
process.stderr.write(`[agentvalet] Failed to start MCP server: ${err.message}
|
|
37
|
+
`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
40
|
+
child.on("exit", (code) => {
|
|
41
|
+
process.exit(code ?? 0);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
mcpServerCommand
|
|
46
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentvalet/register",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
4
4
|
"description": "Self-register AI agents with AgentValet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,14 +10,8 @@
|
|
|
10
10
|
"dist",
|
|
11
11
|
"README.md"
|
|
12
12
|
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "tsup src/index.ts --format esm --dts --clean --shims",
|
|
15
|
-
"dev": "tsx src/index.ts",
|
|
16
|
-
"test": "vitest run",
|
|
17
|
-
"typecheck": "tsc --noEmit",
|
|
18
|
-
"prepublishOnly": "pnpm build"
|
|
19
|
-
},
|
|
20
13
|
"dependencies": {
|
|
14
|
+
"@agentvalet/mcp-server": "*",
|
|
21
15
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
22
16
|
"@octokit/auth-oauth-device": "^7.0.0",
|
|
23
17
|
"chalk": "^5.3.0",
|
|
@@ -54,5 +48,11 @@
|
|
|
54
48
|
"valet",
|
|
55
49
|
"agentvalet"
|
|
56
50
|
],
|
|
57
|
-
"license": "MIT"
|
|
58
|
-
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup src/index.ts --format esm --dts --clean --shims",
|
|
54
|
+
"dev": "tsx src/index.ts",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"typecheck": "tsc --noEmit"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
// src/commands/mcp-server.ts
|
|
2
|
-
import { readFileSync } from "fs";
|
|
3
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
-
import { SignJWT, importPKCS8 } from "jose";
|
|
7
|
-
if (!process.env.AGENT_PRIVATE_KEY && process.env.AGENT_PRIVATE_KEY_PATH) {
|
|
8
|
-
try {
|
|
9
|
-
process.env.AGENT_PRIVATE_KEY = readFileSync(process.env.AGENT_PRIVATE_KEY_PATH, "utf-8").trim();
|
|
10
|
-
} catch (err) {
|
|
11
|
-
process.stderr.write(`[agentvalet] Cannot read AGENT_PRIVATE_KEY_PATH: ${err instanceof Error ? err.message : err}
|
|
12
|
-
`);
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
var REQUIRED = ["AGENT_PRIVATE_KEY", "AGENT_ID", "OWNER_ID", "PROXY_URL"];
|
|
17
|
-
for (const key of REQUIRED) {
|
|
18
|
-
if (!process.env[key]) {
|
|
19
|
-
process.stderr.write(`[agentvalet] Missing env var: ${key}
|
|
20
|
-
`);
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
var AGENT_PRIVATE_KEY_RAW = process.env.AGENT_PRIVATE_KEY;
|
|
25
|
-
var AGENT_ID = process.env.AGENT_ID;
|
|
26
|
-
var OWNER_ID = process.env.OWNER_ID;
|
|
27
|
-
var PROXY_URL = process.env.PROXY_URL.replace(/\/$/, "");
|
|
28
|
-
var _privateKey = null;
|
|
29
|
-
var _keyError = null;
|
|
30
|
-
async function getPrivateKey() {
|
|
31
|
-
if (_keyError) throw new Error(_keyError);
|
|
32
|
-
if (_privateKey) return _privateKey;
|
|
33
|
-
if (AGENT_PRIVATE_KEY_RAW === "PENDING_FIRST_CALL") return null;
|
|
34
|
-
const raw = AGENT_PRIVATE_KEY_RAW.trim();
|
|
35
|
-
const pem = raw.startsWith("-----") ? raw.replace(/\\n/g, "\n") : `-----BEGIN PRIVATE KEY-----
|
|
36
|
-
${raw}
|
|
37
|
-
-----END PRIVATE KEY-----`;
|
|
38
|
-
try {
|
|
39
|
-
_privateKey = await importPKCS8(pem, "RS256");
|
|
40
|
-
return _privateKey;
|
|
41
|
-
} catch (err) {
|
|
42
|
-
_keyError = `Invalid private key: ${err instanceof Error ? err.message : err}`;
|
|
43
|
-
throw new Error(_keyError);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
async function signJWT() {
|
|
47
|
-
const key = await getPrivateKey();
|
|
48
|
-
if (!key) throw new Error("Private key not loaded");
|
|
49
|
-
return new SignJWT({ agent_id: AGENT_ID, owner_id: OWNER_ID }).setProtectedHeader({ alg: "RS256" }).setIssuedAt().setExpirationTime("60s").sign(key);
|
|
50
|
-
}
|
|
51
|
-
function fetchWithTimeout(url, init, ms = 15e3) {
|
|
52
|
-
const ac = new AbortController();
|
|
53
|
-
const t = setTimeout(() => ac.abort(), ms);
|
|
54
|
-
return fetch(url, { ...init, signal: ac.signal }).finally(() => clearTimeout(t));
|
|
55
|
-
}
|
|
56
|
-
async function bearerFetch(url, init) {
|
|
57
|
-
const token = await signJWT();
|
|
58
|
-
return fetchWithTimeout(url, {
|
|
59
|
-
...init,
|
|
60
|
-
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...init.headers }
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
function errResult(msg) {
|
|
64
|
-
return { content: [{ type: "text", text: msg }], isError: true };
|
|
65
|
-
}
|
|
66
|
-
var ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
67
|
-
var server = new Server(
|
|
68
|
-
{ name: "agentvalet", version: "1.0.0" },
|
|
69
|
-
{ capabilities: { tools: {} } }
|
|
70
|
-
);
|
|
71
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
72
|
-
tools: [
|
|
73
|
-
{
|
|
74
|
-
name: "list_platforms",
|
|
75
|
-
description: "List the platforms and permission scopes this agent has access to.",
|
|
76
|
-
inputSchema: { type: "object", properties: {} }
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: "use_platform",
|
|
80
|
-
description: "Call an external platform API through the AgentValet proxy.",
|
|
81
|
-
inputSchema: {
|
|
82
|
-
type: "object",
|
|
83
|
-
properties: {
|
|
84
|
-
platform: { type: "string", description: "Platform ID (e.g. slack, github)" },
|
|
85
|
-
endpoint: { type: "string", description: "API path on the target platform" },
|
|
86
|
-
method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
|
|
87
|
-
scope: { type: "string", description: "Permission scope required" },
|
|
88
|
-
data: { type: "object", description: "Optional request body" }
|
|
89
|
-
},
|
|
90
|
-
required: ["platform", "endpoint", "method", "scope"]
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
}));
|
|
95
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
96
|
-
const { name, arguments: args } = request.params;
|
|
97
|
-
if (name === "list_platforms") {
|
|
98
|
-
if (AGENT_PRIVATE_KEY_RAW === "PENDING_FIRST_CALL") {
|
|
99
|
-
return errResult("Agent not yet activated \u2014 owner confirmation pending. Retry in 60 seconds.");
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
const res = await bearerFetch(`${PROXY_URL}/v1/agent/permissions`, { method: "GET", headers: {} });
|
|
103
|
-
const body = await res.text();
|
|
104
|
-
if (!res.ok) return errResult(`Proxy error ${res.status}: ${body}`);
|
|
105
|
-
return { content: [{ type: "text", text: body }] };
|
|
106
|
-
} catch (e) {
|
|
107
|
-
return errResult(`Error: ${e instanceof Error ? e.message : e}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
if (name === "use_platform") {
|
|
111
|
-
if (AGENT_PRIVATE_KEY_RAW === "PENDING_FIRST_CALL") {
|
|
112
|
-
return errResult("Agent not yet activated \u2014 owner confirmation pending. Retry in 60 seconds.");
|
|
113
|
-
}
|
|
114
|
-
if (!args || typeof args.platform !== "string" || typeof args.endpoint !== "string" || typeof args.method !== "string" || typeof args.scope !== "string") {
|
|
115
|
-
return errResult("Missing required args: platform, endpoint, method, scope");
|
|
116
|
-
}
|
|
117
|
-
if (!ALLOWED_METHODS.includes(args.method)) return errResult(`Invalid method: ${args.method}`);
|
|
118
|
-
try {
|
|
119
|
-
const res = await bearerFetch(`${PROXY_URL}/v1/actions`, {
|
|
120
|
-
method: "POST",
|
|
121
|
-
body: JSON.stringify({
|
|
122
|
-
platform: args.platform,
|
|
123
|
-
endpoint: args.endpoint,
|
|
124
|
-
method: args.method,
|
|
125
|
-
scope: args.scope,
|
|
126
|
-
...args.data !== void 0 && { data: args.data }
|
|
127
|
-
})
|
|
128
|
-
});
|
|
129
|
-
const body = await res.text();
|
|
130
|
-
if (!res.ok) return errResult(`Proxy error ${res.status}: ${body}`);
|
|
131
|
-
return { content: [{ type: "text", text: body }] };
|
|
132
|
-
} catch (e) {
|
|
133
|
-
return errResult(`Error: ${e instanceof Error ? e.message : e}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return errResult(`Unknown tool: ${name}`);
|
|
137
|
-
});
|
|
138
|
-
var transport = new StdioServerTransport();
|
|
139
|
-
await server.connect(transport);
|