@keeperhub/wallet 0.1.10 → 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.cjs CHANGED
@@ -62,31 +62,51 @@ var AGENT_SPECS = [
62
62
  agent: "claude-code",
63
63
  skillsRel: [".claude", "skills"],
64
64
  settingsRel: [".claude", "settings.json"],
65
- hookSupport: "claude-code"
65
+ hookSupport: "claude-code",
66
+ // ~/.claude.json is at HOME root (not under .claude/) and is large
67
+ // (100+KB on real installs). registerMcpServer reads/parses/rewrites it
68
+ // while preserving every other top-level key byte-for-byte.
69
+ mcpConfigRel: [".claude.json"],
70
+ mcpSupport: "claude-code"
66
71
  },
67
72
  {
68
73
  agent: "cursor",
69
74
  skillsRel: [".cursor", "skills"],
70
75
  settingsRel: [".cursor", "settings.json"],
71
- hookSupport: "notice"
76
+ hookSupport: "notice",
77
+ mcpConfigRel: [".cursor", "mcp.json"],
78
+ mcpSupport: "cursor"
72
79
  },
73
80
  {
74
81
  agent: "cline",
75
82
  skillsRel: [".cline", "skills"],
76
83
  settingsRel: [".cline", "settings.json"],
77
- hookSupport: "notice"
84
+ hookSupport: "notice",
85
+ // Cline keeps MCP state in a per-VS-Code-variant globalStorage path
86
+ // (e.g. ~/Library/Application Support/Code/User/globalStorage/
87
+ // saoudrizwan.claude-dev/settings/cline_mcp_settings.json) that is too
88
+ // fragile to auto-detect. Ship "notice" with a copy-paste entry shape
89
+ // instead of guessing the variant.
90
+ mcpSupport: "notice"
78
91
  },
79
92
  {
80
93
  agent: "windsurf",
81
94
  skillsRel: [".windsurf", "skills"],
82
95
  settingsRel: [".windsurf", "settings.json"],
83
- hookSupport: "notice"
96
+ hookSupport: "notice",
97
+ mcpConfigRel: [".codeium", "windsurf", "mcp_config.json"],
98
+ mcpSupport: "windsurf",
99
+ // Windsurf historically ships under both `.windsurf/` and the legacy
100
+ // `.codeium/windsurf/`; detect either.
101
+ extraDetect: [[".codeium", "windsurf"]]
84
102
  },
85
103
  {
86
104
  agent: "opencode",
87
105
  skillsRel: [".config", "opencode", "skills"],
88
106
  settingsRel: [".config", "opencode", "settings.json"],
89
- hookSupport: "notice"
107
+ hookSupport: "notice",
108
+ mcpConfigRel: [".config", "opencode", "opencode.json"],
109
+ mcpSupport: "opencode"
90
110
  }
91
111
  ];
92
112
  function detectAgents(homeOverride) {
@@ -95,14 +115,26 @@ function detectAgents(homeOverride) {
95
115
  for (const spec of AGENT_SPECS) {
96
116
  const skillsDir = (0, import_node_path.join)(home, ...spec.skillsRel);
97
117
  const settingsFile = (0, import_node_path.join)(home, ...spec.settingsRel);
98
- if ((0, import_node_fs.existsSync)((0, import_node_path.dirname)(skillsDir))) {
99
- results.push({
100
- agent: spec.agent,
101
- skillsDir,
102
- settingsFile,
103
- hookSupport: spec.hookSupport
104
- });
118
+ let detected = (0, import_node_fs.existsSync)((0, import_node_path.dirname)(skillsDir));
119
+ if (!detected && spec.extraDetect) {
120
+ for (const seg of spec.extraDetect) {
121
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(home, ...seg))) {
122
+ detected = true;
123
+ break;
124
+ }
125
+ }
105
126
  }
127
+ if (!detected) {
128
+ continue;
129
+ }
130
+ results.push({
131
+ agent: spec.agent,
132
+ skillsDir,
133
+ settingsFile,
134
+ hookSupport: spec.hookSupport,
135
+ mcpConfigRel: spec.mcpConfigRel,
136
+ mcpSupport: spec.mcpSupport
137
+ });
106
138
  }
107
139
  return results;
108
140
  }
@@ -196,19 +228,169 @@ function fund(walletAddress) {
196
228
  };
197
229
  }
198
230
 
231
+ // src/storage.ts
232
+ var import_node_crypto = require("crypto");
233
+ var import_promises = require("fs/promises");
234
+ var import_node_os2 = require("os");
235
+ var import_node_path2 = require("path");
236
+
237
+ // src/types.ts
238
+ var KeeperHubError = class extends Error {
239
+ code;
240
+ constructor(code, message) {
241
+ super(message);
242
+ this.name = "KeeperHubError";
243
+ this.code = code;
244
+ }
245
+ };
246
+ var WalletConfigMissingError = class extends Error {
247
+ constructor() {
248
+ super(
249
+ "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
250
+ );
251
+ this.name = "WalletConfigMissingError";
252
+ }
253
+ };
254
+ var WalletConfigCorruptError = class extends Error {
255
+ path;
256
+ constructor(path, reason) {
257
+ super(
258
+ `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).`
259
+ );
260
+ this.name = "WalletConfigCorruptError";
261
+ this.path = path;
262
+ }
263
+ };
264
+
265
+ // src/storage.ts
266
+ async function readWalletConfig() {
267
+ const walletPath = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
268
+ let raw;
269
+ try {
270
+ raw = await (0, import_promises.readFile)(walletPath, "utf-8");
271
+ } catch (err) {
272
+ if (err.code === "ENOENT") {
273
+ throw new WalletConfigMissingError();
274
+ }
275
+ throw err;
276
+ }
277
+ let parsed;
278
+ try {
279
+ parsed = JSON.parse(raw);
280
+ } catch (err) {
281
+ const reason = err instanceof Error ? err.message : String(err);
282
+ throw new WalletConfigCorruptError(walletPath, reason);
283
+ }
284
+ if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
285
+ throw new WalletConfigCorruptError(walletPath, "missing required fields");
286
+ }
287
+ return parsed;
288
+ }
289
+ async function writeWalletConfig(config) {
290
+ const walletPath = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
291
+ await (0, import_promises.mkdir)((0, import_node_path2.dirname)(walletPath), { recursive: true, mode: 448 });
292
+ const suffix = (0, import_node_crypto.randomBytes)(8).toString("hex");
293
+ const tmpPath = `${walletPath}.${process.pid}.${suffix}.tmp`;
294
+ await (0, import_promises.writeFile)(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
295
+ await (0, import_promises.chmod)(tmpPath, 384);
296
+ await (0, import_promises.rename)(tmpPath, walletPath);
297
+ }
298
+ function getWalletConfigPath() {
299
+ return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
300
+ }
301
+
302
+ // src/provision.ts
303
+ var TRAILING_SLASH = /\/$/;
304
+ var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
305
+ var ProvisionResponseInvalidError = class extends Error {
306
+ code = "PROVISION_RESPONSE_INVALID";
307
+ constructor(message) {
308
+ super(message);
309
+ this.name = "ProvisionResponseInvalidError";
310
+ }
311
+ };
312
+ var ProvisionHttpError = class extends Error {
313
+ code = "PROVISION_HTTP_ERROR";
314
+ status;
315
+ body;
316
+ constructor(status, body) {
317
+ super(`provision failed: HTTP ${status}: ${body}`);
318
+ this.name = "ProvisionHttpError";
319
+ this.status = status;
320
+ this.body = body;
321
+ }
322
+ };
323
+ function resolveBaseUrl(override) {
324
+ const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
325
+ return candidate.replace(TRAILING_SLASH, "");
326
+ }
327
+ function isNonEmptyString(value) {
328
+ return typeof value === "string" && value.length > 0;
329
+ }
330
+ function validateProvisionResponse(data) {
331
+ if (typeof data !== "object" || data === null) {
332
+ throw new ProvisionResponseInvalidError(
333
+ "provision response is not an object"
334
+ );
335
+ }
336
+ const { subOrgId, walletAddress, hmacSecret } = data;
337
+ if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
338
+ throw new ProvisionResponseInvalidError(
339
+ "provision response missing subOrgId, walletAddress, or hmacSecret"
340
+ );
341
+ }
342
+ if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
343
+ throw new ProvisionResponseInvalidError(
344
+ `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
345
+ );
346
+ }
347
+ return {
348
+ subOrgId,
349
+ walletAddress,
350
+ hmacSecret
351
+ };
352
+ }
353
+ async function provisionWallet(options = {}) {
354
+ const baseUrl = resolveBaseUrl(options.baseUrl);
355
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
356
+ const response = await fetchImpl(`${baseUrl}/api/agentic-wallet/provision`, {
357
+ method: "POST",
358
+ headers: { "content-type": "application/json" },
359
+ body: "{}",
360
+ signal: AbortSignal.timeout(3e4)
361
+ });
362
+ if (!response.ok) {
363
+ const text = await response.text();
364
+ throw new ProvisionHttpError(response.status, text);
365
+ }
366
+ const raw = await response.json();
367
+ const data = validateProvisionResponse(raw);
368
+ await writeWalletConfig(data);
369
+ return data;
370
+ }
371
+
199
372
  // src/skill-install.ts
373
+ var import_node_crypto3 = require("crypto");
374
+ var import_promises3 = require("fs/promises");
375
+ var import_node_path5 = require("path");
376
+ var import_node_url2 = require("url");
377
+
378
+ // src/mcp-register.ts
379
+ var import_node_crypto2 = require("crypto");
380
+ var import_promises2 = require("fs/promises");
381
+ var import_node_os3 = require("os");
382
+ var import_node_path4 = require("path");
383
+
384
+ // src/runtime-detect.ts
200
385
  var import_node_child_process = require("child_process");
201
- var import_promises = require("fs/promises");
202
386
  var import_node_fs2 = require("fs");
203
- var import_node_path2 = require("path");
387
+ var import_node_path3 = require("path");
204
388
  var import_node_url = require("url");
205
- var HOOK_BIN = "keeperhub-wallet-hook";
206
- var HOOK_COMMAND_BARE = HOOK_BIN;
207
389
  var PACKAGE_NAME = "@keeperhub/wallet";
208
390
  function readPackageVersion() {
209
391
  try {
210
- const here = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(__filename));
211
- const pkgPath = (0, import_node_path2.join)(here, "..", "package.json");
392
+ const here = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(__filename));
393
+ const pkgPath = (0, import_node_path3.join)(here, "..", "package.json");
212
394
  const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
213
395
  const parsed = JSON.parse(raw);
214
396
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
@@ -218,9 +400,171 @@ function readPackageVersion() {
218
400
  }
219
401
  return "latest";
220
402
  }
221
- function buildNpxCommand(version) {
222
- return `npx -y -p ${PACKAGE_NAME}@${version} ${HOOK_BIN}`;
403
+ function isNpxExecution() {
404
+ const execPath = process.env.npm_execpath;
405
+ if (typeof execPath !== "string" || execPath.length === 0) {
406
+ return false;
407
+ }
408
+ if (/(?:^|[\\/])npx-cli\.(?:js|cjs|mjs)$/i.test(execPath)) {
409
+ return true;
410
+ }
411
+ if (/(?:^|[\\/])npx(?:\.cmd|\.exe|\.ps1)?$/i.test(execPath)) {
412
+ return true;
413
+ }
414
+ return false;
415
+ }
416
+ var TRANSIENT_CACHE_PATTERNS = [
417
+ /[\\/]_npx[\\/]/,
418
+ /[\\/]dlx-[A-Za-z0-9]+[\\/]/,
419
+ /[\\/]xfs-[A-Za-z0-9]+[\\/]/,
420
+ /[\\/]\.bun[\\/]install[\\/]cache[\\/]/
421
+ ];
422
+ function isPathUnderTransientCache(resolvedPath) {
423
+ for (const re of TRANSIENT_CACHE_PATTERNS) {
424
+ if (re.test(resolvedPath)) {
425
+ return true;
426
+ }
427
+ }
428
+ return false;
429
+ }
430
+ function resolveBinCommand(binName) {
431
+ const version = readPackageVersion();
432
+ const npxArgs = ["-y", "-p", `${PACKAGE_NAME}@${version}`, binName];
433
+ const npxCommandString = `npx ${npxArgs.join(" ")}`;
434
+ if (isNpxExecution()) {
435
+ return {
436
+ commandString: npxCommandString,
437
+ command: "npx",
438
+ args: npxArgs
439
+ };
440
+ }
441
+ try {
442
+ const resolved = (0, import_node_child_process.execFileSync)("/bin/sh", ["-c", `command -v ${binName}`], {
443
+ stdio: ["ignore", "pipe", "ignore"]
444
+ }).toString().trim();
445
+ if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
446
+ return {
447
+ commandString: binName,
448
+ command: binName,
449
+ args: []
450
+ };
451
+ }
452
+ } catch {
453
+ }
454
+ return {
455
+ commandString: npxCommandString,
456
+ command: "npx",
457
+ args: npxArgs
458
+ };
459
+ }
460
+
461
+ // src/mcp-register.ts
462
+ var MCP_BIN = "keeperhub-wallet-mcp";
463
+ var MCP_SERVER_NAME = "keeperhub-wallet";
464
+ function resolveMcpCommand() {
465
+ const envOverride = process.env.KEEPERHUB_WALLET_MCP_COMMAND;
466
+ if (envOverride && envOverride.length > 0) {
467
+ const parts = envOverride.trim().split(/\s+/);
468
+ const head = parts[0] ?? envOverride;
469
+ return { command: head, args: parts.slice(1) };
470
+ }
471
+ const resolved = resolveBinCommand(MCP_BIN);
472
+ return { command: resolved.command, args: resolved.args };
473
+ }
474
+ function buildStandardEntry(cmd) {
475
+ const entry = {
476
+ command: cmd.command,
477
+ args: cmd.args
478
+ };
479
+ if (cmd.env && Object.keys(cmd.env).length > 0) {
480
+ entry.env = cmd.env;
481
+ }
482
+ return entry;
483
+ }
484
+ function buildOpencodeEntry(cmd) {
485
+ return {
486
+ type: "local",
487
+ command: [cmd.command, ...cmd.args],
488
+ enabled: true,
489
+ environment: cmd.env ?? {}
490
+ };
491
+ }
492
+ async function readJsonOrEmpty(path) {
493
+ let raw = null;
494
+ try {
495
+ raw = await (0, import_promises2.readFile)(path, "utf-8");
496
+ } catch (err) {
497
+ if (err.code !== "ENOENT") {
498
+ throw err;
499
+ }
500
+ }
501
+ if (raw === null) {
502
+ return {};
503
+ }
504
+ try {
505
+ return JSON.parse(raw);
506
+ } catch {
507
+ throw new Error(
508
+ `MCP config at ${path} is not valid JSON; aborting MCP registration`
509
+ );
510
+ }
511
+ }
512
+ async function writeJsonAtomic(path, payload) {
513
+ await (0, import_promises2.mkdir)((0, import_node_path4.dirname)(path), { recursive: true, mode: 448 });
514
+ const suffix = (0, import_node_crypto2.randomBytes)(8).toString("hex");
515
+ const tmpPath = `${path}.${process.pid}.${suffix}.tmp`;
516
+ try {
517
+ await (0, import_promises2.writeFile)(tmpPath, payload, { mode: 384 });
518
+ await (0, import_promises2.chmod)(tmpPath, 384);
519
+ await (0, import_promises2.rename)(tmpPath, path);
520
+ } catch (err) {
521
+ await (0, import_promises2.unlink)(tmpPath).catch(() => {
522
+ });
523
+ throw err;
524
+ }
525
+ }
526
+ async function writeStandardMcp(path, entry) {
527
+ const config = await readJsonOrEmpty(path);
528
+ const servers = typeof config.mcpServers === "object" && config.mcpServers !== null ? config.mcpServers : {};
529
+ servers[MCP_SERVER_NAME] = entry;
530
+ config.mcpServers = servers;
531
+ const payload = `${JSON.stringify(config, null, 2)}
532
+ `;
533
+ await writeJsonAtomic(path, payload);
534
+ }
535
+ async function writeOpencodeMcp(path, entry) {
536
+ const config = await readJsonOrEmpty(path);
537
+ const servers = typeof config.mcp === "object" && config.mcp !== null ? config.mcp : {};
538
+ servers[MCP_SERVER_NAME] = entry;
539
+ config.mcp = servers;
540
+ const payload = `${JSON.stringify(config, null, 2)}
541
+ `;
542
+ await writeJsonAtomic(path, payload);
543
+ }
544
+ async function registerMcpServer(target, options = {}) {
545
+ if (target.mcpSupport === "notice") {
546
+ throw new Error(
547
+ `agent ${target.agent} does not support auto-registered MCP servers; surface a notice instead`
548
+ );
549
+ }
550
+ if (!target.mcpConfigRel) {
551
+ throw new Error(
552
+ `agent ${target.agent} has mcpSupport=${target.mcpSupport} but no mcpConfigRel path`
553
+ );
554
+ }
555
+ const home = options.homeOverride ?? (0, import_node_os3.homedir)();
556
+ const path = (0, import_node_path4.join)(home, ...target.mcpConfigRel);
557
+ const cmd = options.command ?? resolveMcpCommand();
558
+ if (target.mcpSupport === "opencode") {
559
+ await writeOpencodeMcp(path, buildOpencodeEntry(cmd));
560
+ } else {
561
+ await writeStandardMcp(path, buildStandardEntry(cmd));
562
+ }
563
+ return { path, name: MCP_SERVER_NAME };
223
564
  }
565
+
566
+ // src/skill-install.ts
567
+ var HOOK_BIN = "keeperhub-wallet-hook";
224
568
  var KEEPERHUB_HOOK_MARKER = HOOK_BIN;
225
569
  function filterKeeperhubHooksFromEntry(entry) {
226
570
  if (typeof entry !== "object" || entry === null) {
@@ -247,14 +591,7 @@ function resolveHookCommand() {
247
591
  if (envOverride && envOverride.length > 0) {
248
592
  return envOverride;
249
593
  }
250
- try {
251
- (0, import_node_child_process.execFileSync)("/bin/sh", ["-c", `command -v ${HOOK_BIN}`], {
252
- stdio: "ignore"
253
- });
254
- return HOOK_COMMAND_BARE;
255
- } catch {
256
- return buildNpxCommand(readPackageVersion());
257
- }
594
+ return resolveBinCommand(HOOK_BIN).commandString;
258
595
  }
259
596
  function buildKeeperhubEntry(command) {
260
597
  return {
@@ -263,8 +600,8 @@ function buildKeeperhubEntry(command) {
263
600
  };
264
601
  }
265
602
  function resolveDefaultSkillSource() {
266
- const here = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(__filename));
267
- return (0, import_node_path2.join)(here, "..", "skill", "keeperhub-wallet.skill.md");
603
+ const here = (0, import_node_path5.dirname)((0, import_node_url2.fileURLToPath)(__filename));
604
+ return (0, import_node_path5.join)(here, "..", "skill", "keeperhub-wallet.skill.md");
268
605
  }
269
606
  function defaultNotice(msg) {
270
607
  process.stderr.write(`${msg}
@@ -274,7 +611,7 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
274
611
  const command = options.hookCommand ?? resolveHookCommand();
275
612
  let raw = null;
276
613
  try {
277
- raw = await (0, import_promises.readFile)(settingsPath, "utf-8");
614
+ raw = await (0, import_promises3.readFile)(settingsPath, "utf-8");
278
615
  } catch (err) {
279
616
  if (err.code !== "ENOENT") {
280
617
  throw err;
@@ -302,166 +639,136 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
302
639
  filtered.push(buildKeeperhubEntry(command));
303
640
  hooks.PreToolUse = filtered;
304
641
  config.hooks = hooks;
305
- await (0, import_promises.mkdir)((0, import_node_path2.dirname)(settingsPath), { recursive: true, mode: 448 });
642
+ await (0, import_promises3.mkdir)((0, import_node_path5.dirname)(settingsPath), { recursive: true, mode: 448 });
306
643
  const payload = `${JSON.stringify(config, null, 2)}
307
644
  `;
308
- await (0, import_promises.writeFile)(settingsPath, payload, { mode: 384 });
309
- await (0, import_promises.chmod)(settingsPath, 384);
645
+ const suffix = (0, import_node_crypto3.randomBytes)(8).toString("hex");
646
+ const tmpPath = `${settingsPath}.${process.pid}.${suffix}.tmp`;
647
+ try {
648
+ await (0, import_promises3.writeFile)(tmpPath, payload, { mode: 384 });
649
+ await (0, import_promises3.chmod)(tmpPath, 384);
650
+ await (0, import_promises3.rename)(tmpPath, settingsPath);
651
+ } catch (err) {
652
+ await (0, import_promises3.unlink)(tmpPath).catch(() => {
653
+ });
654
+ throw err;
655
+ }
310
656
  }
311
657
  async function writeSkillToAgent(agent, skillSource) {
312
- await (0, import_promises.mkdir)(agent.skillsDir, { recursive: true, mode: 493 });
313
- const target = (0, import_node_path2.join)(agent.skillsDir, "keeperhub-wallet.skill.md");
314
- await (0, import_promises.copyFile)(skillSource, target);
315
- await (0, import_promises.chmod)(target, 420);
658
+ await (0, import_promises3.mkdir)(agent.skillsDir, { recursive: true, mode: 493 });
659
+ const target = (0, import_node_path5.join)(agent.skillsDir, "keeperhub-wallet.skill.md");
660
+ await (0, import_promises3.copyFile)(skillSource, target);
661
+ await (0, import_promises3.chmod)(target, 420);
316
662
  return { agent: agent.agent, path: target, status: "written" };
317
663
  }
318
- function buildNoticeMessage(agent, command) {
664
+ function buildHookNoticeMessage(agent, command) {
319
665
  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}`;
320
666
  }
667
+ function buildMcpNoticeMessage(agent, command) {
668
+ const cmd = [command.command, ...command.args].join(" ");
669
+ return `${agent.agent} does not support auto-registered MCP servers; add an entry named \`keeperhub-wallet\` running \`${cmd}\` to your MCP config manually`;
670
+ }
321
671
  async function installSkill(options = {}) {
322
672
  const agents = detectAgents(options.homeOverride);
323
673
  const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();
324
674
  const onNotice = options.onNotice ?? defaultNotice;
325
675
  const hookCommand = options.hookCommand ?? resolveHookCommand();
676
+ const mcpCommand = options.mcpCommand ?? resolveMcpCommand();
326
677
  const skillWrites = [];
327
678
  const hookRegistrations = [];
679
+ const mcpRegistrations = [];
328
680
  for (const agent of agents) {
329
- const write = await writeSkillToAgent(agent, skillSource);
330
- skillWrites.push(write);
331
- if (agent.hookSupport === "claude-code") {
332
- await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
333
- hookRegistrations.push({
681
+ try {
682
+ const write = await writeSkillToAgent(agent, skillSource);
683
+ skillWrites.push(write);
684
+ } catch (err) {
685
+ const message = err instanceof Error ? err.message : String(err);
686
+ skillWrites.push({
334
687
  agent: agent.agent,
335
- status: "registered"
688
+ path: "",
689
+ status: "skipped"
336
690
  });
691
+ onNotice(`${agent.agent}: skill copy failed (${message})`);
692
+ continue;
693
+ }
694
+ if (agent.hookSupport === "claude-code") {
695
+ try {
696
+ await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
697
+ hookRegistrations.push({
698
+ agent: agent.agent,
699
+ status: "registered"
700
+ });
701
+ } catch (err) {
702
+ const message = err instanceof Error ? err.message : String(err);
703
+ hookRegistrations.push({
704
+ agent: agent.agent,
705
+ status: "failed",
706
+ message
707
+ });
708
+ onNotice(`${agent.agent}: hook registration failed (${message})`);
709
+ }
337
710
  } else {
338
- const message = buildNoticeMessage(agent, hookCommand);
711
+ const noticeMessage = buildHookNoticeMessage(agent, hookCommand);
339
712
  hookRegistrations.push({
340
713
  agent: agent.agent,
341
714
  status: "notice",
342
- message
715
+ message: noticeMessage
343
716
  });
344
- onNotice(message);
717
+ onNotice(noticeMessage);
345
718
  }
346
- }
347
- return { skillWrites, hookRegistrations };
348
- }
349
-
350
- // src/storage.ts
351
- var import_promises2 = require("fs/promises");
352
- var import_node_os2 = require("os");
353
- var import_node_path3 = require("path");
354
-
355
- // src/types.ts
356
- var KeeperHubError = class extends Error {
357
- code;
358
- constructor(code, message) {
359
- super(message);
360
- this.name = "KeeperHubError";
361
- this.code = code;
362
- }
363
- };
364
- var WalletConfigMissingError = class extends Error {
365
- constructor() {
366
- super(
367
- "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
368
- );
369
- this.name = "WalletConfigMissingError";
370
- }
371
- };
372
-
373
- // src/storage.ts
374
- async function readWalletConfig() {
375
- const walletPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
376
- let raw;
377
- try {
378
- raw = await (0, import_promises2.readFile)(walletPath, "utf-8");
379
- } catch (err) {
380
- if (err.code === "ENOENT") {
381
- throw new WalletConfigMissingError();
719
+ if (agent.mcpSupport === "notice") {
720
+ const noticeMessage = buildMcpNoticeMessage(agent, mcpCommand);
721
+ mcpRegistrations.push({
722
+ agent: agent.agent,
723
+ status: "notice",
724
+ message: noticeMessage
725
+ });
726
+ onNotice(noticeMessage);
727
+ continue;
728
+ }
729
+ try {
730
+ const mcpResult = await registerMcpServer(agent, {
731
+ homeOverride: options.homeOverride,
732
+ command: mcpCommand
733
+ });
734
+ mcpRegistrations.push({
735
+ agent: agent.agent,
736
+ status: "registered",
737
+ path: mcpResult.path
738
+ });
739
+ } catch (err) {
740
+ const message = err instanceof Error ? err.message : String(err);
741
+ mcpRegistrations.push({
742
+ agent: agent.agent,
743
+ status: "failed",
744
+ message
745
+ });
746
+ onNotice(`${agent.agent}: MCP registration failed (${message})`);
382
747
  }
383
- throw err;
384
- }
385
- const parsed = JSON.parse(raw);
386
- if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
387
- throw new Error(`Malformed wallet.json at ${walletPath}`);
388
748
  }
389
- return parsed;
390
- }
391
- async function writeWalletConfig(config) {
392
- const walletPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
393
- await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(walletPath), { recursive: true, mode: 448 });
394
- await (0, import_promises2.writeFile)(walletPath, JSON.stringify(config, null, 2), { mode: 384 });
395
- await (0, import_promises2.chmod)(walletPath, 384);
396
- }
397
- function getWalletConfigPath() {
398
- return (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
749
+ return { skillWrites, hookRegistrations, mcpRegistrations };
399
750
  }
400
751
 
401
752
  // src/cli.ts
402
- var TRAILING_SLASH = /\/$/;
403
- var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
404
- function resolveBaseUrl(override) {
405
- const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
406
- return candidate.replace(TRAILING_SLASH, "");
407
- }
408
- function isNonEmptyString(value) {
409
- return typeof value === "string" && value.length > 0;
410
- }
411
- function provisionInvalidError(message) {
412
- const err = new Error(message);
413
- err.code = "PROVISION_RESPONSE_INVALID";
414
- return err;
415
- }
416
- function validateProvisionResponse(data) {
417
- if (typeof data !== "object" || data === null) {
418
- throw provisionInvalidError("provision response is not an object");
419
- }
420
- const { subOrgId, walletAddress, hmacSecret } = data;
421
- if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
422
- throw provisionInvalidError(
423
- "provision response missing subOrgId, walletAddress, or hmacSecret"
424
- );
425
- }
426
- if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
427
- throw provisionInvalidError(
428
- `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
429
- );
430
- }
431
- return {
432
- subOrgId,
433
- walletAddress,
434
- hmacSecret
435
- };
436
- }
437
753
  async function cmdAdd(opts = {}) {
438
- const baseUrl = resolveBaseUrl(opts.baseUrl);
439
- const response = await fetch(`${baseUrl}/api/agentic-wallet/provision`, {
440
- method: "POST",
441
- headers: { "content-type": "application/json" },
442
- body: "{}"
443
- });
444
- if (!response.ok) {
445
- const text = await response.text();
446
- process.stderr.write(
447
- `[keeperhub-wallet] provision failed: HTTP ${response.status}: ${text}
448
- `
449
- );
450
- process.exit(1);
451
- }
452
- const raw = await response.json();
453
- const data = validateProvisionResponse(raw);
454
- await writeWalletConfig({
455
- subOrgId: data.subOrgId,
456
- walletAddress: data.walletAddress,
457
- hmacSecret: data.hmacSecret
458
- });
459
- process.stdout.write(`subOrgId: ${data.subOrgId}
754
+ try {
755
+ const data = await provisionWallet({ baseUrl: opts.baseUrl });
756
+ process.stdout.write(`subOrgId: ${data.subOrgId}
460
757
  `);
461
- process.stdout.write(`walletAddress: ${data.walletAddress}
758
+ process.stdout.write(`walletAddress: ${data.walletAddress}
462
759
  `);
463
- process.stdout.write(`config written to ${getWalletConfigPath()}
760
+ process.stdout.write(`config written to ${getWalletConfigPath()}
464
761
  `);
762
+ } catch (err) {
763
+ if (err instanceof ProvisionHttpError) {
764
+ process.stderr.write(
765
+ `[keeperhub-wallet] provision failed: HTTP ${err.status}: ${err.body}
766
+ `
767
+ );
768
+ process.exit(1);
769
+ }
770
+ throw err;
771
+ }
465
772
  }
466
773
  async function cmdFund() {
467
774
  const wallet = await readWalletConfig();
@@ -529,6 +836,29 @@ async function runCli(argv = process.argv) {
529
836
  } else if (reg.status === "notice") {
530
837
  process.stderr.write(
531
838
  `notice: ${reg.agent} -> ${reg.message ?? ""}
839
+ `
840
+ );
841
+ } else if (reg.status === "failed") {
842
+ process.stderr.write(
843
+ `hook: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
844
+ `
845
+ );
846
+ }
847
+ }
848
+ for (const reg of result.mcpRegistrations) {
849
+ if (reg.status === "registered") {
850
+ process.stdout.write(
851
+ `mcp: ${reg.agent} -> registered at ${reg.path ?? "(unknown path)"}
852
+ `
853
+ );
854
+ } else if (reg.status === "notice") {
855
+ process.stderr.write(
856
+ `notice: ${reg.agent} mcp -> ${reg.message ?? ""}
857
+ `
858
+ );
859
+ } else if (reg.status === "failed") {
860
+ process.stderr.write(
861
+ `mcp: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
532
862
  `
533
863
  );
534
864
  }
@@ -557,15 +887,15 @@ async function runCli(argv = process.argv) {
557
887
  }
558
888
 
559
889
  // src/hmac.ts
560
- var import_node_crypto = require("crypto");
890
+ var import_node_crypto4 = require("crypto");
561
891
  function computeSignature(secret, method, path, subOrgId, body, timestamp) {
562
- const bodyDigest = (0, import_node_crypto.createHash)("sha256").update(body).digest("hex");
892
+ const bodyDigest = (0, import_node_crypto4.createHash)("sha256").update(body).digest("hex");
563
893
  const signingString = `${method}
564
894
  ${path}
565
895
  ${subOrgId}
566
896
  ${bodyDigest}
567
897
  ${timestamp}`;
568
- return (0, import_node_crypto.createHmac)("sha256", secret).update(signingString).digest("hex");
898
+ return (0, import_node_crypto4.createHmac)("sha256", secret).update(signingString).digest("hex");
569
899
  }
570
900
  function buildHmacHeaders(secret, method, path, subOrgId, body) {
571
901
  const timestamp = String(Math.floor(Date.now() / 1e3));
@@ -657,9 +987,9 @@ var KeeperHubClient = class {
657
987
  };
658
988
 
659
989
  // src/safety-config.ts
660
- var import_promises3 = require("fs/promises");
661
- var import_node_os3 = require("os");
662
- var import_node_path4 = require("path");
990
+ var import_promises4 = require("fs/promises");
991
+ var import_node_os4 = require("os");
992
+ var import_node_path6 = require("path");
663
993
  var DEFAULT_SAFETY_CONFIG = {
664
994
  auto_approve_max_usd: 5,
665
995
  ask_threshold_usd: 50,
@@ -672,20 +1002,20 @@ var DEFAULT_SAFETY_CONFIG = {
672
1002
  ]
673
1003
  };
674
1004
  function getSafetyPath() {
675
- return (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".keeperhub", "safety.json");
1005
+ return (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".keeperhub", "safety.json");
676
1006
  }
677
1007
  async function loadSafetyConfig() {
678
1008
  const path = getSafetyPath();
679
1009
  let raw;
680
1010
  try {
681
- raw = await (0, import_promises3.readFile)(path, "utf-8");
1011
+ raw = await (0, import_promises4.readFile)(path, "utf-8");
682
1012
  } catch (err) {
683
1013
  if (err.code === "ENOENT") {
684
- await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(path), { recursive: true, mode: 448 });
685
- await (0, import_promises3.writeFile)(path, JSON.stringify(DEFAULT_SAFETY_CONFIG, null, 2), {
1014
+ await (0, import_promises4.mkdir)((0, import_node_path6.dirname)(path), { recursive: true, mode: 448 });
1015
+ await (0, import_promises4.writeFile)(path, JSON.stringify(DEFAULT_SAFETY_CONFIG, null, 2), {
686
1016
  mode: 420
687
1017
  });
688
- await (0, import_promises3.chmod)(path, 420);
1018
+ await (0, import_promises4.chmod)(path, 420);
689
1019
  return DEFAULT_SAFETY_CONFIG;
690
1020
  }
691
1021
  throw err;
@@ -882,7 +1212,7 @@ function parseMppChallenge(response) {
882
1212
  }
883
1213
 
884
1214
  // src/payment-signer.ts
885
- var import_node_crypto2 = require("crypto");
1215
+ var import_node_crypto5 = require("crypto");
886
1216
 
887
1217
  // src/workflow-slug.ts
888
1218
  var KEEPERHUB_WORKFLOW_RE = /\/api\/mcp\/workflows\/([a-zA-Z0-9_-]+)\/call(?:\/?)(?:\?|$|#)/;
@@ -1062,7 +1392,7 @@ function createPaymentSigner(opts = {}) {
1062
1392
  const now = Math.floor(Date.now() / 1e3);
1063
1393
  const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;
1064
1394
  const validBefore = now + accept.maxTimeoutSeconds;
1065
- const nonce = `0x${(0, import_node_crypto2.randomBytes)(NONCE_BYTES).toString("hex")}`;
1395
+ const nonce = `0x${(0, import_node_crypto5.randomBytes)(NONCE_BYTES).toString("hex")}`;
1066
1396
  const client = clientFactory(wallet);
1067
1397
  const signature = await signOrPoll(client, {
1068
1398
  chain: "base",