@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.js
CHANGED
|
@@ -7,31 +7,51 @@ var AGENT_SPECS = [
|
|
|
7
7
|
agent: "claude-code",
|
|
8
8
|
skillsRel: [".claude", "skills"],
|
|
9
9
|
settingsRel: [".claude", "settings.json"],
|
|
10
|
-
hookSupport: "claude-code"
|
|
10
|
+
hookSupport: "claude-code",
|
|
11
|
+
// ~/.claude.json is at HOME root (not under .claude/) and is large
|
|
12
|
+
// (100+KB on real installs). registerMcpServer reads/parses/rewrites it
|
|
13
|
+
// while preserving every other top-level key byte-for-byte.
|
|
14
|
+
mcpConfigRel: [".claude.json"],
|
|
15
|
+
mcpSupport: "claude-code"
|
|
11
16
|
},
|
|
12
17
|
{
|
|
13
18
|
agent: "cursor",
|
|
14
19
|
skillsRel: [".cursor", "skills"],
|
|
15
20
|
settingsRel: [".cursor", "settings.json"],
|
|
16
|
-
hookSupport: "notice"
|
|
21
|
+
hookSupport: "notice",
|
|
22
|
+
mcpConfigRel: [".cursor", "mcp.json"],
|
|
23
|
+
mcpSupport: "cursor"
|
|
17
24
|
},
|
|
18
25
|
{
|
|
19
26
|
agent: "cline",
|
|
20
27
|
skillsRel: [".cline", "skills"],
|
|
21
28
|
settingsRel: [".cline", "settings.json"],
|
|
22
|
-
hookSupport: "notice"
|
|
29
|
+
hookSupport: "notice",
|
|
30
|
+
// Cline keeps MCP state in a per-VS-Code-variant globalStorage path
|
|
31
|
+
// (e.g. ~/Library/Application Support/Code/User/globalStorage/
|
|
32
|
+
// saoudrizwan.claude-dev/settings/cline_mcp_settings.json) that is too
|
|
33
|
+
// fragile to auto-detect. Ship "notice" with a copy-paste entry shape
|
|
34
|
+
// instead of guessing the variant.
|
|
35
|
+
mcpSupport: "notice"
|
|
23
36
|
},
|
|
24
37
|
{
|
|
25
38
|
agent: "windsurf",
|
|
26
39
|
skillsRel: [".windsurf", "skills"],
|
|
27
40
|
settingsRel: [".windsurf", "settings.json"],
|
|
28
|
-
hookSupport: "notice"
|
|
41
|
+
hookSupport: "notice",
|
|
42
|
+
mcpConfigRel: [".codeium", "windsurf", "mcp_config.json"],
|
|
43
|
+
mcpSupport: "windsurf",
|
|
44
|
+
// Windsurf historically ships under both `.windsurf/` and the legacy
|
|
45
|
+
// `.codeium/windsurf/`; detect either.
|
|
46
|
+
extraDetect: [[".codeium", "windsurf"]]
|
|
29
47
|
},
|
|
30
48
|
{
|
|
31
49
|
agent: "opencode",
|
|
32
50
|
skillsRel: [".config", "opencode", "skills"],
|
|
33
51
|
settingsRel: [".config", "opencode", "settings.json"],
|
|
34
|
-
hookSupport: "notice"
|
|
52
|
+
hookSupport: "notice",
|
|
53
|
+
mcpConfigRel: [".config", "opencode", "opencode.json"],
|
|
54
|
+
mcpSupport: "opencode"
|
|
35
55
|
}
|
|
36
56
|
];
|
|
37
57
|
function detectAgents(homeOverride) {
|
|
@@ -40,14 +60,26 @@ function detectAgents(homeOverride) {
|
|
|
40
60
|
for (const spec of AGENT_SPECS) {
|
|
41
61
|
const skillsDir = join(home, ...spec.skillsRel);
|
|
42
62
|
const settingsFile = join(home, ...spec.settingsRel);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
let detected = existsSync(dirname(skillsDir));
|
|
64
|
+
if (!detected && spec.extraDetect) {
|
|
65
|
+
for (const seg of spec.extraDetect) {
|
|
66
|
+
if (existsSync(join(home, ...seg))) {
|
|
67
|
+
detected = true;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!detected) {
|
|
73
|
+
continue;
|
|
50
74
|
}
|
|
75
|
+
results.push({
|
|
76
|
+
agent: spec.agent,
|
|
77
|
+
skillsDir,
|
|
78
|
+
settingsFile,
|
|
79
|
+
hookSupport: spec.hookSupport,
|
|
80
|
+
mcpConfigRel: spec.mcpConfigRel,
|
|
81
|
+
mcpSupport: spec.mcpSupport
|
|
82
|
+
});
|
|
51
83
|
}
|
|
52
84
|
return results;
|
|
53
85
|
}
|
|
@@ -146,19 +178,212 @@ function fund(walletAddress) {
|
|
|
146
178
|
};
|
|
147
179
|
}
|
|
148
180
|
|
|
181
|
+
// src/hmac.ts
|
|
182
|
+
import { createHash, createHmac } from "crypto";
|
|
183
|
+
function computeSignature(secret, method, path, subOrgId, body, timestamp) {
|
|
184
|
+
const bodyDigest = createHash("sha256").update(body).digest("hex");
|
|
185
|
+
const signingString = `${method}
|
|
186
|
+
${path}
|
|
187
|
+
${subOrgId}
|
|
188
|
+
${bodyDigest}
|
|
189
|
+
${timestamp}`;
|
|
190
|
+
return createHmac("sha256", secret).update(signingString).digest("hex");
|
|
191
|
+
}
|
|
192
|
+
function buildHmacHeaders(secret, method, path, subOrgId, body) {
|
|
193
|
+
const timestamp = String(Math.floor(Date.now() / 1e3));
|
|
194
|
+
const signature = computeSignature(
|
|
195
|
+
secret,
|
|
196
|
+
method,
|
|
197
|
+
path,
|
|
198
|
+
subOrgId,
|
|
199
|
+
body,
|
|
200
|
+
timestamp
|
|
201
|
+
);
|
|
202
|
+
return {
|
|
203
|
+
"X-KH-Sub-Org": subOrgId,
|
|
204
|
+
"X-KH-Timestamp": timestamp,
|
|
205
|
+
"X-KH-Signature": signature
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/storage.ts
|
|
210
|
+
import { randomBytes } from "crypto";
|
|
211
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
212
|
+
import { homedir as homedir2 } from "os";
|
|
213
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
214
|
+
|
|
215
|
+
// src/types.ts
|
|
216
|
+
var KeeperHubError = class extends Error {
|
|
217
|
+
code;
|
|
218
|
+
constructor(code, message) {
|
|
219
|
+
super(message);
|
|
220
|
+
this.name = "KeeperHubError";
|
|
221
|
+
this.code = code;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var WalletConfigMissingError = class extends Error {
|
|
225
|
+
constructor() {
|
|
226
|
+
super(
|
|
227
|
+
"Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
|
|
228
|
+
);
|
|
229
|
+
this.name = "WalletConfigMissingError";
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var WalletConfigCorruptError = class extends Error {
|
|
233
|
+
path;
|
|
234
|
+
constructor(path, reason) {
|
|
235
|
+
super(
|
|
236
|
+
`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).`
|
|
237
|
+
);
|
|
238
|
+
this.name = "WalletConfigCorruptError";
|
|
239
|
+
this.path = path;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/storage.ts
|
|
244
|
+
async function readWalletConfig() {
|
|
245
|
+
const walletPath = join2(homedir2(), ".keeperhub", "wallet.json");
|
|
246
|
+
let raw;
|
|
247
|
+
try {
|
|
248
|
+
raw = await readFile(walletPath, "utf-8");
|
|
249
|
+
} catch (err) {
|
|
250
|
+
if (err.code === "ENOENT") {
|
|
251
|
+
throw new WalletConfigMissingError();
|
|
252
|
+
}
|
|
253
|
+
throw err;
|
|
254
|
+
}
|
|
255
|
+
let parsed;
|
|
256
|
+
try {
|
|
257
|
+
parsed = JSON.parse(raw);
|
|
258
|
+
} catch (err) {
|
|
259
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
260
|
+
throw new WalletConfigCorruptError(walletPath, reason);
|
|
261
|
+
}
|
|
262
|
+
if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
|
|
263
|
+
throw new WalletConfigCorruptError(walletPath, "missing required fields");
|
|
264
|
+
}
|
|
265
|
+
return parsed;
|
|
266
|
+
}
|
|
267
|
+
async function writeWalletConfig(config) {
|
|
268
|
+
const walletPath = join2(homedir2(), ".keeperhub", "wallet.json");
|
|
269
|
+
await mkdir(dirname2(walletPath), { recursive: true, mode: 448 });
|
|
270
|
+
const suffix = randomBytes(8).toString("hex");
|
|
271
|
+
const tmpPath = `${walletPath}.${process.pid}.${suffix}.tmp`;
|
|
272
|
+
await writeFile(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
273
|
+
await chmod(tmpPath, 384);
|
|
274
|
+
await rename(tmpPath, walletPath);
|
|
275
|
+
}
|
|
276
|
+
function getWalletConfigPath() {
|
|
277
|
+
return join2(homedir2(), ".keeperhub", "wallet.json");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/provision.ts
|
|
281
|
+
var TRAILING_SLASH = /\/$/;
|
|
282
|
+
var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
|
|
283
|
+
var ProvisionResponseInvalidError = class extends Error {
|
|
284
|
+
code = "PROVISION_RESPONSE_INVALID";
|
|
285
|
+
constructor(message) {
|
|
286
|
+
super(message);
|
|
287
|
+
this.name = "ProvisionResponseInvalidError";
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var ProvisionHttpError = class extends Error {
|
|
291
|
+
code = "PROVISION_HTTP_ERROR";
|
|
292
|
+
status;
|
|
293
|
+
body;
|
|
294
|
+
constructor(status, body) {
|
|
295
|
+
super(`provision failed: HTTP ${status}: ${body}`);
|
|
296
|
+
this.name = "ProvisionHttpError";
|
|
297
|
+
this.status = status;
|
|
298
|
+
this.body = body;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
function resolveBaseUrl(override) {
|
|
302
|
+
const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
|
|
303
|
+
return candidate.replace(TRAILING_SLASH, "");
|
|
304
|
+
}
|
|
305
|
+
function isNonEmptyString(value) {
|
|
306
|
+
return typeof value === "string" && value.length > 0;
|
|
307
|
+
}
|
|
308
|
+
function validateProvisionResponse(data) {
|
|
309
|
+
if (typeof data !== "object" || data === null) {
|
|
310
|
+
throw new ProvisionResponseInvalidError(
|
|
311
|
+
"provision response is not an object"
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const { subOrgId, walletAddress, hmacSecret } = data;
|
|
315
|
+
if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
|
|
316
|
+
throw new ProvisionResponseInvalidError(
|
|
317
|
+
"provision response missing subOrgId, walletAddress, or hmacSecret"
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
|
|
321
|
+
throw new ProvisionResponseInvalidError(
|
|
322
|
+
`provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
subOrgId,
|
|
327
|
+
walletAddress,
|
|
328
|
+
hmacSecret
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
async function provisionWallet(options = {}) {
|
|
332
|
+
const baseUrl = resolveBaseUrl(options.baseUrl);
|
|
333
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
334
|
+
const response = await fetchImpl(`${baseUrl}/api/agentic-wallet/provision`, {
|
|
335
|
+
method: "POST",
|
|
336
|
+
headers: { "content-type": "application/json" },
|
|
337
|
+
body: "{}",
|
|
338
|
+
signal: AbortSignal.timeout(3e4)
|
|
339
|
+
});
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
const text = await response.text();
|
|
342
|
+
throw new ProvisionHttpError(response.status, text);
|
|
343
|
+
}
|
|
344
|
+
const raw = await response.json();
|
|
345
|
+
const data = validateProvisionResponse(raw);
|
|
346
|
+
await writeWalletConfig(data);
|
|
347
|
+
return data;
|
|
348
|
+
}
|
|
349
|
+
|
|
149
350
|
// src/skill-install.ts
|
|
351
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
352
|
+
import {
|
|
353
|
+
chmod as chmod3,
|
|
354
|
+
copyFile,
|
|
355
|
+
mkdir as mkdir3,
|
|
356
|
+
readFile as readFile3,
|
|
357
|
+
rename as rename3,
|
|
358
|
+
unlink as unlink2,
|
|
359
|
+
writeFile as writeFile3
|
|
360
|
+
} from "fs/promises";
|
|
361
|
+
import { dirname as dirname5, join as join5 } from "path";
|
|
362
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
363
|
+
|
|
364
|
+
// src/mcp-register.ts
|
|
365
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
366
|
+
import {
|
|
367
|
+
chmod as chmod2,
|
|
368
|
+
mkdir as mkdir2,
|
|
369
|
+
readFile as readFile2,
|
|
370
|
+
rename as rename2,
|
|
371
|
+
unlink,
|
|
372
|
+
writeFile as writeFile2
|
|
373
|
+
} from "fs/promises";
|
|
374
|
+
import { homedir as homedir3 } from "os";
|
|
375
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
376
|
+
|
|
377
|
+
// src/runtime-detect.ts
|
|
150
378
|
import { execFileSync } from "child_process";
|
|
151
379
|
import { readFileSync } from "fs";
|
|
152
|
-
import {
|
|
153
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
380
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
154
381
|
import { fileURLToPath } from "url";
|
|
155
|
-
var HOOK_BIN = "keeperhub-wallet-hook";
|
|
156
|
-
var HOOK_COMMAND_BARE = HOOK_BIN;
|
|
157
382
|
var PACKAGE_NAME = "@keeperhub/wallet";
|
|
158
383
|
function readPackageVersion() {
|
|
159
384
|
try {
|
|
160
|
-
const here =
|
|
161
|
-
const pkgPath =
|
|
385
|
+
const here = dirname3(fileURLToPath(import.meta.url));
|
|
386
|
+
const pkgPath = join3(here, "..", "package.json");
|
|
162
387
|
const raw = readFileSync(pkgPath, "utf-8");
|
|
163
388
|
const parsed = JSON.parse(raw);
|
|
164
389
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
@@ -168,9 +393,6 @@ function readPackageVersion() {
|
|
|
168
393
|
}
|
|
169
394
|
return "latest";
|
|
170
395
|
}
|
|
171
|
-
function buildNpxCommand(version) {
|
|
172
|
-
return `npx -y -p ${PACKAGE_NAME}@${version} ${HOOK_BIN}`;
|
|
173
|
-
}
|
|
174
396
|
function isNpxExecution() {
|
|
175
397
|
const execPath = process.env.npm_execpath;
|
|
176
398
|
if (typeof execPath !== "string" || execPath.length === 0) {
|
|
@@ -198,6 +420,144 @@ function isPathUnderTransientCache(resolvedPath) {
|
|
|
198
420
|
}
|
|
199
421
|
return false;
|
|
200
422
|
}
|
|
423
|
+
function resolveBinCommand(binName) {
|
|
424
|
+
const version = readPackageVersion();
|
|
425
|
+
const npxArgs = ["-y", "-p", `${PACKAGE_NAME}@${version}`, binName];
|
|
426
|
+
const npxCommandString = `npx ${npxArgs.join(" ")}`;
|
|
427
|
+
if (isNpxExecution()) {
|
|
428
|
+
return {
|
|
429
|
+
commandString: npxCommandString,
|
|
430
|
+
command: "npx",
|
|
431
|
+
args: npxArgs
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
const resolved = execFileSync("/bin/sh", ["-c", `command -v ${binName}`], {
|
|
436
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
437
|
+
}).toString().trim();
|
|
438
|
+
if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
|
|
439
|
+
return {
|
|
440
|
+
commandString: binName,
|
|
441
|
+
command: binName,
|
|
442
|
+
args: []
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
commandString: npxCommandString,
|
|
449
|
+
command: "npx",
|
|
450
|
+
args: npxArgs
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/mcp-register.ts
|
|
455
|
+
var MCP_BIN = "keeperhub-wallet-mcp";
|
|
456
|
+
var MCP_SERVER_NAME = "keeperhub-wallet";
|
|
457
|
+
function resolveMcpCommand() {
|
|
458
|
+
const envOverride = process.env.KEEPERHUB_WALLET_MCP_COMMAND;
|
|
459
|
+
if (envOverride && envOverride.length > 0) {
|
|
460
|
+
const parts = envOverride.trim().split(/\s+/);
|
|
461
|
+
const head = parts[0] ?? envOverride;
|
|
462
|
+
return { command: head, args: parts.slice(1) };
|
|
463
|
+
}
|
|
464
|
+
const resolved = resolveBinCommand(MCP_BIN);
|
|
465
|
+
return { command: resolved.command, args: resolved.args };
|
|
466
|
+
}
|
|
467
|
+
function buildStandardEntry(cmd) {
|
|
468
|
+
const entry = {
|
|
469
|
+
command: cmd.command,
|
|
470
|
+
args: cmd.args
|
|
471
|
+
};
|
|
472
|
+
if (cmd.env && Object.keys(cmd.env).length > 0) {
|
|
473
|
+
entry.env = cmd.env;
|
|
474
|
+
}
|
|
475
|
+
return entry;
|
|
476
|
+
}
|
|
477
|
+
function buildOpencodeEntry(cmd) {
|
|
478
|
+
return {
|
|
479
|
+
type: "local",
|
|
480
|
+
command: [cmd.command, ...cmd.args],
|
|
481
|
+
enabled: true,
|
|
482
|
+
environment: cmd.env ?? {}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
async function readJsonOrEmpty(path) {
|
|
486
|
+
let raw = null;
|
|
487
|
+
try {
|
|
488
|
+
raw = await readFile2(path, "utf-8");
|
|
489
|
+
} catch (err) {
|
|
490
|
+
if (err.code !== "ENOENT") {
|
|
491
|
+
throw err;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (raw === null) {
|
|
495
|
+
return {};
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
return JSON.parse(raw);
|
|
499
|
+
} catch {
|
|
500
|
+
throw new Error(
|
|
501
|
+
`MCP config at ${path} is not valid JSON; aborting MCP registration`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async function writeJsonAtomic(path, payload) {
|
|
506
|
+
await mkdir2(dirname4(path), { recursive: true, mode: 448 });
|
|
507
|
+
const suffix = randomBytes2(8).toString("hex");
|
|
508
|
+
const tmpPath = `${path}.${process.pid}.${suffix}.tmp`;
|
|
509
|
+
try {
|
|
510
|
+
await writeFile2(tmpPath, payload, { mode: 384 });
|
|
511
|
+
await chmod2(tmpPath, 384);
|
|
512
|
+
await rename2(tmpPath, path);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
await unlink(tmpPath).catch(() => {
|
|
515
|
+
});
|
|
516
|
+
throw err;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async function writeStandardMcp(path, entry) {
|
|
520
|
+
const config = await readJsonOrEmpty(path);
|
|
521
|
+
const servers = typeof config.mcpServers === "object" && config.mcpServers !== null ? config.mcpServers : {};
|
|
522
|
+
servers[MCP_SERVER_NAME] = entry;
|
|
523
|
+
config.mcpServers = servers;
|
|
524
|
+
const payload = `${JSON.stringify(config, null, 2)}
|
|
525
|
+
`;
|
|
526
|
+
await writeJsonAtomic(path, payload);
|
|
527
|
+
}
|
|
528
|
+
async function writeOpencodeMcp(path, entry) {
|
|
529
|
+
const config = await readJsonOrEmpty(path);
|
|
530
|
+
const servers = typeof config.mcp === "object" && config.mcp !== null ? config.mcp : {};
|
|
531
|
+
servers[MCP_SERVER_NAME] = entry;
|
|
532
|
+
config.mcp = servers;
|
|
533
|
+
const payload = `${JSON.stringify(config, null, 2)}
|
|
534
|
+
`;
|
|
535
|
+
await writeJsonAtomic(path, payload);
|
|
536
|
+
}
|
|
537
|
+
async function registerMcpServer(target, options = {}) {
|
|
538
|
+
if (target.mcpSupport === "notice") {
|
|
539
|
+
throw new Error(
|
|
540
|
+
`agent ${target.agent} does not support auto-registered MCP servers; surface a notice instead`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
if (!target.mcpConfigRel) {
|
|
544
|
+
throw new Error(
|
|
545
|
+
`agent ${target.agent} has mcpSupport=${target.mcpSupport} but no mcpConfigRel path`
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
const home = options.homeOverride ?? homedir3();
|
|
549
|
+
const path = join4(home, ...target.mcpConfigRel);
|
|
550
|
+
const cmd = options.command ?? resolveMcpCommand();
|
|
551
|
+
if (target.mcpSupport === "opencode") {
|
|
552
|
+
await writeOpencodeMcp(path, buildOpencodeEntry(cmd));
|
|
553
|
+
} else {
|
|
554
|
+
await writeStandardMcp(path, buildStandardEntry(cmd));
|
|
555
|
+
}
|
|
556
|
+
return { path, name: MCP_SERVER_NAME };
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/skill-install.ts
|
|
560
|
+
var HOOK_BIN = "keeperhub-wallet-hook";
|
|
201
561
|
var KEEPERHUB_HOOK_MARKER = HOOK_BIN;
|
|
202
562
|
function filterKeeperhubHooksFromEntry(entry) {
|
|
203
563
|
if (typeof entry !== "object" || entry === null) {
|
|
@@ -224,19 +584,7 @@ function resolveHookCommand() {
|
|
|
224
584
|
if (envOverride && envOverride.length > 0) {
|
|
225
585
|
return envOverride;
|
|
226
586
|
}
|
|
227
|
-
|
|
228
|
-
return buildNpxCommand(readPackageVersion());
|
|
229
|
-
}
|
|
230
|
-
try {
|
|
231
|
-
const resolved = execFileSync("/bin/sh", ["-c", `command -v ${HOOK_BIN}`], {
|
|
232
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
233
|
-
}).toString().trim();
|
|
234
|
-
if (resolved.length > 0 && !isPathUnderTransientCache(resolved)) {
|
|
235
|
-
return HOOK_COMMAND_BARE;
|
|
236
|
-
}
|
|
237
|
-
} catch {
|
|
238
|
-
}
|
|
239
|
-
return buildNpxCommand(readPackageVersion());
|
|
587
|
+
return resolveBinCommand(HOOK_BIN).commandString;
|
|
240
588
|
}
|
|
241
589
|
function buildKeeperhubEntry(command) {
|
|
242
590
|
return {
|
|
@@ -245,8 +593,8 @@ function buildKeeperhubEntry(command) {
|
|
|
245
593
|
};
|
|
246
594
|
}
|
|
247
595
|
function resolveDefaultSkillSource() {
|
|
248
|
-
const here =
|
|
249
|
-
return
|
|
596
|
+
const here = dirname5(fileURLToPath2(import.meta.url));
|
|
597
|
+
return join5(here, "..", "skill", "keeperhub-wallet.skill.md");
|
|
250
598
|
}
|
|
251
599
|
function defaultNotice(msg) {
|
|
252
600
|
process.stderr.write(`${msg}
|
|
@@ -256,7 +604,7 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
|
|
|
256
604
|
const command = options.hookCommand ?? resolveHookCommand();
|
|
257
605
|
let raw = null;
|
|
258
606
|
try {
|
|
259
|
-
raw = await
|
|
607
|
+
raw = await readFile3(settingsPath, "utf-8");
|
|
260
608
|
} catch (err) {
|
|
261
609
|
if (err.code !== "ENOENT") {
|
|
262
610
|
throw err;
|
|
@@ -284,166 +632,136 @@ async function registerClaudeCodeHook(settingsPath, options = {}) {
|
|
|
284
632
|
filtered.push(buildKeeperhubEntry(command));
|
|
285
633
|
hooks.PreToolUse = filtered;
|
|
286
634
|
config.hooks = hooks;
|
|
287
|
-
await
|
|
635
|
+
await mkdir3(dirname5(settingsPath), { recursive: true, mode: 448 });
|
|
288
636
|
const payload = `${JSON.stringify(config, null, 2)}
|
|
289
637
|
`;
|
|
290
|
-
|
|
291
|
-
|
|
638
|
+
const suffix = randomBytes3(8).toString("hex");
|
|
639
|
+
const tmpPath = `${settingsPath}.${process.pid}.${suffix}.tmp`;
|
|
640
|
+
try {
|
|
641
|
+
await writeFile3(tmpPath, payload, { mode: 384 });
|
|
642
|
+
await chmod3(tmpPath, 384);
|
|
643
|
+
await rename3(tmpPath, settingsPath);
|
|
644
|
+
} catch (err) {
|
|
645
|
+
await unlink2(tmpPath).catch(() => {
|
|
646
|
+
});
|
|
647
|
+
throw err;
|
|
648
|
+
}
|
|
292
649
|
}
|
|
293
650
|
async function writeSkillToAgent(agent, skillSource) {
|
|
294
|
-
await
|
|
295
|
-
const target =
|
|
651
|
+
await mkdir3(agent.skillsDir, { recursive: true, mode: 493 });
|
|
652
|
+
const target = join5(agent.skillsDir, "keeperhub-wallet.skill.md");
|
|
296
653
|
await copyFile(skillSource, target);
|
|
297
|
-
await
|
|
654
|
+
await chmod3(target, 420);
|
|
298
655
|
return { agent: agent.agent, path: target, status: "written" };
|
|
299
656
|
}
|
|
300
|
-
function
|
|
657
|
+
function buildHookNoticeMessage(agent, command) {
|
|
301
658
|
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}`;
|
|
302
659
|
}
|
|
660
|
+
function buildMcpNoticeMessage(agent, command) {
|
|
661
|
+
const cmd = [command.command, ...command.args].join(" ");
|
|
662
|
+
return `${agent.agent} does not support auto-registered MCP servers; add an entry named \`keeperhub-wallet\` running \`${cmd}\` to your MCP config manually`;
|
|
663
|
+
}
|
|
303
664
|
async function installSkill(options = {}) {
|
|
304
665
|
const agents = detectAgents(options.homeOverride);
|
|
305
666
|
const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();
|
|
306
667
|
const onNotice = options.onNotice ?? defaultNotice;
|
|
307
668
|
const hookCommand = options.hookCommand ?? resolveHookCommand();
|
|
669
|
+
const mcpCommand = options.mcpCommand ?? resolveMcpCommand();
|
|
308
670
|
const skillWrites = [];
|
|
309
671
|
const hookRegistrations = [];
|
|
672
|
+
const mcpRegistrations = [];
|
|
310
673
|
for (const agent of agents) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
674
|
+
try {
|
|
675
|
+
const write = await writeSkillToAgent(agent, skillSource);
|
|
676
|
+
skillWrites.push(write);
|
|
677
|
+
} catch (err) {
|
|
678
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
679
|
+
skillWrites.push({
|
|
316
680
|
agent: agent.agent,
|
|
317
|
-
|
|
681
|
+
path: "",
|
|
682
|
+
status: "skipped"
|
|
318
683
|
});
|
|
684
|
+
onNotice(`${agent.agent}: skill copy failed (${message})`);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (agent.hookSupport === "claude-code") {
|
|
688
|
+
try {
|
|
689
|
+
await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
|
|
690
|
+
hookRegistrations.push({
|
|
691
|
+
agent: agent.agent,
|
|
692
|
+
status: "registered"
|
|
693
|
+
});
|
|
694
|
+
} catch (err) {
|
|
695
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
696
|
+
hookRegistrations.push({
|
|
697
|
+
agent: agent.agent,
|
|
698
|
+
status: "failed",
|
|
699
|
+
message
|
|
700
|
+
});
|
|
701
|
+
onNotice(`${agent.agent}: hook registration failed (${message})`);
|
|
702
|
+
}
|
|
319
703
|
} else {
|
|
320
|
-
const
|
|
704
|
+
const noticeMessage = buildHookNoticeMessage(agent, hookCommand);
|
|
321
705
|
hookRegistrations.push({
|
|
322
706
|
agent: agent.agent,
|
|
323
707
|
status: "notice",
|
|
324
|
-
message
|
|
708
|
+
message: noticeMessage
|
|
325
709
|
});
|
|
326
|
-
onNotice(
|
|
710
|
+
onNotice(noticeMessage);
|
|
327
711
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
async function readWalletConfig() {
|
|
357
|
-
const walletPath = join3(homedir2(), ".keeperhub", "wallet.json");
|
|
358
|
-
let raw;
|
|
359
|
-
try {
|
|
360
|
-
raw = await readFile2(walletPath, "utf-8");
|
|
361
|
-
} catch (err) {
|
|
362
|
-
if (err.code === "ENOENT") {
|
|
363
|
-
throw new WalletConfigMissingError();
|
|
712
|
+
if (agent.mcpSupport === "notice") {
|
|
713
|
+
const noticeMessage = buildMcpNoticeMessage(agent, mcpCommand);
|
|
714
|
+
mcpRegistrations.push({
|
|
715
|
+
agent: agent.agent,
|
|
716
|
+
status: "notice",
|
|
717
|
+
message: noticeMessage
|
|
718
|
+
});
|
|
719
|
+
onNotice(noticeMessage);
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
try {
|
|
723
|
+
const mcpResult = await registerMcpServer(agent, {
|
|
724
|
+
homeOverride: options.homeOverride,
|
|
725
|
+
command: mcpCommand
|
|
726
|
+
});
|
|
727
|
+
mcpRegistrations.push({
|
|
728
|
+
agent: agent.agent,
|
|
729
|
+
status: "registered",
|
|
730
|
+
path: mcpResult.path
|
|
731
|
+
});
|
|
732
|
+
} catch (err) {
|
|
733
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
734
|
+
mcpRegistrations.push({
|
|
735
|
+
agent: agent.agent,
|
|
736
|
+
status: "failed",
|
|
737
|
+
message
|
|
738
|
+
});
|
|
739
|
+
onNotice(`${agent.agent}: MCP registration failed (${message})`);
|
|
364
740
|
}
|
|
365
|
-
throw err;
|
|
366
|
-
}
|
|
367
|
-
const parsed = JSON.parse(raw);
|
|
368
|
-
if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
|
|
369
|
-
throw new Error(`Malformed wallet.json at ${walletPath}`);
|
|
370
741
|
}
|
|
371
|
-
return
|
|
372
|
-
}
|
|
373
|
-
async function writeWalletConfig(config) {
|
|
374
|
-
const walletPath = join3(homedir2(), ".keeperhub", "wallet.json");
|
|
375
|
-
await mkdir2(dirname3(walletPath), { recursive: true, mode: 448 });
|
|
376
|
-
await writeFile2(walletPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
377
|
-
await chmod2(walletPath, 384);
|
|
378
|
-
}
|
|
379
|
-
function getWalletConfigPath() {
|
|
380
|
-
return join3(homedir2(), ".keeperhub", "wallet.json");
|
|
742
|
+
return { skillWrites, hookRegistrations, mcpRegistrations };
|
|
381
743
|
}
|
|
382
744
|
|
|
383
745
|
// src/cli.ts
|
|
384
|
-
var TRAILING_SLASH = /\/$/;
|
|
385
|
-
var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
|
|
386
|
-
function resolveBaseUrl(override) {
|
|
387
|
-
const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
|
|
388
|
-
return candidate.replace(TRAILING_SLASH, "");
|
|
389
|
-
}
|
|
390
|
-
function isNonEmptyString(value) {
|
|
391
|
-
return typeof value === "string" && value.length > 0;
|
|
392
|
-
}
|
|
393
|
-
function provisionInvalidError(message) {
|
|
394
|
-
const err = new Error(message);
|
|
395
|
-
err.code = "PROVISION_RESPONSE_INVALID";
|
|
396
|
-
return err;
|
|
397
|
-
}
|
|
398
|
-
function validateProvisionResponse(data) {
|
|
399
|
-
if (typeof data !== "object" || data === null) {
|
|
400
|
-
throw provisionInvalidError("provision response is not an object");
|
|
401
|
-
}
|
|
402
|
-
const { subOrgId, walletAddress, hmacSecret } = data;
|
|
403
|
-
if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
|
|
404
|
-
throw provisionInvalidError(
|
|
405
|
-
"provision response missing subOrgId, walletAddress, or hmacSecret"
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
|
|
409
|
-
throw provisionInvalidError(
|
|
410
|
-
`provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
|
|
411
|
-
);
|
|
412
|
-
}
|
|
413
|
-
return {
|
|
414
|
-
subOrgId,
|
|
415
|
-
walletAddress,
|
|
416
|
-
hmacSecret
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
746
|
async function cmdAdd(opts = {}) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
headers: { "content-type": "application/json" },
|
|
424
|
-
body: "{}"
|
|
425
|
-
});
|
|
426
|
-
if (!response.ok) {
|
|
427
|
-
const text = await response.text();
|
|
428
|
-
process.stderr.write(
|
|
429
|
-
`[keeperhub-wallet] provision failed: HTTP ${response.status}: ${text}
|
|
430
|
-
`
|
|
431
|
-
);
|
|
432
|
-
process.exit(1);
|
|
433
|
-
}
|
|
434
|
-
const raw = await response.json();
|
|
435
|
-
const data = validateProvisionResponse(raw);
|
|
436
|
-
await writeWalletConfig({
|
|
437
|
-
subOrgId: data.subOrgId,
|
|
438
|
-
walletAddress: data.walletAddress,
|
|
439
|
-
hmacSecret: data.hmacSecret
|
|
440
|
-
});
|
|
441
|
-
process.stdout.write(`subOrgId: ${data.subOrgId}
|
|
747
|
+
try {
|
|
748
|
+
const data = await provisionWallet({ baseUrl: opts.baseUrl });
|
|
749
|
+
process.stdout.write(`subOrgId: ${data.subOrgId}
|
|
442
750
|
`);
|
|
443
|
-
|
|
751
|
+
process.stdout.write(`walletAddress: ${data.walletAddress}
|
|
444
752
|
`);
|
|
445
|
-
|
|
753
|
+
process.stdout.write(`config written to ${getWalletConfigPath()}
|
|
446
754
|
`);
|
|
755
|
+
} catch (err) {
|
|
756
|
+
if (err instanceof ProvisionHttpError) {
|
|
757
|
+
process.stderr.write(
|
|
758
|
+
`[keeperhub-wallet] provision failed: HTTP ${err.status}: ${err.body}
|
|
759
|
+
`
|
|
760
|
+
);
|
|
761
|
+
process.exit(1);
|
|
762
|
+
}
|
|
763
|
+
throw err;
|
|
764
|
+
}
|
|
447
765
|
}
|
|
448
766
|
async function cmdFund() {
|
|
449
767
|
const wallet = await readWalletConfig();
|
|
@@ -470,6 +788,58 @@ async function cmdInfo() {
|
|
|
470
788
|
process.stdout.write(`walletAddress: ${wallet.walletAddress}
|
|
471
789
|
`);
|
|
472
790
|
}
|
|
791
|
+
var FEEDBACK_DEFAULT_BASE_URL = "https://app.keeperhub.com";
|
|
792
|
+
async function cmdFeedback(opts) {
|
|
793
|
+
const wallet = await readWalletConfig();
|
|
794
|
+
const baseUrl = (opts.baseUrl ?? FEEDBACK_DEFAULT_BASE_URL).replace(
|
|
795
|
+
/\/$/,
|
|
796
|
+
""
|
|
797
|
+
);
|
|
798
|
+
const path = "/api/agentic-wallet/feedback";
|
|
799
|
+
const body = {
|
|
800
|
+
executionId: opts.executionId,
|
|
801
|
+
value: Number.parseInt(opts.value, 10),
|
|
802
|
+
valueDecimals: Number.parseInt(opts.decimals ?? "0", 10)
|
|
803
|
+
};
|
|
804
|
+
if (opts.comment !== void 0) {
|
|
805
|
+
body.comment = opts.comment;
|
|
806
|
+
}
|
|
807
|
+
if (opts.agentId !== void 0) {
|
|
808
|
+
body.agentId = opts.agentId;
|
|
809
|
+
}
|
|
810
|
+
if (opts.chainId !== void 0) {
|
|
811
|
+
body.agentChainId = Number.parseInt(opts.chainId, 10);
|
|
812
|
+
}
|
|
813
|
+
const bodyJson = JSON.stringify(body);
|
|
814
|
+
const headers = buildHmacHeaders(
|
|
815
|
+
wallet.hmacSecret,
|
|
816
|
+
"POST",
|
|
817
|
+
path,
|
|
818
|
+
wallet.subOrgId,
|
|
819
|
+
bodyJson
|
|
820
|
+
);
|
|
821
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
822
|
+
method: "POST",
|
|
823
|
+
headers: {
|
|
824
|
+
"content-type": "application/json",
|
|
825
|
+
...headers
|
|
826
|
+
},
|
|
827
|
+
body: bodyJson
|
|
828
|
+
});
|
|
829
|
+
const text = await response.text();
|
|
830
|
+
if (!response.ok) {
|
|
831
|
+
process.stderr.write(`HTTP ${response.status}: ${text}
|
|
832
|
+
`);
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
const parsed = JSON.parse(text);
|
|
836
|
+
process.stdout.write(`feedbackId: ${parsed.feedbackId ?? ""}
|
|
837
|
+
`);
|
|
838
|
+
process.stdout.write(`txHash: ${parsed.txHash ?? ""}
|
|
839
|
+
`);
|
|
840
|
+
process.stdout.write(`publicUrl: ${parsed.publicUrl ?? ""}
|
|
841
|
+
`);
|
|
842
|
+
}
|
|
473
843
|
async function runCli(argv = process.argv) {
|
|
474
844
|
const program = new Command();
|
|
475
845
|
program.name("keeperhub-wallet").description(
|
|
@@ -489,6 +859,27 @@ async function runCli(argv = process.argv) {
|
|
|
489
859
|
program.command("info").description("Print subOrgId and walletAddress from local config").action(async () => {
|
|
490
860
|
await cmdInfo();
|
|
491
861
|
});
|
|
862
|
+
program.command("feedback").description(
|
|
863
|
+
"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."
|
|
864
|
+
).requiredOption(
|
|
865
|
+
"--execution-id <id>",
|
|
866
|
+
"workflow execution id to leave feedback for"
|
|
867
|
+
).requiredOption(
|
|
868
|
+
"--value <int>",
|
|
869
|
+
"raw int128 rating value (e.g. 5 with --decimals 0 for a 5-star rating)"
|
|
870
|
+
).option(
|
|
871
|
+
"--decimals <int>",
|
|
872
|
+
"decimals for value (0..18); 0 for integer scores, 1 for 0.1-step",
|
|
873
|
+
"0"
|
|
874
|
+
).option("--comment <text>", "optional plaintext comment").option(
|
|
875
|
+
"--agent-id <id>",
|
|
876
|
+
"rated agent NFT id (uint256 decimal); defaults to KeeperHub agent 31875"
|
|
877
|
+
).option(
|
|
878
|
+
"--chain-id <int>",
|
|
879
|
+
"agent chain id; defaults to 1 (Ethereum mainnet, only chain supported today)"
|
|
880
|
+
).option("--base-url <url>", "KeeperHub API base URL").action(async (opts) => {
|
|
881
|
+
await cmdFeedback(opts);
|
|
882
|
+
});
|
|
492
883
|
program.command("skill").description(
|
|
493
884
|
"Install the KeeperHub skill file into detected agent directories"
|
|
494
885
|
).addCommand(
|
|
@@ -511,6 +902,29 @@ async function runCli(argv = process.argv) {
|
|
|
511
902
|
} else if (reg.status === "notice") {
|
|
512
903
|
process.stderr.write(
|
|
513
904
|
`notice: ${reg.agent} -> ${reg.message ?? ""}
|
|
905
|
+
`
|
|
906
|
+
);
|
|
907
|
+
} else if (reg.status === "failed") {
|
|
908
|
+
process.stderr.write(
|
|
909
|
+
`hook: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
|
|
910
|
+
`
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
for (const reg of result.mcpRegistrations) {
|
|
915
|
+
if (reg.status === "registered") {
|
|
916
|
+
process.stdout.write(
|
|
917
|
+
`mcp: ${reg.agent} -> registered at ${reg.path ?? "(unknown path)"}
|
|
918
|
+
`
|
|
919
|
+
);
|
|
920
|
+
} else if (reg.status === "notice") {
|
|
921
|
+
process.stderr.write(
|
|
922
|
+
`notice: ${reg.agent} mcp -> ${reg.message ?? ""}
|
|
923
|
+
`
|
|
924
|
+
);
|
|
925
|
+
} else if (reg.status === "failed") {
|
|
926
|
+
process.stderr.write(
|
|
927
|
+
`mcp: ${reg.agent} -> FAILED (${reg.message ?? "unknown error"})
|
|
514
928
|
`
|
|
515
929
|
);
|
|
516
930
|
}
|
|
@@ -538,34 +952,6 @@ async function runCli(argv = process.argv) {
|
|
|
538
952
|
}
|
|
539
953
|
}
|
|
540
954
|
|
|
541
|
-
// src/hmac.ts
|
|
542
|
-
import { createHash, createHmac } from "crypto";
|
|
543
|
-
function computeSignature(secret, method, path, subOrgId, body, timestamp) {
|
|
544
|
-
const bodyDigest = createHash("sha256").update(body).digest("hex");
|
|
545
|
-
const signingString = `${method}
|
|
546
|
-
${path}
|
|
547
|
-
${subOrgId}
|
|
548
|
-
${bodyDigest}
|
|
549
|
-
${timestamp}`;
|
|
550
|
-
return createHmac("sha256", secret).update(signingString).digest("hex");
|
|
551
|
-
}
|
|
552
|
-
function buildHmacHeaders(secret, method, path, subOrgId, body) {
|
|
553
|
-
const timestamp = String(Math.floor(Date.now() / 1e3));
|
|
554
|
-
const signature = computeSignature(
|
|
555
|
-
secret,
|
|
556
|
-
method,
|
|
557
|
-
path,
|
|
558
|
-
subOrgId,
|
|
559
|
-
body,
|
|
560
|
-
timestamp
|
|
561
|
-
);
|
|
562
|
-
return {
|
|
563
|
-
"X-KH-Sub-Org": subOrgId,
|
|
564
|
-
"X-KH-Timestamp": timestamp,
|
|
565
|
-
"X-KH-Signature": signature
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
|
|
569
955
|
// src/client.ts
|
|
570
956
|
var TRAILING_SLASH2 = /\/$/;
|
|
571
957
|
function defaultCodeForStatus(status) {
|
|
@@ -639,9 +1025,9 @@ var KeeperHubClient = class {
|
|
|
639
1025
|
};
|
|
640
1026
|
|
|
641
1027
|
// src/safety-config.ts
|
|
642
|
-
import { chmod as
|
|
643
|
-
import { homedir as
|
|
644
|
-
import { dirname as
|
|
1028
|
+
import { chmod as chmod4, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
1029
|
+
import { homedir as homedir4 } from "os";
|
|
1030
|
+
import { dirname as dirname6, join as join6 } from "path";
|
|
645
1031
|
var DEFAULT_SAFETY_CONFIG = {
|
|
646
1032
|
auto_approve_max_usd: 5,
|
|
647
1033
|
ask_threshold_usd: 50,
|
|
@@ -654,20 +1040,20 @@ var DEFAULT_SAFETY_CONFIG = {
|
|
|
654
1040
|
]
|
|
655
1041
|
};
|
|
656
1042
|
function getSafetyPath() {
|
|
657
|
-
return
|
|
1043
|
+
return join6(homedir4(), ".keeperhub", "safety.json");
|
|
658
1044
|
}
|
|
659
1045
|
async function loadSafetyConfig() {
|
|
660
1046
|
const path = getSafetyPath();
|
|
661
1047
|
let raw;
|
|
662
1048
|
try {
|
|
663
|
-
raw = await
|
|
1049
|
+
raw = await readFile4(path, "utf-8");
|
|
664
1050
|
} catch (err) {
|
|
665
1051
|
if (err.code === "ENOENT") {
|
|
666
|
-
await
|
|
667
|
-
await
|
|
1052
|
+
await mkdir4(dirname6(path), { recursive: true, mode: 448 });
|
|
1053
|
+
await writeFile4(path, JSON.stringify(DEFAULT_SAFETY_CONFIG, null, 2), {
|
|
668
1054
|
mode: 420
|
|
669
1055
|
});
|
|
670
|
-
await
|
|
1056
|
+
await chmod4(path, 420);
|
|
671
1057
|
return DEFAULT_SAFETY_CONFIG;
|
|
672
1058
|
}
|
|
673
1059
|
throw err;
|
|
@@ -864,7 +1250,7 @@ function parseMppChallenge(response) {
|
|
|
864
1250
|
}
|
|
865
1251
|
|
|
866
1252
|
// src/payment-signer.ts
|
|
867
|
-
import { randomBytes } from "crypto";
|
|
1253
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
868
1254
|
|
|
869
1255
|
// src/workflow-slug.ts
|
|
870
1256
|
var KEEPERHUB_WORKFLOW_RE = /\/api\/mcp\/workflows\/([a-zA-Z0-9_-]+)\/call(?:\/?)(?:\?|$|#)/;
|
|
@@ -1044,7 +1430,7 @@ function createPaymentSigner(opts = {}) {
|
|
|
1044
1430
|
const now = Math.floor(Date.now() / 1e3);
|
|
1045
1431
|
const validAfter = now - VALID_AFTER_PAST_SLACK_SECONDS;
|
|
1046
1432
|
const validBefore = now + accept.maxTimeoutSeconds;
|
|
1047
|
-
const nonce = `0x${
|
|
1433
|
+
const nonce = `0x${randomBytes4(NONCE_BYTES).toString("hex")}`;
|
|
1048
1434
|
const client = clientFactory(wallet);
|
|
1049
1435
|
const signature = await signOrPoll(client, {
|
|
1050
1436
|
chain: "base",
|