@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/README.md +21 -5
- package/bin/keeperhub-wallet-mcp.js +21 -0
- package/dist/cli.cjs +562 -165
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +576 -164
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +573 -202
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -245
- package/dist/index.d.ts +71 -245
- package/dist/index.js +587 -201
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +1305 -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 +1283 -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 +16 -9
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
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!detected) {
|
|
128
|
+
continue;
|
|
105
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,197 @@ function fund(walletAddress) {
|
|
|
196
228
|
};
|
|
197
229
|
}
|
|
198
230
|
|
|
231
|
+
// src/hmac.ts
|
|
232
|
+
var import_node_crypto = require("crypto");
|
|
233
|
+
function computeSignature(secret, method, path, subOrgId, body, timestamp) {
|
|
234
|
+
const bodyDigest = (0, import_node_crypto.createHash)("sha256").update(body).digest("hex");
|
|
235
|
+
const signingString = `${method}
|
|
236
|
+
${path}
|
|
237
|
+
${subOrgId}
|
|
238
|
+
${bodyDigest}
|
|
239
|
+
${timestamp}`;
|
|
240
|
+
return (0, import_node_crypto.createHmac)("sha256", secret).update(signingString).digest("hex");
|
|
241
|
+
}
|
|
242
|
+
function buildHmacHeaders(secret, method, path, subOrgId, body) {
|
|
243
|
+
const timestamp = String(Math.floor(Date.now() / 1e3));
|
|
244
|
+
const signature = computeSignature(
|
|
245
|
+
secret,
|
|
246
|
+
method,
|
|
247
|
+
path,
|
|
248
|
+
subOrgId,
|
|
249
|
+
body,
|
|
250
|
+
timestamp
|
|
251
|
+
);
|
|
252
|
+
return {
|
|
253
|
+
"X-KH-Sub-Org": subOrgId,
|
|
254
|
+
"X-KH-Timestamp": timestamp,
|
|
255
|
+
"X-KH-Signature": signature
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/storage.ts
|
|
260
|
+
var import_node_crypto2 = require("crypto");
|
|
261
|
+
var import_promises = require("fs/promises");
|
|
262
|
+
var import_node_os2 = require("os");
|
|
263
|
+
var import_node_path2 = require("path");
|
|
264
|
+
|
|
265
|
+
// src/types.ts
|
|
266
|
+
var KeeperHubError = class extends Error {
|
|
267
|
+
code;
|
|
268
|
+
constructor(code, message) {
|
|
269
|
+
super(message);
|
|
270
|
+
this.name = "KeeperHubError";
|
|
271
|
+
this.code = code;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
var WalletConfigMissingError = class extends Error {
|
|
275
|
+
constructor() {
|
|
276
|
+
super(
|
|
277
|
+
"Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
|
|
278
|
+
);
|
|
279
|
+
this.name = "WalletConfigMissingError";
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
var WalletConfigCorruptError = class extends Error {
|
|
283
|
+
path;
|
|
284
|
+
constructor(path, reason) {
|
|
285
|
+
super(
|
|
286
|
+
`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).`
|
|
287
|
+
);
|
|
288
|
+
this.name = "WalletConfigCorruptError";
|
|
289
|
+
this.path = path;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/storage.ts
|
|
294
|
+
async function readWalletConfig() {
|
|
295
|
+
const walletPath = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
|
|
296
|
+
let raw;
|
|
297
|
+
try {
|
|
298
|
+
raw = await (0, import_promises.readFile)(walletPath, "utf-8");
|
|
299
|
+
} catch (err) {
|
|
300
|
+
if (err.code === "ENOENT") {
|
|
301
|
+
throw new WalletConfigMissingError();
|
|
302
|
+
}
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
305
|
+
let parsed;
|
|
306
|
+
try {
|
|
307
|
+
parsed = JSON.parse(raw);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
310
|
+
throw new WalletConfigCorruptError(walletPath, reason);
|
|
311
|
+
}
|
|
312
|
+
if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
|
|
313
|
+
throw new WalletConfigCorruptError(walletPath, "missing required fields");
|
|
314
|
+
}
|
|
315
|
+
return parsed;
|
|
316
|
+
}
|
|
317
|
+
async function writeWalletConfig(config) {
|
|
318
|
+
const walletPath = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
|
|
319
|
+
await (0, import_promises.mkdir)((0, import_node_path2.dirname)(walletPath), { recursive: true, mode: 448 });
|
|
320
|
+
const suffix = (0, import_node_crypto2.randomBytes)(8).toString("hex");
|
|
321
|
+
const tmpPath = `${walletPath}.${process.pid}.${suffix}.tmp`;
|
|
322
|
+
await (0, import_promises.writeFile)(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
323
|
+
await (0, import_promises.chmod)(tmpPath, 384);
|
|
324
|
+
await (0, import_promises.rename)(tmpPath, walletPath);
|
|
325
|
+
}
|
|
326
|
+
function getWalletConfigPath() {
|
|
327
|
+
return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/provision.ts
|
|
331
|
+
var TRAILING_SLASH = /\/$/;
|
|
332
|
+
var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
|
|
333
|
+
var ProvisionResponseInvalidError = class extends Error {
|
|
334
|
+
code = "PROVISION_RESPONSE_INVALID";
|
|
335
|
+
constructor(message) {
|
|
336
|
+
super(message);
|
|
337
|
+
this.name = "ProvisionResponseInvalidError";
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
var ProvisionHttpError = class extends Error {
|
|
341
|
+
code = "PROVISION_HTTP_ERROR";
|
|
342
|
+
status;
|
|
343
|
+
body;
|
|
344
|
+
constructor(status, body) {
|
|
345
|
+
super(`provision failed: HTTP ${status}: ${body}`);
|
|
346
|
+
this.name = "ProvisionHttpError";
|
|
347
|
+
this.status = status;
|
|
348
|
+
this.body = body;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
function resolveBaseUrl(override) {
|
|
352
|
+
const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
|
|
353
|
+
return candidate.replace(TRAILING_SLASH, "");
|
|
354
|
+
}
|
|
355
|
+
function isNonEmptyString(value) {
|
|
356
|
+
return typeof value === "string" && value.length > 0;
|
|
357
|
+
}
|
|
358
|
+
function validateProvisionResponse(data) {
|
|
359
|
+
if (typeof data !== "object" || data === null) {
|
|
360
|
+
throw new ProvisionResponseInvalidError(
|
|
361
|
+
"provision response is not an object"
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
const { subOrgId, walletAddress, hmacSecret } = data;
|
|
365
|
+
if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
|
|
366
|
+
throw new ProvisionResponseInvalidError(
|
|
367
|
+
"provision response missing subOrgId, walletAddress, or hmacSecret"
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
|
|
371
|
+
throw new ProvisionResponseInvalidError(
|
|
372
|
+
`provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
subOrgId,
|
|
377
|
+
walletAddress,
|
|
378
|
+
hmacSecret
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
async function provisionWallet(options = {}) {
|
|
382
|
+
const baseUrl = resolveBaseUrl(options.baseUrl);
|
|
383
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
384
|
+
const response = await fetchImpl(`${baseUrl}/api/agentic-wallet/provision`, {
|
|
385
|
+
method: "POST",
|
|
386
|
+
headers: { "content-type": "application/json" },
|
|
387
|
+
body: "{}",
|
|
388
|
+
signal: AbortSignal.timeout(3e4)
|
|
389
|
+
});
|
|
390
|
+
if (!response.ok) {
|
|
391
|
+
const text = await response.text();
|
|
392
|
+
throw new ProvisionHttpError(response.status, text);
|
|
393
|
+
}
|
|
394
|
+
const raw = await response.json();
|
|
395
|
+
const data = validateProvisionResponse(raw);
|
|
396
|
+
await writeWalletConfig(data);
|
|
397
|
+
return data;
|
|
398
|
+
}
|
|
399
|
+
|
|
199
400
|
// src/skill-install.ts
|
|
401
|
+
var import_node_crypto4 = require("crypto");
|
|
402
|
+
var import_promises3 = require("fs/promises");
|
|
403
|
+
var import_node_path5 = require("path");
|
|
404
|
+
var import_node_url2 = require("url");
|
|
405
|
+
|
|
406
|
+
// src/mcp-register.ts
|
|
407
|
+
var import_node_crypto3 = require("crypto");
|
|
408
|
+
var import_promises2 = require("fs/promises");
|
|
409
|
+
var import_node_os3 = require("os");
|
|
410
|
+
var import_node_path4 = require("path");
|
|
411
|
+
|
|
412
|
+
// src/runtime-detect.ts
|
|
200
413
|
var import_node_child_process = require("child_process");
|
|
201
414
|
var import_node_fs2 = require("fs");
|
|
202
|
-
var
|
|
203
|
-
var import_node_path2 = require("path");
|
|
415
|
+
var import_node_path3 = require("path");
|
|
204
416
|
var import_node_url = require("url");
|
|
205
|
-
var HOOK_BIN = "keeperhub-wallet-hook";
|
|
206
|
-
var HOOK_COMMAND_BARE = HOOK_BIN;
|
|
207
417
|
var PACKAGE_NAME = "@keeperhub/wallet";
|
|
208
418
|
function readPackageVersion() {
|
|
209
419
|
try {
|
|
210
|
-
const here = (0,
|
|
211
|
-
const pkgPath = (0,
|
|
420
|
+
const here = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(__filename));
|
|
421
|
+
const pkgPath = (0, import_node_path3.join)(here, "..", "package.json");
|
|
212
422
|
const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
|
|
213
423
|
const parsed = JSON.parse(raw);
|
|
214
424
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
@@ -218,9 +428,6 @@ function readPackageVersion() {
|
|
|
218
428
|
}
|
|
219
429
|
return "latest";
|
|
220
430
|
}
|
|
221
|
-
function buildNpxCommand(version) {
|
|
222
|
-
return `npx -y -p ${PACKAGE_NAME}@${version} ${HOOK_BIN}`;
|
|
223
|
-
}
|
|
224
431
|
function isNpxExecution() {
|
|
225
432
|
const execPath = process.env.npm_execpath;
|
|
226
433
|
if (typeof execPath !== "string" || execPath.length === 0) {
|
|
@@ -248,6 +455,144 @@ function isPathUnderTransientCache(resolvedPath) {
|
|
|
248
455
|
}
|
|
249
456
|
return false;
|
|
250
457
|
}
|
|
458
|
+
function resolveBinCommand(binName) {
|
|
459
|
+
const version = readPackageVersion();
|
|
460
|
+
const npxArgs = ["-y", "-p", `${PACKAGE_NAME}@${version}`, binName];
|
|
461
|
+
const npxCommandString = `npx ${npxArgs.join(" ")}`;
|
|
462
|
+
if (isNpxExecution()) {
|
|
463
|
+
return {
|
|
464
|
+
commandString: npxCommandString,
|
|
465
|
+
command: "npx",
|
|
466
|
+
args: npxArgs
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
try {
|
|
470
|
+
const resolved = (0, import_node_child_process.execFileSync)("/bin/sh", ["-c", `command -v ${binName}`], {
|
|
471
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
472
|
+
}).toString().trim();
|
|
473
|
+
if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
|
|
474
|
+
return {
|
|
475
|
+
commandString: binName,
|
|
476
|
+
command: binName,
|
|
477
|
+
args: []
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
} catch {
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
commandString: npxCommandString,
|
|
484
|
+
command: "npx",
|
|
485
|
+
args: npxArgs
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/mcp-register.ts
|
|
490
|
+
var MCP_BIN = "keeperhub-wallet-mcp";
|
|
491
|
+
var MCP_SERVER_NAME = "keeperhub-wallet";
|
|
492
|
+
function resolveMcpCommand() {
|
|
493
|
+
const envOverride = process.env.KEEPERHUB_WALLET_MCP_COMMAND;
|
|
494
|
+
if (envOverride && envOverride.length > 0) {
|
|
495
|
+
const parts = envOverride.trim().split(/\s+/);
|
|
496
|
+
const head = parts[0] ?? envOverride;
|
|
497
|
+
return { command: head, args: parts.slice(1) };
|
|
498
|
+
}
|
|
499
|
+
const resolved = resolveBinCommand(MCP_BIN);
|
|
500
|
+
return { command: resolved.command, args: resolved.args };
|
|
501
|
+
}
|
|
502
|
+
function buildStandardEntry(cmd) {
|
|
503
|
+
const entry = {
|
|
504
|
+
command: cmd.command,
|
|
505
|
+
args: cmd.args
|
|
506
|
+
};
|
|
507
|
+
if (cmd.env && Object.keys(cmd.env).length > 0) {
|
|
508
|
+
entry.env = cmd.env;
|
|
509
|
+
}
|
|
510
|
+
return entry;
|
|
511
|
+
}
|
|
512
|
+
function buildOpencodeEntry(cmd) {
|
|
513
|
+
return {
|
|
514
|
+
type: "local",
|
|
515
|
+
command: [cmd.command, ...cmd.args],
|
|
516
|
+
enabled: true,
|
|
517
|
+
environment: cmd.env ?? {}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
async function readJsonOrEmpty(path) {
|
|
521
|
+
let raw = null;
|
|
522
|
+
try {
|
|
523
|
+
raw = await (0, import_promises2.readFile)(path, "utf-8");
|
|
524
|
+
} catch (err) {
|
|
525
|
+
if (err.code !== "ENOENT") {
|
|
526
|
+
throw err;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (raw === null) {
|
|
530
|
+
return {};
|
|
531
|
+
}
|
|
532
|
+
try {
|
|
533
|
+
return JSON.parse(raw);
|
|
534
|
+
} catch {
|
|
535
|
+
throw new Error(
|
|
536
|
+
`MCP config at ${path} is not valid JSON; aborting MCP registration`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async function writeJsonAtomic(path, payload) {
|
|
541
|
+
await (0, import_promises2.mkdir)((0, import_node_path4.dirname)(path), { recursive: true, mode: 448 });
|
|
542
|
+
const suffix = (0, import_node_crypto3.randomBytes)(8).toString("hex");
|
|
543
|
+
const tmpPath = `${path}.${process.pid}.${suffix}.tmp`;
|
|
544
|
+
try {
|
|
545
|
+
await (0, import_promises2.writeFile)(tmpPath, payload, { mode: 384 });
|
|
546
|
+
await (0, import_promises2.chmod)(tmpPath, 384);
|
|
547
|
+
await (0, import_promises2.rename)(tmpPath, path);
|
|
548
|
+
} catch (err) {
|
|
549
|
+
await (0, import_promises2.unlink)(tmpPath).catch(() => {
|
|
550
|
+
});
|
|
551
|
+
throw err;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
async function writeStandardMcp(path, entry) {
|
|
555
|
+
const config = await readJsonOrEmpty(path);
|
|
556
|
+
const servers = typeof config.mcpServers === "object" && config.mcpServers !== null ? config.mcpServers : {};
|
|
557
|
+
servers[MCP_SERVER_NAME] = entry;
|
|
558
|
+
config.mcpServers = servers;
|
|
559
|
+
const payload = `${JSON.stringify(config, null, 2)}
|
|
560
|
+
`;
|
|
561
|
+
await writeJsonAtomic(path, payload);
|
|
562
|
+
}
|
|
563
|
+
async function writeOpencodeMcp(path, entry) {
|
|
564
|
+
const config = await readJsonOrEmpty(path);
|
|
565
|
+
const servers = typeof config.mcp === "object" && config.mcp !== null ? config.mcp : {};
|
|
566
|
+
servers[MCP_SERVER_NAME] = entry;
|
|
567
|
+
config.mcp = servers;
|
|
568
|
+
const payload = `${JSON.stringify(config, null, 2)}
|
|
569
|
+
`;
|
|
570
|
+
await writeJsonAtomic(path, payload);
|
|
571
|
+
}
|
|
572
|
+
async function registerMcpServer(target, options = {}) {
|
|
573
|
+
if (target.mcpSupport === "notice") {
|
|
574
|
+
throw new Error(
|
|
575
|
+
`agent ${target.agent} does not support auto-registered MCP servers; surface a notice instead`
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
if (!target.mcpConfigRel) {
|
|
579
|
+
throw new Error(
|
|
580
|
+
`agent ${target.agent} has mcpSupport=${target.mcpSupport} but no mcpConfigRel path`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
const home = options.homeOverride ?? (0, import_node_os3.homedir)();
|
|
584
|
+
const path = (0, import_node_path4.join)(home, ...target.mcpConfigRel);
|
|
585
|
+
const cmd = options.command ?? resolveMcpCommand();
|
|
586
|
+
if (target.mcpSupport === "opencode") {
|
|
587
|
+
await writeOpencodeMcp(path, buildOpencodeEntry(cmd));
|
|
588
|
+
} else {
|
|
589
|
+
await writeStandardMcp(path, buildStandardEntry(cmd));
|
|
590
|
+
}
|
|
591
|
+
return { path, name: MCP_SERVER_NAME };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/skill-install.ts
|
|
595
|
+
var HOOK_BIN = "keeperhub-wallet-hook";
|
|
251
596
|
var KEEPERHUB_HOOK_MARKER = HOOK_BIN;
|
|
252
597
|
function filterKeeperhubHooksFromEntry(entry) {
|
|
253
598
|
if (typeof entry !== "object" || entry === null) {
|
|
@@ -274,19 +619,7 @@ function resolveHookCommand() {
|
|
|
274
619
|
if (envOverride && envOverride.length > 0) {
|
|
275
620
|
return envOverride;
|
|
276
621
|
}
|
|
277
|
-
|
|
278
|
-
return buildNpxCommand(readPackageVersion());
|
|
279
|
-
}
|
|
280
|
-
try {
|
|
281
|
-
const resolved = (0, import_node_child_process.execFileSync)("/bin/sh", ["-c", `command -v ${HOOK_BIN}`], {
|
|
282
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
283
|
-
}).toString().trim();
|
|
284
|
-
if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
|
|
285
|
-
return HOOK_COMMAND_BARE;
|
|
286
|
-
}
|
|
287
|
-
} catch {
|
|
288
|
-
}
|
|
289
|
-
return buildNpxCommand(readPackageVersion());
|
|
622
|
+
return resolveBinCommand(HOOK_BIN).commandString;
|
|
290
623
|
}
|
|
291
624
|
function buildKeeperhubEntry(command) {
|
|
292
625
|
return {
|
|
@@ -295,8 +628,8 @@ function buildKeeperhubEntry(command) {
|
|
|
295
628
|
};
|
|
296
629
|
}
|
|
297
630
|
function resolveDefaultSkillSource() {
|
|
298
|
-
const here = (0,
|
|
299
|
-
return (0,
|
|
631
|
+
const here = (0, import_node_path5.dirname)((0, import_node_url2.fileURLToPath)(__filename));
|
|
632
|
+
return (0, import_node_path5.join)(here, "..", "skill", "keeperhub-wallet.skill.md");
|
|
300
633
|
}
|
|
301
634
|
function defaultNotice(msg) {
|
|
302
635
|
process.stderr.write(`${msg}
|
|
@@ -306,7 +639,7 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
|
|
|
306
639
|
const command = options.hookCommand ?? resolveHookCommand();
|
|
307
640
|
let raw = null;
|
|
308
641
|
try {
|
|
309
|
-
raw = await (0,
|
|
642
|
+
raw = await (0, import_promises3.readFile)(settingsPath, "utf-8");
|
|
310
643
|
} catch (err) {
|
|
311
644
|
if (err.code !== "ENOENT") {
|
|
312
645
|
throw err;
|
|
@@ -334,166 +667,136 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
|
|
|
334
667
|
filtered.push(buildKeeperhubEntry(command));
|
|
335
668
|
hooks.PreToolUse = filtered;
|
|
336
669
|
config.hooks = hooks;
|
|
337
|
-
await (0,
|
|
670
|
+
await (0, import_promises3.mkdir)((0, import_node_path5.dirname)(settingsPath), { recursive: true, mode: 448 });
|
|
338
671
|
const payload = `${JSON.stringify(config, null, 2)}
|
|
339
672
|
`;
|
|
340
|
-
|
|
341
|
-
|
|
673
|
+
const suffix = (0, import_node_crypto4.randomBytes)(8).toString("hex");
|
|
674
|
+
const tmpPath = `${settingsPath}.${process.pid}.${suffix}.tmp`;
|
|
675
|
+
try {
|
|
676
|
+
await (0, import_promises3.writeFile)(tmpPath, payload, { mode: 384 });
|
|
677
|
+
await (0, import_promises3.chmod)(tmpPath, 384);
|
|
678
|
+
await (0, import_promises3.rename)(tmpPath, settingsPath);
|
|
679
|
+
} catch (err) {
|
|
680
|
+
await (0, import_promises3.unlink)(tmpPath).catch(() => {
|
|
681
|
+
});
|
|
682
|
+
throw err;
|
|
683
|
+
}
|
|
342
684
|
}
|
|
343
685
|
async function writeSkillToAgent(agent, skillSource) {
|
|
344
|
-
await (0,
|
|
345
|
-
const target = (0,
|
|
346
|
-
await (0,
|
|
347
|
-
await (0,
|
|
686
|
+
await (0, import_promises3.mkdir)(agent.skillsDir, { recursive: true, mode: 493 });
|
|
687
|
+
const target = (0, import_node_path5.join)(agent.skillsDir, "keeperhub-wallet.skill.md");
|
|
688
|
+
await (0, import_promises3.copyFile)(skillSource, target);
|
|
689
|
+
await (0, import_promises3.chmod)(target, 420);
|
|
348
690
|
return { agent: agent.agent, path: target, status: "written" };
|
|
349
691
|
}
|
|
350
|
-
function
|
|
692
|
+
function buildHookNoticeMessage(agent, command) {
|
|
351
693
|
return `${agent.agent} does not support auto-registered PreToolUse hooks; run \`${command}\` on every tool use via ${agent.agent}'s settings file at ${agent.settingsFile}`;
|
|
352
694
|
}
|
|
695
|
+
function buildMcpNoticeMessage(agent, command) {
|
|
696
|
+
const cmd = [command.command, ...command.args].join(" ");
|
|
697
|
+
return `${agent.agent} does not support auto-registered MCP servers; add an entry named \`keeperhub-wallet\` running \`${cmd}\` to your MCP config manually`;
|
|
698
|
+
}
|
|
353
699
|
async function installSkill(options = {}) {
|
|
354
700
|
const agents = detectAgents(options.homeOverride);
|
|
355
701
|
const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();
|
|
356
702
|
const onNotice = options.onNotice ?? defaultNotice;
|
|
357
703
|
const hookCommand = options.hookCommand ?? resolveHookCommand();
|
|
704
|
+
const mcpCommand = options.mcpCommand ?? resolveMcpCommand();
|
|
358
705
|
const skillWrites = [];
|
|
359
706
|
const hookRegistrations = [];
|
|
707
|
+
const mcpRegistrations = [];
|
|
360
708
|
for (const agent of agents) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
709
|
+
try {
|
|
710
|
+
const write = await writeSkillToAgent(agent, skillSource);
|
|
711
|
+
skillWrites.push(write);
|
|
712
|
+
} catch (err) {
|
|
713
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
714
|
+
skillWrites.push({
|
|
366
715
|
agent: agent.agent,
|
|
367
|
-
|
|
716
|
+
path: "",
|
|
717
|
+
status: "skipped"
|
|
368
718
|
});
|
|
719
|
+
onNotice(`${agent.agent}: skill copy failed (${message})`);
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (agent.hookSupport === "claude-code") {
|
|
723
|
+
try {
|
|
724
|
+
await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
|
|
725
|
+
hookRegistrations.push({
|
|
726
|
+
agent: agent.agent,
|
|
727
|
+
status: "registered"
|
|
728
|
+
});
|
|
729
|
+
} catch (err) {
|
|
730
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
731
|
+
hookRegistrations.push({
|
|
732
|
+
agent: agent.agent,
|
|
733
|
+
status: "failed",
|
|
734
|
+
message
|
|
735
|
+
});
|
|
736
|
+
onNotice(`${agent.agent}: hook registration failed (${message})`);
|
|
737
|
+
}
|
|
369
738
|
} else {
|
|
370
|
-
const
|
|
739
|
+
const noticeMessage = buildHookNoticeMessage(agent, hookCommand);
|
|
371
740
|
hookRegistrations.push({
|
|
372
741
|
agent: agent.agent,
|
|
373
742
|
status: "notice",
|
|
374
|
-
message
|
|
743
|
+
message: noticeMessage
|
|
375
744
|
});
|
|
376
|
-
onNotice(
|
|
745
|
+
onNotice(noticeMessage);
|
|
377
746
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
async function readWalletConfig() {
|
|
407
|
-
const walletPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
|
|
408
|
-
let raw;
|
|
409
|
-
try {
|
|
410
|
-
raw = await (0, import_promises2.readFile)(walletPath, "utf-8");
|
|
411
|
-
} catch (err) {
|
|
412
|
-
if (err.code === "ENOENT") {
|
|
413
|
-
throw new WalletConfigMissingError();
|
|
747
|
+
if (agent.mcpSupport === "notice") {
|
|
748
|
+
const noticeMessage = buildMcpNoticeMessage(agent, mcpCommand);
|
|
749
|
+
mcpRegistrations.push({
|
|
750
|
+
agent: agent.agent,
|
|
751
|
+
status: "notice",
|
|
752
|
+
message: noticeMessage
|
|
753
|
+
});
|
|
754
|
+
onNotice(noticeMessage);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
try {
|
|
758
|
+
const mcpResult = await registerMcpServer(agent, {
|
|
759
|
+
homeOverride: options.homeOverride,
|
|
760
|
+
command: mcpCommand
|
|
761
|
+
});
|
|
762
|
+
mcpRegistrations.push({
|
|
763
|
+
agent: agent.agent,
|
|
764
|
+
status: "registered",
|
|
765
|
+
path: mcpResult.path
|
|
766
|
+
});
|
|
767
|
+
} catch (err) {
|
|
768
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
769
|
+
mcpRegistrations.push({
|
|
770
|
+
agent: agent.agent,
|
|
771
|
+
status: "failed",
|
|
772
|
+
message
|
|
773
|
+
});
|
|
774
|
+
onNotice(`${agent.agent}: MCP registration failed (${message})`);
|
|
414
775
|
}
|
|
415
|
-
throw err;
|
|
416
|
-
}
|
|
417
|
-
const parsed = JSON.parse(raw);
|
|
418
|
-
if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
|
|
419
|
-
throw new Error(`Malformed wallet.json at ${walletPath}`);
|
|
420
776
|
}
|
|
421
|
-
return
|
|
422
|
-
}
|
|
423
|
-
async function writeWalletConfig(config) {
|
|
424
|
-
const walletPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
|
|
425
|
-
await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(walletPath), { recursive: true, mode: 448 });
|
|
426
|
-
await (0, import_promises2.writeFile)(walletPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
427
|
-
await (0, import_promises2.chmod)(walletPath, 384);
|
|
428
|
-
}
|
|
429
|
-
function getWalletConfigPath() {
|
|
430
|
-
return (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
|
|
777
|
+
return { skillWrites, hookRegistrations, mcpRegistrations };
|
|
431
778
|
}
|
|
432
779
|
|
|
433
780
|
// src/cli.ts
|
|
434
|
-
var TRAILING_SLASH = /\/$/;
|
|
435
|
-
var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
|
|
436
|
-
function resolveBaseUrl(override) {
|
|
437
|
-
const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
|
|
438
|
-
return candidate.replace(TRAILING_SLASH, "");
|
|
439
|
-
}
|
|
440
|
-
function isNonEmptyString(value) {
|
|
441
|
-
return typeof value === "string" && value.length > 0;
|
|
442
|
-
}
|
|
443
|
-
function provisionInvalidError(message) {
|
|
444
|
-
const err = new Error(message);
|
|
445
|
-
err.code = "PROVISION_RESPONSE_INVALID";
|
|
446
|
-
return err;
|
|
447
|
-
}
|
|
448
|
-
function validateProvisionResponse(data) {
|
|
449
|
-
if (typeof data !== "object" || data === null) {
|
|
450
|
-
throw provisionInvalidError("provision response is not an object");
|
|
451
|
-
}
|
|
452
|
-
const { subOrgId, walletAddress, hmacSecret } = data;
|
|
453
|
-
if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
|
|
454
|
-
throw provisionInvalidError(
|
|
455
|
-
"provision response missing subOrgId, walletAddress, or hmacSecret"
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
|
|
459
|
-
throw provisionInvalidError(
|
|
460
|
-
`provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
return {
|
|
464
|
-
subOrgId,
|
|
465
|
-
walletAddress,
|
|
466
|
-
hmacSecret
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
781
|
async function cmdAdd(opts = {}) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
headers: { "content-type": "application/json" },
|
|
474
|
-
body: "{}"
|
|
475
|
-
});
|
|
476
|
-
if (!response.ok) {
|
|
477
|
-
const text = await response.text();
|
|
478
|
-
process.stderr.write(
|
|
479
|
-
`[keeperhub-wallet] provision failed: HTTP ${response.status}: ${text}
|
|
480
|
-
`
|
|
481
|
-
);
|
|
482
|
-
process.exit(1);
|
|
483
|
-
}
|
|
484
|
-
const raw = await response.json();
|
|
485
|
-
const data = validateProvisionResponse(raw);
|
|
486
|
-
await writeWalletConfig({
|
|
487
|
-
subOrgId: data.subOrgId,
|
|
488
|
-
walletAddress: data.walletAddress,
|
|
489
|
-
hmacSecret: data.hmacSecret
|
|
490
|
-
});
|
|
491
|
-
process.stdout.write(`subOrgId: ${data.subOrgId}
|
|
782
|
+
try {
|
|
783
|
+
const data = await provisionWallet({ baseUrl: opts.baseUrl });
|
|
784
|
+
process.stdout.write(`subOrgId: ${data.subOrgId}
|
|
492
785
|
`);
|
|
493
|
-
|
|
786
|
+
process.stdout.write(`walletAddress: ${data.walletAddress}
|
|
494
787
|
`);
|
|
495
|
-
|
|
788
|
+
process.stdout.write(`config written to ${getWalletConfigPath()}
|
|
496
789
|
`);
|
|
790
|
+
} catch (err) {
|
|
791
|
+
if (err instanceof ProvisionHttpError) {
|
|
792
|
+
process.stderr.write(
|
|
793
|
+
`[keeperhub-wallet] provision failed: HTTP ${err.status}: ${err.body}
|
|
794
|
+
`
|
|
795
|
+
);
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
throw err;
|
|
799
|
+
}
|
|
497
800
|
}
|
|
498
801
|
async function cmdFund() {
|
|
499
802
|
const wallet = await readWalletConfig();
|
|
@@ -520,6 +823,58 @@ async function cmdInfo() {
|
|
|
520
823
|
process.stdout.write(`walletAddress: ${wallet.walletAddress}
|
|
521
824
|
`);
|
|
522
825
|
}
|
|
826
|
+
var FEEDBACK_DEFAULT_BASE_URL = "https://app.keeperhub.com";
|
|
827
|
+
async function cmdFeedback(opts) {
|
|
828
|
+
const wallet = await readWalletConfig();
|
|
829
|
+
const baseUrl = (opts.baseUrl ?? FEEDBACK_DEFAULT_BASE_URL).replace(
|
|
830
|
+
/\/$/,
|
|
831
|
+
""
|
|
832
|
+
);
|
|
833
|
+
const path = "/api/agentic-wallet/feedback";
|
|
834
|
+
const body = {
|
|
835
|
+
executionId: opts.executionId,
|
|
836
|
+
value: Number.parseInt(opts.value, 10),
|
|
837
|
+
valueDecimals: Number.parseInt(opts.decimals ?? "0", 10)
|
|
838
|
+
};
|
|
839
|
+
if (opts.comment !== void 0) {
|
|
840
|
+
body.comment = opts.comment;
|
|
841
|
+
}
|
|
842
|
+
if (opts.agentId !== void 0) {
|
|
843
|
+
body.agentId = opts.agentId;
|
|
844
|
+
}
|
|
845
|
+
if (opts.chainId !== void 0) {
|
|
846
|
+
body.agentChainId = Number.parseInt(opts.chainId, 10);
|
|
847
|
+
}
|
|
848
|
+
const bodyJson = JSON.stringify(body);
|
|
849
|
+
const headers = buildHmacHeaders(
|
|
850
|
+
wallet.hmacSecret,
|
|
851
|
+
"POST",
|
|
852
|
+
path,
|
|
853
|
+
wallet.subOrgId,
|
|
854
|
+
bodyJson
|
|
855
|
+
);
|
|
856
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
857
|
+
method: "POST",
|
|
858
|
+
headers: {
|
|
859
|
+
"content-type": "application/json",
|
|
860
|
+
...headers
|
|
861
|
+
},
|
|
862
|
+
body: bodyJson
|
|
863
|
+
});
|
|
864
|
+
const text = await response.text();
|
|
865
|
+
if (!response.ok) {
|
|
866
|
+
process.stderr.write(`HTTP ${response.status}: ${text}
|
|
867
|
+
`);
|
|
868
|
+
process.exit(1);
|
|
869
|
+
}
|
|
870
|
+
const parsed = JSON.parse(text);
|
|
871
|
+
process.stdout.write(`feedbackId: ${parsed.feedbackId ?? ""}
|
|
872
|
+
`);
|
|
873
|
+
process.stdout.write(`txHash: ${parsed.txHash ?? ""}
|
|
874
|
+
`);
|
|
875
|
+
process.stdout.write(`publicUrl: ${parsed.publicUrl ?? ""}
|
|
876
|
+
`);
|
|
877
|
+
}
|
|
523
878
|
async function runCli(argv = process.argv) {
|
|
524
879
|
const program = new import_commander.Command();
|
|
525
880
|
program.name("keeperhub-wallet").description(
|
|
@@ -539,6 +894,27 @@ async function runCli(argv = process.argv) {
|
|
|
539
894
|
program.command("info").description("Print subOrgId and walletAddress from local config").action(async () => {
|
|
540
895
|
await cmdInfo();
|
|
541
896
|
});
|
|
897
|
+
program.command("feedback").description(
|
|
898
|
+
"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."
|
|
899
|
+
).requiredOption(
|
|
900
|
+
"--execution-id <id>",
|
|
901
|
+
"workflow execution id to leave feedback for"
|
|
902
|
+
).requiredOption(
|
|
903
|
+
"--value <int>",
|
|
904
|
+
"raw int128 rating value (e.g. 5 with --decimals 0 for a 5-star rating)"
|
|
905
|
+
).option(
|
|
906
|
+
"--decimals <int>",
|
|
907
|
+
"decimals for value (0..18); 0 for integer scores, 1 for 0.1-step",
|
|
908
|
+
"0"
|
|
909
|
+
).option("--comment <text>", "optional plaintext comment").option(
|
|
910
|
+
"--agent-id <id>",
|
|
911
|
+
"rated agent NFT id (uint256 decimal); defaults to KeeperHub agent 31875"
|
|
912
|
+
).option(
|
|
913
|
+
"--chain-id <int>",
|
|
914
|
+
"agent chain id; defaults to 1 (Ethereum mainnet, only chain supported today)"
|
|
915
|
+
).option("--base-url <url>", "KeeperHub API base URL").action(async (opts) => {
|
|
916
|
+
await cmdFeedback(opts);
|
|
917
|
+
});
|
|
542
918
|
program.command("skill").description(
|
|
543
919
|
"Install the KeeperHub skill file into detected agent directories"
|
|
544
920
|
).addCommand(
|
|
@@ -561,6 +937,29 @@ async function runCli(argv = process.argv) {
|
|
|
561
937
|
} else if (reg.status === "notice") {
|
|
562
938
|
process.stderr.write(
|
|
563
939
|
`notice: ${reg.agent} -> ${reg.message ?? ""}
|
|
940
|
+
`
|
|
941
|
+
);
|
|
942
|
+
} else if (reg.status === "failed") {
|
|
943
|
+
process.stderr.write(
|
|
944
|
+
`hook: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
|
|
945
|
+
`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
for (const reg of result.mcpRegistrations) {
|
|
950
|
+
if (reg.status === "registered") {
|
|
951
|
+
process.stdout.write(
|
|
952
|
+
`mcp: ${reg.agent} -> registered at ${reg.path ?? "(unknown path)"}
|
|
953
|
+
`
|
|
954
|
+
);
|
|
955
|
+
} else if (reg.status === "notice") {
|
|
956
|
+
process.stderr.write(
|
|
957
|
+
`notice: ${reg.agent} mcp -> ${reg.message ?? ""}
|
|
958
|
+
`
|
|
959
|
+
);
|
|
960
|
+
} else if (reg.status === "failed") {
|
|
961
|
+
process.stderr.write(
|
|
962
|
+
`mcp: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
|
|
564
963
|
`
|
|
565
964
|
);
|
|
566
965
|
}
|
|
@@ -588,34 +987,6 @@ async function runCli(argv = process.argv) {
|
|
|
588
987
|
}
|
|
589
988
|
}
|
|
590
989
|
|
|
591
|
-
// src/hmac.ts
|
|
592
|
-
var import_node_crypto = require("crypto");
|
|
593
|
-
function computeSignature(secret, method, path, subOrgId, body, timestamp) {
|
|
594
|
-
const bodyDigest = (0, import_node_crypto.createHash)("sha256").update(body).digest("hex");
|
|
595
|
-
const signingString = `${method}
|
|
596
|
-
${path}
|
|
597
|
-
${subOrgId}
|
|
598
|
-
${bodyDigest}
|
|
599
|
-
${timestamp}`;
|
|
600
|
-
return (0, import_node_crypto.createHmac)("sha256", secret).update(signingString).digest("hex");
|
|
601
|
-
}
|
|
602
|
-
function buildHmacHeaders(secret, method, path, subOrgId, body) {
|
|
603
|
-
const timestamp = String(Math.floor(Date.now() / 1e3));
|
|
604
|
-
const signature = computeSignature(
|
|
605
|
-
secret,
|
|
606
|
-
method,
|
|
607
|
-
path,
|
|
608
|
-
subOrgId,
|
|
609
|
-
body,
|
|
610
|
-
timestamp
|
|
611
|
-
);
|
|
612
|
-
return {
|
|
613
|
-
"X-KH-Sub-Org": subOrgId,
|
|
614
|
-
"X-KH-Timestamp": timestamp,
|
|
615
|
-
"X-KH-Signature": signature
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
|
|
619
990
|
// src/client.ts
|
|
620
991
|
var TRAILING_SLASH2 = /\/$/;
|
|
621
992
|
function defaultCodeForStatus(status) {
|
|
@@ -689,9 +1060,9 @@ var KeeperHubClient = class {
|
|
|
689
1060
|
};
|
|
690
1061
|
|
|
691
1062
|
// src/safety-config.ts
|
|
692
|
-
var
|
|
693
|
-
var
|
|
694
|
-
var
|
|
1063
|
+
var import_promises4 = require("fs/promises");
|
|
1064
|
+
var import_node_os4 = require("os");
|
|
1065
|
+
var import_node_path6 = require("path");
|
|
695
1066
|
var DEFAULT_SAFETY_CONFIG = {
|
|
696
1067
|
auto_approve_max_usd: 5,
|
|
697
1068
|
ask_threshold_usd: 50,
|
|
@@ -704,20 +1075,20 @@ var DEFAULT_SAFETY_CONFIG = {
|
|
|
704
1075
|
]
|
|
705
1076
|
};
|
|
706
1077
|
function getSafetyPath() {
|
|
707
|
-
return (0,
|
|
1078
|
+
return (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".keeperhub", "safety.json");
|
|
708
1079
|
}
|
|
709
1080
|
async function loadSafetyConfig() {
|
|
710
1081
|
const path = getSafetyPath();
|
|
711
1082
|
let raw;
|
|
712
1083
|
try {
|
|
713
|
-
raw = await (0,
|
|
1084
|
+
raw = await (0, import_promises4.readFile)(path, "utf-8");
|
|
714
1085
|
} catch (err) {
|
|
715
1086
|
if (err.code === "ENOENT") {
|
|
716
|
-
await (0,
|
|
717
|
-
await (0,
|
|
1087
|
+
await (0, import_promises4.mkdir)((0, import_node_path6.dirname)(path), { recursive: true, mode: 448 });
|
|
1088
|
+
await (0, import_promises4.writeFile)(path, JSON.stringify(DEFAULT_SAFETY_CONFIG, null, 2), {
|
|
718
1089
|
mode: 420
|
|
719
1090
|
});
|
|
720
|
-
await (0,
|
|
1091
|
+
await (0, import_promises4.chmod)(path, 420);
|
|
721
1092
|
return DEFAULT_SAFETY_CONFIG;
|
|
722
1093
|
}
|
|
723
1094
|
throw err;
|
|
@@ -914,7 +1285,7 @@ function parseMppChallenge(response) {
|
|
|
914
1285
|
}
|
|
915
1286
|
|
|
916
1287
|
// src/payment-signer.ts
|
|
917
|
-
var
|
|
1288
|
+
var import_node_crypto5 = require("crypto");
|
|
918
1289
|
|
|
919
1290
|
// src/workflow-slug.ts
|
|
920
1291
|
var KEEPERHUB_WORKFLOW_RE = /\/api\/mcp\/workflows\/([a-zA-Z0-9_-]+)\/call(?:\/?)(?:\?|$|#)/;
|
|
@@ -1094,7 +1465,7 @@ function createPaymentSigner(opts = {}) {
|
|
|
1094
1465
|
const now = Math.floor(Date.now() / 1e3);
|
|
1095
1466
|
const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;
|
|
1096
1467
|
const validBefore = now + accept.maxTimeoutSeconds;
|
|
1097
|
-
const nonce = `0x${(0,
|
|
1468
|
+
const nonce = `0x${(0, import_node_crypto5.randomBytes)(NONCE_BYTES).toString("hex")}`;
|
|
1098
1469
|
const client = clientFactory(wallet);
|
|
1099
1470
|
const signature = await signOrPoll(client, {
|
|
1100
1471
|
chain: "base",
|