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