@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.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
- import { chmod, copyFile, mkdir, readFile, writeFile } from "fs/promises";
152
351
  import { readFileSync } from "fs";
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,171 @@ function readPackageVersion() {
168
365
  }
169
366
  return "latest";
170
367
  }
171
- function buildNpxCommand(version) {
172
- return `npx -y -p ${PACKAGE_NAME}@${version} ${HOOK_BIN}`;
368
+ function isNpxExecution() {
369
+ const execPath = process.env.npm_execpath;
370
+ if (typeof execPath !== "string" || execPath.length === 0) {
371
+ return false;
372
+ }
373
+ if (/(?:^|[\\/])npx-cli\.(?:js|cjs|mjs)$/i.test(execPath)) {
374
+ return true;
375
+ }
376
+ if (/(?:^|[\\/])npx(?:\.cmd|\.exe|\.ps1)?$/i.test(execPath)) {
377
+ return true;
378
+ }
379
+ return false;
380
+ }
381
+ var TRANSIENT_CACHE_PATTERNS = [
382
+ /[\\/]_npx[\\/]/,
383
+ /[\\/]dlx-[A-Za-z0-9]+[\\/]/,
384
+ /[\\/]xfs-[A-Za-z0-9]+[\\/]/,
385
+ /[\\/]\.bun[\\/]install[\\/]cache[\\/]/
386
+ ];
387
+ function isPathUnderTransientCache(resolvedPath) {
388
+ for (const re of TRANSIENT_CACHE_PATTERNS) {
389
+ if (re.test(resolvedPath)) {
390
+ return true;
391
+ }
392
+ }
393
+ return false;
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 };
173
529
  }
530
+
531
+ // src/skill-install.ts
532
+ var HOOK_BIN = "keeperhub-wallet-hook";
174
533
  var KEEPERHUB_HOOK_MARKER = HOOK_BIN;
175
534
  function filterKeeperhubHooksFromEntry(entry) {
176
535
  if (typeof entry !== "object" || entry === null) {
@@ -197,14 +556,7 @@ function resolveHookCommand() {
197
556
  if (envOverride && envOverride.length > 0) {
198
557
  return envOverride;
199
558
  }
200
- try {
201
- execFileSync("/bin/sh", ["-c", `command -v ${HOOK_BIN}`], {
202
- stdio: "ignore"
203
- });
204
- return HOOK_COMMAND_BARE;
205
- } catch {
206
- return buildNpxCommand(readPackageVersion());
207
- }
559
+ return resolveBinCommand(HOOK_BIN).commandString;
208
560
  }
209
561
  function buildKeeperhubEntry(command) {
210
562
  return {
@@ -213,8 +565,8 @@ function buildKeeperhubEntry(command) {
213
565
  };
214
566
  }
215
567
  function resolveDefaultSkillSource() {
216
- const here = dirname2(fileURLToPath(import.meta.url));
217
- 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");
218
570
  }
219
571
  function defaultNotice(msg) {
220
572
  process.stderr.write(`${msg}
@@ -224,7 +576,7 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
224
576
  const command = options.hookCommand ?? resolveHookCommand();
225
577
  let raw = null;
226
578
  try {
227
- raw = await readFile(settingsPath, "utf-8");
579
+ raw = await readFile3(settingsPath, "utf-8");
228
580
  } catch (err) {
229
581
  if (err.code !== "ENOENT") {
230
582
  throw err;
@@ -252,166 +604,136 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
252
604
  filtered.push(buildKeeperhubEntry(command));
253
605
  hooks.PreToolUse = filtered;
254
606
  config.hooks = hooks;
255
- await mkdir(dirname2(settingsPath), { recursive: true, mode: 448 });
607
+ await mkdir3(dirname5(settingsPath), { recursive: true, mode: 448 });
256
608
  const payload = `${JSON.stringify(config, null, 2)}
257
609
  `;
258
- await writeFile(settingsPath, payload, { mode: 384 });
259
- 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
+ }
260
621
  }
261
622
  async function writeSkillToAgent(agent, skillSource) {
262
- await mkdir(agent.skillsDir, { recursive: true, mode: 493 });
263
- 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");
264
625
  await copyFile(skillSource, target);
265
- await chmod(target, 420);
626
+ await chmod3(target, 420);
266
627
  return { agent: agent.agent, path: target, status: "written" };
267
628
  }
268
- function buildNoticeMessage(agent, command) {
629
+ function buildHookNoticeMessage(agent, command) {
269
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}`;
270
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
+ }
271
636
  async function installSkill(options = {}) {
272
637
  const agents = detectAgents(options.homeOverride);
273
638
  const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();
274
639
  const onNotice = options.onNotice ?? defaultNotice;
275
640
  const hookCommand = options.hookCommand ?? resolveHookCommand();
641
+ const mcpCommand = options.mcpCommand ?? resolveMcpCommand();
276
642
  const skillWrites = [];
277
643
  const hookRegistrations = [];
644
+ const mcpRegistrations = [];
278
645
  for (const agent of agents) {
279
- const write = await writeSkillToAgent(agent, skillSource);
280
- skillWrites.push(write);
281
- if (agent.hookSupport === "claude-code") {
282
- await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
283
- 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({
284
652
  agent: agent.agent,
285
- status: "registered"
653
+ path: "",
654
+ status: "skipped"
286
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
+ }
287
675
  } else {
288
- const message = buildNoticeMessage(agent, hookCommand);
676
+ const noticeMessage = buildHookNoticeMessage(agent, hookCommand);
289
677
  hookRegistrations.push({
290
678
  agent: agent.agent,
291
679
  status: "notice",
292
- message
680
+ message: noticeMessage
293
681
  });
294
- onNotice(message);
682
+ onNotice(noticeMessage);
295
683
  }
296
- }
297
- return { skillWrites, hookRegistrations };
298
- }
299
-
300
- // src/storage.ts
301
- import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
302
- import { homedir as homedir2 } from "os";
303
- import { dirname as dirname3, join as join3 } from "path";
304
-
305
- // src/types.ts
306
- var KeeperHubError = class extends Error {
307
- code;
308
- constructor(code, message) {
309
- super(message);
310
- this.name = "KeeperHubError";
311
- this.code = code;
312
- }
313
- };
314
- var WalletConfigMissingError = class extends Error {
315
- constructor() {
316
- super(
317
- "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
318
- );
319
- this.name = "WalletConfigMissingError";
320
- }
321
- };
322
-
323
- // src/storage.ts
324
- async function readWalletConfig() {
325
- const walletPath = join3(homedir2(), ".keeperhub", "wallet.json");
326
- let raw;
327
- try {
328
- raw = await readFile2(walletPath, "utf-8");
329
- } catch (err) {
330
- if (err.code === "ENOENT") {
331
- 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})`);
332
712
  }
333
- throw err;
334
- }
335
- const parsed = JSON.parse(raw);
336
- if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
337
- throw new Error(`Malformed wallet.json at ${walletPath}`);
338
713
  }
339
- return parsed;
340
- }
341
- async function writeWalletConfig(config) {
342
- const walletPath = join3(homedir2(), ".keeperhub", "wallet.json");
343
- await mkdir2(dirname3(walletPath), { recursive: true, mode: 448 });
344
- await writeFile2(walletPath, JSON.stringify(config, null, 2), { mode: 384 });
345
- await chmod2(walletPath, 384);
346
- }
347
- function getWalletConfigPath() {
348
- return join3(homedir2(), ".keeperhub", "wallet.json");
714
+ return { skillWrites, hookRegistrations, mcpRegistrations };
349
715
  }
350
716
 
351
717
  // src/cli.ts
352
- var TRAILING_SLASH = /\/$/;
353
- var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
354
- function resolveBaseUrl(override) {
355
- const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
356
- return candidate.replace(TRAILING_SLASH, "");
357
- }
358
- function isNonEmptyString(value) {
359
- return typeof value === "string" && value.length > 0;
360
- }
361
- function provisionInvalidError(message) {
362
- const err = new Error(message);
363
- err.code = "PROVISION_RESPONSE_INVALID";
364
- return err;
365
- }
366
- function validateProvisionResponse(data) {
367
- if (typeof data !== "object" || data === null) {
368
- throw provisionInvalidError("provision response is not an object");
369
- }
370
- const { subOrgId, walletAddress, hmacSecret } = data;
371
- if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
372
- throw provisionInvalidError(
373
- "provision response missing subOrgId, walletAddress, or hmacSecret"
374
- );
375
- }
376
- if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
377
- throw provisionInvalidError(
378
- `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
379
- );
380
- }
381
- return {
382
- subOrgId,
383
- walletAddress,
384
- hmacSecret
385
- };
386
- }
387
718
  async function cmdAdd(opts = {}) {
388
- const baseUrl = resolveBaseUrl(opts.baseUrl);
389
- const response = await fetch(`${baseUrl}/api/agentic-wallet/provision`, {
390
- method: "POST",
391
- headers: { "content-type": "application/json" },
392
- body: "{}"
393
- });
394
- if (!response.ok) {
395
- const text = await response.text();
396
- process.stderr.write(
397
- `[keeperhub-wallet] provision failed: HTTP ${response.status}: ${text}
398
- `
399
- );
400
- process.exit(1);
401
- }
402
- const raw = await response.json();
403
- const data = validateProvisionResponse(raw);
404
- await writeWalletConfig({
405
- subOrgId: data.subOrgId,
406
- walletAddress: data.walletAddress,
407
- hmacSecret: data.hmacSecret
408
- });
409
- process.stdout.write(`subOrgId: ${data.subOrgId}
719
+ try {
720
+ const data = await provisionWallet({ baseUrl: opts.baseUrl });
721
+ process.stdout.write(`subOrgId: ${data.subOrgId}
410
722
  `);
411
- process.stdout.write(`walletAddress: ${data.walletAddress}
723
+ process.stdout.write(`walletAddress: ${data.walletAddress}
412
724
  `);
413
- process.stdout.write(`config written to ${getWalletConfigPath()}
725
+ process.stdout.write(`config written to ${getWalletConfigPath()}
414
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
+ }
415
737
  }
416
738
  async function cmdFund() {
417
739
  const wallet = await readWalletConfig();
@@ -479,6 +801,29 @@ async function runCli(argv = process.argv) {
479
801
  } else if (reg.status === "notice") {
480
802
  process.stderr.write(
481
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"})
482
827
  `
483
828
  );
484
829
  }
@@ -607,9 +952,9 @@ var KeeperHubClient = class {
607
952
  };
608
953
 
609
954
  // src/safety-config.ts
610
- import { chmod as chmod3, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
611
- import { homedir as homedir3 } from "os";
612
- 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";
613
958
  var DEFAULT_SAFETY_CONFIG = {
614
959
  auto_approve_max_usd: 5,
615
960
  ask_threshold_usd: 50,
@@ -622,20 +967,20 @@ var DEFAULT_SAFETY_CONFIG = {
622
967
  ]
623
968
  };
624
969
  function getSafetyPath() {
625
- return join4(homedir3(), ".keeperhub", "safety.json");
970
+ return join6(homedir4(), ".keeperhub", "safety.json");
626
971
  }
627
972
  async function loadSafetyConfig() {
628
973
  const path = getSafetyPath();
629
974
  let raw;
630
975
  try {
631
- raw = await readFile3(path, "utf-8");
976
+ raw = await readFile4(path, "utf-8");
632
977
  } catch (err) {
633
978
  if (err.code === "ENOENT") {
634
- await mkdir3(dirname4(path), { recursive: true, mode: 448 });
635
- 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), {
636
981
  mode: 420
637
982
  });
638
- await chmod3(path, 420);
983
+ await chmod4(path, 420);
639
984
  return DEFAULT_SAFETY_CONFIG;
640
985
  }
641
986
  throw err;
@@ -832,7 +1177,7 @@ function parseMppChallenge(response) {
832
1177
  }
833
1178
 
834
1179
  // src/payment-signer.ts
835
- import { randomBytes } from "crypto";
1180
+ import { randomBytes as randomBytes4 } from "crypto";
836
1181
 
837
1182
  // src/workflow-slug.ts
838
1183
  var KEEPERHUB_WORKFLOW_RE = /\/api\/mcp\/workflows\/([a-zA-Z0-9_-]+)\/call(?:\/?)(?:\?|$|#)/;
@@ -1012,7 +1357,7 @@ function createPaymentSigner(opts = {}) {
1012
1357
  const now = Math.floor(Date.now() / 1e3);
1013
1358
  const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;
1014
1359
  const validBefore = now + accept.maxTimeoutSeconds;
1015
- const nonce = `0x${randomBytes(NONCE_BYTES).toString("hex")}`;
1360
+ const nonce = `0x${randomBytes4(NONCE_BYTES).toString("hex")}`;
1016
1361
  const client = clientFactory(wallet);
1017
1362
  const signature = await signOrPoll(client, {
1018
1363
  chain: "base",