@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/cli.cjs CHANGED
@@ -111,75 +111,247 @@ function fund(walletAddress) {
111
111
  };
112
112
  }
113
113
 
114
- // src/skill-install.ts
115
- var import_node_child_process = require("child_process");
116
- var import_node_fs2 = require("fs");
114
+ // src/storage.ts
115
+ var import_node_crypto = require("crypto");
117
116
  var import_promises = require("fs/promises");
118
- var import_node_path2 = require("path");
119
- var import_node_url = require("url");
117
+ var import_node_os = require("os");
118
+ var import_node_path = require("path");
119
+
120
+ // src/types.ts
121
+ var WalletConfigMissingError = class extends Error {
122
+ constructor() {
123
+ super(
124
+ "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
125
+ );
126
+ this.name = "WalletConfigMissingError";
127
+ }
128
+ };
129
+ var WalletConfigCorruptError = class extends Error {
130
+ path;
131
+ constructor(path, reason) {
132
+ super(
133
+ `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).`
134
+ );
135
+ this.name = "WalletConfigCorruptError";
136
+ this.path = path;
137
+ }
138
+ };
139
+
140
+ // src/storage.ts
141
+ async function readWalletConfig() {
142
+ const walletPath = (0, import_node_path.join)((0, import_node_os.homedir)(), ".keeperhub", "wallet.json");
143
+ let raw;
144
+ try {
145
+ raw = await (0, import_promises.readFile)(walletPath, "utf-8");
146
+ } catch (err) {
147
+ if (err.code === "ENOENT") {
148
+ throw new WalletConfigMissingError();
149
+ }
150
+ throw err;
151
+ }
152
+ let parsed;
153
+ try {
154
+ parsed = JSON.parse(raw);
155
+ } catch (err) {
156
+ const reason = err instanceof Error ? err.message : String(err);
157
+ throw new WalletConfigCorruptError(walletPath, reason);
158
+ }
159
+ if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
160
+ throw new WalletConfigCorruptError(walletPath, "missing required fields");
161
+ }
162
+ return parsed;
163
+ }
164
+ async function writeWalletConfig(config) {
165
+ const walletPath = (0, import_node_path.join)((0, import_node_os.homedir)(), ".keeperhub", "wallet.json");
166
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(walletPath), { recursive: true, mode: 448 });
167
+ const suffix = (0, import_node_crypto.randomBytes)(8).toString("hex");
168
+ const tmpPath = `${walletPath}.${process.pid}.${suffix}.tmp`;
169
+ await (0, import_promises.writeFile)(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
170
+ await (0, import_promises.chmod)(tmpPath, 384);
171
+ await (0, import_promises.rename)(tmpPath, walletPath);
172
+ }
173
+ function getWalletConfigPath() {
174
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".keeperhub", "wallet.json");
175
+ }
176
+
177
+ // src/provision.ts
178
+ var TRAILING_SLASH = /\/$/;
179
+ var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
180
+ var ProvisionResponseInvalidError = class extends Error {
181
+ code = "PROVISION_RESPONSE_INVALID";
182
+ constructor(message) {
183
+ super(message);
184
+ this.name = "ProvisionResponseInvalidError";
185
+ }
186
+ };
187
+ var ProvisionHttpError = class extends Error {
188
+ code = "PROVISION_HTTP_ERROR";
189
+ status;
190
+ body;
191
+ constructor(status, body) {
192
+ super(`provision failed: HTTP ${status}: ${body}`);
193
+ this.name = "ProvisionHttpError";
194
+ this.status = status;
195
+ this.body = body;
196
+ }
197
+ };
198
+ function resolveBaseUrl(override) {
199
+ const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
200
+ return candidate.replace(TRAILING_SLASH, "");
201
+ }
202
+ function isNonEmptyString(value) {
203
+ return typeof value === "string" && value.length > 0;
204
+ }
205
+ function validateProvisionResponse(data) {
206
+ if (typeof data !== "object" || data === null) {
207
+ throw new ProvisionResponseInvalidError(
208
+ "provision response is not an object"
209
+ );
210
+ }
211
+ const { subOrgId, walletAddress, hmacSecret } = data;
212
+ if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
213
+ throw new ProvisionResponseInvalidError(
214
+ "provision response missing subOrgId, walletAddress, or hmacSecret"
215
+ );
216
+ }
217
+ if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
218
+ throw new ProvisionResponseInvalidError(
219
+ `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
220
+ );
221
+ }
222
+ return {
223
+ subOrgId,
224
+ walletAddress,
225
+ hmacSecret
226
+ };
227
+ }
228
+ async function provisionWallet(options = {}) {
229
+ const baseUrl = resolveBaseUrl(options.baseUrl);
230
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
231
+ const response = await fetchImpl(`${baseUrl}/api/agentic-wallet/provision`, {
232
+ method: "POST",
233
+ headers: { "content-type": "application/json" },
234
+ body: "{}",
235
+ signal: AbortSignal.timeout(3e4)
236
+ });
237
+ if (!response.ok) {
238
+ const text = await response.text();
239
+ throw new ProvisionHttpError(response.status, text);
240
+ }
241
+ const raw = await response.json();
242
+ const data = validateProvisionResponse(raw);
243
+ await writeWalletConfig(data);
244
+ return data;
245
+ }
246
+
247
+ // src/skill-install.ts
248
+ var import_node_crypto3 = require("crypto");
249
+ var import_promises3 = require("fs/promises");
250
+ var import_node_path5 = require("path");
251
+ var import_node_url2 = require("url");
120
252
 
121
253
  // src/agent-detect.ts
122
254
  var import_node_fs = require("fs");
123
- var import_node_os = require("os");
124
- var import_node_path = require("path");
255
+ var import_node_os2 = require("os");
256
+ var import_node_path2 = require("path");
125
257
  var AGENT_SPECS = [
126
258
  {
127
259
  agent: "claude-code",
128
260
  skillsRel: [".claude", "skills"],
129
261
  settingsRel: [".claude", "settings.json"],
130
- hookSupport: "claude-code"
262
+ hookSupport: "claude-code",
263
+ // ~/.claude.json is at HOME root (not under .claude/) and is large
264
+ // (100+KB on real installs). registerMcpServer reads/parses/rewrites it
265
+ // while preserving every other top-level key byte-for-byte.
266
+ mcpConfigRel: [".claude.json"],
267
+ mcpSupport: "claude-code"
131
268
  },
132
269
  {
133
270
  agent: "cursor",
134
271
  skillsRel: [".cursor", "skills"],
135
272
  settingsRel: [".cursor", "settings.json"],
136
- hookSupport: "notice"
273
+ hookSupport: "notice",
274
+ mcpConfigRel: [".cursor", "mcp.json"],
275
+ mcpSupport: "cursor"
137
276
  },
138
277
  {
139
278
  agent: "cline",
140
279
  skillsRel: [".cline", "skills"],
141
280
  settingsRel: [".cline", "settings.json"],
142
- hookSupport: "notice"
281
+ hookSupport: "notice",
282
+ // Cline keeps MCP state in a per-VS-Code-variant globalStorage path
283
+ // (e.g. ~/Library/Application Support/Code/User/globalStorage/
284
+ // saoudrizwan.claude-dev/settings/cline_mcp_settings.json) that is too
285
+ // fragile to auto-detect. Ship "notice" with a copy-paste entry shape
286
+ // instead of guessing the variant.
287
+ mcpSupport: "notice"
143
288
  },
144
289
  {
145
290
  agent: "windsurf",
146
291
  skillsRel: [".windsurf", "skills"],
147
292
  settingsRel: [".windsurf", "settings.json"],
148
- hookSupport: "notice"
293
+ hookSupport: "notice",
294
+ mcpConfigRel: [".codeium", "windsurf", "mcp_config.json"],
295
+ mcpSupport: "windsurf",
296
+ // Windsurf historically ships under both `.windsurf/` and the legacy
297
+ // `.codeium/windsurf/`; detect either.
298
+ extraDetect: [[".codeium", "windsurf"]]
149
299
  },
150
300
  {
151
301
  agent: "opencode",
152
302
  skillsRel: [".config", "opencode", "skills"],
153
303
  settingsRel: [".config", "opencode", "settings.json"],
154
- hookSupport: "notice"
304
+ hookSupport: "notice",
305
+ mcpConfigRel: [".config", "opencode", "opencode.json"],
306
+ mcpSupport: "opencode"
155
307
  }
156
308
  ];
157
309
  function detectAgents(homeOverride) {
158
- const home = homeOverride ?? (0, import_node_os.homedir)();
310
+ const home = homeOverride ?? (0, import_node_os2.homedir)();
159
311
  const results = [];
160
312
  for (const spec of AGENT_SPECS) {
161
- const skillsDir = (0, import_node_path.join)(home, ...spec.skillsRel);
162
- const settingsFile = (0, import_node_path.join)(home, ...spec.settingsRel);
163
- if ((0, import_node_fs.existsSync)((0, import_node_path.dirname)(skillsDir))) {
164
- results.push({
165
- agent: spec.agent,
166
- skillsDir,
167
- settingsFile,
168
- hookSupport: spec.hookSupport
169
- });
313
+ const skillsDir = (0, import_node_path2.join)(home, ...spec.skillsRel);
314
+ const settingsFile = (0, import_node_path2.join)(home, ...spec.settingsRel);
315
+ let detected = (0, import_node_fs.existsSync)((0, import_node_path2.dirname)(skillsDir));
316
+ if (!detected && spec.extraDetect) {
317
+ for (const seg of spec.extraDetect) {
318
+ if ((0, import_node_fs.existsSync)((0, import_node_path2.join)(home, ...seg))) {
319
+ detected = true;
320
+ break;
321
+ }
322
+ }
323
+ }
324
+ if (!detected) {
325
+ continue;
170
326
  }
327
+ results.push({
328
+ agent: spec.agent,
329
+ skillsDir,
330
+ settingsFile,
331
+ hookSupport: spec.hookSupport,
332
+ mcpConfigRel: spec.mcpConfigRel,
333
+ mcpSupport: spec.mcpSupport
334
+ });
171
335
  }
172
336
  return results;
173
337
  }
174
338
 
175
- // src/skill-install.ts
176
- var HOOK_BIN = "keeperhub-wallet-hook";
177
- var HOOK_COMMAND_BARE = HOOK_BIN;
339
+ // src/mcp-register.ts
340
+ var import_node_crypto2 = require("crypto");
341
+ var import_promises2 = require("fs/promises");
342
+ var import_node_os3 = require("os");
343
+ var import_node_path4 = require("path");
344
+
345
+ // src/runtime-detect.ts
346
+ var import_node_child_process = require("child_process");
347
+ var import_node_fs2 = require("fs");
348
+ var import_node_path3 = require("path");
349
+ var import_node_url = require("url");
178
350
  var PACKAGE_NAME = "@keeperhub/wallet";
179
351
  function readPackageVersion() {
180
352
  try {
181
- const here = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(__filename));
182
- const pkgPath = (0, import_node_path2.join)(here, "..", "package.json");
353
+ const here = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(__filename));
354
+ const pkgPath = (0, import_node_path3.join)(here, "..", "package.json");
183
355
  const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
184
356
  const parsed = JSON.parse(raw);
185
357
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
@@ -189,9 +361,6 @@ function readPackageVersion() {
189
361
  }
190
362
  return "latest";
191
363
  }
192
- function buildNpxCommand(version) {
193
- return `npx -y -p ${PACKAGE_NAME}@${version} ${HOOK_BIN}`;
194
- }
195
364
  function isNpxExecution() {
196
365
  const execPath = process.env.npm_execpath;
197
366
  if (typeof execPath !== "string" || execPath.length === 0) {
@@ -219,6 +388,144 @@ function isPathUnderTransientCache(resolvedPath) {
219
388
  }
220
389
  return false;
221
390
  }
391
+ function resolveBinCommand(binName) {
392
+ const version = readPackageVersion();
393
+ const npxArgs = ["-y", "-p", `${PACKAGE_NAME}@${version}`, binName];
394
+ const npxCommandString = `npx ${npxArgs.join(" ")}`;
395
+ if (isNpxExecution()) {
396
+ return {
397
+ commandString: npxCommandString,
398
+ command: "npx",
399
+ args: npxArgs
400
+ };
401
+ }
402
+ try {
403
+ const resolved = (0, import_node_child_process.execFileSync)("/bin/sh", ["-c", `command -v ${binName}`], {
404
+ stdio: ["ignore", "pipe", "ignore"]
405
+ }).toString().trim();
406
+ if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
407
+ return {
408
+ commandString: binName,
409
+ command: binName,
410
+ args: []
411
+ };
412
+ }
413
+ } catch {
414
+ }
415
+ return {
416
+ commandString: npxCommandString,
417
+ command: "npx",
418
+ args: npxArgs
419
+ };
420
+ }
421
+
422
+ // src/mcp-register.ts
423
+ var MCP_BIN = "keeperhub-wallet-mcp";
424
+ var MCP_SERVER_NAME = "keeperhub-wallet";
425
+ function resolveMcpCommand() {
426
+ const envOverride = process.env.KEEPERHUB_WALLET_MCP_COMMAND;
427
+ if (envOverride && envOverride.length > 0) {
428
+ const parts = envOverride.trim().split(/\s+/);
429
+ const head = parts[0] ?? envOverride;
430
+ return { command: head, args: parts.slice(1) };
431
+ }
432
+ const resolved = resolveBinCommand(MCP_BIN);
433
+ return { command: resolved.command, args: resolved.args };
434
+ }
435
+ function buildStandardEntry(cmd) {
436
+ const entry = {
437
+ command: cmd.command,
438
+ args: cmd.args
439
+ };
440
+ if (cmd.env && Object.keys(cmd.env).length > 0) {
441
+ entry.env = cmd.env;
442
+ }
443
+ return entry;
444
+ }
445
+ function buildOpencodeEntry(cmd) {
446
+ return {
447
+ type: "local",
448
+ command: [cmd.command, ...cmd.args],
449
+ enabled: true,
450
+ environment: cmd.env ?? {}
451
+ };
452
+ }
453
+ async function readJsonOrEmpty(path) {
454
+ let raw = null;
455
+ try {
456
+ raw = await (0, import_promises2.readFile)(path, "utf-8");
457
+ } catch (err) {
458
+ if (err.code !== "ENOENT") {
459
+ throw err;
460
+ }
461
+ }
462
+ if (raw === null) {
463
+ return {};
464
+ }
465
+ try {
466
+ return JSON.parse(raw);
467
+ } catch {
468
+ throw new Error(
469
+ `MCP config at ${path} is not valid JSON; aborting MCP registration`
470
+ );
471
+ }
472
+ }
473
+ async function writeJsonAtomic(path, payload) {
474
+ await (0, import_promises2.mkdir)((0, import_node_path4.dirname)(path), { recursive: true, mode: 448 });
475
+ const suffix = (0, import_node_crypto2.randomBytes)(8).toString("hex");
476
+ const tmpPath = `${path}.${process.pid}.${suffix}.tmp`;
477
+ try {
478
+ await (0, import_promises2.writeFile)(tmpPath, payload, { mode: 384 });
479
+ await (0, import_promises2.chmod)(tmpPath, 384);
480
+ await (0, import_promises2.rename)(tmpPath, path);
481
+ } catch (err) {
482
+ await (0, import_promises2.unlink)(tmpPath).catch(() => {
483
+ });
484
+ throw err;
485
+ }
486
+ }
487
+ async function writeStandardMcp(path, entry) {
488
+ const config = await readJsonOrEmpty(path);
489
+ const servers = typeof config.mcpServers === "object" && config.mcpServers !== null ? config.mcpServers : {};
490
+ servers[MCP_SERVER_NAME] = entry;
491
+ config.mcpServers = servers;
492
+ const payload = `${JSON.stringify(config, null, 2)}
493
+ `;
494
+ await writeJsonAtomic(path, payload);
495
+ }
496
+ async function writeOpencodeMcp(path, entry) {
497
+ const config = await readJsonOrEmpty(path);
498
+ const servers = typeof config.mcp === "object" && config.mcp !== null ? config.mcp : {};
499
+ servers[MCP_SERVER_NAME] = entry;
500
+ config.mcp = servers;
501
+ const payload = `${JSON.stringify(config, null, 2)}
502
+ `;
503
+ await writeJsonAtomic(path, payload);
504
+ }
505
+ async function registerMcpServer(target, options = {}) {
506
+ if (target.mcpSupport === "notice") {
507
+ throw new Error(
508
+ `agent ${target.agent} does not support auto-registered MCP servers; surface a notice instead`
509
+ );
510
+ }
511
+ if (!target.mcpConfigRel) {
512
+ throw new Error(
513
+ `agent ${target.agent} has mcpSupport=${target.mcpSupport} but no mcpConfigRel path`
514
+ );
515
+ }
516
+ const home = options.homeOverride ?? (0, import_node_os3.homedir)();
517
+ const path = (0, import_node_path4.join)(home, ...target.mcpConfigRel);
518
+ const cmd = options.command ?? resolveMcpCommand();
519
+ if (target.mcpSupport === "opencode") {
520
+ await writeOpencodeMcp(path, buildOpencodeEntry(cmd));
521
+ } else {
522
+ await writeStandardMcp(path, buildStandardEntry(cmd));
523
+ }
524
+ return { path, name: MCP_SERVER_NAME };
525
+ }
526
+
527
+ // src/skill-install.ts
528
+ var HOOK_BIN = "keeperhub-wallet-hook";
222
529
  var KEEPERHUB_HOOK_MARKER = HOOK_BIN;
223
530
  function filterKeeperhubHooksFromEntry(entry) {
224
531
  if (typeof entry !== "object" || entry === null) {
@@ -245,19 +552,7 @@ function resolveHookCommand() {
245
552
  if (envOverride && envOverride.length > 0) {
246
553
  return envOverride;
247
554
  }
248
- if (isNpxExecution()) {
249
- return buildNpxCommand(readPackageVersion());
250
- }
251
- try {
252
- const resolved = (0, import_node_child_process.execFileSync)("/bin/sh", ["-c", `command -v ${HOOK_BIN}`], {
253
- stdio: ["ignore", "pipe", "ignore"]
254
- }).toString().trim();
255
- if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
256
- return HOOK_COMMAND_BARE;
257
- }
258
- } catch {
259
- }
260
- return buildNpxCommand(readPackageVersion());
555
+ return resolveBinCommand(HOOK_BIN).commandString;
261
556
  }
262
557
  function buildKeeperhubEntry(command) {
263
558
  return {
@@ -266,8 +561,8 @@ function buildKeeperhubEntry(command) {
266
561
  };
267
562
  }
268
563
  function resolveDefaultSkillSource() {
269
- const here = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(__filename));
270
- return (0, import_node_path2.join)(here, "..", "skill", "keeperhub-wallet.skill.md");
564
+ const here = (0, import_node_path5.dirname)((0, import_node_url2.fileURLToPath)(__filename));
565
+ return (0, import_node_path5.join)(here, "..", "skill", "keeperhub-wallet.skill.md");
271
566
  }
272
567
  function defaultNotice(msg) {
273
568
  process.stderr.write(`${msg}
@@ -277,7 +572,7 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
277
572
  const command = options.hookCommand ?? resolveHookCommand();
278
573
  let raw = null;
279
574
  try {
280
- raw = await (0, import_promises.readFile)(settingsPath, "utf-8");
575
+ raw = await (0, import_promises3.readFile)(settingsPath, "utf-8");
281
576
  } catch (err) {
282
577
  if (err.code !== "ENOENT") {
283
578
  throw err;
@@ -305,158 +600,136 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
305
600
  filtered.push(buildKeeperhubEntry(command));
306
601
  hooks.PreToolUse = filtered;
307
602
  config.hooks = hooks;
308
- await (0, import_promises.mkdir)((0, import_node_path2.dirname)(settingsPath), { recursive: true, mode: 448 });
603
+ await (0, import_promises3.mkdir)((0, import_node_path5.dirname)(settingsPath), { recursive: true, mode: 448 });
309
604
  const payload = `${JSON.stringify(config, null, 2)}
310
605
  `;
311
- await (0, import_promises.writeFile)(settingsPath, payload, { mode: 384 });
312
- await (0, import_promises.chmod)(settingsPath, 384);
606
+ const suffix = (0, import_node_crypto3.randomBytes)(8).toString("hex");
607
+ const tmpPath = `${settingsPath}.${process.pid}.${suffix}.tmp`;
608
+ try {
609
+ await (0, import_promises3.writeFile)(tmpPath, payload, { mode: 384 });
610
+ await (0, import_promises3.chmod)(tmpPath, 384);
611
+ await (0, import_promises3.rename)(tmpPath, settingsPath);
612
+ } catch (err) {
613
+ await (0, import_promises3.unlink)(tmpPath).catch(() => {
614
+ });
615
+ throw err;
616
+ }
313
617
  }
314
618
  async function writeSkillToAgent(agent, skillSource) {
315
- await (0, import_promises.mkdir)(agent.skillsDir, { recursive: true, mode: 493 });
316
- const target = (0, import_node_path2.join)(agent.skillsDir, "keeperhub-wallet.skill.md");
317
- await (0, import_promises.copyFile)(skillSource, target);
318
- await (0, import_promises.chmod)(target, 420);
619
+ await (0, import_promises3.mkdir)(agent.skillsDir, { recursive: true, mode: 493 });
620
+ const target = (0, import_node_path5.join)(agent.skillsDir, "keeperhub-wallet.skill.md");
621
+ await (0, import_promises3.copyFile)(skillSource, target);
622
+ await (0, import_promises3.chmod)(target, 420);
319
623
  return { agent: agent.agent, path: target, status: "written" };
320
624
  }
321
- function buildNoticeMessage(agent, command) {
625
+ function buildHookNoticeMessage(agent, command) {
322
626
  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}`;
323
627
  }
628
+ function buildMcpNoticeMessage(agent, command) {
629
+ const cmd = [command.command, ...command.args].join(" ");
630
+ return `${agent.agent} does not support auto-registered MCP servers; add an entry named \`keeperhub-wallet\` running \`${cmd}\` to your MCP config manually`;
631
+ }
324
632
  async function installSkill(options = {}) {
325
633
  const agents = detectAgents(options.homeOverride);
326
634
  const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();
327
635
  const onNotice = options.onNotice ?? defaultNotice;
328
636
  const hookCommand = options.hookCommand ?? resolveHookCommand();
637
+ const mcpCommand = options.mcpCommand ?? resolveMcpCommand();
329
638
  const skillWrites = [];
330
639
  const hookRegistrations = [];
640
+ const mcpRegistrations = [];
331
641
  for (const agent of agents) {
332
- const write = await writeSkillToAgent(agent, skillSource);
333
- skillWrites.push(write);
334
- if (agent.hookSupport === "claude-code") {
335
- await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
336
- hookRegistrations.push({
642
+ try {
643
+ const write = await writeSkillToAgent(agent, skillSource);
644
+ skillWrites.push(write);
645
+ } catch (err) {
646
+ const message = err instanceof Error ? err.message : String(err);
647
+ skillWrites.push({
337
648
  agent: agent.agent,
338
- status: "registered"
649
+ path: "",
650
+ status: "skipped"
339
651
  });
652
+ onNotice(`${agent.agent}: skill copy failed (${message})`);
653
+ continue;
654
+ }
655
+ if (agent.hookSupport === "claude-code") {
656
+ try {
657
+ await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
658
+ hookRegistrations.push({
659
+ agent: agent.agent,
660
+ status: "registered"
661
+ });
662
+ } catch (err) {
663
+ const message = err instanceof Error ? err.message : String(err);
664
+ hookRegistrations.push({
665
+ agent: agent.agent,
666
+ status: "failed",
667
+ message
668
+ });
669
+ onNotice(`${agent.agent}: hook registration failed (${message})`);
670
+ }
340
671
  } else {
341
- const message = buildNoticeMessage(agent, hookCommand);
672
+ const noticeMessage = buildHookNoticeMessage(agent, hookCommand);
342
673
  hookRegistrations.push({
343
674
  agent: agent.agent,
344
675
  status: "notice",
345
- message
676
+ message: noticeMessage
346
677
  });
347
- onNotice(message);
678
+ onNotice(noticeMessage);
348
679
  }
349
- }
350
- return { skillWrites, hookRegistrations };
351
- }
352
-
353
- // src/storage.ts
354
- var import_promises2 = require("fs/promises");
355
- var import_node_os2 = require("os");
356
- var import_node_path3 = require("path");
357
-
358
- // src/types.ts
359
- var WalletConfigMissingError = class extends Error {
360
- constructor() {
361
- super(
362
- "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
363
- );
364
- this.name = "WalletConfigMissingError";
365
- }
366
- };
367
-
368
- // src/storage.ts
369
- async function readWalletConfig() {
370
- const walletPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
371
- let raw;
372
- try {
373
- raw = await (0, import_promises2.readFile)(walletPath, "utf-8");
374
- } catch (err) {
375
- if (err.code === "ENOENT") {
376
- throw new WalletConfigMissingError();
680
+ if (agent.mcpSupport === "notice") {
681
+ const noticeMessage = buildMcpNoticeMessage(agent, mcpCommand);
682
+ mcpRegistrations.push({
683
+ agent: agent.agent,
684
+ status: "notice",
685
+ message: noticeMessage
686
+ });
687
+ onNotice(noticeMessage);
688
+ continue;
689
+ }
690
+ try {
691
+ const mcpResult = await registerMcpServer(agent, {
692
+ homeOverride: options.homeOverride,
693
+ command: mcpCommand
694
+ });
695
+ mcpRegistrations.push({
696
+ agent: agent.agent,
697
+ status: "registered",
698
+ path: mcpResult.path
699
+ });
700
+ } catch (err) {
701
+ const message = err instanceof Error ? err.message : String(err);
702
+ mcpRegistrations.push({
703
+ agent: agent.agent,
704
+ status: "failed",
705
+ message
706
+ });
707
+ onNotice(`${agent.agent}: MCP registration failed (${message})`);
377
708
  }
378
- throw err;
379
- }
380
- const parsed = JSON.parse(raw);
381
- if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
382
- throw new Error(`Malformed wallet.json at ${walletPath}`);
383
709
  }
384
- return parsed;
385
- }
386
- async function writeWalletConfig(config) {
387
- const walletPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
388
- await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(walletPath), { recursive: true, mode: 448 });
389
- await (0, import_promises2.writeFile)(walletPath, JSON.stringify(config, null, 2), { mode: 384 });
390
- await (0, import_promises2.chmod)(walletPath, 384);
391
- }
392
- function getWalletConfigPath() {
393
- return (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
710
+ return { skillWrites, hookRegistrations, mcpRegistrations };
394
711
  }
395
712
 
396
713
  // src/cli.ts
397
- var TRAILING_SLASH = /\/$/;
398
- var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
399
- function resolveBaseUrl(override) {
400
- const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
401
- return candidate.replace(TRAILING_SLASH, "");
402
- }
403
- function isNonEmptyString(value) {
404
- return typeof value === "string" && value.length > 0;
405
- }
406
- function provisionInvalidError(message) {
407
- const err = new Error(message);
408
- err.code = "PROVISION_RESPONSE_INVALID";
409
- return err;
410
- }
411
- function validateProvisionResponse(data) {
412
- if (typeof data !== "object" || data === null) {
413
- throw provisionInvalidError("provision response is not an object");
414
- }
415
- const { subOrgId, walletAddress, hmacSecret } = data;
416
- if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
417
- throw provisionInvalidError(
418
- "provision response missing subOrgId, walletAddress, or hmacSecret"
419
- );
420
- }
421
- if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
422
- throw provisionInvalidError(
423
- `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
424
- );
425
- }
426
- return {
427
- subOrgId,
428
- walletAddress,
429
- hmacSecret
430
- };
431
- }
432
714
  async function cmdAdd(opts = {}) {
433
- const baseUrl = resolveBaseUrl(opts.baseUrl);
434
- const response = await fetch(`${baseUrl}/api/agentic-wallet/provision`, {
435
- method: "POST",
436
- headers: { "content-type": "application/json" },
437
- body: "{}"
438
- });
439
- if (!response.ok) {
440
- const text = await response.text();
441
- process.stderr.write(
442
- `[keeperhub-wallet] provision failed: HTTP ${response.status}: ${text}
443
- `
444
- );
445
- process.exit(1);
446
- }
447
- const raw = await response.json();
448
- const data = validateProvisionResponse(raw);
449
- await writeWalletConfig({
450
- subOrgId: data.subOrgId,
451
- walletAddress: data.walletAddress,
452
- hmacSecret: data.hmacSecret
453
- });
454
- process.stdout.write(`subOrgId: ${data.subOrgId}
715
+ try {
716
+ const data = await provisionWallet({ baseUrl: opts.baseUrl });
717
+ process.stdout.write(`subOrgId: ${data.subOrgId}
455
718
  `);
456
- process.stdout.write(`walletAddress: ${data.walletAddress}
719
+ process.stdout.write(`walletAddress: ${data.walletAddress}
457
720
  `);
458
- process.stdout.write(`config written to ${getWalletConfigPath()}
721
+ process.stdout.write(`config written to ${getWalletConfigPath()}
459
722
  `);
723
+ } catch (err) {
724
+ if (err instanceof ProvisionHttpError) {
725
+ process.stderr.write(
726
+ `[keeperhub-wallet] provision failed: HTTP ${err.status}: ${err.body}
727
+ `
728
+ );
729
+ process.exit(1);
730
+ }
731
+ throw err;
732
+ }
460
733
  }
461
734
  async function cmdFund() {
462
735
  const wallet = await readWalletConfig();
@@ -524,6 +797,29 @@ async function runCli(argv = process.argv) {
524
797
  } else if (reg.status === "notice") {
525
798
  process.stderr.write(
526
799
  `notice: ${reg.agent} -> ${reg.message ?? ""}
800
+ `
801
+ );
802
+ } else if (reg.status === "failed") {
803
+ process.stderr.write(
804
+ `hook: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
805
+ `
806
+ );
807
+ }
808
+ }
809
+ for (const reg of result.mcpRegistrations) {
810
+ if (reg.status === "registered") {
811
+ process.stdout.write(
812
+ `mcp: ${reg.agent} -> registered at ${reg.path ?? "(unknown path)"}
813
+ `
814
+ );
815
+ } else if (reg.status === "notice") {
816
+ process.stderr.write(
817
+ `notice: ${reg.agent} mcp -> ${reg.message ?? ""}
818
+ `
819
+ );
820
+ } else if (reg.status === "failed") {
821
+ process.stderr.write(
822
+ `mcp: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
527
823
  `
528
824
  );
529
825
  }