@keeperhub/wallet 0.1.11 → 0.1.13

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,275 @@ 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/hmac.ts
115
+ var import_node_crypto = require("crypto");
116
+ function computeSignature(secret, method, path, subOrgId, body, timestamp) {
117
+ const bodyDigest = (0, import_node_crypto.createHash)("sha256").update(body).digest("hex");
118
+ const signingString = `${method}
119
+ ${path}
120
+ ${subOrgId}
121
+ ${bodyDigest}
122
+ ${timestamp}`;
123
+ return (0, import_node_crypto.createHmac)("sha256", secret).update(signingString).digest("hex");
124
+ }
125
+ function buildHmacHeaders(secret, method, path, subOrgId, body) {
126
+ const timestamp = String(Math.floor(Date.now() / 1e3));
127
+ const signature = computeSignature(
128
+ secret,
129
+ method,
130
+ path,
131
+ subOrgId,
132
+ body,
133
+ timestamp
134
+ );
135
+ return {
136
+ "X-KH-Sub-Org": subOrgId,
137
+ "X-KH-Timestamp": timestamp,
138
+ "X-KH-Signature": signature
139
+ };
140
+ }
141
+
142
+ // src/storage.ts
143
+ var import_node_crypto2 = require("crypto");
117
144
  var import_promises = require("fs/promises");
118
- var import_node_path2 = require("path");
119
- var import_node_url = require("url");
145
+ var import_node_os = require("os");
146
+ var import_node_path = require("path");
147
+
148
+ // src/types.ts
149
+ var WalletConfigMissingError = class extends Error {
150
+ constructor() {
151
+ super(
152
+ "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
153
+ );
154
+ this.name = "WalletConfigMissingError";
155
+ }
156
+ };
157
+ var WalletConfigCorruptError = class extends Error {
158
+ path;
159
+ constructor(path, reason) {
160
+ super(
161
+ `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).`
162
+ );
163
+ this.name = "WalletConfigCorruptError";
164
+ this.path = path;
165
+ }
166
+ };
167
+
168
+ // src/storage.ts
169
+ async function readWalletConfig() {
170
+ const walletPath = (0, import_node_path.join)((0, import_node_os.homedir)(), ".keeperhub", "wallet.json");
171
+ let raw;
172
+ try {
173
+ raw = await (0, import_promises.readFile)(walletPath, "utf-8");
174
+ } catch (err) {
175
+ if (err.code === "ENOENT") {
176
+ throw new WalletConfigMissingError();
177
+ }
178
+ throw err;
179
+ }
180
+ let parsed;
181
+ try {
182
+ parsed = JSON.parse(raw);
183
+ } catch (err) {
184
+ const reason = err instanceof Error ? err.message : String(err);
185
+ throw new WalletConfigCorruptError(walletPath, reason);
186
+ }
187
+ if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
188
+ throw new WalletConfigCorruptError(walletPath, "missing required fields");
189
+ }
190
+ return parsed;
191
+ }
192
+ async function writeWalletConfig(config) {
193
+ const walletPath = (0, import_node_path.join)((0, import_node_os.homedir)(), ".keeperhub", "wallet.json");
194
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(walletPath), { recursive: true, mode: 448 });
195
+ const suffix = (0, import_node_crypto2.randomBytes)(8).toString("hex");
196
+ const tmpPath = `${walletPath}.${process.pid}.${suffix}.tmp`;
197
+ await (0, import_promises.writeFile)(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
198
+ await (0, import_promises.chmod)(tmpPath, 384);
199
+ await (0, import_promises.rename)(tmpPath, walletPath);
200
+ }
201
+ function getWalletConfigPath() {
202
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".keeperhub", "wallet.json");
203
+ }
204
+
205
+ // src/provision.ts
206
+ var TRAILING_SLASH = /\/$/;
207
+ var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
208
+ var ProvisionResponseInvalidError = class extends Error {
209
+ code = "PROVISION_RESPONSE_INVALID";
210
+ constructor(message) {
211
+ super(message);
212
+ this.name = "ProvisionResponseInvalidError";
213
+ }
214
+ };
215
+ var ProvisionHttpError = class extends Error {
216
+ code = "PROVISION_HTTP_ERROR";
217
+ status;
218
+ body;
219
+ constructor(status, body) {
220
+ super(`provision failed: HTTP ${status}: ${body}`);
221
+ this.name = "ProvisionHttpError";
222
+ this.status = status;
223
+ this.body = body;
224
+ }
225
+ };
226
+ function resolveBaseUrl(override) {
227
+ const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
228
+ return candidate.replace(TRAILING_SLASH, "");
229
+ }
230
+ function isNonEmptyString(value) {
231
+ return typeof value === "string" && value.length > 0;
232
+ }
233
+ function validateProvisionResponse(data) {
234
+ if (typeof data !== "object" || data === null) {
235
+ throw new ProvisionResponseInvalidError(
236
+ "provision response is not an object"
237
+ );
238
+ }
239
+ const { subOrgId, walletAddress, hmacSecret } = data;
240
+ if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
241
+ throw new ProvisionResponseInvalidError(
242
+ "provision response missing subOrgId, walletAddress, or hmacSecret"
243
+ );
244
+ }
245
+ if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
246
+ throw new ProvisionResponseInvalidError(
247
+ `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
248
+ );
249
+ }
250
+ return {
251
+ subOrgId,
252
+ walletAddress,
253
+ hmacSecret
254
+ };
255
+ }
256
+ async function provisionWallet(options = {}) {
257
+ const baseUrl = resolveBaseUrl(options.baseUrl);
258
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
259
+ const response = await fetchImpl(`${baseUrl}/api/agentic-wallet/provision`, {
260
+ method: "POST",
261
+ headers: { "content-type": "application/json" },
262
+ body: "{}",
263
+ signal: AbortSignal.timeout(3e4)
264
+ });
265
+ if (!response.ok) {
266
+ const text = await response.text();
267
+ throw new ProvisionHttpError(response.status, text);
268
+ }
269
+ const raw = await response.json();
270
+ const data = validateProvisionResponse(raw);
271
+ await writeWalletConfig(data);
272
+ return data;
273
+ }
274
+
275
+ // src/skill-install.ts
276
+ var import_node_crypto4 = require("crypto");
277
+ var import_promises3 = require("fs/promises");
278
+ var import_node_path5 = require("path");
279
+ var import_node_url2 = require("url");
120
280
 
121
281
  // src/agent-detect.ts
122
282
  var import_node_fs = require("fs");
123
- var import_node_os = require("os");
124
- var import_node_path = require("path");
283
+ var import_node_os2 = require("os");
284
+ var import_node_path2 = require("path");
125
285
  var AGENT_SPECS = [
126
286
  {
127
287
  agent: "claude-code",
128
288
  skillsRel: [".claude", "skills"],
129
289
  settingsRel: [".claude", "settings.json"],
130
- hookSupport: "claude-code"
290
+ hookSupport: "claude-code",
291
+ // ~/.claude.json is at HOME root (not under .claude/) and is large
292
+ // (100+KB on real installs). registerMcpServer reads/parses/rewrites it
293
+ // while preserving every other top-level key byte-for-byte.
294
+ mcpConfigRel: [".claude.json"],
295
+ mcpSupport: "claude-code"
131
296
  },
132
297
  {
133
298
  agent: "cursor",
134
299
  skillsRel: [".cursor", "skills"],
135
300
  settingsRel: [".cursor", "settings.json"],
136
- hookSupport: "notice"
301
+ hookSupport: "notice",
302
+ mcpConfigRel: [".cursor", "mcp.json"],
303
+ mcpSupport: "cursor"
137
304
  },
138
305
  {
139
306
  agent: "cline",
140
307
  skillsRel: [".cline", "skills"],
141
308
  settingsRel: [".cline", "settings.json"],
142
- hookSupport: "notice"
309
+ hookSupport: "notice",
310
+ // Cline keeps MCP state in a per-VS-Code-variant globalStorage path
311
+ // (e.g. ~/Library/Application Support/Code/User/globalStorage/
312
+ // saoudrizwan.claude-dev/settings/cline_mcp_settings.json) that is too
313
+ // fragile to auto-detect. Ship "notice" with a copy-paste entry shape
314
+ // instead of guessing the variant.
315
+ mcpSupport: "notice"
143
316
  },
144
317
  {
145
318
  agent: "windsurf",
146
319
  skillsRel: [".windsurf", "skills"],
147
320
  settingsRel: [".windsurf", "settings.json"],
148
- hookSupport: "notice"
321
+ hookSupport: "notice",
322
+ mcpConfigRel: [".codeium", "windsurf", "mcp_config.json"],
323
+ mcpSupport: "windsurf",
324
+ // Windsurf historically ships under both `.windsurf/` and the legacy
325
+ // `.codeium/windsurf/`; detect either.
326
+ extraDetect: [[".codeium", "windsurf"]]
149
327
  },
150
328
  {
151
329
  agent: "opencode",
152
330
  skillsRel: [".config", "opencode", "skills"],
153
331
  settingsRel: [".config", "opencode", "settings.json"],
154
- hookSupport: "notice"
332
+ hookSupport: "notice",
333
+ mcpConfigRel: [".config", "opencode", "opencode.json"],
334
+ mcpSupport: "opencode"
155
335
  }
156
336
  ];
157
337
  function detectAgents(homeOverride) {
158
- const home = homeOverride ?? (0, import_node_os.homedir)();
338
+ const home = homeOverride ?? (0, import_node_os2.homedir)();
159
339
  const results = [];
160
340
  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
- });
341
+ const skillsDir = (0, import_node_path2.join)(home, ...spec.skillsRel);
342
+ const settingsFile = (0, import_node_path2.join)(home, ...spec.settingsRel);
343
+ let detected = (0, import_node_fs.existsSync)((0, import_node_path2.dirname)(skillsDir));
344
+ if (!detected && spec.extraDetect) {
345
+ for (const seg of spec.extraDetect) {
346
+ if ((0, import_node_fs.existsSync)((0, import_node_path2.join)(home, ...seg))) {
347
+ detected = true;
348
+ break;
349
+ }
350
+ }
170
351
  }
352
+ if (!detected) {
353
+ continue;
354
+ }
355
+ results.push({
356
+ agent: spec.agent,
357
+ skillsDir,
358
+ settingsFile,
359
+ hookSupport: spec.hookSupport,
360
+ mcpConfigRel: spec.mcpConfigRel,
361
+ mcpSupport: spec.mcpSupport
362
+ });
171
363
  }
172
364
  return results;
173
365
  }
174
366
 
175
- // src/skill-install.ts
176
- var HOOK_BIN = "keeperhub-wallet-hook";
177
- var HOOK_COMMAND_BARE = HOOK_BIN;
367
+ // src/mcp-register.ts
368
+ var import_node_crypto3 = require("crypto");
369
+ var import_promises2 = require("fs/promises");
370
+ var import_node_os3 = require("os");
371
+ var import_node_path4 = require("path");
372
+
373
+ // src/runtime-detect.ts
374
+ var import_node_child_process = require("child_process");
375
+ var import_node_fs2 = require("fs");
376
+ var import_node_path3 = require("path");
377
+ var import_node_url = require("url");
178
378
  var PACKAGE_NAME = "@keeperhub/wallet";
179
379
  function readPackageVersion() {
180
380
  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");
381
+ const here = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(__filename));
382
+ const pkgPath = (0, import_node_path3.join)(here, "..", "package.json");
183
383
  const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
184
384
  const parsed = JSON.parse(raw);
185
385
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
@@ -189,9 +389,6 @@ function readPackageVersion() {
189
389
  }
190
390
  return "latest";
191
391
  }
192
- function buildNpxCommand(version) {
193
- return `npx -y -p ${PACKAGE_NAME}@${version} ${HOOK_BIN}`;
194
- }
195
392
  function isNpxExecution() {
196
393
  const execPath = process.env.npm_execpath;
197
394
  if (typeof execPath !== "string" || execPath.length === 0) {
@@ -219,6 +416,144 @@ function isPathUnderTransientCache(resolvedPath) {
219
416
  }
220
417
  return false;
221
418
  }
419
+ function resolveBinCommand(binName) {
420
+ const version = readPackageVersion();
421
+ const npxArgs = ["-y", "-p", `${PACKAGE_NAME}@${version}`, binName];
422
+ const npxCommandString = `npx ${npxArgs.join(" ")}`;
423
+ if (isNpxExecution()) {
424
+ return {
425
+ commandString: npxCommandString,
426
+ command: "npx",
427
+ args: npxArgs
428
+ };
429
+ }
430
+ try {
431
+ const resolved = (0, import_node_child_process.execFileSync)("/bin/sh", ["-c", `command -v ${binName}`], {
432
+ stdio: ["ignore", "pipe", "ignore"]
433
+ }).toString().trim();
434
+ if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
435
+ return {
436
+ commandString: binName,
437
+ command: binName,
438
+ args: []
439
+ };
440
+ }
441
+ } catch {
442
+ }
443
+ return {
444
+ commandString: npxCommandString,
445
+ command: "npx",
446
+ args: npxArgs
447
+ };
448
+ }
449
+
450
+ // src/mcp-register.ts
451
+ var MCP_BIN = "keeperhub-wallet-mcp";
452
+ var MCP_SERVER_NAME = "keeperhub-wallet";
453
+ function resolveMcpCommand() {
454
+ const envOverride = process.env.KEEPERHUB_WALLET_MCP_COMMAND;
455
+ if (envOverride && envOverride.length > 0) {
456
+ const parts = envOverride.trim().split(/\s+/);
457
+ const head = parts[0] ?? envOverride;
458
+ return { command: head, args: parts.slice(1) };
459
+ }
460
+ const resolved = resolveBinCommand(MCP_BIN);
461
+ return { command: resolved.command, args: resolved.args };
462
+ }
463
+ function buildStandardEntry(cmd) {
464
+ const entry = {
465
+ command: cmd.command,
466
+ args: cmd.args
467
+ };
468
+ if (cmd.env && Object.keys(cmd.env).length > 0) {
469
+ entry.env = cmd.env;
470
+ }
471
+ return entry;
472
+ }
473
+ function buildOpencodeEntry(cmd) {
474
+ return {
475
+ type: "local",
476
+ command: [cmd.command, ...cmd.args],
477
+ enabled: true,
478
+ environment: cmd.env ?? {}
479
+ };
480
+ }
481
+ async function readJsonOrEmpty(path) {
482
+ let raw = null;
483
+ try {
484
+ raw = await (0, import_promises2.readFile)(path, "utf-8");
485
+ } catch (err) {
486
+ if (err.code !== "ENOENT") {
487
+ throw err;
488
+ }
489
+ }
490
+ if (raw === null) {
491
+ return {};
492
+ }
493
+ try {
494
+ return JSON.parse(raw);
495
+ } catch {
496
+ throw new Error(
497
+ `MCP config at ${path} is not valid JSON; aborting MCP registration`
498
+ );
499
+ }
500
+ }
501
+ async function writeJsonAtomic(path, payload) {
502
+ await (0, import_promises2.mkdir)((0, import_node_path4.dirname)(path), { recursive: true, mode: 448 });
503
+ const suffix = (0, import_node_crypto3.randomBytes)(8).toString("hex");
504
+ const tmpPath = `${path}.${process.pid}.${suffix}.tmp`;
505
+ try {
506
+ await (0, import_promises2.writeFile)(tmpPath, payload, { mode: 384 });
507
+ await (0, import_promises2.chmod)(tmpPath, 384);
508
+ await (0, import_promises2.rename)(tmpPath, path);
509
+ } catch (err) {
510
+ await (0, import_promises2.unlink)(tmpPath).catch(() => {
511
+ });
512
+ throw err;
513
+ }
514
+ }
515
+ async function writeStandardMcp(path, entry) {
516
+ const config = await readJsonOrEmpty(path);
517
+ const servers = typeof config.mcpServers === "object" && config.mcpServers !== null ? config.mcpServers : {};
518
+ servers[MCP_SERVER_NAME] = entry;
519
+ config.mcpServers = servers;
520
+ const payload = `${JSON.stringify(config, null, 2)}
521
+ `;
522
+ await writeJsonAtomic(path, payload);
523
+ }
524
+ async function writeOpencodeMcp(path, entry) {
525
+ const config = await readJsonOrEmpty(path);
526
+ const servers = typeof config.mcp === "object" && config.mcp !== null ? config.mcp : {};
527
+ servers[MCP_SERVER_NAME] = entry;
528
+ config.mcp = servers;
529
+ const payload = `${JSON.stringify(config, null, 2)}
530
+ `;
531
+ await writeJsonAtomic(path, payload);
532
+ }
533
+ async function registerMcpServer(target, options = {}) {
534
+ if (target.mcpSupport === "notice") {
535
+ throw new Error(
536
+ `agent ${target.agent} does not support auto-registered MCP servers; surface a notice instead`
537
+ );
538
+ }
539
+ if (!target.mcpConfigRel) {
540
+ throw new Error(
541
+ `agent ${target.agent} has mcpSupport=${target.mcpSupport} but no mcpConfigRel path`
542
+ );
543
+ }
544
+ const home = options.homeOverride ?? (0, import_node_os3.homedir)();
545
+ const path = (0, import_node_path4.join)(home, ...target.mcpConfigRel);
546
+ const cmd = options.command ?? resolveMcpCommand();
547
+ if (target.mcpSupport === "opencode") {
548
+ await writeOpencodeMcp(path, buildOpencodeEntry(cmd));
549
+ } else {
550
+ await writeStandardMcp(path, buildStandardEntry(cmd));
551
+ }
552
+ return { path, name: MCP_SERVER_NAME };
553
+ }
554
+
555
+ // src/skill-install.ts
556
+ var HOOK_BIN = "keeperhub-wallet-hook";
222
557
  var KEEPERHUB_HOOK_MARKER = HOOK_BIN;
223
558
  function filterKeeperhubHooksFromEntry(entry) {
224
559
  if (typeof entry !== "object" || entry === null) {
@@ -245,19 +580,7 @@ function resolveHookCommand() {
245
580
  if (envOverride && envOverride.length > 0) {
246
581
  return envOverride;
247
582
  }
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());
583
+ return resolveBinCommand(HOOK_BIN).commandString;
261
584
  }
262
585
  function buildKeeperhubEntry(command) {
263
586
  return {
@@ -266,8 +589,8 @@ function buildKeeperhubEntry(command) {
266
589
  };
267
590
  }
268
591
  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");
592
+ const here = (0, import_node_path5.dirname)((0, import_node_url2.fileURLToPath)(__filename));
593
+ return (0, import_node_path5.join)(here, "..", "skill", "keeperhub-wallet.skill.md");
271
594
  }
272
595
  function defaultNotice(msg) {
273
596
  process.stderr.write(`${msg}
@@ -277,7 +600,7 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
277
600
  const command = options.hookCommand ?? resolveHookCommand();
278
601
  let raw = null;
279
602
  try {
280
- raw = await (0, import_promises.readFile)(settingsPath, "utf-8");
603
+ raw = await (0, import_promises3.readFile)(settingsPath, "utf-8");
281
604
  } catch (err) {
282
605
  if (err.code !== "ENOENT") {
283
606
  throw err;
@@ -305,158 +628,136 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
305
628
  filtered.push(buildKeeperhubEntry(command));
306
629
  hooks.PreToolUse = filtered;
307
630
  config.hooks = hooks;
308
- await (0, import_promises.mkdir)((0, import_node_path2.dirname)(settingsPath), { recursive: true, mode: 448 });
631
+ await (0, import_promises3.mkdir)((0, import_node_path5.dirname)(settingsPath), { recursive: true, mode: 448 });
309
632
  const payload = `${JSON.stringify(config, null, 2)}
310
633
  `;
311
- await (0, import_promises.writeFile)(settingsPath, payload, { mode: 384 });
312
- await (0, import_promises.chmod)(settingsPath, 384);
634
+ const suffix = (0, import_node_crypto4.randomBytes)(8).toString("hex");
635
+ const tmpPath = `${settingsPath}.${process.pid}.${suffix}.tmp`;
636
+ try {
637
+ await (0, import_promises3.writeFile)(tmpPath, payload, { mode: 384 });
638
+ await (0, import_promises3.chmod)(tmpPath, 384);
639
+ await (0, import_promises3.rename)(tmpPath, settingsPath);
640
+ } catch (err) {
641
+ await (0, import_promises3.unlink)(tmpPath).catch(() => {
642
+ });
643
+ throw err;
644
+ }
313
645
  }
314
646
  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);
647
+ await (0, import_promises3.mkdir)(agent.skillsDir, { recursive: true, mode: 493 });
648
+ const target = (0, import_node_path5.join)(agent.skillsDir, "keeperhub-wallet.skill.md");
649
+ await (0, import_promises3.copyFile)(skillSource, target);
650
+ await (0, import_promises3.chmod)(target, 420);
319
651
  return { agent: agent.agent, path: target, status: "written" };
320
652
  }
321
- function buildNoticeMessage(agent, command) {
653
+ function buildHookNoticeMessage(agent, command) {
322
654
  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
655
  }
656
+ function buildMcpNoticeMessage(agent, command) {
657
+ const cmd = [command.command, ...command.args].join(" ");
658
+ return `${agent.agent} does not support auto-registered MCP servers; add an entry named \`keeperhub-wallet\` running \`${cmd}\` to your MCP config manually`;
659
+ }
324
660
  async function installSkill(options = {}) {
325
661
  const agents = detectAgents(options.homeOverride);
326
662
  const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();
327
663
  const onNotice = options.onNotice ?? defaultNotice;
328
664
  const hookCommand = options.hookCommand ?? resolveHookCommand();
665
+ const mcpCommand = options.mcpCommand ?? resolveMcpCommand();
329
666
  const skillWrites = [];
330
667
  const hookRegistrations = [];
668
+ const mcpRegistrations = [];
331
669
  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({
670
+ try {
671
+ const write = await writeSkillToAgent(agent, skillSource);
672
+ skillWrites.push(write);
673
+ } catch (err) {
674
+ const message = err instanceof Error ? err.message : String(err);
675
+ skillWrites.push({
337
676
  agent: agent.agent,
338
- status: "registered"
677
+ path: "",
678
+ status: "skipped"
339
679
  });
680
+ onNotice(`${agent.agent}: skill copy failed (${message})`);
681
+ continue;
682
+ }
683
+ if (agent.hookSupport === "claude-code") {
684
+ try {
685
+ await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
686
+ hookRegistrations.push({
687
+ agent: agent.agent,
688
+ status: "registered"
689
+ });
690
+ } catch (err) {
691
+ const message = err instanceof Error ? err.message : String(err);
692
+ hookRegistrations.push({
693
+ agent: agent.agent,
694
+ status: "failed",
695
+ message
696
+ });
697
+ onNotice(`${agent.agent}: hook registration failed (${message})`);
698
+ }
340
699
  } else {
341
- const message = buildNoticeMessage(agent, hookCommand);
700
+ const noticeMessage = buildHookNoticeMessage(agent, hookCommand);
342
701
  hookRegistrations.push({
343
702
  agent: agent.agent,
344
703
  status: "notice",
345
- message
704
+ message: noticeMessage
346
705
  });
347
- onNotice(message);
706
+ onNotice(noticeMessage);
348
707
  }
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();
708
+ if (agent.mcpSupport === "notice") {
709
+ const noticeMessage = buildMcpNoticeMessage(agent, mcpCommand);
710
+ mcpRegistrations.push({
711
+ agent: agent.agent,
712
+ status: "notice",
713
+ message: noticeMessage
714
+ });
715
+ onNotice(noticeMessage);
716
+ continue;
717
+ }
718
+ try {
719
+ const mcpResult = await registerMcpServer(agent, {
720
+ homeOverride: options.homeOverride,
721
+ command: mcpCommand
722
+ });
723
+ mcpRegistrations.push({
724
+ agent: agent.agent,
725
+ status: "registered",
726
+ path: mcpResult.path
727
+ });
728
+ } catch (err) {
729
+ const message = err instanceof Error ? err.message : String(err);
730
+ mcpRegistrations.push({
731
+ agent: agent.agent,
732
+ status: "failed",
733
+ message
734
+ });
735
+ onNotice(`${agent.agent}: MCP registration failed (${message})`);
377
736
  }
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
737
  }
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");
738
+ return { skillWrites, hookRegistrations, mcpRegistrations };
394
739
  }
395
740
 
396
741
  // 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
742
  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}
743
+ try {
744
+ const data = await provisionWallet({ baseUrl: opts.baseUrl });
745
+ process.stdout.write(`subOrgId: ${data.subOrgId}
455
746
  `);
456
- process.stdout.write(`walletAddress: ${data.walletAddress}
747
+ process.stdout.write(`walletAddress: ${data.walletAddress}
457
748
  `);
458
- process.stdout.write(`config written to ${getWalletConfigPath()}
749
+ process.stdout.write(`config written to ${getWalletConfigPath()}
459
750
  `);
751
+ } catch (err) {
752
+ if (err instanceof ProvisionHttpError) {
753
+ process.stderr.write(
754
+ `[keeperhub-wallet] provision failed: HTTP ${err.status}: ${err.body}
755
+ `
756
+ );
757
+ process.exit(1);
758
+ }
759
+ throw err;
760
+ }
460
761
  }
461
762
  async function cmdFund() {
462
763
  const wallet = await readWalletConfig();
@@ -483,6 +784,58 @@ async function cmdInfo() {
483
784
  process.stdout.write(`walletAddress: ${wallet.walletAddress}
484
785
  `);
485
786
  }
787
+ var FEEDBACK_DEFAULT_BASE_URL = "https://app.keeperhub.com";
788
+ async function cmdFeedback(opts) {
789
+ const wallet = await readWalletConfig();
790
+ const baseUrl = (opts.baseUrl ?? FEEDBACK_DEFAULT_BASE_URL).replace(
791
+ /\/$/,
792
+ ""
793
+ );
794
+ const path = "/api/agentic-wallet/feedback";
795
+ const body = {
796
+ executionId: opts.executionId,
797
+ value: Number.parseInt(opts.value, 10),
798
+ valueDecimals: Number.parseInt(opts.decimals ?? "0", 10)
799
+ };
800
+ if (opts.comment !== void 0) {
801
+ body.comment = opts.comment;
802
+ }
803
+ if (opts.agentId !== void 0) {
804
+ body.agentId = opts.agentId;
805
+ }
806
+ if (opts.chainId !== void 0) {
807
+ body.agentChainId = Number.parseInt(opts.chainId, 10);
808
+ }
809
+ const bodyJson = JSON.stringify(body);
810
+ const headers = buildHmacHeaders(
811
+ wallet.hmacSecret,
812
+ "POST",
813
+ path,
814
+ wallet.subOrgId,
815
+ bodyJson
816
+ );
817
+ const response = await fetch(`${baseUrl}${path}`, {
818
+ method: "POST",
819
+ headers: {
820
+ "content-type": "application/json",
821
+ ...headers
822
+ },
823
+ body: bodyJson
824
+ });
825
+ const text = await response.text();
826
+ if (!response.ok) {
827
+ process.stderr.write(`HTTP ${response.status}: ${text}
828
+ `);
829
+ process.exit(1);
830
+ }
831
+ const parsed = JSON.parse(text);
832
+ process.stdout.write(`feedbackId: ${parsed.feedbackId ?? ""}
833
+ `);
834
+ process.stdout.write(`txHash: ${parsed.txHash ?? ""}
835
+ `);
836
+ process.stdout.write(`publicUrl: ${parsed.publicUrl ?? ""}
837
+ `);
838
+ }
486
839
  async function runCli(argv = process.argv) {
487
840
  const program = new import_commander.Command();
488
841
  program.name("keeperhub-wallet").description(
@@ -502,6 +855,27 @@ async function runCli(argv = process.argv) {
502
855
  program.command("info").description("Print subOrgId and walletAddress from local config").action(async () => {
503
856
  await cmdInfo();
504
857
  });
858
+ program.command("feedback").description(
859
+ "Submit ERC-8004 ReputationRegistry feedback for a workflow execution this wallet paid for. Signs giveFeedback() via Turnkey and broadcasts on Ethereum mainnet. Caller wallet pays gas natively."
860
+ ).requiredOption(
861
+ "--execution-id <id>",
862
+ "workflow execution id to leave feedback for"
863
+ ).requiredOption(
864
+ "--value <int>",
865
+ "raw int128 rating value (e.g. 5 with --decimals 0 for a 5-star rating)"
866
+ ).option(
867
+ "--decimals <int>",
868
+ "decimals for value (0..18); 0 for integer scores, 1 for 0.1-step",
869
+ "0"
870
+ ).option("--comment <text>", "optional plaintext comment").option(
871
+ "--agent-id <id>",
872
+ "rated agent NFT id (uint256 decimal); defaults to KeeperHub agent 31875"
873
+ ).option(
874
+ "--chain-id <int>",
875
+ "agent chain id; defaults to 1 (Ethereum mainnet, only chain supported today)"
876
+ ).option("--base-url <url>", "KeeperHub API base URL").action(async (opts) => {
877
+ await cmdFeedback(opts);
878
+ });
505
879
  program.command("skill").description(
506
880
  "Install the KeeperHub skill file into detected agent directories"
507
881
  ).addCommand(
@@ -524,6 +898,29 @@ async function runCli(argv = process.argv) {
524
898
  } else if (reg.status === "notice") {
525
899
  process.stderr.write(
526
900
  `notice: ${reg.agent} -> ${reg.message ?? ""}
901
+ `
902
+ );
903
+ } else if (reg.status === "failed") {
904
+ process.stderr.write(
905
+ `hook: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
906
+ `
907
+ );
908
+ }
909
+ }
910
+ for (const reg of result.mcpRegistrations) {
911
+ if (reg.status === "registered") {
912
+ process.stdout.write(
913
+ `mcp: ${reg.agent} -> registered at ${reg.path ?? "(unknown path)"}
914
+ `
915
+ );
916
+ } else if (reg.status === "notice") {
917
+ process.stderr.write(
918
+ `notice: ${reg.agent} mcp -> ${reg.message ?? ""}
919
+ `
920
+ );
921
+ } else if (reg.status === "failed") {
922
+ process.stderr.write(
923
+ `mcp: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
527
924
  `
528
925
  );
529
926
  }