@keeperhub/wallet 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2026 KeeperHub
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+ The full Apache License 2.0 text is available at the URL above.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # @keeperhub/wallet
2
+
3
+ Agentic wallet for AI agents. Auto-pays x402 (Base USDC) and MPP (Tempo USDC.e) 402 responses with server-side Turnkey custody. Ships a three-tier PreToolUse safety hook (auto/ask/block).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npx skills add keeperhub/agentic-wallet-skills
9
+ npx @keeperhub/wallet add
10
+ ```
11
+
12
+ ## First use
13
+
14
+ ```ts
15
+ import { paymentSigner } from "@keeperhub/wallet";
16
+ const paid = await paymentSigner.pay(await fetch(url));
17
+ ```
18
+
19
+ Full walkthrough (safety hooks, approval flow, comparison with agentcash + Coinbase): https://docs.keeperhub.com/ai-tools/agentic-wallet
20
+
21
+ ## License
22
+
23
+ Apache-2.0
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { runHookCli } from "../dist/hook-entrypoint.js";
3
+
4
+ runHookCli().catch((err) => {
5
+ process.stderr.write(
6
+ `[keeperhub-wallet] hook crashed: ${err?.message ?? err}\n`
7
+ );
8
+ process.exit(2);
9
+ });
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "../dist/cli.js";
3
+
4
+ runCli().catch((err) => {
5
+ process.stderr.write(`[keeperhub-wallet] fatal: ${err?.message ?? err}\n`);
6
+ process.exit(1);
7
+ });
package/dist/cli.cjs ADDED
@@ -0,0 +1,636 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/cli.ts
21
+ var cli_exports = {};
22
+ __export(cli_exports, {
23
+ runCli: () => runCli
24
+ });
25
+ module.exports = __toCommonJS(cli_exports);
26
+ var import_commander = require("commander");
27
+
28
+ // src/balance.ts
29
+ var import_viem2 = require("viem");
30
+
31
+ // src/chains.ts
32
+ var import_viem = require("viem");
33
+ var import_chains = require("viem/chains");
34
+ var tempo = (0, import_viem.defineChain)({
35
+ id: 4217,
36
+ name: "Tempo",
37
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
38
+ rpcUrls: {
39
+ default: {
40
+ http: [process.env.TEMPO_RPC_URL ?? "https://rpc.tempo.xyz"]
41
+ }
42
+ },
43
+ blockExplorers: {
44
+ default: { name: "Tempo Explorer", url: "https://explorer.tempo.xyz" }
45
+ }
46
+ });
47
+ var BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
48
+ var TEMPO_USDC_E = "0x20c000000000000000000000b9537d11c60e8b50";
49
+
50
+ // src/hmac.ts
51
+ var import_node_crypto = require("crypto");
52
+ function computeSignature(secret, method, path, subOrgId, body, timestamp) {
53
+ const bodyDigest = (0, import_node_crypto.createHash)("sha256").update(body).digest("hex");
54
+ const signingString = `${method}
55
+ ${path}
56
+ ${subOrgId}
57
+ ${bodyDigest}
58
+ ${timestamp}`;
59
+ return (0, import_node_crypto.createHmac)("sha256", secret).update(signingString).digest("hex");
60
+ }
61
+ function buildHmacHeaders(secret, method, path, subOrgId, body) {
62
+ const timestamp = String(Math.floor(Date.now() / 1e3));
63
+ const signature = computeSignature(
64
+ secret,
65
+ method,
66
+ path,
67
+ subOrgId,
68
+ body,
69
+ timestamp
70
+ );
71
+ return {
72
+ "X-KH-Sub-Org": subOrgId,
73
+ "X-KH-Timestamp": timestamp,
74
+ "X-KH-Signature": signature
75
+ };
76
+ }
77
+
78
+ // src/types.ts
79
+ var KeeperHubError = class extends Error {
80
+ code;
81
+ constructor(code, message) {
82
+ super(message);
83
+ this.name = "KeeperHubError";
84
+ this.code = code;
85
+ }
86
+ };
87
+ var WalletConfigMissingError = class extends Error {
88
+ constructor() {
89
+ super(
90
+ "Wallet config not found at ~/.keeperhub/wallet.json. Run `npx @keeperhub/wallet add` to provision."
91
+ );
92
+ this.name = "WalletConfigMissingError";
93
+ }
94
+ };
95
+
96
+ // src/client.ts
97
+ var TRAILING_SLASH = /\/$/;
98
+ function defaultCodeForStatus(status) {
99
+ if (status === 401) {
100
+ return "HMAC_INVALID";
101
+ }
102
+ if (status === 403) {
103
+ return "POLICY_BLOCKED";
104
+ }
105
+ if (status === 404) {
106
+ return "NOT_FOUND";
107
+ }
108
+ if (status === 502) {
109
+ return "TURNKEY_UPSTREAM";
110
+ }
111
+ return `HTTP_${status}`;
112
+ }
113
+ var KeeperHubClient = class {
114
+ baseUrl;
115
+ fetchImpl;
116
+ wallet;
117
+ constructor(wallet, opts = {}) {
118
+ this.wallet = wallet;
119
+ const envBase = process.env.KEEPERHUB_API_URL;
120
+ this.baseUrl = (opts.baseUrl ?? envBase ?? "https://app.keeperhub.com").replace(TRAILING_SLASH, "");
121
+ this.fetchImpl = opts.fetch ?? globalThis.fetch;
122
+ }
123
+ /**
124
+ * HMAC-signed POST/GET to any /api/agentic-wallet/* route except
125
+ * /provision. Path MUST start with a leading slash. Body is
126
+ * JSON.stringify'd (or the empty string for GET).
127
+ *
128
+ * Error mapping: non-2xx/non-202 surface as `KeeperHubError(code,
129
+ * message)` where `code` is the server-supplied field or the default
130
+ * taxonomy (`HMAC_INVALID`, `POLICY_BLOCKED`, `NOT_FOUND`,
131
+ * `TURNKEY_UPSTREAM`, `HTTP_<status>`). 202 ask-tier surfaces as an
132
+ * AskTierResponse envelope.
133
+ */
134
+ async request(method, path, body) {
135
+ const bodyStr = body === void 0 ? "" : JSON.stringify(body);
136
+ const hmacHeaders = buildHmacHeaders(
137
+ this.wallet.hmacSecret,
138
+ method,
139
+ path,
140
+ this.wallet.subOrgId,
141
+ bodyStr
142
+ );
143
+ const headers = method === "POST" ? { ...hmacHeaders, "content-type": "application/json" } : { ...hmacHeaders };
144
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
145
+ method,
146
+ headers,
147
+ body: method === "POST" ? bodyStr : void 0
148
+ });
149
+ if (response.status === 202) {
150
+ const data = await response.json();
151
+ return { _status: 202, approvalRequestId: data.approvalRequestId };
152
+ }
153
+ if (!response.ok) {
154
+ let code = "UNKNOWN";
155
+ let message = `HTTP ${response.status}`;
156
+ try {
157
+ const data = await response.json();
158
+ code = data.code ?? defaultCodeForStatus(response.status);
159
+ message = data.error ?? message;
160
+ } catch {
161
+ }
162
+ throw new KeeperHubError(code, message);
163
+ }
164
+ return await response.json();
165
+ }
166
+ };
167
+
168
+ // src/balance.ts
169
+ var USDC_DECIMALS = 6;
170
+ async function checkBalance(wallet, opts = {}) {
171
+ const baseClient = opts.baseClient ?? (0, import_viem2.createPublicClient)({
172
+ chain: import_chains.base,
173
+ transport: (0, import_viem2.http)()
174
+ });
175
+ const tempoClient = opts.tempoClient ?? (0, import_viem2.createPublicClient)({
176
+ chain: tempo,
177
+ transport: (0, import_viem2.http)()
178
+ });
179
+ const khClient = opts.khClient ?? new KeeperHubClient(wallet);
180
+ const [baseRaw, tempoRaw, credit] = await Promise.all([
181
+ baseClient.readContract({
182
+ address: BASE_USDC,
183
+ abi: import_viem2.erc20Abi,
184
+ functionName: "balanceOf",
185
+ args: [wallet.walletAddress]
186
+ }),
187
+ tempoClient.readContract({
188
+ address: TEMPO_USDC_E,
189
+ abi: import_viem2.erc20Abi,
190
+ functionName: "balanceOf",
191
+ args: [wallet.walletAddress]
192
+ }),
193
+ khClient.request("GET", "/api/agentic-wallet/credit")
194
+ ]);
195
+ if ("_status" in credit) {
196
+ throw new Error("Unexpected 202 response from /api/agentic-wallet/credit");
197
+ }
198
+ return {
199
+ base: {
200
+ chain: "base",
201
+ token: "USDC",
202
+ amount: (0, import_viem2.formatUnits)(baseRaw, USDC_DECIMALS),
203
+ address: wallet.walletAddress
204
+ },
205
+ tempo: {
206
+ chain: "tempo",
207
+ token: "USDC.e",
208
+ amount: (0, import_viem2.formatUnits)(tempoRaw, USDC_DECIMALS),
209
+ address: wallet.walletAddress
210
+ },
211
+ offChainCredit: {
212
+ amount: credit.amount,
213
+ currency: "USD"
214
+ }
215
+ };
216
+ }
217
+
218
+ // src/fund.ts
219
+ var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
220
+ var COINBASE_HOST = "pay.coinbase.com";
221
+ var COINBASE_PATH = "/buy/select-asset";
222
+ function fund(walletAddress) {
223
+ if (!EVM_ADDRESS_RE.test(walletAddress)) {
224
+ throw new Error(`Invalid EVM wallet address: ${walletAddress}`);
225
+ }
226
+ const params = new URLSearchParams({
227
+ defaultNetwork: "base",
228
+ defaultAsset: "USDC",
229
+ addresses: JSON.stringify({ [walletAddress]: ["base"] }),
230
+ presetCryptoAmount: "5"
231
+ });
232
+ const coinbaseOnrampUrl = `https://${COINBASE_HOST}${COINBASE_PATH}?${params.toString()}`;
233
+ const disclaimer = "If the Coinbase page does not pre-fill, paste your address manually. For Tempo USDC.e, transfer from an exchange or another wallet to the address above -- Onramp does not support Tempo directly. Coinbase sessionToken URLs are the 2025+ canonical form; legacy query-param URLs may drop prefill on some accounts.";
234
+ return {
235
+ coinbaseOnrampUrl,
236
+ tempoAddress: walletAddress,
237
+ disclaimer
238
+ };
239
+ }
240
+
241
+ // src/skill-install.ts
242
+ var import_promises = require("fs/promises");
243
+ var import_node_path2 = require("path");
244
+ var import_node_url = require("url");
245
+
246
+ // src/agent-detect.ts
247
+ var import_node_fs = require("fs");
248
+ var import_node_os = require("os");
249
+ var import_node_path = require("path");
250
+ var AGENT_SPECS = [
251
+ {
252
+ agent: "claude-code",
253
+ skillsRel: [".claude", "skills"],
254
+ settingsRel: [".claude", "settings.json"],
255
+ hookSupport: "claude-code"
256
+ },
257
+ {
258
+ agent: "cursor",
259
+ skillsRel: [".cursor", "skills"],
260
+ settingsRel: [".cursor", "settings.json"],
261
+ hookSupport: "notice"
262
+ },
263
+ {
264
+ agent: "cline",
265
+ skillsRel: [".cline", "skills"],
266
+ settingsRel: [".cline", "settings.json"],
267
+ hookSupport: "notice"
268
+ },
269
+ {
270
+ agent: "windsurf",
271
+ skillsRel: [".windsurf", "skills"],
272
+ settingsRel: [".windsurf", "settings.json"],
273
+ hookSupport: "notice"
274
+ },
275
+ {
276
+ agent: "opencode",
277
+ skillsRel: [".config", "opencode", "skills"],
278
+ settingsRel: [".config", "opencode", "settings.json"],
279
+ hookSupport: "notice"
280
+ }
281
+ ];
282
+ function detectAgents(homeOverride) {
283
+ const home = homeOverride ?? (0, import_node_os.homedir)();
284
+ const results = [];
285
+ for (const spec of AGENT_SPECS) {
286
+ const skillsDir = (0, import_node_path.join)(home, ...spec.skillsRel);
287
+ const settingsFile = (0, import_node_path.join)(home, ...spec.settingsRel);
288
+ if ((0, import_node_fs.existsSync)((0, import_node_path.dirname)(skillsDir))) {
289
+ results.push({
290
+ agent: spec.agent,
291
+ skillsDir,
292
+ settingsFile,
293
+ hookSupport: spec.hookSupport
294
+ });
295
+ }
296
+ }
297
+ return results;
298
+ }
299
+
300
+ // src/skill-install.ts
301
+ var HOOK_COMMAND = "keeperhub-wallet-hook";
302
+ var KEEPERHUB_HOOK_MARKER = "keeperhub-wallet-hook";
303
+ function buildKeeperhubEntry() {
304
+ return {
305
+ matcher: "*",
306
+ hooks: [{ type: "command", command: HOOK_COMMAND }]
307
+ };
308
+ }
309
+ function resolveDefaultSkillSource() {
310
+ const here = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(__filename));
311
+ return (0, import_node_path2.join)(here, "..", "skill", "keeperhub-wallet.skill.md");
312
+ }
313
+ function defaultNotice(msg) {
314
+ process.stderr.write(`${msg}
315
+ `);
316
+ }
317
+ async function registerClaudeCodeHook(settingsPath) {
318
+ let raw = null;
319
+ try {
320
+ raw = await (0, import_promises.readFile)(settingsPath, "utf-8");
321
+ } catch (err) {
322
+ if (err.code !== "ENOENT") {
323
+ throw err;
324
+ }
325
+ }
326
+ let config = {};
327
+ if (raw !== null) {
328
+ try {
329
+ config = JSON.parse(raw);
330
+ } catch {
331
+ throw new Error(
332
+ `settings.json at ${settingsPath} is not valid JSON; aborting hook registration`
333
+ );
334
+ }
335
+ }
336
+ const hooks = typeof config.hooks === "object" && config.hooks !== null ? config.hooks : {};
337
+ const existingPreToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
338
+ const filtered = [];
339
+ for (const entry of existingPreToolUse) {
340
+ const serialised = JSON.stringify(entry);
341
+ if (!serialised.includes(KEEPERHUB_HOOK_MARKER)) {
342
+ filtered.push(entry);
343
+ }
344
+ }
345
+ filtered.push(buildKeeperhubEntry());
346
+ hooks.PreToolUse = filtered;
347
+ config.hooks = hooks;
348
+ await (0, import_promises.mkdir)((0, import_node_path2.dirname)(settingsPath), { recursive: true, mode: 448 });
349
+ const payload = `${JSON.stringify(config, null, 2)}
350
+ `;
351
+ await (0, import_promises.writeFile)(settingsPath, payload, { mode: 384 });
352
+ await (0, import_promises.chmod)(settingsPath, 384);
353
+ }
354
+ async function writeSkillToAgent(agent, skillSource) {
355
+ await (0, import_promises.mkdir)(agent.skillsDir, { recursive: true, mode: 493 });
356
+ const target = (0, import_node_path2.join)(agent.skillsDir, "keeperhub-wallet.skill.md");
357
+ await (0, import_promises.copyFile)(skillSource, target);
358
+ await (0, import_promises.chmod)(target, 420);
359
+ return { agent: agent.agent, path: target, status: "written" };
360
+ }
361
+ function buildNoticeMessage(agent) {
362
+ return `${agent.agent} does not support auto-registered PreToolUse hooks; run \`${HOOK_COMMAND}\` on every tool use via ${agent.agent}'s settings file at ${agent.settingsFile}`;
363
+ }
364
+ async function installSkill(options = {}) {
365
+ const agents = detectAgents(options.homeOverride);
366
+ const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();
367
+ const onNotice = options.onNotice ?? defaultNotice;
368
+ const skillWrites = [];
369
+ const hookRegistrations = [];
370
+ for (const agent of agents) {
371
+ const write = await writeSkillToAgent(agent, skillSource);
372
+ skillWrites.push(write);
373
+ if (agent.hookSupport === "claude-code") {
374
+ await registerClaudeCodeHook(agent.settingsFile);
375
+ hookRegistrations.push({
376
+ agent: agent.agent,
377
+ status: "registered"
378
+ });
379
+ } else {
380
+ const message = buildNoticeMessage(agent);
381
+ hookRegistrations.push({
382
+ agent: agent.agent,
383
+ status: "notice",
384
+ message
385
+ });
386
+ onNotice(message);
387
+ }
388
+ }
389
+ return { skillWrites, hookRegistrations };
390
+ }
391
+
392
+ // src/storage.ts
393
+ var import_promises2 = require("fs/promises");
394
+ var import_node_os2 = require("os");
395
+ var import_node_path3 = require("path");
396
+ async function readWalletConfig() {
397
+ const walletPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
398
+ let raw;
399
+ try {
400
+ raw = await (0, import_promises2.readFile)(walletPath, "utf-8");
401
+ } catch (err) {
402
+ if (err.code === "ENOENT") {
403
+ throw new WalletConfigMissingError();
404
+ }
405
+ throw err;
406
+ }
407
+ const parsed = JSON.parse(raw);
408
+ if (!(parsed.subOrgId && parsed.walletAddress && parsed.hmacSecret)) {
409
+ throw new Error(`Malformed wallet.json at ${walletPath}`);
410
+ }
411
+ return parsed;
412
+ }
413
+ async function writeWalletConfig(config) {
414
+ const walletPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
415
+ await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(walletPath), { recursive: true, mode: 448 });
416
+ await (0, import_promises2.writeFile)(walletPath, JSON.stringify(config, null, 2), { mode: 384 });
417
+ await (0, import_promises2.chmod)(walletPath, 384);
418
+ }
419
+ function getWalletConfigPath() {
420
+ return (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".keeperhub", "wallet.json");
421
+ }
422
+
423
+ // src/cli.ts
424
+ var TRAILING_SLASH2 = /\/$/;
425
+ var WALLET_ADDRESS_PATTERN = /^0x[a-fA-F0-9]{40}$/;
426
+ function resolveBaseUrl(override) {
427
+ const candidate = override ?? process.env.KEEPERHUB_API_URL ?? "https://app.keeperhub.com";
428
+ return candidate.replace(TRAILING_SLASH2, "");
429
+ }
430
+ function isNonEmptyString(value) {
431
+ return typeof value === "string" && value.length > 0;
432
+ }
433
+ function provisionInvalidError(message) {
434
+ const err = new Error(message);
435
+ err.code = "PROVISION_RESPONSE_INVALID";
436
+ return err;
437
+ }
438
+ function validateProvisionResponse(data) {
439
+ if (typeof data !== "object" || data === null) {
440
+ throw provisionInvalidError("provision response is not an object");
441
+ }
442
+ const { subOrgId, walletAddress, hmacSecret } = data;
443
+ if (!(isNonEmptyString(subOrgId) && isNonEmptyString(walletAddress) && isNonEmptyString(hmacSecret))) {
444
+ throw provisionInvalidError(
445
+ "provision response missing subOrgId, walletAddress, or hmacSecret"
446
+ );
447
+ }
448
+ if (!WALLET_ADDRESS_PATTERN.test(walletAddress)) {
449
+ throw provisionInvalidError(
450
+ `provision response walletAddress is not a valid 0x-prefixed 40-hex address: ${walletAddress}`
451
+ );
452
+ }
453
+ return {
454
+ subOrgId,
455
+ walletAddress,
456
+ hmacSecret
457
+ };
458
+ }
459
+ async function cmdAdd(opts = {}) {
460
+ const baseUrl = resolveBaseUrl(opts.baseUrl);
461
+ const response = await fetch(`${baseUrl}/api/agentic-wallet/provision`, {
462
+ method: "POST",
463
+ headers: { "content-type": "application/json" },
464
+ body: "{}"
465
+ });
466
+ if (!response.ok) {
467
+ const text = await response.text();
468
+ process.stderr.write(
469
+ `[keeperhub-wallet] provision failed: HTTP ${response.status}: ${text}
470
+ `
471
+ );
472
+ process.exit(1);
473
+ }
474
+ const raw = await response.json();
475
+ const data = validateProvisionResponse(raw);
476
+ await writeWalletConfig({
477
+ subOrgId: data.subOrgId,
478
+ walletAddress: data.walletAddress,
479
+ hmacSecret: data.hmacSecret
480
+ });
481
+ process.stdout.write(`subOrgId: ${data.subOrgId}
482
+ `);
483
+ process.stdout.write(`walletAddress: ${data.walletAddress}
484
+ `);
485
+ process.stdout.write(`config written to ${getWalletConfigPath()}
486
+ `);
487
+ }
488
+ async function cmdLink(opts = {}) {
489
+ const wallet = await readWalletConfig();
490
+ const baseUrl = resolveBaseUrl(opts.baseUrl);
491
+ const sessionCookie = process.env.KH_SESSION_COOKIE;
492
+ if (!sessionCookie) {
493
+ process.stderr.write(
494
+ "[keeperhub-wallet] link requires KH_SESSION_COOKIE env var.\nSign in at app.keeperhub.com, copy the session cookie, and re-run with:\n KH_SESSION_COOKIE='<cookie>' npx @keeperhub/wallet link\n"
495
+ );
496
+ process.exit(1);
497
+ }
498
+ const body = JSON.stringify({ subOrgId: wallet.subOrgId });
499
+ const headers = buildHmacHeaders(
500
+ wallet.hmacSecret,
501
+ "POST",
502
+ "/api/agentic-wallet/link",
503
+ wallet.subOrgId,
504
+ body
505
+ );
506
+ const response = await fetch(`${baseUrl}/api/agentic-wallet/link`, {
507
+ method: "POST",
508
+ headers: {
509
+ ...headers,
510
+ "content-type": "application/json",
511
+ cookie: sessionCookie
512
+ },
513
+ body
514
+ });
515
+ const json = await response.json().catch(() => ({}));
516
+ if (!response.ok) {
517
+ process.stderr.write(
518
+ `[keeperhub-wallet] link failed: ${json.code ?? response.status}: ${json.error ?? ""}
519
+ `
520
+ );
521
+ process.exit(1);
522
+ }
523
+ if (json.already) {
524
+ process.stdout.write("already linked\n");
525
+ return;
526
+ }
527
+ process.stdout.write("linked\n");
528
+ }
529
+ async function cmdFund() {
530
+ const wallet = await readWalletConfig();
531
+ const out = fund(wallet.walletAddress);
532
+ process.stdout.write(`${out.coinbaseOnrampUrl}
533
+ `);
534
+ process.stdout.write(`Tempo address: ${out.tempoAddress}
535
+ `);
536
+ process.stdout.write(`${out.disclaimer}
537
+ `);
538
+ }
539
+ async function cmdBalance() {
540
+ const wallet = await readWalletConfig();
541
+ const snap = await checkBalance(wallet);
542
+ process.stdout.write(`Base USDC: ${snap.base.amount}
543
+ `);
544
+ process.stdout.write(`Tempo USDC.e: ${snap.tempo.amount}
545
+ `);
546
+ process.stdout.write(
547
+ `KeeperHub credit: ${snap.offChainCredit.amount} ${snap.offChainCredit.currency}
548
+ `
549
+ );
550
+ }
551
+ async function cmdInfo() {
552
+ const wallet = await readWalletConfig();
553
+ process.stdout.write(`subOrgId: ${wallet.subOrgId}
554
+ `);
555
+ process.stdout.write(`walletAddress: ${wallet.walletAddress}
556
+ `);
557
+ }
558
+ async function runCli(argv = process.argv) {
559
+ const program = new import_commander.Command();
560
+ program.name("keeperhub-wallet").description(
561
+ "KeeperHub agentic wallet CLI (auto-pay x402 + MPP 402 responses)"
562
+ ).version("0.1.0");
563
+ program.command("add").description("Provision a new agentic wallet (no account required)").option("--base-url <url>", "KeeperHub API base URL").action(async (opts) => {
564
+ await cmdAdd(opts);
565
+ });
566
+ program.command("link").description(
567
+ "Link the current wallet to your KeeperHub account (requires KH_SESSION_COOKIE env)"
568
+ ).option("--base-url <url>", "KeeperHub API base URL").action(async (opts) => {
569
+ await cmdLink(opts);
570
+ });
571
+ program.command("fund").description(
572
+ "Print Coinbase Onramp URL (Base USDC) and Tempo deposit address"
573
+ ).action(async () => {
574
+ await cmdFund();
575
+ });
576
+ program.command("balance").description(
577
+ "Print unified balance: Base USDC + Tempo USDC.e + off-chain KeeperHub credit"
578
+ ).action(async () => {
579
+ await cmdBalance();
580
+ });
581
+ program.command("info").description("Print subOrgId and walletAddress from local config").action(async () => {
582
+ await cmdInfo();
583
+ });
584
+ program.command("skill").description(
585
+ "Install the KeeperHub skill file into detected agent directories"
586
+ ).addCommand(
587
+ new import_commander.Command("install").description(
588
+ "Write skill file + register PreToolUse hook in all detected agents"
589
+ ).action(async () => {
590
+ const result = await installSkill();
591
+ for (const write of result.skillWrites) {
592
+ process.stdout.write(
593
+ `skill: ${write.agent} -> ${write.path} (${write.status})
594
+ `
595
+ );
596
+ }
597
+ for (const reg of result.hookRegistrations) {
598
+ if (reg.status === "registered") {
599
+ process.stdout.write(
600
+ `hook: ${reg.agent} -> PreToolUse registered
601
+ `
602
+ );
603
+ } else if (reg.status === "notice") {
604
+ process.stderr.write(
605
+ `notice: ${reg.agent} -> ${reg.message ?? ""}
606
+ `
607
+ );
608
+ }
609
+ }
610
+ if (result.skillWrites.length === 0) {
611
+ process.stderr.write(
612
+ "No supported agent skill directories detected under $HOME. Create ~/.claude/, ~/.cursor/, ~/.cline/, ~/.windsurf/, or ~/.config/opencode/ and re-run.\n"
613
+ );
614
+ }
615
+ })
616
+ );
617
+ try {
618
+ await program.parseAsync(argv);
619
+ } catch (err) {
620
+ if (err instanceof WalletConfigMissingError) {
621
+ process.stderr.write(`[keeperhub-wallet] ${err.message}
622
+ `);
623
+ process.exit(1);
624
+ }
625
+ process.stderr.write(
626
+ `[keeperhub-wallet] ${err.message ?? String(err)}
627
+ `
628
+ );
629
+ process.exit(1);
630
+ }
631
+ }
632
+ // Annotate the CommonJS export names for ESM import in node:
633
+ 0 && (module.exports = {
634
+ runCli
635
+ });
636
+ //# sourceMappingURL=cli.cjs.map