@agentvalet/register 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +174 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -56,9 +56,9 @@ async function runWizard(prefilled) {
56
56
  }
57
57
 
58
58
  // src/register.ts
59
- import { fetch } from "undici";
59
+ import { fetch as fetch2 } from "undici";
60
60
  async function registerAgent(apiUrl, body) {
61
- const res = await fetch(`${apiUrl}/v1/register`, {
61
+ const res = await fetch2(`${apiUrl}/v1/register`, {
62
62
  method: "POST",
63
63
  headers: { "Content-Type": "application/json" },
64
64
  body: JSON.stringify(body)
@@ -76,7 +76,7 @@ async function registerAgent(apiUrl, body) {
76
76
  }
77
77
 
78
78
  // src/wait.ts
79
- import { fetch as fetch2 } from "undici";
79
+ import { fetch as fetch3 } from "undici";
80
80
  async function waitForApproval(apiUrl, token, timeoutMs, onTick) {
81
81
  const deadline = Date.now() + timeoutMs;
82
82
  const sseResult = await trySSE(apiUrl, token, Math.min(3e4, timeoutMs), onTick);
@@ -87,7 +87,7 @@ async function waitForApproval(apiUrl, token, timeoutMs, onTick) {
87
87
  async function enrichWithKey(apiUrl, token, result) {
88
88
  if (result.status !== "approved") return result;
89
89
  try {
90
- const res = await fetch2(`${apiUrl}/v1/register/claim-key/${token}`);
90
+ const res = await fetch3(`${apiUrl}/v1/register/claim-key/${token}`);
91
91
  if (!res.ok) return result;
92
92
  const json = await res.json();
93
93
  return { ...result, privateKey: json.private_key ?? "" };
@@ -99,7 +99,7 @@ async function trySSE(apiUrl, token, windowMs, onTick) {
99
99
  const controller = new AbortController();
100
100
  const timer = setTimeout(() => controller.abort(), windowMs);
101
101
  try {
102
- const res = await fetch2(`${apiUrl}/v1/register/stream/${token}`, {
102
+ const res = await fetch3(`${apiUrl}/v1/register/stream/${token}`, {
103
103
  signal: controller.signal,
104
104
  headers: { Accept: "text/event-stream" }
105
105
  });
@@ -142,7 +142,7 @@ async function poll(apiUrl, token, deadline, onTick) {
142
142
  await sleep(5e3);
143
143
  onTick?.();
144
144
  try {
145
- const res = await fetch2(`${apiUrl}/v1/register/status/${token}`);
145
+ const res = await fetch3(`${apiUrl}/v1/register/status/${token}`);
146
146
  if (!res.ok) continue;
147
147
  const json = await res.json();
148
148
  const data = json.data ?? json;
@@ -164,7 +164,7 @@ function parseEvent(data) {
164
164
  return null;
165
165
  }
166
166
  function sleep(ms) {
167
- return new Promise((resolve2) => setTimeout(resolve2, ms));
167
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
168
168
  }
169
169
 
170
170
  // src/save.ts
@@ -179,6 +179,7 @@ async function saveOutput(opts) {
179
179
  const envContent = [
180
180
  `# AgentValet \u2014 generated by agentvalet-register`,
181
181
  `AGENTVALET_AGENT_ID=${opts.agentId}`,
182
+ `AGENTVALET_OWNER_ID=${opts.ownerId}`,
182
183
  `AGENTVALET_API_URL=${opts.apiUrl}`,
183
184
  `AGENTVALET_PRIVATE_KEY_PATH=${keyPath}`,
184
185
  ""
@@ -187,6 +188,140 @@ async function saveOutput(opts) {
187
188
  return { keyPath, envPath };
188
189
  }
189
190
 
191
+ // src/commands/refresh.ts
192
+ import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
193
+ import { createSign, createPrivateKey } from "crypto";
194
+ import { dirname as dirname2, resolve as resolve2 } from "path";
195
+
196
+ // src/lib/skill-writer.ts
197
+ function generatePermissionsFile(agentId, ownerId, permissions) {
198
+ const now = (/* @__PURE__ */ new Date()).toISOString();
199
+ const platformSections = permissions.length === 0 ? "_No platforms approved yet. Visit https://agentvalet.ai to connect platforms._" : permissions.map((p) => {
200
+ const scopeList = p.scopes.map((s) => ` - \`${s}\``).join("\n");
201
+ const approvalNote = p.requireApproval ? "\n > Requires human approval before executing.\n" : "";
202
+ return `### ${p.platformName} (\`${p.platformId}\`)
203
+
204
+ Approved scopes:
205
+ ${scopeList}
206
+ ${approvalNote}`;
207
+ }).join("\n\n");
208
+ return `# AgentValet Permissions
209
+
210
+ Agent ID: \`${agentId}\`
211
+ Owner ID: \`${ownerId}\`
212
+ Last refreshed: ${now}
213
+
214
+ ---
215
+
216
+ ## Approved Platforms
217
+
218
+ ${platformSections}
219
+
220
+ ---
221
+
222
+ ## How to call a platform
223
+
224
+ All platform calls **must** go through AgentValet \u2014 never call Slack, Gmail, Stripe etc. directly.
225
+
226
+ Copy the pattern from \`test-agent.mjs\` (already in this repo):
227
+
228
+ 1. Read the private key from \`agentvalet.key\`
229
+ 2. Sign a short-lived RS256 JWT: \`{ agent_id, owner_id, iat, exp: iat + 300 }\`
230
+ 3. POST to \`https://api.agentvalet.ai/v1/actions\`:
231
+
232
+ \`\`\`json
233
+ {
234
+ "platform": "<platformId>",
235
+ "endpoint": "<endpoint>",
236
+ "method": "POST",
237
+ "scope": "<scope>",
238
+ "data": { ... }
239
+ }
240
+ \`\`\`
241
+
242
+ Run \`node test-agent.mjs\` for a live smoke-test.
243
+ To refresh this file: \`agentvalet-register refresh\`
244
+ `;
245
+ }
246
+
247
+ // src/commands/refresh.ts
248
+ function base64url(input) {
249
+ return Buffer.from(input).toString("base64url");
250
+ }
251
+ function signJwt(agentId, ownerId, privateKeyPem) {
252
+ const now = Math.floor(Date.now() / 1e3);
253
+ const header = base64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
254
+ const payload = base64url(JSON.stringify({ agent_id: agentId, owner_id: ownerId, iat: now, exp: now + 300 }));
255
+ const signingInput = `${header}.${payload}`;
256
+ const sign = createSign("RSA-SHA256");
257
+ sign.update(signingInput);
258
+ const signature = sign.sign(createPrivateKey(privateKeyPem), "base64url");
259
+ return `${signingInput}.${signature}`;
260
+ }
261
+ function parseEnvFile(content) {
262
+ const result = {};
263
+ for (const line of content.split("\n")) {
264
+ const trimmed = line.trim();
265
+ if (!trimmed || trimmed.startsWith("#")) continue;
266
+ const eq = trimmed.indexOf("=");
267
+ if (eq === -1) continue;
268
+ result[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
269
+ }
270
+ return result;
271
+ }
272
+ async function runRefresh(options) {
273
+ const outputPath = resolve2(options.outputPath);
274
+ if (options.ifStale) {
275
+ try {
276
+ const stat = statSync(outputPath);
277
+ if (Date.now() - stat.mtimeMs < 36e5) {
278
+ return;
279
+ }
280
+ } catch {
281
+ }
282
+ }
283
+ const envPath = resolve2(options.envFile);
284
+ let envVars;
285
+ try {
286
+ envVars = parseEnvFile(readFileSync(envPath, "utf-8"));
287
+ } catch {
288
+ throw new Error(`Cannot read env file: ${envPath}
289
+ Run 'agentvalet-register' first to create it.`);
290
+ }
291
+ const agentId = envVars["AGENTVALET_AGENT_ID"];
292
+ const ownerId = envVars["AGENTVALET_OWNER_ID"];
293
+ const apiUrl = envVars["AGENTVALET_API_URL"] ?? "https://api.agentvalet.ai";
294
+ const keyPath = envVars["AGENTVALET_PRIVATE_KEY_PATH"];
295
+ if (!agentId || !ownerId || !keyPath) {
296
+ throw new Error(`Env file is missing required keys (AGENTVALET_AGENT_ID, AGENTVALET_OWNER_ID, AGENTVALET_PRIVATE_KEY_PATH)`);
297
+ }
298
+ let privateKeyPem;
299
+ try {
300
+ privateKeyPem = readFileSync(keyPath, "utf-8");
301
+ } catch {
302
+ throw new Error(`Cannot read private key: ${keyPath}`);
303
+ }
304
+ if (!privateKeyPem.trim()) {
305
+ throw new Error(`Private key file is empty: ${keyPath}`);
306
+ }
307
+ const jwt = signJwt(agentId, ownerId, privateKeyPem);
308
+ const res = await fetch(`${apiUrl}/v1/agent/permissions`, {
309
+ headers: { Authorization: `Bearer ${jwt}` }
310
+ });
311
+ if (!res.ok) {
312
+ const body = await res.text().catch(() => "");
313
+ throw new Error(`Permissions API returned ${res.status}: ${body}`);
314
+ }
315
+ const json = await res.json();
316
+ const platforms = json.data?.platforms ?? [];
317
+ const content = generatePermissionsFile(agentId, ownerId, platforms);
318
+ mkdirSync(dirname2(outputPath), { recursive: true });
319
+ writeFileSync(outputPath, content, "utf-8");
320
+ if (!options.quiet) {
321
+ console.log(`Permissions refreshed \u2192 ${outputPath} (${platforms.length} platform${platforms.length !== 1 ? "s" : ""})`);
322
+ }
323
+ }
324
+
190
325
  // src/index.ts
191
326
  function collectScope(value, previous) {
192
327
  return [...previous, value];
@@ -293,6 +428,7 @@ Approve at: https://agentvalet.ai/registrations
293
428
  try {
294
429
  ({ keyPath, envPath } = await saveOutput({
295
430
  agentId,
431
+ ownerId,
296
432
  privateKey: privateKey ?? "",
297
433
  apiUrl: opts.apiUrl,
298
434
  keyFilePath: opts.keyPath,
@@ -319,8 +455,39 @@ Approved! Agent ID: ${agentId}`));
319
455
  Source the env file before calling any tool:`);
320
456
  console.log(chalk2.cyan(` source ${envPath}`));
321
457
  }
458
+ if (keyWritten) {
459
+ try {
460
+ await runRefresh({
461
+ envFile: opts.output,
462
+ outputPath: ".claude/agentvalet-permissions.md",
463
+ ifStale: false,
464
+ quiet: isJson
465
+ });
466
+ if (!isJson) {
467
+ console.log(chalk2.gray(`Permissions: .claude/agentvalet-permissions.md`));
468
+ }
469
+ } catch {
470
+ if (!isJson) {
471
+ console.log(chalk2.yellow(`Permissions: run 'agentvalet-register refresh' to generate`));
472
+ }
473
+ }
474
+ }
322
475
  process.exit(0);
323
476
  });
477
+ program.command("refresh").description("Refresh approved permissions and update .claude/agentvalet-permissions.md").option("--env-file <path>", "Path to .env.agentvalet file", "./.env.agentvalet").option("--output <path>", "Path to write permissions file", "./.claude/agentvalet-permissions.md").option("--if-stale", "Skip if file is less than 1 hour old").option("--quiet", "Suppress output").action(async (opts) => {
478
+ try {
479
+ await runRefresh({
480
+ envFile: opts.envFile,
481
+ outputPath: opts.output,
482
+ ifStale: !!opts.ifStale,
483
+ quiet: !!opts.quiet
484
+ });
485
+ } catch (err) {
486
+ const msg = err instanceof Error ? err.message : String(err);
487
+ console.error(chalk2.red(`Refresh failed: ${msg}`));
488
+ process.exit(3);
489
+ }
490
+ });
324
491
  program.parseAsync().catch((err) => {
325
492
  console.error(err);
326
493
  process.exit(3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvalet/register",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Self-register AI agents with AgentValet",
5
5
  "type": "module",
6
6
  "bin": {