@keeperhub/wallet 0.1.11 → 0.1.12

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 CHANGED
@@ -7,31 +7,51 @@ var AGENT_SPECS = [
7
7
  agent: "claude-code",
8
8
  skillsRel: [".claude", "skills"],
9
9
  settingsRel: [".claude", "settings.json"],
10
- hookSupport: "claude-code"
10
+ hookSupport: "claude-code",
11
+ // ~/.claude.json is at HOME root (not under .claude/) and is large
12
+ // (100+KB on real installs). registerMcpServer reads/parses/rewrites it
13
+ // while preserving every other top-level key byte-for-byte.
14
+ mcpConfigRel: [".claude.json"],
15
+ mcpSupport: "claude-code"
11
16
  },
12
17
  {
13
18
  agent: "cursor",
14
19
  skillsRel: [".cursor", "skills"],
15
20
  settingsRel: [".cursor", "settings.json"],
16
- hookSupport: "notice"
21
+ hookSupport: "notice",
22
+ mcpConfigRel: [".cursor", "mcp.json"],
23
+ mcpSupport: "cursor"
17
24
  },
18
25
  {
19
26
  agent: "cline",
20
27
  skillsRel: [".cline", "skills"],
21
28
  settingsRel: [".cline", "settings.json"],
22
- hookSupport: "notice"
29
+ hookSupport: "notice",
30
+ // Cline keeps MCP state in a per-VS-Code-variant globalStorage path
31
+ // (e.g. ~/Library/Application Support/Code/User/globalStorage/
32
+ // saoudrizwan.claude-dev/settings/cline_mcp_settings.json) that is too
33
+ // fragile to auto-detect. Ship "notice" with a copy-paste entry shape
34
+ // instead of guessing the variant.
35
+ mcpSupport: "notice"
23
36
  },
24
37
  {
25
38
  agent: "windsurf",
26
39
  skillsRel: [".windsurf", "skills"],
27
40
  settingsRel: [".windsurf", "settings.json"],
28
- hookSupport: "notice"
41
+ hookSupport: "notice",
42
+ mcpConfigRel: [".codeium", "windsurf", "mcp_config.json"],
43
+ mcpSupport: "windsurf",
44
+ // Windsurf historically ships under both `.windsurf/` and the legacy
45
+ // `.codeium/windsurf/`; detect either.
46
+ extraDetect: [[".codeium", "windsurf"]]
29
47
  },
30
48
  {
31
49
  agent: "opencode",
32
50
  skillsRel: [".config", "opencode", "skills"],
33
51
  settingsRel: [".config", "opencode", "settings.json"],
34
- hookSupport: "notice"
52
+ hookSupport: "notice",
53
+ mcpConfigRel: [".config", "opencode", "opencode.json"],
54
+ mcpSupport: "opencode"
35
55
  }
36
56
  ];
37
57
  function detectAgents(homeOverride) {
@@ -40,14 +60,26 @@ function detectAgents(homeOverride) {
40
60
  for (const spec of AGENT_SPECS) {
41
61
  const skillsDir = join(home, ...spec.skillsRel);
42
62
  const settingsFile = join(home, ...spec.settingsRel);
43
- if (existsSync(dirname(skillsDir))) {
44
- results.push({
45
- agent: spec.agent,
46
- skillsDir,
47
- settingsFile,
48
- hookSupport: spec.hookSupport
49
- });
63
+ let detected = existsSync(dirname(skillsDir));
64
+ if (!detected && spec.extraDetect) {
65
+ for (const seg of spec.extraDetect) {
66
+ if (existsSync(join(home, ...seg))) {
67
+ detected = true;
68
+ break;
69
+ }
70
+ }
50
71
  }
72
+ if (!detected) {
73
+ continue;
74
+ }
75
+ results.push({
76
+ agent: spec.agent,
77
+ skillsDir,
78
+ settingsFile,
79
+ hookSupport: spec.hookSupport,
80
+ mcpConfigRel: spec.mcpConfigRel,
81
+ mcpSupport: spec.mcpSupport
82
+ });
51
83
  }
52
84
  return results;
53
85
  }
@@ -146,19 +178,184 @@ function fund(walletAddress) {
146
178
  };
147
179
  }
148
180
 
181
+ // src/storage.ts
182
+ import { randomBytes } from "crypto";
183
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
184
+ import { homedir as homedir2 } from "os";
185
+ import { dirname as dirname2, join as join2 } from "path";
186
+
187
+ // src/types.ts
188
+ var KeeperHubError = class extends Error {
189
+ code;
190
+ constructor(code, message) {
191
+ super(message);
192
+ this.name = "KeeperHubError";
193
+ this.code = code;
194
+ }
195
+ };
196
+ var WalletConfigMissingError = class extends Error {
197
+ constructor() {
198
+ super(
199
+ "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
200
+ );
201
+ this.name = "WalletConfigMissingError";
202
+ }
203
+ };
204
+ var WalletConfigCorruptError = class extends Error {
205
+ path;
206
+ constructor(path, reason) {
207
+ super(
208
+ `Wallet config at ${path} is unreadable: ${reason}. Repair the file by hand or delete it to re-provision a new wallet (this will abandon any funds held in the current wallet).`
209
+ );
210
+ this.name = "WalletConfigCorruptError";
211
+ this.path = path;
212
+ }
213
+ };
214
+
215
+ // src/storage.ts
216
+ async function readWalletConfig() {
217
+ const walletPath = join2(homedir2(), ".keeperhub", "wallet.json");
218
+ let raw;
219
+ try {
220
+ raw = await readFile(walletPath, "utf-8");
221
+ } catch (err) {
222
+ if (err.code === "ENOENT") {
223
+ throw new WalletConfigMissingError();
224
+ }
225
+ throw err;
226
+ }
227
+ let parsed;
228
+ try {
229
+ parsed = JSON.parse(raw);
230
+ } catch (err) {
231
+ const reason = err instanceof Error ? err.message : String(err);
232
+ throw new WalletConfigCorruptError(walletPath, reason);
233
+ }
234
+ if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
235
+ throw new WalletConfigCorruptError(walletPath, "missing required fields");
236
+ }
237
+ return parsed;
238
+ }
239
+ async function writeWalletConfig(config) {
240
+ const walletPath = join2(homedir2(), ".keeperhub", "wallet.json");
241
+ await mkdir(dirname2(walletPath), { recursive: true, mode: 448 });
242
+ const suffix = randomBytes(8).toString("hex");
243
+ const tmpPath = `${walletPath}.${process.pid}.${suffix}.tmp`;
244
+ await writeFile(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
245
+ await chmod(tmpPath, 384);
246
+ await rename(tmpPath, walletPath);
247
+ }
248
+ function getWalletConfigPath() {
249
+ return join2(homedir2(), ".keeperhub", "wallet.json");
250
+ }
251
+
252
+ // src/provision.ts
253
+ var TRAILING_SLASH = /\/$/;
254
+ var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
255
+ var ProvisionResponseInvalidError = class extends Error {
256
+ code = "PROVISION_RESPONSE_INVALID";
257
+ constructor(message) {
258
+ super(message);
259
+ this.name = "ProvisionResponseInvalidError";
260
+ }
261
+ };
262
+ var ProvisionHttpError = class extends Error {
263
+ code = "PROVISION_HTTP_ERROR";
264
+ status;
265
+ body;
266
+ constructor(status, body) {
267
+ super(`provision failed: HTTP ${status}: ${body}`);
268
+ this.name = "ProvisionHttpError";
269
+ this.status = status;
270
+ this.body = body;
271
+ }
272
+ };
273
+ function resolveBaseUrl(override) {
274
+ const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
275
+ return candidate.replace(TRAILING_SLASH, "");
276
+ }
277
+ function isNonEmptyString(value) {
278
+ return typeof value === "string" && value.length > 0;
279
+ }
280
+ function validateProvisionResponse(data) {
281
+ if (typeof data !== "object" || data === null) {
282
+ throw new ProvisionResponseInvalidError(
283
+ "provision response is not an object"
284
+ );
285
+ }
286
+ const { subOrgId, walletAddress, hmacSecret } = data;
287
+ if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
288
+ throw new ProvisionResponseInvalidError(
289
+ "provision response missing subOrgId, walletAddress, or hmacSecret"
290
+ );
291
+ }
292
+ if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
293
+ throw new ProvisionResponseInvalidError(
294
+ `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
295
+ );
296
+ }
297
+ return {
298
+ subOrgId,
299
+ walletAddress,
300
+ hmacSecret
301
+ };
302
+ }
303
+ async function provisionWallet(options = {}) {
304
+ const baseUrl = resolveBaseUrl(options.baseUrl);
305
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
306
+ const response = await fetchImpl(`${baseUrl}/api/agentic-wallet/provision`, {
307
+ method: "POST",
308
+ headers: { "content-type": "application/json" },
309
+ body: "{}",
310
+ signal: AbortSignal.timeout(3e4)
311
+ });
312
+ if (!response.ok) {
313
+ const text = await response.text();
314
+ throw new ProvisionHttpError(response.status, text);
315
+ }
316
+ const raw = await response.json();
317
+ const data = validateProvisionResponse(raw);
318
+ await writeWalletConfig(data);
319
+ return data;
320
+ }
321
+
149
322
  // src/skill-install.ts
323
+ import { randomBytes as randomBytes3 } from "crypto";
324
+ import {
325
+ chmod as chmod3,
326
+ copyFile,
327
+ mkdir as mkdir3,
328
+ readFile as readFile3,
329
+ rename as rename3,
330
+ unlink as unlink2,
331
+ writeFile as writeFile3
332
+ } from "fs/promises";
333
+ import { dirname as dirname5, join as join5 } from "path";
334
+ import { fileURLToPath as fileURLToPath2 } from "url";
335
+
336
+ // src/mcp-register.ts
337
+ import { randomBytes as randomBytes2 } from "crypto";
338
+ import {
339
+ chmod as chmod2,
340
+ mkdir as mkdir2,
341
+ readFile as readFile2,
342
+ rename as rename2,
343
+ unlink,
344
+ writeFile as writeFile2
345
+ } from "fs/promises";
346
+ import { homedir as homedir3 } from "os";
347
+ import { dirname as dirname4, join as join4 } from "path";
348
+
349
+ // src/runtime-detect.ts
150
350
  import { execFileSync } from "child_process";
151
351
  import { readFileSync } from "fs";
152
- import { chmod, copyFile, mkdir, readFile, writeFile } from "fs/promises";
153
- import { dirname as dirname2, join as join2 } from "path";
352
+ import { dirname as dirname3, join as join3 } from "path";
154
353
  import { fileURLToPath } from "url";
155
- var HOOK_BIN = "keeperhub-wallet-hook";
156
- var HOOK_COMMAND_BARE = HOOK_BIN;
157
354
  var PACKAGE_NAME = "@keeperhub/wallet";
158
355
  function readPackageVersion() {
159
356
  try {
160
- const here = dirname2(fileURLToPath(import.meta.url));
161
- const pkgPath = join2(here, "..", "package.json");
357
+ const here = dirname3(fileURLToPath(import.meta.url));
358
+ const pkgPath = join3(here, "..", "package.json");
162
359
  const raw = readFileSync(pkgPath, "utf-8");
163
360
  const parsed = JSON.parse(raw);
164
361
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
@@ -168,9 +365,6 @@ function readPackageVersion() {
168
365
  }
169
366
  return "latest";
170
367
  }
171
- function buildNpxCommand(version) {
172
- return `npx -y -p ${PACKAGE_NAME}@${version} ${HOOK_BIN}`;
173
- }
174
368
  function isNpxExecution() {
175
369
  const execPath = process.env.npm_execpath;
176
370
  if (typeof execPath !== "string" || execPath.length === 0) {
@@ -198,6 +392,144 @@ function isPathUnderTransientCache(resolvedPath) {
198
392
  }
199
393
  return false;
200
394
  }
395
+ function resolveBinCommand(binName) {
396
+ const version = readPackageVersion();
397
+ const npxArgs = ["-y", "-p", `${PACKAGE_NAME}@${version}`, binName];
398
+ const npxCommandString = `npx ${npxArgs.join(" ")}`;
399
+ if (isNpxExecution()) {
400
+ return {
401
+ commandString: npxCommandString,
402
+ command: "npx",
403
+ args: npxArgs
404
+ };
405
+ }
406
+ try {
407
+ const resolved = execFileSync("/bin/sh", ["-c", `command -v ${binName}`], {
408
+ stdio: ["ignore", "pipe", "ignore"]
409
+ }).toString().trim();
410
+ if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
411
+ return {
412
+ commandString: binName,
413
+ command: binName,
414
+ args: []
415
+ };
416
+ }
417
+ } catch {
418
+ }
419
+ return {
420
+ commandString: npxCommandString,
421
+ command: "npx",
422
+ args: npxArgs
423
+ };
424
+ }
425
+
426
+ // src/mcp-register.ts
427
+ var MCP_BIN = "keeperhub-wallet-mcp";
428
+ var MCP_SERVER_NAME = "keeperhub-wallet";
429
+ function resolveMcpCommand() {
430
+ const envOverride = process.env.KEEPERHUB_WALLET_MCP_COMMAND;
431
+ if (envOverride && envOverride.length > 0) {
432
+ const parts = envOverride.trim().split(/\s+/);
433
+ const head = parts[0] ?? envOverride;
434
+ return { command: head, args: parts.slice(1) };
435
+ }
436
+ const resolved = resolveBinCommand(MCP_BIN);
437
+ return { command: resolved.command, args: resolved.args };
438
+ }
439
+ function buildStandardEntry(cmd) {
440
+ const entry = {
441
+ command: cmd.command,
442
+ args: cmd.args
443
+ };
444
+ if (cmd.env && Object.keys(cmd.env).length > 0) {
445
+ entry.env = cmd.env;
446
+ }
447
+ return entry;
448
+ }
449
+ function buildOpencodeEntry(cmd) {
450
+ return {
451
+ type: "local",
452
+ command: [cmd.command, ...cmd.args],
453
+ enabled: true,
454
+ environment: cmd.env ?? {}
455
+ };
456
+ }
457
+ async function readJsonOrEmpty(path) {
458
+ let raw = null;
459
+ try {
460
+ raw = await readFile2(path, "utf-8");
461
+ } catch (err) {
462
+ if (err.code !== "ENOENT") {
463
+ throw err;
464
+ }
465
+ }
466
+ if (raw === null) {
467
+ return {};
468
+ }
469
+ try {
470
+ return JSON.parse(raw);
471
+ } catch {
472
+ throw new Error(
473
+ `MCP config at ${path} is not valid JSON; aborting MCP registration`
474
+ );
475
+ }
476
+ }
477
+ async function writeJsonAtomic(path, payload) {
478
+ await mkdir2(dirname4(path), { recursive: true, mode: 448 });
479
+ const suffix = randomBytes2(8).toString("hex");
480
+ const tmpPath = `${path}.${process.pid}.${suffix}.tmp`;
481
+ try {
482
+ await writeFile2(tmpPath, payload, { mode: 384 });
483
+ await chmod2(tmpPath, 384);
484
+ await rename2(tmpPath, path);
485
+ } catch (err) {
486
+ await unlink(tmpPath).catch(() => {
487
+ });
488
+ throw err;
489
+ }
490
+ }
491
+ async function writeStandardMcp(path, entry) {
492
+ const config = await readJsonOrEmpty(path);
493
+ const servers = typeof config.mcpServers === "object" && config.mcpServers !== null ? config.mcpServers : {};
494
+ servers[MCP_SERVER_NAME] = entry;
495
+ config.mcpServers = servers;
496
+ const payload = `${JSON.stringify(config, null, 2)}
497
+ `;
498
+ await writeJsonAtomic(path, payload);
499
+ }
500
+ async function writeOpencodeMcp(path, entry) {
501
+ const config = await readJsonOrEmpty(path);
502
+ const servers = typeof config.mcp === "object" && config.mcp !== null ? config.mcp : {};
503
+ servers[MCP_SERVER_NAME] = entry;
504
+ config.mcp = servers;
505
+ const payload = `${JSON.stringify(config, null, 2)}
506
+ `;
507
+ await writeJsonAtomic(path, payload);
508
+ }
509
+ async function registerMcpServer(target, options = {}) {
510
+ if (target.mcpSupport === "notice") {
511
+ throw new Error(
512
+ `agent ${target.agent} does not support auto-registered MCP servers; surface a notice instead`
513
+ );
514
+ }
515
+ if (!target.mcpConfigRel) {
516
+ throw new Error(
517
+ `agent ${target.agent} has mcpSupport=${target.mcpSupport} but no mcpConfigRel path`
518
+ );
519
+ }
520
+ const home = options.homeOverride ?? homedir3();
521
+ const path = join4(home, ...target.mcpConfigRel);
522
+ const cmd = options.command ?? resolveMcpCommand();
523
+ if (target.mcpSupport === "opencode") {
524
+ await writeOpencodeMcp(path, buildOpencodeEntry(cmd));
525
+ } else {
526
+ await writeStandardMcp(path, buildStandardEntry(cmd));
527
+ }
528
+ return { path, name: MCP_SERVER_NAME };
529
+ }
530
+
531
+ // src/skill-install.ts
532
+ var HOOK_BIN = "keeperhub-wallet-hook";
201
533
  var KEEPERHUB_HOOK_MARKER = HOOK_BIN;
202
534
  function filterKeeperhubHooksFromEntry(entry) {
203
535
  if (typeof entry !== "object" || entry === null) {
@@ -224,19 +556,7 @@ function resolveHookCommand() {
224
556
  if (envOverride && envOverride.length > 0) {
225
557
  return envOverride;
226
558
  }
227
- if (isNpxExecution()) {
228
- return buildNpxCommand(readPackageVersion());
229
- }
230
- try {
231
- const resolved = execFileSync("/bin/sh", ["-c", `command -v ${HOOK_BIN}`], {
232
- stdio: ["ignore", "pipe", "ignore"]
233
- }).toString().trim();
234
- if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
235
- return HOOK_COMMAND_BARE;
236
- }
237
- } catch {
238
- }
239
- return buildNpxCommand(readPackageVersion());
559
+ return resolveBinCommand(HOOK_BIN).commandString;
240
560
  }
241
561
  function buildKeeperhubEntry(command) {
242
562
  return {
@@ -245,8 +565,8 @@ function buildKeeperhubEntry(command) {
245
565
  };
246
566
  }
247
567
  function resolveDefaultSkillSource() {
248
- const here = dirname2(fileURLToPath(import.meta.url));
249
- return join2(here, "..", "skill", "keeperhub-wallet.skill.md");
568
+ const here = dirname5(fileURLToPath2(import.meta.url));
569
+ return join5(here, "..", "skill", "keeperhub-wallet.skill.md");
250
570
  }
251
571
  function defaultNotice(msg) {
252
572
  process.stderr.write(`${msg}
@@ -256,7 +576,7 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
256
576
  const command = options.hookCommand ?? resolveHookCommand();
257
577
  let raw = null;
258
578
  try {
259
- raw = await readFile(settingsPath, "utf-8");
579
+ raw = await readFile3(settingsPath, "utf-8");
260
580
  } catch (err) {
261
581
  if (err.code !== "ENOENT") {
262
582
  throw err;
@@ -284,166 +604,136 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
284
604
  filtered.push(buildKeeperhubEntry(command));
285
605
  hooks.PreToolUse = filtered;
286
606
  config.hooks = hooks;
287
- await mkdir(dirname2(settingsPath), { recursive: true, mode: 448 });
607
+ await mkdir3(dirname5(settingsPath), { recursive: true, mode: 448 });
288
608
  const payload = `${JSON.stringify(config, null, 2)}
289
609
  `;
290
- await writeFile(settingsPath, payload, { mode: 384 });
291
- await chmod(settingsPath, 384);
610
+ const suffix = randomBytes3(8).toString("hex");
611
+ const tmpPath = `${settingsPath}.${process.pid}.${suffix}.tmp`;
612
+ try {
613
+ await writeFile3(tmpPath, payload, { mode: 384 });
614
+ await chmod3(tmpPath, 384);
615
+ await rename3(tmpPath, settingsPath);
616
+ } catch (err) {
617
+ await unlink2(tmpPath).catch(() => {
618
+ });
619
+ throw err;
620
+ }
292
621
  }
293
622
  async function writeSkillToAgent(agent, skillSource) {
294
- await mkdir(agent.skillsDir, { recursive: true, mode: 493 });
295
- const target = join2(agent.skillsDir, "keeperhub-wallet.skill.md");
623
+ await mkdir3(agent.skillsDir, { recursive: true, mode: 493 });
624
+ const target = join5(agent.skillsDir, "keeperhub-wallet.skill.md");
296
625
  await copyFile(skillSource, target);
297
- await chmod(target, 420);
626
+ await chmod3(target, 420);
298
627
  return { agent: agent.agent, path: target, status: "written" };
299
628
  }
300
- function buildNoticeMessage(agent, command) {
629
+ function buildHookNoticeMessage(agent, command) {
301
630
  return `${agent.agent} does not support auto-registered PreToolUse hooks; run \`${command}\` on every tool use via ${agent.agent}'s settings file at ${agent.settingsFile}`;
302
631
  }
632
+ function buildMcpNoticeMessage(agent, command) {
633
+ const cmd = [command.command, ...command.args].join(" ");
634
+ return `${agent.agent} does not support auto-registered MCP servers; add an entry named \`keeperhub-wallet\` running \`${cmd}\` to your MCP config manually`;
635
+ }
303
636
  async function installSkill(options = {}) {
304
637
  const agents = detectAgents(options.homeOverride);
305
638
  const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();
306
639
  const onNotice = options.onNotice ?? defaultNotice;
307
640
  const hookCommand = options.hookCommand ?? resolveHookCommand();
641
+ const mcpCommand = options.mcpCommand ?? resolveMcpCommand();
308
642
  const skillWrites = [];
309
643
  const hookRegistrations = [];
644
+ const mcpRegistrations = [];
310
645
  for (const agent of agents) {
311
- const write = await writeSkillToAgent(agent, skillSource);
312
- skillWrites.push(write);
313
- if (agent.hookSupport === "claude-code") {
314
- await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
315
- hookRegistrations.push({
646
+ try {
647
+ const write = await writeSkillToAgent(agent, skillSource);
648
+ skillWrites.push(write);
649
+ } catch (err) {
650
+ const message = err instanceof Error ? err.message : String(err);
651
+ skillWrites.push({
316
652
  agent: agent.agent,
317
- status: "registered"
653
+ path: "",
654
+ status: "skipped"
318
655
  });
656
+ onNotice(`${agent.agent}: skill copy failed (${message})`);
657
+ continue;
658
+ }
659
+ if (agent.hookSupport === "claude-code") {
660
+ try {
661
+ await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
662
+ hookRegistrations.push({
663
+ agent: agent.agent,
664
+ status: "registered"
665
+ });
666
+ } catch (err) {
667
+ const message = err instanceof Error ? err.message : String(err);
668
+ hookRegistrations.push({
669
+ agent: agent.agent,
670
+ status: "failed",
671
+ message
672
+ });
673
+ onNotice(`${agent.agent}: hook registration failed (${message})`);
674
+ }
319
675
  } else {
320
- const message = buildNoticeMessage(agent, hookCommand);
676
+ const noticeMessage = buildHookNoticeMessage(agent, hookCommand);
321
677
  hookRegistrations.push({
322
678
  agent: agent.agent,
323
679
  status: "notice",
324
- message
680
+ message: noticeMessage
325
681
  });
326
- onNotice(message);
682
+ onNotice(noticeMessage);
327
683
  }
328
- }
329
- return { skillWrites, hookRegistrations };
330
- }
331
-
332
- // src/storage.ts
333
- import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
334
- import { homedir as homedir2 } from "os";
335
- import { dirname as dirname3, join as join3 } from "path";
336
-
337
- // src/types.ts
338
- var KeeperHubError = class extends Error {
339
- code;
340
- constructor(code, message) {
341
- super(message);
342
- this.name = "KeeperHubError";
343
- this.code = code;
344
- }
345
- };
346
- var WalletConfigMissingError = class extends Error {
347
- constructor() {
348
- super(
349
- "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
350
- );
351
- this.name = "WalletConfigMissingError";
352
- }
353
- };
354
-
355
- // src/storage.ts
356
- async function readWalletConfig() {
357
- const walletPath = join3(homedir2(), ".keeperhub", "wallet.json");
358
- let raw;
359
- try {
360
- raw = await readFile2(walletPath, "utf-8");
361
- } catch (err) {
362
- if (err.code === "ENOENT") {
363
- throw new WalletConfigMissingError();
684
+ if (agent.mcpSupport === "notice") {
685
+ const noticeMessage = buildMcpNoticeMessage(agent, mcpCommand);
686
+ mcpRegistrations.push({
687
+ agent: agent.agent,
688
+ status: "notice",
689
+ message: noticeMessage
690
+ });
691
+ onNotice(noticeMessage);
692
+ continue;
693
+ }
694
+ try {
695
+ const mcpResult = await registerMcpServer(agent, {
696
+ homeOverride: options.homeOverride,
697
+ command: mcpCommand
698
+ });
699
+ mcpRegistrations.push({
700
+ agent: agent.agent,
701
+ status: "registered",
702
+ path: mcpResult.path
703
+ });
704
+ } catch (err) {
705
+ const message = err instanceof Error ? err.message : String(err);
706
+ mcpRegistrations.push({
707
+ agent: agent.agent,
708
+ status: "failed",
709
+ message
710
+ });
711
+ onNotice(`${agent.agent}: MCP registration failed (${message})`);
364
712
  }
365
- throw err;
366
- }
367
- const parsed = JSON.parse(raw);
368
- if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
369
- throw new Error(`Malformed wallet.json at ${walletPath}`);
370
713
  }
371
- return parsed;
372
- }
373
- async function writeWalletConfig(config) {
374
- const walletPath = join3(homedir2(), ".keeperhub", "wallet.json");
375
- await mkdir2(dirname3(walletPath), { recursive: true, mode: 448 });
376
- await writeFile2(walletPath, JSON.stringify(config, null, 2), { mode: 384 });
377
- await chmod2(walletPath, 384);
378
- }
379
- function getWalletConfigPath() {
380
- return join3(homedir2(), ".keeperhub", "wallet.json");
714
+ return { skillWrites, hookRegistrations, mcpRegistrations };
381
715
  }
382
716
 
383
717
  // src/cli.ts
384
- var TRAILING_SLASH = /\/$/;
385
- var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
386
- function resolveBaseUrl(override) {
387
- const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
388
- return candidate.replace(TRAILING_SLASH, "");
389
- }
390
- function isNonEmptyString(value) {
391
- return typeof value === "string" && value.length > 0;
392
- }
393
- function provisionInvalidError(message) {
394
- const err = new Error(message);
395
- err.code = "PROVISION_RESPONSE_INVALID";
396
- return err;
397
- }
398
- function validateProvisionResponse(data) {
399
- if (typeof data !== "object" || data === null) {
400
- throw provisionInvalidError("provision response is not an object");
401
- }
402
- const { subOrgId, walletAddress, hmacSecret } = data;
403
- if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
404
- throw provisionInvalidError(
405
- "provision response missing subOrgId, walletAddress, or hmacSecret"
406
- );
407
- }
408
- if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
409
- throw provisionInvalidError(
410
- `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
411
- );
412
- }
413
- return {
414
- subOrgId,
415
- walletAddress,
416
- hmacSecret
417
- };
418
- }
419
718
  async function cmdAdd(opts = {}) {
420
- const baseUrl = resolveBaseUrl(opts.baseUrl);
421
- const response = await fetch(`${baseUrl}/api/agentic-wallet/provision`, {
422
- method: "POST",
423
- headers: { "content-type": "application/json" },
424
- body: "{}"
425
- });
426
- if (!response.ok) {
427
- const text = await response.text();
428
- process.stderr.write(
429
- `[keeperhub-wallet] provision failed: HTTP ${response.status}: ${text}
430
- `
431
- );
432
- process.exit(1);
433
- }
434
- const raw = await response.json();
435
- const data = validateProvisionResponse(raw);
436
- await writeWalletConfig({
437
- subOrgId: data.subOrgId,
438
- walletAddress: data.walletAddress,
439
- hmacSecret: data.hmacSecret
440
- });
441
- process.stdout.write(`subOrgId: ${data.subOrgId}
719
+ try {
720
+ const data = await provisionWallet({ baseUrl: opts.baseUrl });
721
+ process.stdout.write(`subOrgId: ${data.subOrgId}
442
722
  `);
443
- process.stdout.write(`walletAddress: ${data.walletAddress}
723
+ process.stdout.write(`walletAddress: ${data.walletAddress}
444
724
  `);
445
- process.stdout.write(`config written to ${getWalletConfigPath()}
725
+ process.stdout.write(`config written to ${getWalletConfigPath()}
446
726
  `);
727
+ } catch (err) {
728
+ if (err instanceof ProvisionHttpError) {
729
+ process.stderr.write(
730
+ `[keeperhub-wallet] provision failed: HTTP ${err.status}: ${err.body}
731
+ `
732
+ );
733
+ process.exit(1);
734
+ }
735
+ throw err;
736
+ }
447
737
  }
448
738
  async function cmdFund() {
449
739
  const wallet = await readWalletConfig();
@@ -511,6 +801,29 @@ async function runCli(argv = process.argv) {
511
801
  } else if (reg.status === "notice") {
512
802
  process.stderr.write(
513
803
  `notice: ${reg.agent} -> ${reg.message ?? ""}
804
+ `
805
+ );
806
+ } else if (reg.status === "failed") {
807
+ process.stderr.write(
808
+ `hook: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
809
+ `
810
+ );
811
+ }
812
+ }
813
+ for (const reg of result.mcpRegistrations) {
814
+ if (reg.status === "registered") {
815
+ process.stdout.write(
816
+ `mcp: ${reg.agent} -> registered at ${reg.path ?? "(unknown path)"}
817
+ `
818
+ );
819
+ } else if (reg.status === "notice") {
820
+ process.stderr.write(
821
+ `notice: ${reg.agent} mcp -> ${reg.message ?? ""}
822
+ `
823
+ );
824
+ } else if (reg.status === "failed") {
825
+ process.stderr.write(
826
+ `mcp: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
514
827
  `
515
828
  );
516
829
  }
@@ -639,9 +952,9 @@ var KeeperHubClient = class {
639
952
  };
640
953
 
641
954
  // src/safety-config.ts
642
- import { chmod as chmod3, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
643
- import { homedir as homedir3 } from "os";
644
- import { dirname as dirname4, join as join4 } from "path";
955
+ import { chmod as chmod4, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
956
+ import { homedir as homedir4 } from "os";
957
+ import { dirname as dirname6, join as join6 } from "path";
645
958
  var DEFAULT_SAFETY_CONFIG = {
646
959
  auto_approve_max_usd: 5,
647
960
  ask_threshold_usd: 50,
@@ -654,20 +967,20 @@ var DEFAULT_SAFETY_CONFIG = {
654
967
  ]
655
968
  };
656
969
  function getSafetyPath() {
657
- return join4(homedir3(), ".keeperhub", "safety.json");
970
+ return join6(homedir4(), ".keeperhub", "safety.json");
658
971
  }
659
972
  async function loadSafetyConfig() {
660
973
  const path = getSafetyPath();
661
974
  let raw;
662
975
  try {
663
- raw = await readFile3(path, "utf-8");
976
+ raw = await readFile4(path, "utf-8");
664
977
  } catch (err) {
665
978
  if (err.code === "ENOENT") {
666
- await mkdir3(dirname4(path), { recursive: true, mode: 448 });
667
- await writeFile3(path, JSON.stringify(DEFAULT_SAFETY_CONFIG, null, 2), {
979
+ await mkdir4(dirname6(path), { recursive: true, mode: 448 });
980
+ await writeFile4(path, JSON.stringify(DEFAULT_SAFETY_CONFIG, null, 2), {
668
981
  mode: 420
669
982
  });
670
- await chmod3(path, 420);
983
+ await chmod4(path, 420);
671
984
  return DEFAULT_SAFETY_CONFIG;
672
985
  }
673
986
  throw err;
@@ -864,7 +1177,7 @@ function parseMppChallenge(response) {
864
1177
  }
865
1178
 
866
1179
  // src/payment-signer.ts
867
- import { randomBytes } from "crypto";
1180
+ import { randomBytes as randomBytes4 } from "crypto";
868
1181
 
869
1182
  // src/workflow-slug.ts
870
1183
  var KEEPERHUB_WORKFLOW_RE = /\/api\/mcp\/workflows\/([a-zA-Z0-9_-]+)\/call(?:\/?)(?:\?|$|#)/;
@@ -1044,7 +1357,7 @@ function createPaymentSigner(opts = {}) {
1044
1357
  const now = Math.floor(Date.now() / 1e3);
1045
1358
  const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;
1046
1359
  const validBefore = now + accept.maxTimeoutSeconds;
1047
- const nonce = `0x${randomBytes(NONCE_BYTES).toString("hex")}`;
1360
+ const nonce = `0x${randomBytes4(NONCE_BYTES).toString("hex")}`;
1048
1361
  const client = clientFactory(wallet);
1049
1362
  const signature = await signOrPoll(client, {
1050
1363
  chain: "base",