@keeperhub/wallet 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -33,6 +33,9 @@ declare class KeeperHubError extends Error {
33
33
  readonly code: string;
34
34
  constructor(code: string, message: string);
35
35
  }
36
+ /** Protocol preference for a single pay() or fetch() call. "auto" preserves
37
+ * the x402-first default when both challenges are offered. */
38
+ type PaymentHint = "x402" | "mpp" | "auto";
36
39
  declare class WalletConfigMissingError extends Error {
37
40
  constructor();
38
41
  }
@@ -259,6 +262,25 @@ type MppChallenge = {
259
262
  };
260
263
  declare function parseMppChallenge(response: Response): MppChallenge | null;
261
264
 
265
+ type X402Challenge = {
266
+ x402Version: 2;
267
+ accepts: Array<{
268
+ scheme: "exact";
269
+ network: string;
270
+ asset: string;
271
+ amount: string;
272
+ payTo: string;
273
+ maxTimeoutSeconds: number;
274
+ extra: Record<string, unknown>;
275
+ }>;
276
+ resource: {
277
+ url: string;
278
+ description: string;
279
+ mimeType: string;
280
+ };
281
+ };
282
+ declare function parseX402Challenge(response: Response): Promise<X402Challenge | null>;
283
+
262
284
  type PaySignerOptions = {
263
285
  /** Override wallet loader (primarily for tests). */
264
286
  walletLoader?: () => Promise<WalletConfig>;
@@ -296,6 +318,18 @@ type PayRetryOptions = {
296
318
  headers?: RequestInit["headers"];
297
319
  /** HTTP method for the retry. Defaults to "POST". */
298
320
  method?: string;
321
+ /**
322
+ * Per-call protocol preference. "x402" forces Base USDC; "mpp" forces Tempo
323
+ * USDC.e; "auto" (default, also the behaviour when omitted) uses x402 when
324
+ * offered, MPP otherwise. Throws KeeperHubError("X402_NOT_OFFERED") or
325
+ * KeeperHubError("MPP_NOT_OFFERED") when the requested protocol is absent
326
+ * from the challenge (KEEP-361).
327
+ */
328
+ paymentHint?: PaymentHint;
329
+ };
330
+ /** RequestInit extended with paymentHint for per-call protocol selection. */
331
+ type FetchInit = RequestInit & {
332
+ paymentHint?: PaymentHint;
299
333
  };
300
334
  type PaymentSigner = {
301
335
  /**
@@ -315,12 +349,36 @@ type PaymentSigner = {
315
349
  * `pay()` with `init.body` + `init.headers` so the retry carries the
316
350
  * original payload. Returns whatever the retry (or first response, if not
317
351
  * 402) returns. No-op for non-402 responses.
352
+ *
353
+ * Pass `init.paymentHint` to force a specific payment protocol for this
354
+ * call. Omitting it is equivalent to `paymentHint: "auto"` (x402-first).
318
355
  */
319
- fetch: (input: string | URL, init?: RequestInit) => Promise<Response>;
356
+ fetch: (input: string | URL, init?: FetchInit) => Promise<Response>;
320
357
  };
358
+ /**
359
+ * Pure function that decides which payment protocol to use given challenge
360
+ * availability and caller's hint. Exported for unit testing.
361
+ *
362
+ * Returns "x402" or "mpp" to direct the caller to the appropriate path,
363
+ * or null when hint is "auto" and no challenge is present (pay() then
364
+ * returns the original 402 response unchanged). Throws KeeperHubError with
365
+ * a specific code when the requested protocol is unavailable (KEEP-361).
366
+ */
367
+ declare function selectProtocol(x402: X402Challenge | null, mpp: MppChallenge | null, hint: PaymentHint | undefined): "x402" | "mpp" | null;
321
368
  declare function createPaymentSigner(opts?: PaySignerOptions): PaymentSigner;
322
369
  declare const paymentSigner: PaymentSigner;
323
370
 
371
+ /**
372
+ * Pick the hook command to write into settings.json.
373
+ *
374
+ * Returns the bare bin name if it resolves on PATH (global install or a
375
+ * dev-time `npm link`), otherwise a version-pinned `npx` invocation that
376
+ * pulls the installer's own version of `@keeperhub/wallet` on demand.
377
+ * Override-able via the env var `KEEPERHUB_WALLET_HOOK_COMMAND` for
378
+ * test fixtures and unusual deployments (env input is trusted — it is
379
+ * written verbatim into settings.json and executed by the user's shell).
380
+ */
381
+ declare function resolveHookCommand(): string;
324
382
  type InstallResult = {
325
383
  skillWrites: Array<{
326
384
  agent: string;
@@ -337,31 +395,26 @@ type InstallOptions = {
337
395
  homeOverride?: string;
338
396
  skillSourcePath?: string;
339
397
  onNotice?: (msg: string) => void;
398
+ /**
399
+ * Hook command to write into settings.json (and reference in stderr
400
+ * notices for non-Claude agents). Defaults to {@link resolveHookCommand}.
401
+ * Override for tests, monorepo setups, or unusual deployments.
402
+ */
403
+ hookCommand?: string;
404
+ };
405
+ type RegisterClaudeCodeHookOptions = {
406
+ /**
407
+ * Hook command to write. Defaults to {@link resolveHookCommand}. Tests
408
+ * pass a deterministic value to keep assertions stable across host
409
+ * environments (CI may or may not have the bin on PATH).
410
+ */
411
+ hookCommand?: string;
340
412
  };
341
- declare function registerClaudeCodeHook(settingsPath: string): Promise<void>;
413
+ declare function registerClaudeCodeHook(settingsPath: string, options?: RegisterClaudeCodeHookOptions): Promise<void>;
342
414
  declare function installSkill(options?: InstallOptions): Promise<InstallResult>;
343
415
 
344
416
  declare function readWalletConfig(): Promise<WalletConfig>;
345
417
  declare function writeWalletConfig(config: WalletConfig): Promise<void>;
346
418
  declare function getWalletConfigPath(): string;
347
419
 
348
- type X402Challenge = {
349
- x402Version: 2;
350
- accepts: Array<{
351
- scheme: "exact";
352
- network: string;
353
- asset: string;
354
- amount: string;
355
- payTo: string;
356
- maxTimeoutSeconds: number;
357
- extra: Record<string, unknown>;
358
- }>;
359
- resource: {
360
- url: string;
361
- description: string;
362
- mimeType: string;
363
- };
364
- };
365
- declare function parseX402Challenge(response: Response): Promise<X402Challenge | null>;
366
-
367
- export { type AgentTarget, type AskTierResponse, BASE_USDC, type BalanceSnapshot, type CheckBalanceOptions, type ClientOptions, type CreateHookOptions, DEFAULT_SAFETY_CONFIG, type FundInstructions, type HmacHeaders, type HookDecision, type InstallOptions, type InstallResult, KeeperHubClient, KeeperHubError, type MppChallenge, type PaymentSigner, type SafetyConfig, TEMPO_USDC_E, type WalletConfig, WalletConfigMissingError, type X402Challenge, buildHmacHeaders, checkBalance, computeSignature, createPaymentSigner, createPreToolUseHook, detectAgents, fund, getSafetyConfigPath, getWalletConfigPath, installSkill, loadSafetyConfig, parseMppChallenge, parseX402Challenge, paymentSigner, readWalletConfig, registerClaudeCodeHook, tempo, validateAndMerge, writeWalletConfig };
420
+ export { type AgentTarget, type AskTierResponse, BASE_USDC, type BalanceSnapshot, type CheckBalanceOptions, type ClientOptions, type CreateHookOptions, DEFAULT_SAFETY_CONFIG, type FetchInit, type FundInstructions, type HmacHeaders, type HookDecision, type InstallOptions, type InstallResult, KeeperHubClient, KeeperHubError, type MppChallenge, type PayRetryOptions, type PaymentHint, type PaymentSigner, type RegisterClaudeCodeHookOptions, type SafetyConfig, TEMPO_USDC_E, type WalletConfig, WalletConfigMissingError, type X402Challenge, buildHmacHeaders, checkBalance, computeSignature, createPaymentSigner, createPreToolUseHook, detectAgents, fund, getSafetyConfigPath, getWalletConfigPath, installSkill, loadSafetyConfig, parseMppChallenge, parseX402Challenge, paymentSigner, readWalletConfig, registerClaudeCodeHook, resolveHookCommand, selectProtocol, tempo, validateAndMerge, writeWalletConfig };
package/dist/index.js CHANGED
@@ -147,15 +147,69 @@ function fund(walletAddress) {
147
147
  }
148
148
 
149
149
  // src/skill-install.ts
150
+ import { execFileSync } from "child_process";
150
151
  import { chmod, copyFile, mkdir, readFile, writeFile } from "fs/promises";
152
+ import { readFileSync } from "fs";
151
153
  import { dirname as dirname2, join as join2 } from "path";
152
154
  import { fileURLToPath } from "url";
153
- var HOOK_COMMAND = "keeperhub-wallet-hook";
154
- var KEEPERHUB_HOOK_MARKER = "keeperhub-wallet-hook";
155
- function buildKeeperhubEntry() {
155
+ var HOOK_BIN = "keeperhub-wallet-hook";
156
+ var HOOK_COMMAND_BARE = HOOK_BIN;
157
+ var PACKAGE_NAME = "@keeperhub/wallet";
158
+ function readPackageVersion() {
159
+ try {
160
+ const here = dirname2(fileURLToPath(import.meta.url));
161
+ const pkgPath = join2(here, "..", "package.json");
162
+ const raw = readFileSync(pkgPath, "utf-8");
163
+ const parsed = JSON.parse(raw);
164
+ if (typeof parsed.version === "string" && parsed.version.length > 0) {
165
+ return parsed.version;
166
+ }
167
+ } catch {
168
+ }
169
+ return "latest";
170
+ }
171
+ function buildNpxCommand(version) {
172
+ return `npx -y -p ${PACKAGE_NAME}@${version} ${HOOK_BIN}`;
173
+ }
174
+ var KEEPERHUB_HOOK_MARKER = HOOK_BIN;
175
+ function filterKeeperhubHooksFromEntry(entry) {
176
+ if (typeof entry !== "object" || entry === null) {
177
+ return entry;
178
+ }
179
+ const candidate = entry;
180
+ if (!Array.isArray(candidate.hooks)) {
181
+ return entry;
182
+ }
183
+ const survivors = candidate.hooks.filter((h) => {
184
+ const cmd = h?.command;
185
+ return !(typeof cmd === "string" && cmd.includes(KEEPERHUB_HOOK_MARKER));
186
+ });
187
+ if (survivors.length === candidate.hooks.length) {
188
+ return entry;
189
+ }
190
+ if (survivors.length === 0) {
191
+ return null;
192
+ }
193
+ return { ...candidate, hooks: survivors };
194
+ }
195
+ function resolveHookCommand() {
196
+ const envOverride = process.env.KEEPERHUB_WALLET_HOOK_COMMAND;
197
+ if (envOverride && envOverride.length > 0) {
198
+ return envOverride;
199
+ }
200
+ try {
201
+ execFileSync("/bin/sh", ["-c", `command -v ${HOOK_BIN}`], {
202
+ stdio: "ignore"
203
+ });
204
+ return HOOK_COMMAND_BARE;
205
+ } catch {
206
+ return buildNpxCommand(readPackageVersion());
207
+ }
208
+ }
209
+ function buildKeeperhubEntry(command) {
156
210
  return {
157
211
  matcher: "*",
158
- hooks: [{ type: "command", command: HOOK_COMMAND }]
212
+ hooks: [{ type: "command", command }]
159
213
  };
160
214
  }
161
215
  function resolveDefaultSkillSource() {
@@ -166,7 +220,8 @@ function defaultNotice(msg) {
166
220
  process.stderr.write(`${msg}
167
221
  `);
168
222
  }
169
- async function registerClaudeCodeHook(settingsPath) {
223
+ async function registerClaudeCodeHook(settingsPath, options = {}) {
224
+ const command = options.hookCommand ?? resolveHookCommand();
170
225
  let raw = null;
171
226
  try {
172
227
  raw = await readFile(settingsPath, "utf-8");
@@ -189,12 +244,12 @@ async function registerClaudeCodeHook(settingsPath) {
189
244
  const existingPreToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
190
245
  const filtered = [];
191
246
  for (const entry of existingPreToolUse) {
192
- const serialised = JSON.stringify(entry);
193
- if (!serialised.includes(KEEPERHUB_HOOK_MARKER)) {
194
- filtered.push(entry);
247
+ const survivor = filterKeeperhubHooksFromEntry(entry);
248
+ if (survivor !== null) {
249
+ filtered.push(survivor);
195
250
  }
196
251
  }
197
- filtered.push(buildKeeperhubEntry());
252
+ filtered.push(buildKeeperhubEntry(command));
198
253
  hooks.PreToolUse = filtered;
199
254
  config.hooks = hooks;
200
255
  await mkdir(dirname2(settingsPath), { recursive: true, mode: 448 });
@@ -210,26 +265,27 @@ async function writeSkillToAgent(agent, skillSource) {
210
265
  await chmod(target, 420);
211
266
  return { agent: agent.agent, path: target, status: "written" };
212
267
  }
213
- function buildNoticeMessage(agent) {
214
- 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}`;
268
+ function buildNoticeMessage(agent, command) {
269
+ 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}`;
215
270
  }
216
271
  async function installSkill(options = {}) {
217
272
  const agents = detectAgents(options.homeOverride);
218
273
  const skillSource = options.skillSourcePath ?? resolveDefaultSkillSource();
219
274
  const onNotice = options.onNotice ?? defaultNotice;
275
+ const hookCommand = options.hookCommand ?? resolveHookCommand();
220
276
  const skillWrites = [];
221
277
  const hookRegistrations = [];
222
278
  for (const agent of agents) {
223
279
  const write = await writeSkillToAgent(agent, skillSource);
224
280
  skillWrites.push(write);
225
281
  if (agent.hookSupport === "claude-code") {
226
- await registerClaudeCodeHook(agent.settingsFile);
282
+ await registerClaudeCodeHook(agent.settingsFile, { hookCommand });
227
283
  hookRegistrations.push({
228
284
  agent: agent.agent,
229
285
  status: "registered"
230
286
  });
231
287
  } else {
232
- const message = buildNoticeMessage(agent);
288
+ const message = buildNoticeMessage(agent, hookCommand);
233
289
  hookRegistrations.push({
234
290
  agent: agent.agent,
235
291
  status: "notice",
@@ -637,6 +693,26 @@ var DEFAULT_TOOL_RE = /keeperhub|wallet|sign/i;
637
693
  function defaultToolMatcher(name) {
638
694
  return DEFAULT_TOOL_RE.test(name);
639
695
  }
696
+ function hasPaymentShape(input) {
697
+ const ti = input.tool_input ?? {};
698
+ const challenge = ti.paymentChallenge;
699
+ if (challenge !== void 0 && challenge !== null) {
700
+ return true;
701
+ }
702
+ if (ti.amount !== void 0 && ti.amount !== null) {
703
+ return true;
704
+ }
705
+ if (ti.unit !== void 0 && ti.unit !== null) {
706
+ return true;
707
+ }
708
+ for (const field of ["to", "contract", "assetAddress"]) {
709
+ const v = ti[field];
710
+ if (typeof v === "string" && ADDRESS_RE.test(v)) {
711
+ return true;
712
+ }
713
+ }
714
+ return false;
715
+ }
640
716
  function extractAmountMicroUsdc(input) {
641
717
  const ti = input.tool_input ?? {};
642
718
  const challenge = ti.paymentChallenge ?? {};
@@ -686,6 +762,9 @@ async function createPreToolUseHook(options = {}) {
686
762
  if (!(typeof hookInput.tool_name === "string" && toolMatcher(hookInput.tool_name))) {
687
763
  return { decision: "allow" };
688
764
  }
765
+ if (!hasPaymentShape(hookInput)) {
766
+ return { decision: "allow" };
767
+ }
689
768
  const contractAddr = extractContractAddress(hookInput);
690
769
  const amountMicro = extractAmountMicroUsdc(hookInput);
691
770
  if (contractAddr && !safety.allowlisted_contracts.includes(contractAddr)) {
@@ -818,6 +897,30 @@ var NONCE_BYTES = 32;
818
897
  async function sleep(ms) {
819
898
  await new Promise((resolve) => setTimeout(resolve, ms));
820
899
  }
900
+ function selectProtocol(x402, mpp, hint) {
901
+ const h = hint ?? "auto";
902
+ if (h === "x402") {
903
+ if (!x402) {
904
+ throw new KeeperHubError(
905
+ "X402_NOT_OFFERED",
906
+ "x402 is not offered by this endpoint"
907
+ );
908
+ }
909
+ return "x402";
910
+ }
911
+ if (h === "mpp") {
912
+ if (!mpp) {
913
+ throw new KeeperHubError(
914
+ "MPP_NOT_OFFERED",
915
+ "mpp is not offered by this endpoint"
916
+ );
917
+ }
918
+ return "mpp";
919
+ }
920
+ if (x402) return "x402";
921
+ if (mpp) return "mpp";
922
+ return null;
923
+ }
821
924
  function createPaymentSigner(opts = {}) {
822
925
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
823
926
  const walletLoader = opts.walletLoader ?? readWalletConfig;
@@ -960,12 +1063,13 @@ function createPaymentSigner(opts = {}) {
960
1063
  return response;
961
1064
  }
962
1065
  const wallet = await walletLoader();
963
- if (mpp) {
964
- return payViaMpp(response, mpp, wallet, options);
965
- }
966
- if (x402) {
1066
+ const protocol = selectProtocol(x402, mpp, options?.paymentHint);
1067
+ if (protocol === "x402") {
967
1068
  return payViaX402(response, x402, wallet, options);
968
1069
  }
1070
+ if (protocol === "mpp") {
1071
+ return payViaMpp(response, mpp, wallet, options);
1072
+ }
969
1073
  return response;
970
1074
  }
971
1075
  return {
@@ -978,7 +1082,8 @@ function createPaymentSigner(opts = {}) {
978
1082
  return pay(first, {
979
1083
  body: init?.body ?? void 0,
980
1084
  headers: init?.headers,
981
- method: init?.method
1085
+ method: init?.method,
1086
+ paymentHint: init?.paymentHint
982
1087
  });
983
1088
  }
984
1089
  };
@@ -1008,8 +1113,10 @@ export {
1008
1113
  paymentSigner,
1009
1114
  readWalletConfig,
1010
1115
  registerClaudeCodeHook,
1116
+ resolveHookCommand,
1011
1117
  runCli,
1012
1118
  runHookCli,
1119
+ selectProtocol,
1013
1120
  tempo,
1014
1121
  validateAndMerge,
1015
1122
  writeWalletConfig