@jaw.id/cli 0.1.26 → 0.2.1
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/base-command.js +3 -1
- package/dist/base-command.js.map +1 -1
- package/dist/commands/config/set.js +140 -12
- package/dist/commands/config/set.js.map +1 -1
- package/dist/commands/config/show.js +3 -1
- package/dist/commands/config/show.js.map +1 -1
- package/dist/commands/config/write.js +6 -4
- package/dist/commands/config/write.js.map +1 -1
- package/dist/commands/disconnect.js +24 -10
- package/dist/commands/disconnect.js.map +1 -1
- package/dist/commands/mcp/index.js +2426 -94
- package/dist/commands/mcp/index.js.map +1 -1
- package/dist/commands/rpc/call.js +197 -45
- package/dist/commands/rpc/call.js.map +1 -1
- package/dist/commands/session/add.js +1547 -0
- package/dist/commands/session/add.js.map +1 -0
- package/dist/commands/session/revoke.js +181 -54
- package/dist/commands/session/revoke.js.map +1 -1
- package/dist/commands/session/setup.js +516 -65
- package/dist/commands/session/setup.js.map +1 -1
- package/dist/commands/session/status.js +315 -6
- package/dist/commands/session/status.js.map +1 -1
- package/dist/commands/version.js +3 -1
- package/dist/commands/version.js.map +1 -1
- package/dist/commands/x402/log.js +344 -0
- package/dist/commands/x402/log.js.map +1 -0
- package/dist/commands/x402/pay.js +2122 -0
- package/dist/commands/x402/pay.js.map +1 -0
- package/dist/commands/x402/status.js +1047 -0
- package/dist/commands/x402/status.js.map +1 -0
- package/dist/index.js +41 -14
- package/dist/index.js.map +1 -1
- package/dist/lib/bridge-singleton.js +41 -14
- package/dist/lib/bridge-singleton.js.map +1 -1
- package/dist/lib/config.js +26 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/keystore.js +13 -2
- package/dist/lib/keystore.js.map +1 -1
- package/dist/lib/paths.js +3 -1
- package/dist/lib/paths.js.map +1 -1
- package/dist/lib/payment-lock.js +121 -0
- package/dist/lib/payment-lock.js.map +1 -0
- package/dist/lib/session-bridge.js +148 -24
- package/dist/lib/session-bridge.js.map +1 -1
- package/dist/lib/session-config.js +78 -11
- package/dist/lib/session-config.js.map +1 -1
- package/dist/lib/terminal.js +22 -0
- package/dist/lib/terminal.js.map +1 -0
- package/dist/lib/validation.js +3 -3
- package/dist/lib/validation.js.map +1 -1
- package/dist/lib/ws-bridge.js +22 -10
- package/dist/lib/ws-bridge.js.map +1 -1
- package/dist/mcp/handlers/config.js +73 -6
- package/dist/mcp/handlers/config.js.map +1 -1
- package/dist/mcp/handlers/daemon.js +43 -12
- package/dist/mcp/handlers/daemon.js.map +1 -1
- package/dist/mcp/handlers/resources.js +119 -0
- package/dist/mcp/handlers/resources.js.map +1 -1
- package/dist/mcp/handlers/rpc.js +269 -60
- package/dist/mcp/handlers/rpc.js.map +1 -1
- package/dist/mcp/helpers.js +50 -3
- package/dist/mcp/helpers.js.map +1 -1
- package/dist/mcp/server.js +2426 -94
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.js +43 -3
- package/dist/mcp/tools.js.map +1 -1
- package/dist/x402/log-view.js +160 -0
- package/dist/x402/log-view.js.map +1 -0
- package/dist/x402/status-report.js +90 -0
- package/dist/x402/status-report.js.map +1 -0
- package/oclif.manifest.json +405 -11
- package/package.json +5 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/paths.ts","../../src/lib/config.ts","../../src/lib/output.ts","../../src/base-command.ts","../../src/commands/version.ts"],"names":["require"],"mappings":"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","file":"version.js","sourcesContent":["import * as path from 'node:path';\nimport * as os from 'node:os';\n\nconst JAW_DIR = path.join(os.homedir(), '.jaw');\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, 'config.json'),\n session: path.join(JAW_DIR, 'session.json'),\n relay: path.join(JAW_DIR, 'relay.json'),\n keystore: path.join(JAW_DIR, 'keystore.json'),\n sessionConfig: path.join(JAW_DIR, 'session-config.json'),\n} as const;\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport type { JawConfig } from './types.js';\nimport { isValidKeysUrl, isValidRelayUrl } from './validation.js';\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nfunction migrateConfig(config: JawConfig): JawConfig {\n if (config.paymasterUrl && !config.paymasters) {\n const chainId = config.defaultChain ?? 1;\n config.paymasters = { [chainId]: { url: config.paymasterUrl } };\n delete config.paymasterUrl;\n saveConfig(config);\n }\n return config;\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, 'utf-8');\n try {\n const config = JSON.parse(raw) as JawConfig;\n return migrateConfig(config);\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n\n/** Paymaster URLs embed provider API keys as query params (e.g. Pimlico's ?apikey=...). */\nfunction redactUrlSecrets(url: string): string {\n try {\n const parsed = new URL(url);\n for (const key of [...parsed.searchParams.keys()]) {\n parsed.searchParams.set(key, '***');\n }\n return parsed.toString();\n } catch {\n return '***';\n }\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n ...(config.paymasters && {\n paymasters: Object.fromEntries(\n Object.entries(config.paymasters).map(([chainId, pm]) => [\n chainId,\n // context is a free-form object (usually just a sponsorshipPolicyId) but a\n // provider could stash a token there, so mask it rather than hand it to the agent.\n { ...pm, url: redactUrlSecrets(pm.url), context: pm.context ? '***' : undefined },\n ])\n ),\n }),\n };\n}\n\nexport function setConfigValue(key: keyof JawConfig, value: string | number): void {\n if (key === 'keysUrl' && typeof value === 'string' && !isValidKeysUrl(value)) {\n throw new Error(`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`);\n }\n if (key === 'relayUrl' && typeof value === 'string' && !isValidRelayUrl(value)) {\n throw new Error(`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`);\n }\n const config = loadConfig();\n const updated = { ...config, [key]: value };\n saveConfig(updated);\n}\n","import type { OutputFormat } from './types.js';\n\nexport function formatOutput(data: unknown, format: OutputFormat): string {\n if (format === 'json') {\n return JSON.stringify(data, replaceBigInt, 2);\n }\n return formatHuman(data);\n}\n\nfunction replaceBigInt(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') {\n return value.toString();\n }\n return value;\n}\n\nfunction formatHuman(data: unknown, indent = 0): string {\n if (data === null || data === undefined) {\n return 'null';\n }\n\n if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || typeof data === 'bigint') {\n return String(data);\n }\n\n if (Array.isArray(data)) {\n if (data.length === 0) return '(empty)';\n return data.map((item, i) => `${i + 1}. ${formatHuman(item, indent + 2)}`).join('\\n');\n }\n\n if (typeof data === 'object') {\n const entries = Object.entries(data as Record<string, unknown>);\n if (entries.length === 0) return '(empty)';\n const pad = ' '.repeat(indent);\n const maxKeyLen = Math.max(...entries.map(([k]) => k.length));\n return entries\n .map(([key, val]) => {\n const paddedKey = key.padEnd(maxKeyLen);\n const valStr = typeof val === 'object' && val !== null ? '\\n' + formatHuman(val, indent + 2) : String(val);\n return `${pad}${paddedKey} ${valStr}`;\n })\n .join('\\n');\n }\n\n return String(data);\n}\n","import { Command, Flags } from '@oclif/core';\nimport type { OutputFormat } from './lib/types.js';\nimport { loadConfig } from './lib/config.js';\nimport { formatOutput } from './lib/output.js';\n\nexport abstract class BaseCommand extends Command {\n static override baseFlags = {\n output: Flags.string({\n char: 'o',\n description: 'Output format',\n options: ['json', 'human'],\n default: 'human',\n env: 'JAW_OUTPUT',\n }),\n chain: Flags.integer({\n char: 'c',\n description: 'Chain ID',\n env: 'JAW_CHAIN_ID',\n }),\n 'api-key': Flags.string({\n description: 'JAW API key',\n env: 'JAW_API_KEY',\n }),\n yes: Flags.boolean({\n char: 'y',\n description: 'Skip confirmations (for AI agents)',\n default: false,\n }),\n quiet: Flags.boolean({\n char: 'q',\n description: 'Suppress non-essential output',\n default: false,\n }),\n };\n\n protected resolveApiKey(flags: { 'api-key'?: string }): string {\n const apiKey = flags['api-key'] ?? loadConfig().apiKey;\n if (!apiKey) {\n this.error('API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`');\n }\n return apiKey;\n }\n\n protected resolveChainId(flags: { chain?: number }): number {\n const chainId = flags.chain ?? loadConfig().defaultChain;\n if (!chainId) {\n this.error('Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`');\n }\n return chainId;\n }\n\n protected outputResult(data: unknown, format: OutputFormat): void {\n const output = formatOutput(data, format);\n this.log(output);\n }\n}\n","import { BaseCommand } from '../base-command.js';\nimport { createRequire } from 'node:module';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../../package.json') as { version: string };\n\nexport default class Version extends BaseCommand {\n static override description = 'Show the CLI version';\n static override aliases = ['-v'];\n static override flags = {};\n\n async run(): Promise<void> {\n this.log(`@jaw.id/cli/${pkg.version}`);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/paths.ts","../../src/lib/config.ts","../../src/lib/output.ts","../../src/base-command.ts","../../src/commands/version.ts"],"names":["require"],"mappings":"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","file":"version.js","sourcesContent":["import * as path from 'node:path';\nimport * as os from 'node:os';\n\nconst JAW_DIR = path.join(os.homedir(), '.jaw');\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, 'config.json'),\n session: path.join(JAW_DIR, 'session.json'),\n relay: path.join(JAW_DIR, 'relay.json'),\n keystore: path.join(JAW_DIR, 'keystore.json'),\n sessionConfig: path.join(JAW_DIR, 'session-config.json'),\n x402Log: path.join(JAW_DIR, 'x402-log.jsonl'),\n paymentLock: path.join(JAW_DIR, 'x402-payment.lock'),\n} as const;\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport type { JawConfig, SettableConfigKey } from './types.js';\nimport type { X402Policy, X402PolicyKey } from '../x402/policy.js';\nimport { isValidKeysUrl, isValidRelayUrl } from './validation.js';\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nfunction migrateConfig(config: JawConfig): JawConfig {\n if (config.paymasterUrl && !config.paymasters) {\n const chainId = config.defaultChain ?? 1;\n config.paymasters = { [chainId]: { url: config.paymasterUrl } };\n delete config.paymasterUrl;\n saveConfig(config);\n }\n return config;\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, 'utf-8');\n try {\n const config = JSON.parse(raw) as JawConfig;\n return migrateConfig(config);\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n\n/** Paymaster URLs embed provider API keys as query params (e.g. Pimlico's ?apikey=...). */\nfunction redactUrlSecrets(url: string): string {\n try {\n const parsed = new URL(url);\n for (const key of [...parsed.searchParams.keys()]) {\n parsed.searchParams.set(key, '***');\n }\n return parsed.toString();\n } catch {\n return '***';\n }\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n ...(config.paymasters && {\n paymasters: Object.fromEntries(\n Object.entries(config.paymasters).map(([chainId, pm]) => [\n chainId,\n // context is a free-form object (usually just a sponsorshipPolicyId) but a\n // provider could stash a token there, so mask it rather than hand it to the agent.\n { ...pm, url: redactUrlSecrets(pm.url), context: pm.context ? '***' : undefined },\n ])\n ),\n }),\n };\n}\n\n/**\n * Set one field of the `x402` payment policy. Array fields (allow-lists) are\n * comma-split. Intentionally NOT reachable from the MCP tool surface: an agent\n * must not be able to raise its own spending caps — only a human at the CLI.\n */\nexport function setX402PolicyValue(key: X402PolicyKey, value: string): void {\n const config = loadConfig();\n const x402: X402Policy = { ...(config.x402 ?? {}) };\n if (key === 'maxAmountPerPayment' || key === 'maxTotalPerSession' || key === 'topUpFloat') {\n // These are base-unit amounts read via BigInt() on the hot payment path;\n // reject anything that isn't a non-negative integer here so a bad value\n // can never turn into a per-payment failure later.\n if (!/^\\d+$/.test(value.trim())) {\n throw new Error(`${key} must be a non-negative integer (base units), got: ${value}`);\n }\n x402[key] = value.trim();\n } else {\n x402[key] = value\n .split(',')\n .map((v) => v.trim())\n .filter(Boolean);\n }\n saveConfig({ ...config, x402 });\n}\n\nexport function setConfigValue(key: SettableConfigKey, value: string | number): void {\n if (key === 'keysUrl' && typeof value === 'string' && !isValidKeysUrl(value)) {\n throw new Error(`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`);\n }\n if (key === 'relayUrl' && typeof value === 'string' && !isValidRelayUrl(value)) {\n throw new Error(`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`);\n }\n // Numeric keys: coerce + validate here so EVERY caller is safe. The MCP tool\n // passes raw strings (its schema types value as a string), so without this a\n // `sessionExpiry: \"abc\"` would land as a string and turn `expiry * 86400`\n // into NaN, and a `defaultChain` string would ride into an RPC URL verbatim.\n let toStore: string | number = value;\n if (key === 'defaultChain' || key === 'sessionExpiry') {\n // Strict: reject anything parseInt would silently truncate ('1.5' -> 1,\n // '0x10' -> 0, '10abc' -> 10). Only a bare positive decimal integer passes.\n const n = typeof value === 'number' ? value : /^\\d+$/.test(value.trim()) ? parseInt(value.trim(), 10) : NaN;\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(`${key} must be a positive integer, got: ${JSON.stringify(value)}`);\n }\n toStore = n;\n }\n const config = loadConfig();\n const updated = { ...config, [key]: toStore };\n saveConfig(updated);\n}\n","import type { OutputFormat } from './types.js';\n\nexport function formatOutput(data: unknown, format: OutputFormat): string {\n if (format === 'json') {\n return JSON.stringify(data, replaceBigInt, 2);\n }\n return formatHuman(data);\n}\n\nfunction replaceBigInt(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') {\n return value.toString();\n }\n return value;\n}\n\nfunction formatHuman(data: unknown, indent = 0): string {\n if (data === null || data === undefined) {\n return 'null';\n }\n\n if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || typeof data === 'bigint') {\n return String(data);\n }\n\n if (Array.isArray(data)) {\n if (data.length === 0) return '(empty)';\n return data.map((item, i) => `${i + 1}. ${formatHuman(item, indent + 2)}`).join('\\n');\n }\n\n if (typeof data === 'object') {\n const entries = Object.entries(data as Record<string, unknown>);\n if (entries.length === 0) return '(empty)';\n const pad = ' '.repeat(indent);\n const maxKeyLen = Math.max(...entries.map(([k]) => k.length));\n return entries\n .map(([key, val]) => {\n const paddedKey = key.padEnd(maxKeyLen);\n const valStr = typeof val === 'object' && val !== null ? '\\n' + formatHuman(val, indent + 2) : String(val);\n return `${pad}${paddedKey} ${valStr}`;\n })\n .join('\\n');\n }\n\n return String(data);\n}\n","import { Command, Flags } from '@oclif/core';\nimport type { OutputFormat } from './lib/types.js';\nimport { loadConfig } from './lib/config.js';\nimport { formatOutput } from './lib/output.js';\n\nexport abstract class BaseCommand extends Command {\n static override baseFlags = {\n output: Flags.string({\n char: 'o',\n description: 'Output format',\n options: ['json', 'human'],\n default: 'human',\n env: 'JAW_OUTPUT',\n }),\n chain: Flags.integer({\n char: 'c',\n description: 'Chain ID',\n env: 'JAW_CHAIN_ID',\n }),\n 'api-key': Flags.string({\n description: 'JAW API key',\n env: 'JAW_API_KEY',\n }),\n yes: Flags.boolean({\n char: 'y',\n description: 'Skip confirmations (for AI agents)',\n default: false,\n }),\n quiet: Flags.boolean({\n char: 'q',\n description: 'Suppress non-essential output',\n default: false,\n }),\n };\n\n protected resolveApiKey(flags: { 'api-key'?: string }): string {\n const apiKey = flags['api-key'] ?? loadConfig().apiKey;\n if (!apiKey) {\n this.error('API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`');\n }\n return apiKey;\n }\n\n protected resolveChainId(flags: { chain?: number }): number {\n const chainId = flags.chain ?? loadConfig().defaultChain;\n if (!chainId) {\n this.error('Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`');\n }\n return chainId;\n }\n\n protected outputResult(data: unknown, format: OutputFormat): void {\n const output = formatOutput(data, format);\n this.log(output);\n }\n}\n","import { BaseCommand } from '../base-command.js';\nimport { createRequire } from 'node:module';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../../package.json') as { version: string };\n\nexport default class Version extends BaseCommand {\n static override description = 'Show the CLI version';\n static override aliases = ['-v'];\n static override flags = {};\n\n async run(): Promise<void> {\n this.log(`@jaw.id/cli/${pkg.version}`);\n }\n}\n"]}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
|
|
6
|
+
// src/commands/x402/log.ts
|
|
7
|
+
var JAW_DIR = path.join(os.homedir(), ".jaw");
|
|
8
|
+
var PATHS = {
|
|
9
|
+
root: JAW_DIR,
|
|
10
|
+
config: path.join(JAW_DIR, "config.json"),
|
|
11
|
+
session: path.join(JAW_DIR, "session.json"),
|
|
12
|
+
relay: path.join(JAW_DIR, "relay.json"),
|
|
13
|
+
keystore: path.join(JAW_DIR, "keystore.json"),
|
|
14
|
+
sessionConfig: path.join(JAW_DIR, "session-config.json"),
|
|
15
|
+
x402Log: path.join(JAW_DIR, "x402-log.jsonl"),
|
|
16
|
+
paymentLock: path.join(JAW_DIR, "x402-payment.lock")
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/lib/config.ts
|
|
20
|
+
function ensureDir(dir) {
|
|
21
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
22
|
+
fs.chmodSync(dir, 448);
|
|
23
|
+
}
|
|
24
|
+
function migrateConfig(config) {
|
|
25
|
+
if (config.paymasterUrl && !config.paymasters) {
|
|
26
|
+
const chainId = config.defaultChain ?? 1;
|
|
27
|
+
config.paymasters = { [chainId]: { url: config.paymasterUrl } };
|
|
28
|
+
delete config.paymasterUrl;
|
|
29
|
+
saveConfig(config);
|
|
30
|
+
}
|
|
31
|
+
return config;
|
|
32
|
+
}
|
|
33
|
+
function loadConfig() {
|
|
34
|
+
if (!fs.existsSync(PATHS.config)) {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
const raw = fs.readFileSync(PATHS.config, "utf-8");
|
|
38
|
+
try {
|
|
39
|
+
const config = JSON.parse(raw);
|
|
40
|
+
return migrateConfig(config);
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function saveConfig(config) {
|
|
48
|
+
ensureDir(PATHS.root);
|
|
49
|
+
fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + "\n", {
|
|
50
|
+
encoding: "utf-8",
|
|
51
|
+
mode: 384
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/lib/output.ts
|
|
56
|
+
function formatOutput(data, format) {
|
|
57
|
+
if (format === "json") {
|
|
58
|
+
return JSON.stringify(data, replaceBigInt, 2);
|
|
59
|
+
}
|
|
60
|
+
return formatHuman(data);
|
|
61
|
+
}
|
|
62
|
+
function replaceBigInt(_key, value) {
|
|
63
|
+
if (typeof value === "bigint") {
|
|
64
|
+
return value.toString();
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
function formatHuman(data, indent = 0) {
|
|
69
|
+
if (data === null || data === void 0) {
|
|
70
|
+
return "null";
|
|
71
|
+
}
|
|
72
|
+
if (typeof data === "string" || typeof data === "number" || typeof data === "boolean" || typeof data === "bigint") {
|
|
73
|
+
return String(data);
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(data)) {
|
|
76
|
+
if (data.length === 0) return "(empty)";
|
|
77
|
+
return data.map((item, i) => `${i + 1}. ${formatHuman(item, indent + 2)}`).join("\n");
|
|
78
|
+
}
|
|
79
|
+
if (typeof data === "object") {
|
|
80
|
+
const entries = Object.entries(data);
|
|
81
|
+
if (entries.length === 0) return "(empty)";
|
|
82
|
+
const pad = " ".repeat(indent);
|
|
83
|
+
const maxKeyLen = Math.max(...entries.map(([k]) => k.length));
|
|
84
|
+
return entries.map(([key, val]) => {
|
|
85
|
+
const paddedKey = key.padEnd(maxKeyLen);
|
|
86
|
+
const valStr = typeof val === "object" && val !== null ? "\n" + formatHuman(val, indent + 2) : String(val);
|
|
87
|
+
return `${pad}${paddedKey} ${valStr}`;
|
|
88
|
+
}).join("\n");
|
|
89
|
+
}
|
|
90
|
+
return String(data);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/base-command.ts
|
|
94
|
+
var BaseCommand = class extends Command {
|
|
95
|
+
static baseFlags = {
|
|
96
|
+
output: Flags.string({
|
|
97
|
+
char: "o",
|
|
98
|
+
description: "Output format",
|
|
99
|
+
options: ["json", "human"],
|
|
100
|
+
default: "human",
|
|
101
|
+
env: "JAW_OUTPUT"
|
|
102
|
+
}),
|
|
103
|
+
chain: Flags.integer({
|
|
104
|
+
char: "c",
|
|
105
|
+
description: "Chain ID",
|
|
106
|
+
env: "JAW_CHAIN_ID"
|
|
107
|
+
}),
|
|
108
|
+
"api-key": Flags.string({
|
|
109
|
+
description: "JAW API key",
|
|
110
|
+
env: "JAW_API_KEY"
|
|
111
|
+
}),
|
|
112
|
+
yes: Flags.boolean({
|
|
113
|
+
char: "y",
|
|
114
|
+
description: "Skip confirmations (for AI agents)",
|
|
115
|
+
default: false
|
|
116
|
+
}),
|
|
117
|
+
quiet: Flags.boolean({
|
|
118
|
+
char: "q",
|
|
119
|
+
description: "Suppress non-essential output",
|
|
120
|
+
default: false
|
|
121
|
+
})
|
|
122
|
+
};
|
|
123
|
+
resolveApiKey(flags) {
|
|
124
|
+
const apiKey = flags["api-key"] ?? loadConfig().apiKey;
|
|
125
|
+
if (!apiKey) {
|
|
126
|
+
this.error("API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`");
|
|
127
|
+
}
|
|
128
|
+
return apiKey;
|
|
129
|
+
}
|
|
130
|
+
resolveChainId(flags) {
|
|
131
|
+
const chainId = flags.chain ?? loadConfig().defaultChain;
|
|
132
|
+
if (!chainId) {
|
|
133
|
+
this.error("Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`");
|
|
134
|
+
}
|
|
135
|
+
return chainId;
|
|
136
|
+
}
|
|
137
|
+
outputResult(data, format) {
|
|
138
|
+
const output = formatOutput(data, format);
|
|
139
|
+
this.log(output);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
function readX402Log(limit) {
|
|
143
|
+
let raw;
|
|
144
|
+
try {
|
|
145
|
+
raw = fs.readFileSync(PATHS.x402Log, "utf-8");
|
|
146
|
+
} catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const entries = raw.split("\n").filter((line) => line.trim().length > 0).map((line) => {
|
|
150
|
+
try {
|
|
151
|
+
return JSON.parse(line);
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}).filter((e) => e !== null);
|
|
156
|
+
return entries;
|
|
157
|
+
}
|
|
158
|
+
function spendFigureOf(entry) {
|
|
159
|
+
if (entry.status !== "paid" && entry.status !== "failed") return 0n;
|
|
160
|
+
const parse = (value) => {
|
|
161
|
+
if (!value) return 0n;
|
|
162
|
+
try {
|
|
163
|
+
const parsed = BigInt(value);
|
|
164
|
+
return parsed > 0n ? parsed : 0n;
|
|
165
|
+
} catch {
|
|
166
|
+
return 0n;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
if (entry.status === "paid") return parse(entry.amount);
|
|
170
|
+
const ceiling = parse(entry.authorized);
|
|
171
|
+
const charge = parse(entry.amount);
|
|
172
|
+
return ceiling > charge ? ceiling : charge;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/x402/amount.ts
|
|
176
|
+
function parseBigInt(value) {
|
|
177
|
+
if (value === void 0 || value === null || value === "") return null;
|
|
178
|
+
try {
|
|
179
|
+
return BigInt(value);
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/lib/terminal.ts
|
|
186
|
+
var INVISIBLE_AND_BIDI = /[\u200B-\u200F\u2028\u2029\u202A-\u202E\u2066-\u2069\uFEFF]/g;
|
|
187
|
+
var LINE_CONTROLS = /[\u0000-\u001F\u007F-\u009F]/g;
|
|
188
|
+
var REPLACEMENT = "\uFFFD";
|
|
189
|
+
var DEFAULT_LINE_LENGTH = 200;
|
|
190
|
+
function bound(text, maxLength) {
|
|
191
|
+
if (text.length <= maxLength) return text;
|
|
192
|
+
return `${text.slice(0, maxLength)}\u2026 (${text.length - maxLength} more characters)`;
|
|
193
|
+
}
|
|
194
|
+
function sanitizeLine(value, maxLength = DEFAULT_LINE_LENGTH) {
|
|
195
|
+
const text = typeof value === "string" ? value : String(value);
|
|
196
|
+
return bound(text.replace(LINE_CONTROLS, REPLACEMENT).replace(INVISIBLE_AND_BIDI, REPLACEMENT), maxLength);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/x402/status-report.ts
|
|
200
|
+
function formatUsdc(base, decimals) {
|
|
201
|
+
if (base === void 0) return "unlimited";
|
|
202
|
+
const value = parseBigInt(base);
|
|
203
|
+
if (value === null) return `${sanitizeLine(base, 32)} (invalid)`;
|
|
204
|
+
const scale = 10n ** BigInt(decimals);
|
|
205
|
+
const whole = value / scale;
|
|
206
|
+
const frac = (value % scale).toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
207
|
+
return `${whole}${frac ? `.${frac}` : ""} USDC`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/x402/asset-registry.ts
|
|
211
|
+
var USDC_BY_NETWORK = {
|
|
212
|
+
"eip155:8453": {
|
|
213
|
+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
214
|
+
chainId: 8453,
|
|
215
|
+
wireNetwork: "eip155:8453",
|
|
216
|
+
usdcName: "USD Coin",
|
|
217
|
+
usdcVersion: "2",
|
|
218
|
+
decimals: 6
|
|
219
|
+
},
|
|
220
|
+
"eip155:84532": {
|
|
221
|
+
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
222
|
+
chainId: 84532,
|
|
223
|
+
wireNetwork: "eip155:84532",
|
|
224
|
+
usdcName: "USDC",
|
|
225
|
+
usdcVersion: "2",
|
|
226
|
+
decimals: 6
|
|
227
|
+
},
|
|
228
|
+
"eip155:137": {
|
|
229
|
+
address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
230
|
+
chainId: 137,
|
|
231
|
+
wireNetwork: "eip155:137",
|
|
232
|
+
usdcName: "USD Coin",
|
|
233
|
+
usdcVersion: "2",
|
|
234
|
+
decimals: 6
|
|
235
|
+
},
|
|
236
|
+
"eip155:80002": {
|
|
237
|
+
address: "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582",
|
|
238
|
+
chainId: 80002,
|
|
239
|
+
wireNetwork: "eip155:80002",
|
|
240
|
+
usdcName: "USDC",
|
|
241
|
+
usdcVersion: "2",
|
|
242
|
+
decimals: 6
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
function usdcForNetwork(network) {
|
|
246
|
+
return Object.hasOwn(USDC_BY_NETWORK, network) ? USDC_BY_NETWORK[network] : void 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/x402/log-view.ts
|
|
250
|
+
function decimalsOf(entry) {
|
|
251
|
+
return (entry.network ? usdcForNetwork(entry.network)?.decimals : void 0) ?? 6;
|
|
252
|
+
}
|
|
253
|
+
function hostOf(url) {
|
|
254
|
+
try {
|
|
255
|
+
return sanitizeLine(new URL(url).host, 80);
|
|
256
|
+
} catch {
|
|
257
|
+
return sanitizeLine(url, 80);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function renderEntry(entry) {
|
|
261
|
+
const when = sanitizeLine(String(entry.at).replace("T", " ").slice(0, 19), 19);
|
|
262
|
+
const counted = spendFigureOf(entry);
|
|
263
|
+
const amount = entry.amount || entry.authorized ? formatUsdc(counted.toString(), decimalsOf(entry)) : "";
|
|
264
|
+
const head = ` ${when} ${sanitizeLine(entry.status, 7).padEnd(7)} ${amount.padStart(12)} ${hostOf(entry.url)}`;
|
|
265
|
+
const detail = [];
|
|
266
|
+
if (entry.topUpAmount) {
|
|
267
|
+
detail.push(`topped up ${formatUsdc(entry.topUpAmount, decimalsOf(entry))}`);
|
|
268
|
+
}
|
|
269
|
+
if (entry.approvalBatchId) detail.push("granted Permit2 its allowance");
|
|
270
|
+
if (entry.txHash) detail.push(sanitizeLine(entry.txHash, 80));
|
|
271
|
+
if (entry.status === "failed" && entry.nonce) detail.push(`nonce ${sanitizeLine(entry.nonce, 80)}`);
|
|
272
|
+
if (entry.reason) detail.push(sanitizeLine(entry.reason, 200));
|
|
273
|
+
return detail.length > 0 ? `${head}
|
|
274
|
+
${" ".repeat(24)}${detail.join(" ")}` : head;
|
|
275
|
+
}
|
|
276
|
+
function renderSummary(entries) {
|
|
277
|
+
const counts = { paid: 0, failed: 0, refused: 0 };
|
|
278
|
+
const spentByScale = /* @__PURE__ */ new Map();
|
|
279
|
+
let unknown = 0;
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
if (Object.hasOwn(counts, entry.status)) counts[entry.status] += 1;
|
|
282
|
+
else unknown += 1;
|
|
283
|
+
const counted = spendFigureOf(entry);
|
|
284
|
+
if (counted > 0n) {
|
|
285
|
+
try {
|
|
286
|
+
const decimals = decimalsOf(entry);
|
|
287
|
+
spentByScale.set(decimals, (spentByScale.get(decimals) ?? 0n) + counted);
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const parts = [`${counts.paid} paid`];
|
|
293
|
+
if (counts.failed > 0) parts.push(`${counts.failed} failed`);
|
|
294
|
+
if (counts.refused > 0) parts.push(`${counts.refused} refused`);
|
|
295
|
+
if (unknown > 0) parts.push(`${unknown} unreadable`);
|
|
296
|
+
const totals = spentByScale.size === 0 ? formatUsdc("0", 6) : [...spentByScale.entries()].map(([decimals, spent]) => formatUsdc(spent.toString(), decimals)).join(" + ");
|
|
297
|
+
return ` ${parts.join(", ")}, ${totals} out`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/commands/x402/log.ts
|
|
301
|
+
var X402Log = class _X402Log extends BaseCommand {
|
|
302
|
+
static description = "Show the local x402 payment ledger: every attempt, paid, failed or refused, with amounts and transactions.";
|
|
303
|
+
static examples = [
|
|
304
|
+
"<%= config.bin %> x402 log",
|
|
305
|
+
"<%= config.bin %> x402 log --limit 20",
|
|
306
|
+
"<%= config.bin %> x402 log --status failed",
|
|
307
|
+
"<%= config.bin %> x402 log --output json"
|
|
308
|
+
];
|
|
309
|
+
static flags = {
|
|
310
|
+
...BaseCommand.baseFlags,
|
|
311
|
+
limit: Flags.integer({ description: "Show only the most recent N entries." }),
|
|
312
|
+
status: Flags.string({
|
|
313
|
+
description: "Show only entries with this outcome.",
|
|
314
|
+
options: ["paid", "failed", "refused"]
|
|
315
|
+
})
|
|
316
|
+
};
|
|
317
|
+
async run() {
|
|
318
|
+
const { flags } = await this.parse(_X402Log);
|
|
319
|
+
const format = flags.output;
|
|
320
|
+
if (flags.limit !== void 0 && flags.limit < 1) {
|
|
321
|
+
this.error(`--limit must be 1 or more (got ${flags.limit}). Omit it to show every entry.`);
|
|
322
|
+
}
|
|
323
|
+
let entries = readX402Log();
|
|
324
|
+
if (flags.status) entries = entries.filter((e) => e.status === flags.status);
|
|
325
|
+
if (flags.limit !== void 0) entries = entries.slice(-flags.limit);
|
|
326
|
+
if (format === "json") {
|
|
327
|
+
this.outputResult(entries, format);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (entries.length === 0) {
|
|
331
|
+
this.log(flags.status ? `No ${flags.status} payments recorded.` : "No payments recorded yet.");
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
for (const entry of entries) {
|
|
335
|
+
this.log(renderEntry(entry));
|
|
336
|
+
}
|
|
337
|
+
this.log("");
|
|
338
|
+
this.log(renderSummary(entries));
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export { X402Log as default };
|
|
343
|
+
//# sourceMappingURL=log.js.map
|
|
344
|
+
//# sourceMappingURL=log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/paths.ts","../../../src/lib/config.ts","../../../src/lib/output.ts","../../../src/base-command.ts","../../../src/x402/ledger.ts","../../../src/x402/amount.ts","../../../src/lib/terminal.ts","../../../src/x402/status-report.ts","../../../src/x402/asset-registry.ts","../../../src/x402/log-view.ts","../../../src/commands/x402/log.ts"],"names":["fs2","Flags"],"mappings":"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","file":"log.js","sourcesContent":["import * as path from 'node:path';\nimport * as os from 'node:os';\n\nconst JAW_DIR = path.join(os.homedir(), '.jaw');\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, 'config.json'),\n session: path.join(JAW_DIR, 'session.json'),\n relay: path.join(JAW_DIR, 'relay.json'),\n keystore: path.join(JAW_DIR, 'keystore.json'),\n sessionConfig: path.join(JAW_DIR, 'session-config.json'),\n x402Log: path.join(JAW_DIR, 'x402-log.jsonl'),\n paymentLock: path.join(JAW_DIR, 'x402-payment.lock'),\n} as const;\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport type { JawConfig, SettableConfigKey } from './types.js';\nimport type { X402Policy, X402PolicyKey } from '../x402/policy.js';\nimport { isValidKeysUrl, isValidRelayUrl } from './validation.js';\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nfunction migrateConfig(config: JawConfig): JawConfig {\n if (config.paymasterUrl && !config.paymasters) {\n const chainId = config.defaultChain ?? 1;\n config.paymasters = { [chainId]: { url: config.paymasterUrl } };\n delete config.paymasterUrl;\n saveConfig(config);\n }\n return config;\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, 'utf-8');\n try {\n const config = JSON.parse(raw) as JawConfig;\n return migrateConfig(config);\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n\n/** Paymaster URLs embed provider API keys as query params (e.g. Pimlico's ?apikey=...). */\nfunction redactUrlSecrets(url: string): string {\n try {\n const parsed = new URL(url);\n for (const key of [...parsed.searchParams.keys()]) {\n parsed.searchParams.set(key, '***');\n }\n return parsed.toString();\n } catch {\n return '***';\n }\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n ...(config.paymasters && {\n paymasters: Object.fromEntries(\n Object.entries(config.paymasters).map(([chainId, pm]) => [\n chainId,\n // context is a free-form object (usually just a sponsorshipPolicyId) but a\n // provider could stash a token there, so mask it rather than hand it to the agent.\n { ...pm, url: redactUrlSecrets(pm.url), context: pm.context ? '***' : undefined },\n ])\n ),\n }),\n };\n}\n\n/**\n * Set one field of the `x402` payment policy. Array fields (allow-lists) are\n * comma-split. Intentionally NOT reachable from the MCP tool surface: an agent\n * must not be able to raise its own spending caps — only a human at the CLI.\n */\nexport function setX402PolicyValue(key: X402PolicyKey, value: string): void {\n const config = loadConfig();\n const x402: X402Policy = { ...(config.x402 ?? {}) };\n if (key === 'maxAmountPerPayment' || key === 'maxTotalPerSession' || key === 'topUpFloat') {\n // These are base-unit amounts read via BigInt() on the hot payment path;\n // reject anything that isn't a non-negative integer here so a bad value\n // can never turn into a per-payment failure later.\n if (!/^\\d+$/.test(value.trim())) {\n throw new Error(`${key} must be a non-negative integer (base units), got: ${value}`);\n }\n x402[key] = value.trim();\n } else {\n x402[key] = value\n .split(',')\n .map((v) => v.trim())\n .filter(Boolean);\n }\n saveConfig({ ...config, x402 });\n}\n\nexport function setConfigValue(key: SettableConfigKey, value: string | number): void {\n if (key === 'keysUrl' && typeof value === 'string' && !isValidKeysUrl(value)) {\n throw new Error(`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`);\n }\n if (key === 'relayUrl' && typeof value === 'string' && !isValidRelayUrl(value)) {\n throw new Error(`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`);\n }\n // Numeric keys: coerce + validate here so EVERY caller is safe. The MCP tool\n // passes raw strings (its schema types value as a string), so without this a\n // `sessionExpiry: \"abc\"` would land as a string and turn `expiry * 86400`\n // into NaN, and a `defaultChain` string would ride into an RPC URL verbatim.\n let toStore: string | number = value;\n if (key === 'defaultChain' || key === 'sessionExpiry') {\n // Strict: reject anything parseInt would silently truncate ('1.5' -> 1,\n // '0x10' -> 0, '10abc' -> 10). Only a bare positive decimal integer passes.\n const n = typeof value === 'number' ? value : /^\\d+$/.test(value.trim()) ? parseInt(value.trim(), 10) : NaN;\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(`${key} must be a positive integer, got: ${JSON.stringify(value)}`);\n }\n toStore = n;\n }\n const config = loadConfig();\n const updated = { ...config, [key]: toStore };\n saveConfig(updated);\n}\n","import type { OutputFormat } from './types.js';\n\nexport function formatOutput(data: unknown, format: OutputFormat): string {\n if (format === 'json') {\n return JSON.stringify(data, replaceBigInt, 2);\n }\n return formatHuman(data);\n}\n\nfunction replaceBigInt(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') {\n return value.toString();\n }\n return value;\n}\n\nfunction formatHuman(data: unknown, indent = 0): string {\n if (data === null || data === undefined) {\n return 'null';\n }\n\n if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || typeof data === 'bigint') {\n return String(data);\n }\n\n if (Array.isArray(data)) {\n if (data.length === 0) return '(empty)';\n return data.map((item, i) => `${i + 1}. ${formatHuman(item, indent + 2)}`).join('\\n');\n }\n\n if (typeof data === 'object') {\n const entries = Object.entries(data as Record<string, unknown>);\n if (entries.length === 0) return '(empty)';\n const pad = ' '.repeat(indent);\n const maxKeyLen = Math.max(...entries.map(([k]) => k.length));\n return entries\n .map(([key, val]) => {\n const paddedKey = key.padEnd(maxKeyLen);\n const valStr = typeof val === 'object' && val !== null ? '\\n' + formatHuman(val, indent + 2) : String(val);\n return `${pad}${paddedKey} ${valStr}`;\n })\n .join('\\n');\n }\n\n return String(data);\n}\n","import { Command, Flags } from '@oclif/core';\nimport type { OutputFormat } from './lib/types.js';\nimport { loadConfig } from './lib/config.js';\nimport { formatOutput } from './lib/output.js';\n\nexport abstract class BaseCommand extends Command {\n static override baseFlags = {\n output: Flags.string({\n char: 'o',\n description: 'Output format',\n options: ['json', 'human'],\n default: 'human',\n env: 'JAW_OUTPUT',\n }),\n chain: Flags.integer({\n char: 'c',\n description: 'Chain ID',\n env: 'JAW_CHAIN_ID',\n }),\n 'api-key': Flags.string({\n description: 'JAW API key',\n env: 'JAW_API_KEY',\n }),\n yes: Flags.boolean({\n char: 'y',\n description: 'Skip confirmations (for AI agents)',\n default: false,\n }),\n quiet: Flags.boolean({\n char: 'q',\n description: 'Suppress non-essential output',\n default: false,\n }),\n };\n\n protected resolveApiKey(flags: { 'api-key'?: string }): string {\n const apiKey = flags['api-key'] ?? loadConfig().apiKey;\n if (!apiKey) {\n this.error('API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`');\n }\n return apiKey;\n }\n\n protected resolveChainId(flags: { chain?: number }): number {\n const chainId = flags.chain ?? loadConfig().defaultChain;\n if (!chainId) {\n this.error('Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`');\n }\n return chainId;\n }\n\n protected outputResult(data: unknown, format: OutputFormat): void {\n const output = formatOutput(data, format);\n this.log(output);\n }\n}\n","import * as fs from 'node:fs';\nimport { PATHS } from '../lib/paths.js';\nimport { ensureDir } from '../lib/config.js';\nimport { errorMessage } from '../lib/errors.js';\n\n/**\n * One line of the append-only x402 payment ledger (`~/.jaw/x402-log.jsonl`).\n * Every payment attempt an agent makes is recorded so spend is auditable and an\n * ambiguous settlement can be reconciled by nonce/txHash after the fact.\n */\nexport interface X402LogEntry {\n /** ISO timestamp of the attempt. */\n at: string;\n url: string;\n /** The paying EOA. */\n payer: string;\n /** paid = settled; failed = signed+sent but settlement failed; refused = never signed. */\n status: 'paid' | 'failed' | 'refused';\n /**\n * What actually left the payer. Under `exact` that is the amount signed for.\n * Under `upto` the server chooses it at settlement, anywhere from zero up to\n * `authorized`, and the receipt is the only place it exists.\n */\n amount?: string;\n /**\n * The ceiling the signature authorized, which is what a live authorization is\n * worth to whoever holds it. Equal to `amount` under `exact`. Absent on\n * entries written before the field existed, where `amount` was both.\n */\n authorized?: string;\n /**\n * When the authorization expires. Recorded but not yet read: reconciling a\n * failed payment means proving its nonce was never consumed and its deadline\n * has passed, and that check cannot be written against entries that never\n * stored the deadline.\n */\n deadline?: string;\n asset?: string;\n network?: string;\n payTo?: string;\n nonce?: string;\n txHash?: string;\n /** Base units refilled into the payer through the permission, when a top-up ran. */\n topUpAmount?: string;\n /** wallet_sendCalls id of that top-up, for on-chain reconciliation. */\n topUpBatchId?: string;\n /**\n * wallet_sendCalls id of the Permit2 approval, when this payment granted one.\n * Never summed with `topUpAmount`: it moves no principal, only the gas the\n * payer was charged for it. Recorded so a userOp the user paid for is not\n * missing from the audit trail.\n */\n approvalBatchId?: string;\n /** Reason for a refused/failed attempt. */\n reason?: string;\n}\n\n/**\n * Append one entry. Never throws — logging must not break a payment.\n *\n * The newline is a PREFIX, not a suffix: a torn write (crash/ENOSPC mid-append)\n * then leaves an incomplete line that the NEXT append starts on a fresh line\n * instead of concatenating onto, so one bad write loses at most its own record,\n * never the following one too.\n *\n * A write failure is surfaced to stderr (not thrown): the caller's payment\n * still succeeds, but the operator needs to know the audit trail — and the\n * restart-time spend-cap seed that reads it — just lost an entry.\n */\nexport function appendX402Log(entry: X402LogEntry): void {\n try {\n ensureDir(PATHS.root);\n fs.appendFileSync(PATHS.x402Log, '\\n' + JSON.stringify(entry), { encoding: 'utf-8', mode: 0o600 });\n } catch (err) {\n const msg = errorMessage(err);\n process.stderr.write(`[jaw] warning: failed to write x402 ledger (${msg}); spend audit/cap may undercount\\n`);\n }\n}\n\n/**\n * Read the ledger, oldest first. `limit` returns only the most recent N entries.\n * Malformed lines are skipped; a missing file is an empty log.\n */\nexport function readX402Log(limit?: number): X402LogEntry[] {\n let raw: string;\n try {\n raw = fs.readFileSync(PATHS.x402Log, 'utf-8');\n } catch {\n return [];\n }\n const entries = raw\n .split('\\n')\n .filter((line) => line.trim().length > 0)\n .map((line) => {\n try {\n return JSON.parse(line) as X402LogEntry;\n } catch {\n return null;\n }\n })\n .filter((e): e is X402LogEntry => e !== null);\n return limit && limit > 0 ? entries.slice(-limit) : entries;\n}\n\n/**\n * What one row contributes to a spend cap.\n *\n * The single definition of the rule, exported because more than one place needs\n * it and the three copies that existed before this had already drifted apart by\n * hand. `jaw x402 log` reports against it, and the caps enforce against it, so\n * the number a user reads and the number that refuses their next payment are\n * the same number.\n *\n * A settled payment costs what settled. A failed one costs the ceiling it\n * authorized, because an authorization that was signed and sent stays spendable\n * up to that ceiling until its nonce is consumed or its deadline passes, and\n * nothing yet proves either. Under `exact` the two figures are equal and this\n * is the rule that has always applied.\n *\n * Every parse failure reads as zero and the failed case takes the larger of the\n * two, so one unparseable field cannot shrink an enforced cap: a torn write or a\n * hand edit can only ever leave the cap where it was or higher. Negatives clamp\n * for the same reason, since `BigInt('-5')` parses fine and would otherwise\n * subtract.\n */\nexport function spendFigureOf(entry: X402LogEntry): bigint {\n if (entry.status !== 'paid' && entry.status !== 'failed') return 0n;\n const parse = (value?: string): bigint => {\n if (!value) return 0n;\n try {\n const parsed = BigInt(value);\n return parsed > 0n ? parsed : 0n;\n } catch {\n return 0n;\n }\n };\n if (entry.status === 'paid') return parse(entry.amount);\n const ceiling = parse(entry.authorized);\n const charge = parse(entry.amount);\n return ceiling > charge ? ceiling : charge;\n}\n\n/**\n * Sum a payer's settled and attempted payments since an ISO instant (its whole\n * history when `since` is omitted).\n *\n * Reading it from the ledger rather than an in-memory counter is what makes a\n * cap survive a process restart, which an agent could otherwise relaunch its way\n * past. What each row costs is `spendFigureOf`.\n */\nexport function sumSpentSince(payerAddress: string, since?: string): bigint {\n const payer = payerAddress.toLowerCase();\n return readX402Log().reduce((total, entry) => {\n if (entry.payer?.toLowerCase() !== payer) return total;\n if (since && entry.at < since) return total;\n return total + spendFigureOf(entry);\n }, 0n);\n}\n\n/**\n * Sum what a payer pulled through the permission since an ISO instant (its whole\n * history when `since` is omitted).\n *\n * Distinct from `sumSpentSince` because the two meter different things: the\n * on-chain allowance is drawn down by the top-up, not by the payment it later\n * funds. With a `topUpFloat` the two run apart by whatever is still sitting in\n * the payer, so measuring the granted per-period cap by payments reads a\n * permission as having more left than it does.\n *\n * Every status counts, refusals included: the pull settled on-chain before the\n * payment it was for was ever attempted, so the allowance is gone either way.\n */\nexport function sumToppedUpSince(payerAddress: string, since?: string): bigint {\n const payer = payerAddress.toLowerCase();\n return readX402Log().reduce((total, entry) => {\n if (!entry.topUpAmount) return total;\n if (entry.payer?.toLowerCase() !== payer) return total;\n if (since && entry.at < since) return total;\n try {\n return total + BigInt(entry.topUpAmount);\n } catch {\n return total; // a hand-edited amount must not take the cap down\n }\n }, 0n);\n}\n","/**\n * Canonical base-unit amount parser. Every place that turns an amount string\n * (from an untrusted 402 challenge or from hand-edited config) into a bigint\n * goes through here, so the failure surface is uniform and a malformed value\n * can never crash a hot path. Sign policy is left to the caller: this returns\n * the parsed value (which may be negative) or null when it is not an integer.\n */\nexport function parseBigInt(value: string | undefined | null): bigint | null {\n // Empty string is \"unset\", not zero: BigInt('') is 0n, but an absent config\n // value must read as null so callers treat it as \"no bound\", not \"cap of 0\".\n if (value === undefined || value === null || value === '') return null;\n try {\n return BigInt(value);\n } catch {\n return null;\n }\n}\n\n/** Parse a non-negative base-unit amount; undefined when absent, invalid, or negative. */\nexport function parseNonNegativeBigInt(value: string | undefined | null): bigint | undefined {\n const parsed = parseBigInt(value);\n return parsed !== null && parsed >= 0n ? parsed : undefined;\n}\n","/**\n * Make server-controlled text safe to print.\n *\n * A paid endpoint controls its response body, and parts of its 402 challenge\n * reach our refusal messages, which are stored in the payment ledger and\n * reprinted by `jaw x402 log`. Printed raw, an escape sequence lets that server\n * erase the line the CLI just wrote and paint its own in place: a resource that\n * was never paid for can render a convincing green \"Paid. 5 USDC\" the CLI never\n * emitted, and once in the ledger it does so on every later read.\n *\n * Two shapes, because the danger differs. A newline inside a one-line field\n * forges an extra row that reads as a genuine record, while inside a response\n * body it is just a newline. Only the human renderers need any of this: JSON\n * output escapes control characters on its own.\n */\n\n/**\n * Characters that are invisible or reorder what follows them, in any context.\n *\n * Bidi overrides (U+202A-U+202E, U+2066-U+2069) are the Trojan Source trick:\n * they flip rendering direction, so an address can display as something other\n * than the bytes that were signed. The zero-width family hides text outright,\n * splitting an address to the eye while leaving it intact to a copy. Neither is\n * a control character in the C0 or C1 sense, so stripping those alone misses\n * both. U+2028 and U+2029 break lines the way a newline does.\n */\nconst INVISIBLE_AND_BIDI = /[\\u200B-\\u200F\\u2028\\u2029\\u202A-\\u202E\\u2066-\\u2069\\uFEFF]/g;\n\n/** C0 except tab and newline, DEL, and the C1 block. For multi-line text. */\n// eslint-disable-next-line no-control-regex\nconst BLOCK_CONTROLS = /[\\u0000-\\u0008\\u000B-\\u001F\\u007F-\\u009F]/g;\n\n/** Every C0 control, DEL and C1. For text that must stay on one line. */\n// eslint-disable-next-line no-control-regex\nconst LINE_CONTROLS = /[\\u0000-\\u001F\\u007F-\\u009F]/g;\n\n/** Left in place of a stripped character, so tampering shows rather than vanishing. */\nconst REPLACEMENT = '\\uFFFD';\n\nexport const DEFAULT_LINE_LENGTH = 200;\n\nfunction bound(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n // Say it was cut. A silently truncated error message reads as a complete one.\n return `${text.slice(0, maxLength)}\\u2026 (${text.length - maxLength} more characters)`;\n}\n\n/**\n * Disarm a value that has to render as a single line: a refusal reason, a\n * network id, a host, a transaction hash.\n *\n * Newlines go with everything else here. A reason carrying one would otherwise\n * open a second line under a log entry, indistinguishable from the real row\n * above it, which is the same forgery the escape sequences allow.\n */\nexport function sanitizeLine(value: unknown, maxLength: number = DEFAULT_LINE_LENGTH): string {\n const text = typeof value === 'string' ? value : String(value);\n return bound(text.replace(LINE_CONTROLS, REPLACEMENT).replace(INVISIBLE_AND_BIDI, REPLACEMENT), maxLength);\n}\n\n/**\n * Disarm a response body, keeping its shape.\n *\n * Newlines and tabs survive, since a body is legitimately multi-line and\n * neither can move the cursor back over text already written. Nothing is\n * truncated: the caller asked for this resource and may well have paid for it,\n * and the read is already bounded upstream by MAX_BODY_BYTES.\n */\nexport function sanitizeBlock(value: unknown): string {\n const text = typeof value === 'string' ? value : String(value);\n return text.replace(BLOCK_CONTROLS, REPLACEMENT).replace(INVISIBLE_AND_BIDI, REPLACEMENT);\n}\n","import { parseBigInt } from './amount.js';\nimport { sanitizeLine } from '../lib/terminal.js';\nimport type { PermissionLiveness } from './permission-onchain.js';\n\n/**\n * Presentation and diagnosis for `jaw x402 status`, kept apart from the command\n * so the rules can be tested without a session, a keystore or a network.\n */\n\n/** Base units to a readable amount, trailing zeros trimmed. */\nexport function formatUsdc(base: string | undefined, decimals: number): string {\n if (base === undefined) return 'unlimited';\n const value = parseBigInt(base);\n // Echoing the raw value put an unvalidated string on screen: amounts reach\n // here from the ledger and from config, both files that can be edited.\n if (value === null) return `${sanitizeLine(base, 32)} (invalid)`;\n const scale = 10n ** BigInt(decimals);\n const whole = value / scale;\n const frac = (value % scale).toString().padStart(decimals, '0').replace(/0+$/, '');\n return `${whole}${frac ? `.${frac}` : ''} USDC`;\n}\n\nexport function formatRemaining(seconds: number): string {\n const days = Math.floor(seconds / 86400);\n if (days > 0) return `${days} day${days === 1 ? '' : 's'} left`;\n const hours = Math.max(0, Math.floor(seconds / 3600));\n return `${hours}h left`;\n}\n\nexport interface StatusFacts {\n expired: boolean;\n /**\n * Defaults to `unknown`, which reports exactly what every session reported\n * before this could be read: the local file, and nothing more.\n */\n liveness?: PermissionLiveness;\n /**\n * True for a session an older CLI created, whose permission was granted to an\n * address separate from the session key. Auto mode refuses those, so a report\n * that stayed quiet about it would call a setup ready that cannot pay.\n */\n outdated?: boolean;\n ownerAddress: string;\n /** Formatted balance, or null when the read failed. */\n ownerBalance: string | null;\n payerBalance: string | null;\n /** False when the session's chain has no USDC in the registry. */\n hasAsset: boolean;\n spent: bigint;\n sessionCap: bigint | null;\n /**\n * The granted per-period cap and what has gone against it in the current\n * window: top-ups pulled through the permission, not payments, because the\n * cap mirrors the on-chain allowance and payments lag it by whatever float\n * the payer holds. Null when no grant seeded one. Reported separately from\n * the session cap because a grant-seeded policy usually has no session cap\n * at all, and checking only that one stayed quiet while the cap that\n * actually binds was exhausted.\n */\n periodCap?: bigint | null;\n periodSpent?: bigint | null;\n /** How the window reads in a sentence, e.g. \"day\" or \"2 weeks\". */\n periodLabel?: string | null;\n /**\n * The gas reserve refills leave in the payer, in the same formatted units as\n * the balances. A payer holding no more than this is holding what the CLI put\n * there to pay userOp fees, so it is not the misdirected-funds case below.\n */\n payerReserve?: number;\n}\n\n/**\n * What is stopping a payment, most likely cause first. Empty when nothing is.\n *\n * The interesting case is an empty owner next to a funded payer: payments still\n * succeed, so nothing looks wrong, but they are spending the payer's own balance\n * instead of pulling through the permission, which means the cap the user\n * granted is not being applied to anything.\n */\nexport function diagnose(facts: StatusFacts): string[] {\n const problems: string[] = [];\n\n if (facts.expired) {\n problems.push('The session expired. Run `jaw session setup --x402`.');\n }\n\n // Only the chain knows this one. Expiry is the same number the local file\n // carries, so it needs no read; a revoke made from keys.jaw.id or from\n // another machine leaves that file saying the session is fine.\n if (facts.liveness === 'revoked') {\n problems.push(\n 'The permission was revoked on chain, so nothing can be pulled through it any more. ' +\n 'Run `jaw session setup --x402` to grant a new one.'\n );\n }\n\n if (facts.liveness === 'unapproved') {\n problems.push(\n 'The chain has no record of this permission being approved. If the session was just created, ' +\n 'the grant may not have been mined yet; otherwise run `jaw session setup --x402`.'\n );\n }\n\n // `mismatch` is deliberately not here. It says the struct on disk does not\n // hash to the granted id, so the chain cannot be asked about this permission,\n // and nothing about the permission itself is wrong: the caps still apply and\n // payments still go through. Putting it in `problems` flipped `ready` to\n // false, which stops a script or an agent paying against a healthy session\n // over a local serialisation problem. It is reported on the permission line\n // instead.\n\n if (facts.outdated) {\n problems.push(\n 'This session was created by an older CLI and cannot pay: its permission belongs to an address ' +\n 'separate from the session key. Run `jaw session setup --x402` to recreate it.'\n );\n }\n\n if (!facts.hasAsset) {\n problems.push('This chain has no USDC configured, so x402 payments cannot be made on it.');\n }\n\n if (facts.hasAsset && facts.ownerBalance === null) {\n // Blaming the connection while the other balance rendered fine reads as a\n // contradiction, so only do it when both reads failed.\n problems.push(\n facts.payerBalance === null\n ? 'Could not read balances. Check the API key and network.'\n : `Could not read the owner balance for ${facts.ownerAddress}. The address may be malformed.`\n );\n }\n\n if (facts.ownerBalance !== null && Number(facts.ownerBalance) === 0) {\n // Above the reserve, because refills deliberately leave that much in the\n // payer to pay userOp fees with. Reading it back as funds sent to the wrong\n // address would tell the user to move money the CLI put there on purpose.\n const payerHoldsMoreThanItsGas =\n facts.payerBalance !== null && Number(facts.payerBalance) > (facts.payerReserve ?? 0);\n problems.push(\n payerHoldsMoreThanItsGas\n ? 'The owner account is empty but the payer holds USDC. Payments will work, but they bypass the ' +\n 'permission, so the cap you granted is not applying. Move the funds to the owner.'\n : 'The owner account holds no USDC, so there is nothing to pay with.'\n );\n }\n\n // Before the session cap: this is the one that mirrors the permission, so when\n // both are exhausted it is the more useful thing to say, and it frees up on its\n // own rather than needing a config change.\n if (facts.periodCap != null && facts.periodSpent != null && facts.periodSpent >= facts.periodCap) {\n problems.push(\n `The granted allowance for this ${facts.periodLabel ?? 'period'} is used up. It resets at the end of ` +\n 'the window, or grant a new permission with `jaw session setup --x402`.'\n );\n }\n\n if (facts.sessionCap !== null && facts.spent >= facts.sessionCap) {\n problems.push(\n 'The session cap is used up. Raise it with `jaw config set x402.maxTotalPerSession <base units>` ' +\n 'or start a new session.'\n );\n }\n\n return problems;\n}\n","// USDC asset registry, mirrored from the backend's\n// `apps/ens/src/external/payment/asset-registry.ts`. Keep this in sync when the\n// server adds a chain. `wireNetwork` is the CAIP-2 id used on the x402 v2 wire.\n\nexport interface UsdcAsset {\n address: `0x${string}`;\n chainId: number;\n wireNetwork: string;\n /** EIP-712 domain `name` for this deployment's USDC. */\n usdcName: string;\n /** EIP-712 domain `version`. */\n usdcVersion: string;\n /**\n * Token decimals. Every USDC deployment here is 6, but carrying it on the\n * registry entry (rather than a literal `6` at the format site) keeps the\n * source of truth in one place and is ready for a non-6-decimal asset when\n * the registry grows past USDC. Reading it off-chain from the contract is\n * deliberately avoided: the registry is a controlled allowlist, so an extra\n * RPC round-trip and trusting a token's self-reported decimals buy nothing.\n */\n decimals: number;\n}\n\nexport const USDC_BY_NETWORK: Record<string, UsdcAsset> = {\n 'eip155:8453': {\n address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n chainId: 8453,\n wireNetwork: 'eip155:8453',\n usdcName: 'USD Coin',\n usdcVersion: '2',\n decimals: 6,\n },\n 'eip155:84532': {\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n chainId: 84532,\n wireNetwork: 'eip155:84532',\n usdcName: 'USDC',\n usdcVersion: '2',\n decimals: 6,\n },\n 'eip155:137': {\n address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n chainId: 137,\n wireNetwork: 'eip155:137',\n usdcName: 'USD Coin',\n usdcVersion: '2',\n decimals: 6,\n },\n 'eip155:80002': {\n address: '0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582',\n chainId: 80002,\n wireNetwork: 'eip155:80002',\n usdcName: 'USDC',\n usdcVersion: '2',\n decimals: 6,\n },\n};\n\n/**\n * Look up USDC metadata by CAIP-2 network id, or `undefined` if unsupported.\n *\n * Own keys only. The network reaches here from a 402 challenge and from the\n * Bazaar catalogue, both untrusted, and a plain index answers `constructor` or\n * `toString` with something off the prototype: callers then read `.address` off\n * a function and throw where they expected an unsupported network.\n */\nexport function usdcForNetwork(network: string): UsdcAsset | undefined {\n return Object.hasOwn(USDC_BY_NETWORK, network) ? USDC_BY_NETWORK[network] : undefined;\n}\n","import { formatUsdc } from './status-report.js';\nimport { usdcForNetwork } from './asset-registry.js';\nimport { sanitizeLine } from '../lib/terminal.js';\nimport { spendFigureOf, type X402LogEntry } from './ledger.js';\n\n/**\n * Rendering for `jaw x402 log`, kept apart from the command so the accounting in\n * the summary can be tested without a ledger file on disk.\n */\n\n/** Scale by the network the entry was denominated in, not a global assumption. */\nexport function decimalsOf(entry: X402LogEntry): number {\n return (entry.network ? usdcForNetwork(entry.network)?.decimals : undefined) ?? 6;\n}\n\nexport function hostOf(url: string): string {\n try {\n return sanitizeLine(new URL(url).host, 80);\n } catch {\n // Not a URL, so nothing has been parsed away: bound and disarm it.\n return sanitizeLine(url, 80);\n }\n}\n\nexport function renderEntry(entry: X402LogEntry): string {\n // Everything here is read back from a file, so nothing is trusted for being\n // ours originally: a tampered ledger must not be able to paint a row either.\n const when = sanitizeLine(String(entry.at).replace('T', ' ').slice(0, 19), 19);\n // What the caps counted for this row, not what the server charged: on a\n // failed attempt those differ, and the figure a user needs to see is the one\n // that will refuse their next payment.\n const counted = spendFigureOf(entry);\n const amount = entry.amount || entry.authorized ? formatUsdc(counted.toString(), decimalsOf(entry)) : '';\n const head = ` ${when} ${sanitizeLine(entry.status, 7).padEnd(7)} ${amount.padStart(12)} ${hostOf(entry.url)}`;\n\n const detail: string[] = [];\n // A top-up moved user funds through the permission. Always visible, even on an\n // attempt that then failed, since that money left the account regardless.\n if (entry.topUpAmount) {\n detail.push(`topped up ${formatUsdc(entry.topUpAmount, decimalsOf(entry))}`);\n }\n // The Permit2 approval moved no principal, only the gas the payer was charged\n // for it, so it is named rather than totalled.\n if (entry.approvalBatchId) detail.push('granted Permit2 its allowance');\n if (entry.txHash) detail.push(sanitizeLine(entry.txHash, 80));\n // A failed settlement may still have been broadcast: the nonce is what makes\n // it reconcilable on chain, so surface it exactly where it is ambiguous.\n if (entry.status === 'failed' && entry.nonce) detail.push(`nonce ${sanitizeLine(entry.nonce, 80)}`);\n // Stored server text: an endpoint that got refused once would\n // otherwise repaint this line on every later `x402 log`.\n if (entry.reason) detail.push(sanitizeLine(entry.reason, 200));\n\n return detail.length > 0 ? `${head}\\n${' '.repeat(24)}${detail.join(' ')}` : head;\n}\n\nexport function renderSummary(entries: X402LogEntry[]): string {\n const counts = { paid: 0, failed: 0, refused: 0 };\n // Only settled and attempted payments count as money out; a refusal never\n // signed anything. Same rule the spend caps use.\n //\n // Totalled per decimals scale rather than as one number. Base units from\n // tokens with different decimals are not the same unit, so adding them and\n // formatting the result with whichever entry happened to come last would\n // print a confident, wrong figure. Every USDC in the registry is 6 decimals\n // today, so this is a single group in practice and the guard costs nothing.\n const spentByScale = new Map<number, bigint>();\n let unknown = 0;\n for (const entry of entries) {\n // An unrecognised status used to land on `counts` as a stray key and vanish\n // from the tally, so a malformed row silently shrank the reported total.\n // Own keys only: `in` walks the prototype, so a row saying `constructor`\n // took the counted branch, landed on a key nothing reads, and disappeared\n // from both tallies.\n if (Object.hasOwn(counts, entry.status)) counts[entry.status] += 1;\n else unknown += 1;\n const counted = spendFigureOf(entry);\n if (counted > 0n) {\n try {\n const decimals = decimalsOf(entry);\n spentByScale.set(decimals, (spentByScale.get(decimals) ?? 0n) + counted);\n } catch {\n /* a hand-edited asset must not break the summary */\n }\n }\n }\n\n const parts = [`${counts.paid} paid`];\n if (counts.failed > 0) parts.push(`${counts.failed} failed`);\n if (counts.refused > 0) parts.push(`${counts.refused} refused`);\n if (unknown > 0) parts.push(`${unknown} unreadable`);\n\n const totals =\n spentByScale.size === 0\n ? formatUsdc('0', 6)\n : [...spentByScale.entries()].map(([decimals, spent]) => formatUsdc(spent.toString(), decimals)).join(' + ');\n\n return ` ${parts.join(', ')}, ${totals} out`;\n}\n","import { Flags } from '@oclif/core';\nimport { BaseCommand } from '../../base-command.js';\nimport { readX402Log } from '../../x402/ledger.js';\nimport { renderEntry, renderSummary } from '../../x402/log-view.js';\nimport type { OutputFormat } from '../../lib/types.js';\n\n/**\n * What the agent bought, from the terminal.\n *\n * The ledger is the audit trail for money an agent moved without being watched,\n * so the answer to \"what did it spend overnight\" should not require wiring an\n * MCP client to ask.\n */\nexport default class X402Log extends BaseCommand {\n static override description =\n 'Show the local x402 payment ledger: every attempt, paid, failed or refused, with amounts and transactions.';\n\n static override examples = [\n '<%= config.bin %> x402 log',\n '<%= config.bin %> x402 log --limit 20',\n '<%= config.bin %> x402 log --status failed',\n '<%= config.bin %> x402 log --output json',\n ];\n\n static override flags = {\n ...BaseCommand.baseFlags,\n limit: Flags.integer({ description: 'Show only the most recent N entries.' }),\n status: Flags.string({\n description: 'Show only entries with this outcome.',\n options: ['paid', 'failed', 'refused'],\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(X402Log);\n const format = flags.output as OutputFormat;\n\n // `slice(-0)` is `slice(0)`, so a zero limit would quietly dump the whole\n // ledger, which is the worst possible reading of \"show me none\". Reject it\n // rather than pick a meaning; omitting the flag is how you ask for all.\n if (flags.limit !== undefined && flags.limit < 1) {\n this.error(`--limit must be 1 or more (got ${flags.limit}). Omit it to show every entry.`);\n }\n\n // Filter before limiting, so `--limit 5 --status failed` means the last five\n // failures rather than the failures among the last five entries.\n let entries = readX402Log();\n if (flags.status) entries = entries.filter((e) => e.status === flags.status);\n if (flags.limit !== undefined) entries = entries.slice(-flags.limit);\n\n if (format === 'json') {\n this.outputResult(entries, format);\n return;\n }\n\n if (entries.length === 0) {\n this.log(flags.status ? `No ${flags.status} payments recorded.` : 'No payments recorded yet.');\n return;\n }\n\n for (const entry of entries) {\n this.log(renderEntry(entry));\n }\n\n this.log('');\n this.log(renderSummary(entries));\n }\n}\n"]}
|