@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.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
386
  var import_node_fs2 = require("fs");
202
- var import_promises = require("fs/promises");
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,6 @@ function readPackageVersion() {
218
400
  }
219
401
  return "latest";
220
402
  }
221
- function buildNpxCommand(version) {
222
- return `npx -y -p ${PACKAGE_NAME}@${version} ${HOOK_BIN}`;
223
- }
224
403
  function isNpxExecution() {
225
404
  const execPath = process.env.npm_execpath;
226
405
  if (typeof execPath !== "string" || execPath.length === 0) {
@@ -248,6 +427,144 @@ function isPathUnderTransientCache(resolvedPath) {
248
427
  }
249
428
  return false;
250
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 };
564
+ }
565
+
566
+ // src/skill-install.ts
567
+ var HOOK_BIN = "keeperhub-wallet-hook";
251
568
  var KEEPERHUB_HOOK_MARKER = HOOK_BIN;
252
569
  function filterKeeperhubHooksFromEntry(entry) {
253
570
  if (typeof entry !== "object" || entry === null) {
@@ -274,19 +591,7 @@ function resolveHookCommand() {
274
591
  if (envOverride && envOverride.length > 0) {
275
592
  return envOverride;
276
593
  }
277
- if (isNpxExecution()) {
278
- return buildNpxCommand(readPackageVersion());
279
- }
280
- try {
281
- const resolved = (0, import_node_child_process.execFileSync)("/bin/sh", ["-c", `command -v ${HOOK_BIN}`], {
282
- stdio: ["ignore", "pipe", "ignore"]
283
- }).toString().trim();
284
- if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
285
- return HOOK_COMMAND_BARE;
286
- }
287
- } catch {
288
- }
289
- return buildNpxCommand(readPackageVersion());
594
+ return resolveBinCommand(HOOK_BIN).commandString;
290
595
  }
291
596
  function buildKeeperhubEntry(command) {
292
597
  return {
@@ -295,8 +600,8 @@ function buildKeeperhubEntry(command) {
295
600
  };
296
601
  }
297
602
  function resolveDefaultSkillSource() {
298
- const here = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(__filename));
299
- 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");
300
605
  }
301
606
  function defaultNotice(msg) {
302
607
  process.stderr.write(`${msg}
@@ -306,7 +611,7 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
306
611
  const command = options.hookCommand ?? resolveHookCommand();
307
612
  let raw = null;
308
613
  try {
309
- raw = await (0, import_promises.readFile)(settingsPath, "utf-8");
614
+ raw = await (0, import_promises3.readFile)(settingsPath, "utf-8");
310
615
  } catch (err) {
311
616
  if (err.code !== "ENOENT") {
312
617
  throw err;
@@ -334,166 +639,136 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
334
639
  filtered.push(buildKeeperhubEntry(command));
335
640
  hooks.PreToolUse = filtered;
336
641
  config.hooks = hooks;
337
- 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 });
338
643
  const payload = `${JSON.stringify(config, null, 2)}
339
644
  `;
340
- await (0, import_promises.writeFile)(settingsPath, payload, { mode: 384 });
341
- 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
+ }
342
656
  }
343
657
  async function writeSkillToAgent(agent, skillSource) {
344
- await (0, import_promises.mkdir)(agent.skillsDir, { recursive: true, mode: 493 });
345
- const target = (0, import_node_path2.join)(agent.skillsDir, "keeperhub-wallet.skill.md");
346
- await (0, import_promises.copyFile)(skillSource, target);
347
- 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);
348
662
  return { agent: agent.agent, path: target, status: "written" };
349
663
  }
350
- function buildNoticeMessage(agent, command) {
664
+ function buildHookNoticeMessage(agent, command) {
351
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}`;
352
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
+ }
353
671
  async function installSkill(options = {}) {
354
672
  const agents = detectAgents(options.homeOverride);
355
673
  const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();
356
674
  const onNotice = options.onNotice ?? defaultNotice;
357
675
  const hookCommand = options.hookCommand ?? resolveHookCommand();
676
+ const mcpCommand = options.mcpCommand ?? resolveMcpCommand();
358
677
  const skillWrites = [];
359
678
  const hookRegistrations = [];
679
+ const mcpRegistrations = [];
360
680
  for (const agent of agents) {
361
- const write = await writeSkillToAgent(agent, skillSource);
362
- skillWrites.push(write);
363
- if (agent.hookSupport === "claude-code") {
364
- await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
365
- 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({
366
687
  agent: agent.agent,
367
- status: "registered"
688
+ path: "",
689
+ status: "skipped"
368
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
+ }
369
710
  } else {
370
- const message = buildNoticeMessage(agent, hookCommand);
711
+ const noticeMessage = buildHookNoticeMessage(agent, hookCommand);
371
712
  hookRegistrations.push({
372
713
  agent: agent.agent,
373
714
  status: "notice",
374
- message
715
+ message: noticeMessage
375
716
  });
376
- onNotice(message);
717
+ onNotice(noticeMessage);
377
718
  }
378
- }
379
- return { skillWrites, hookRegistrations };
380
- }
381
-
382
- // src/storage.ts
383
- var import_promises2 = require("fs/promises");
384
- var import_node_os2 = require("os");
385
- var import_node_path3 = require("path");
386
-
387
- // src/types.ts
388
- var KeeperHubError = class extends Error {
389
- code;
390
- constructor(code, message) {
391
- super(message);
392
- this.name = "KeeperHubError";
393
- this.code = code;
394
- }
395
- };
396
- var WalletConfigMissingError = class extends Error {
397
- constructor() {
398
- super(
399
- "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
400
- );
401
- this.name = "WalletConfigMissingError";
402
- }
403
- };
404
-
405
- // src/storage.ts
406
- async function readWalletConfig() {
407
- const walletPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
408
- let raw;
409
- try {
410
- raw = await (0, import_promises2.readFile)(walletPath, "utf-8");
411
- } catch (err) {
412
- if (err.code === "ENOENT") {
413
- 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})`);
414
747
  }
415
- throw err;
416
- }
417
- const parsed = JSON.parse(raw);
418
- if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
419
- throw new Error(`Malformed wallet.json at ${walletPath}`);
420
748
  }
421
- return parsed;
422
- }
423
- async function writeWalletConfig(config) {
424
- const walletPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
425
- await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(walletPath), { recursive: true, mode: 448 });
426
- await (0, import_promises2.writeFile)(walletPath, JSON.stringify(config, null, 2), { mode: 384 });
427
- await (0, import_promises2.chmod)(walletPath, 384);
428
- }
429
- function getWalletConfigPath() {
430
- return (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
749
+ return { skillWrites, hookRegistrations, mcpRegistrations };
431
750
  }
432
751
 
433
752
  // src/cli.ts
434
- var TRAILING_SLASH = /\/$/;
435
- var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
436
- function resolveBaseUrl(override) {
437
- const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
438
- return candidate.replace(TRAILING_SLASH, "");
439
- }
440
- function isNonEmptyString(value) {
441
- return typeof value === "string" && value.length > 0;
442
- }
443
- function provisionInvalidError(message) {
444
- const err = new Error(message);
445
- err.code = "PROVISION_RESPONSE_INVALID";
446
- return err;
447
- }
448
- function validateProvisionResponse(data) {
449
- if (typeof data !== "object" || data === null) {
450
- throw provisionInvalidError("provision response is not an object");
451
- }
452
- const { subOrgId, walletAddress, hmacSecret } = data;
453
- if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
454
- throw provisionInvalidError(
455
- "provision response missing subOrgId, walletAddress, or hmacSecret"
456
- );
457
- }
458
- if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
459
- throw provisionInvalidError(
460
- `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
461
- );
462
- }
463
- return {
464
- subOrgId,
465
- walletAddress,
466
- hmacSecret
467
- };
468
- }
469
753
  async function cmdAdd(opts = {}) {
470
- const baseUrl = resolveBaseUrl(opts.baseUrl);
471
- const response = await fetch(`${baseUrl}/api/agentic-wallet/provision`, {
472
- method: "POST",
473
- headers: { "content-type": "application/json" },
474
- body: "{}"
475
- });
476
- if (!response.ok) {
477
- const text = await response.text();
478
- process.stderr.write(
479
- `[keeperhub-wallet] provision failed: HTTP ${response.status}: ${text}
480
- `
481
- );
482
- process.exit(1);
483
- }
484
- const raw = await response.json();
485
- const data = validateProvisionResponse(raw);
486
- await writeWalletConfig({
487
- subOrgId: data.subOrgId,
488
- walletAddress: data.walletAddress,
489
- hmacSecret: data.hmacSecret
490
- });
491
- process.stdout.write(`subOrgId: ${data.subOrgId}
754
+ try {
755
+ const data = await provisionWallet({ baseUrl: opts.baseUrl });
756
+ process.stdout.write(`subOrgId: ${data.subOrgId}
492
757
  `);
493
- process.stdout.write(`walletAddress: ${data.walletAddress}
758
+ process.stdout.write(`walletAddress: ${data.walletAddress}
494
759
  `);
495
- process.stdout.write(`config written to ${getWalletConfigPath()}
760
+ process.stdout.write(`config written to ${getWalletConfigPath()}
496
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
+ }
497
772
  }
498
773
  async function cmdFund() {
499
774
  const wallet = await readWalletConfig();
@@ -561,6 +836,29 @@ async function runCli(argv = process.argv) {
561
836
  } else if (reg.status === "notice") {
562
837
  process.stderr.write(
563
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"})
564
862
  `
565
863
  );
566
864
  }
@@ -589,15 +887,15 @@ async function runCli(argv = process.argv) {
589
887
  }
590
888
 
591
889
  // src/hmac.ts
592
- var import_node_crypto = require("crypto");
890
+ var import_node_crypto4 = require("crypto");
593
891
  function computeSignature(secret, method, path, subOrgId, body, timestamp) {
594
- 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");
595
893
  const signingString = `${method}
596
894
  ${path}
597
895
  ${subOrgId}
598
896
  ${bodyDigest}
599
897
  ${timestamp}`;
600
- 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");
601
899
  }
602
900
  function buildHmacHeaders(secret, method, path, subOrgId, body) {
603
901
  const timestamp = String(Math.floor(Date.now() / 1e3));
@@ -689,9 +987,9 @@ var KeeperHubClient = class {
689
987
  };
690
988
 
691
989
  // src/safety-config.ts
692
- var import_promises3 = require("fs/promises");
693
- var import_node_os3 = require("os");
694
- 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");
695
993
  var DEFAULT_SAFETY_CONFIG = {
696
994
  auto_approve_max_usd: 5,
697
995
  ask_threshold_usd: 50,
@@ -704,20 +1002,20 @@ var DEFAULT_SAFETY_CONFIG = {
704
1002
  ]
705
1003
  };
706
1004
  function getSafetyPath() {
707
- 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");
708
1006
  }
709
1007
  async function loadSafetyConfig() {
710
1008
  const path = getSafetyPath();
711
1009
  let raw;
712
1010
  try {
713
- raw = await (0, import_promises3.readFile)(path, "utf-8");
1011
+ raw = await (0, import_promises4.readFile)(path, "utf-8");
714
1012
  } catch (err) {
715
1013
  if (err.code === "ENOENT") {
716
- await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(path), { recursive: true, mode: 448 });
717
- 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), {
718
1016
  mode: 420
719
1017
  });
720
- await (0, import_promises3.chmod)(path, 420);
1018
+ await (0, import_promises4.chmod)(path, 420);
721
1019
  return DEFAULT_SAFETY_CONFIG;
722
1020
  }
723
1021
  throw err;
@@ -914,7 +1212,7 @@ function parseMppChallenge(response) {
914
1212
  }
915
1213
 
916
1214
  // src/payment-signer.ts
917
- var import_node_crypto2 = require("crypto");
1215
+ var import_node_crypto5 = require("crypto");
918
1216
 
919
1217
  // src/workflow-slug.ts
920
1218
  var KEEPERHUB_WORKFLOW_RE = /\/api\/mcp\/workflows\/([a-zA-Z0-9_-]+)\/call(?:\/?)(?:\?|$|#)/;
@@ -1094,7 +1392,7 @@ function createPaymentSigner(opts = {}) {
1094
1392
  const now = Math.floor(Date.now() / 1e3);
1095
1393
  const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;
1096
1394
  const validBefore = now + accept.maxTimeoutSeconds;
1097
- 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")}`;
1098
1396
  const client = clientFactory(wallet);
1099
1397
  const signature = await signOrPoll(client, {
1100
1398
  chain: "base",