@jaw.id/cli 0.0.7 → 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.
Files changed (62) hide show
  1. package/README.md +24 -24
  2. package/dist/base-command.js +28 -8
  3. package/dist/base-command.js.map +1 -1
  4. package/dist/commands/config/set.js +28 -38
  5. package/dist/commands/config/set.js.map +1 -1
  6. package/dist/commands/config/show.js +29 -12
  7. package/dist/commands/config/show.js.map +1 -1
  8. package/dist/commands/config/write.js +288 -0
  9. package/dist/commands/config/write.js.map +1 -0
  10. package/dist/commands/disconnect.js +42 -54
  11. package/dist/commands/disconnect.js.map +1 -1
  12. package/dist/commands/mcp/index.js +62 -102
  13. package/dist/commands/mcp/index.js.map +1 -1
  14. package/dist/commands/rpc/call.js +218 -92
  15. package/dist/commands/rpc/call.js.map +1 -1
  16. package/dist/commands/session/revoke.js +727 -0
  17. package/dist/commands/session/revoke.js.map +1 -0
  18. package/dist/commands/session/setup.js +960 -0
  19. package/dist/commands/session/setup.js.map +1 -0
  20. package/dist/commands/session/status.js +206 -0
  21. package/dist/commands/session/status.js.map +1 -0
  22. package/dist/commands/version.js +28 -8
  23. package/dist/commands/version.js.map +1 -1
  24. package/dist/index.js +42 -67
  25. package/dist/index.js.map +1 -1
  26. package/dist/lib/bridge-singleton.js +47 -61
  27. package/dist/lib/bridge-singleton.js.map +1 -1
  28. package/dist/lib/config.js +17 -13
  29. package/dist/lib/config.js.map +1 -1
  30. package/dist/lib/crypto.js +3 -15
  31. package/dist/lib/crypto.js.map +1 -1
  32. package/dist/lib/keystore.js +64 -0
  33. package/dist/lib/keystore.js.map +1 -0
  34. package/dist/lib/output.js +1 -16
  35. package/dist/lib/output.js.map +1 -1
  36. package/dist/lib/paths.js +3 -1
  37. package/dist/lib/paths.js.map +1 -1
  38. package/dist/lib/session-bridge.js +168 -0
  39. package/dist/lib/session-bridge.js.map +1 -0
  40. package/dist/lib/session-config.js +52 -0
  41. package/dist/lib/session-config.js.map +1 -0
  42. package/dist/lib/validation.js +62 -27
  43. package/dist/lib/validation.js.map +1 -1
  44. package/dist/lib/ws-bridge.js +15 -46
  45. package/dist/lib/ws-bridge.js.map +1 -1
  46. package/dist/mcp/handlers/config.js +28 -29
  47. package/dist/mcp/handlers/config.js.map +1 -1
  48. package/dist/mcp/handlers/daemon.js +41 -51
  49. package/dist/mcp/handlers/daemon.js.map +1 -1
  50. package/dist/mcp/handlers/resources.js +1 -3
  51. package/dist/mcp/handlers/resources.js.map +1 -1
  52. package/dist/mcp/handlers/rpc.js +63 -79
  53. package/dist/mcp/handlers/rpc.js.map +1 -1
  54. package/dist/mcp/helpers.js.map +1 -1
  55. package/dist/mcp/server.js +61 -99
  56. package/dist/mcp/server.js.map +1 -1
  57. package/dist/mcp/tools.js +2 -4
  58. package/dist/mcp/tools.js.map +1 -1
  59. package/oclif.manifest.json +304 -4
  60. package/package.json +19 -1
  61. package/dist/lib/session-store.js +0 -80
  62. package/dist/lib/session-store.js.map +0 -1
@@ -0,0 +1,288 @@
1
+ import * as fs from 'fs';
2
+ import { Command, Flags } from '@oclif/core';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+
6
+ // src/commands/config/write.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
+ };
16
+
17
+ // src/lib/validation.ts
18
+ var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
19
+ var SELECTOR_RE = /^0x[0-9a-fA-F]{8}$/;
20
+ var HEX_RE = /^0x[0-9a-fA-F]+$/;
21
+ var VALID_SPEND_UNITS = /* @__PURE__ */ new Set(["minute", "hour", "day", "week", "month", "year", "forever"]);
22
+ function parsePermissionsConfig(raw) {
23
+ const errors = [];
24
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
25
+ throw new Error("Invalid permissions:\n - Must be an object");
26
+ }
27
+ const obj = raw;
28
+ const calls = obj.calls;
29
+ const spends = obj.spends;
30
+ if (!calls && !spends) {
31
+ throw new Error('Invalid permissions:\n - Must include at least "calls" or "spends"');
32
+ }
33
+ if (calls !== void 0) {
34
+ if (!Array.isArray(calls) || calls.length === 0) {
35
+ errors.push("calls: Must be a non-empty array");
36
+ } else {
37
+ for (let i = 0; i < calls.length; i++) {
38
+ const c = calls[i];
39
+ if (!c || typeof c !== "object") {
40
+ errors.push(`calls.${i}: Must be an object`);
41
+ continue;
42
+ }
43
+ if (typeof c.target !== "string" || !ADDRESS_RE.test(c.target)) {
44
+ errors.push(`calls.${i}.target: Must be a valid 0x address (40 hex chars)`);
45
+ }
46
+ if (c.selector !== void 0 && (typeof c.selector !== "string" || !SELECTOR_RE.test(c.selector))) {
47
+ errors.push(`calls.${i}.selector: Must be a 4-byte hex selector (e.g. 0xa9059cbb)`);
48
+ }
49
+ }
50
+ }
51
+ }
52
+ if (spends !== void 0) {
53
+ if (!Array.isArray(spends) || spends.length === 0) {
54
+ errors.push("spends: Must be a non-empty array");
55
+ } else {
56
+ for (let i = 0; i < spends.length; i++) {
57
+ const s = spends[i];
58
+ if (!s || typeof s !== "object") {
59
+ errors.push(`spends.${i}: Must be an object`);
60
+ continue;
61
+ }
62
+ if (typeof s.token !== "string" || !ADDRESS_RE.test(s.token)) {
63
+ errors.push(`spends.${i}.token: Must be a valid 0x address (40 hex chars)`);
64
+ }
65
+ if (typeof s.allowance !== "string" || !HEX_RE.test(s.allowance)) {
66
+ errors.push(`spends.${i}.allowance: Must be a non-empty 0x hex value`);
67
+ }
68
+ if (typeof s.unit !== "string" || !VALID_SPEND_UNITS.has(s.unit)) {
69
+ errors.push(`spends.${i}.unit: Must be one of: ${[...VALID_SPEND_UNITS].join(", ")}`);
70
+ }
71
+ if (s.multiplier !== void 0 && (!Number.isInteger(s.multiplier) || s.multiplier < 1)) {
72
+ errors.push(`spends.${i}.multiplier: Must be a positive integer`);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ if (errors.length > 0) {
78
+ throw new Error(`Invalid permissions:
79
+ ${errors.map((e) => ` - ${e}`).join("\n")}`);
80
+ }
81
+ return raw;
82
+ }
83
+ function isValidKeysUrl(url) {
84
+ try {
85
+ const parsed = new URL(url);
86
+ const isTrustedHost = parsed.hostname.endsWith(".jaw.id") || parsed.hostname === "jaw.id" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
87
+ const isSecure = parsed.protocol === "https:" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
88
+ return isTrustedHost && isSecure;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+ function isValidRelayUrl(url) {
94
+ try {
95
+ const parsed = new URL(url);
96
+ const isTrustedHost = parsed.hostname.endsWith(".jaw.id") || parsed.hostname === "jaw.id" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
97
+ const isSecure = parsed.protocol === "wss:" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
98
+ const isWebSocket = parsed.protocol === "wss:" || parsed.protocol === "ws:";
99
+ return isTrustedHost && isSecure && isWebSocket;
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+
105
+ // src/lib/config.ts
106
+ function ensureDir(dir) {
107
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
108
+ fs.chmodSync(dir, 448);
109
+ }
110
+ function migrateConfig(config) {
111
+ if (config.paymasterUrl && !config.paymasters) {
112
+ const chainId = config.defaultChain ?? 1;
113
+ config.paymasters = { [chainId]: { url: config.paymasterUrl } };
114
+ delete config.paymasterUrl;
115
+ saveConfig(config);
116
+ }
117
+ return config;
118
+ }
119
+ function loadConfig() {
120
+ if (!fs.existsSync(PATHS.config)) {
121
+ return {};
122
+ }
123
+ const raw = fs.readFileSync(PATHS.config, "utf-8");
124
+ try {
125
+ const config = JSON.parse(raw);
126
+ return migrateConfig(config);
127
+ } catch {
128
+ throw new Error(
129
+ `Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
130
+ );
131
+ }
132
+ }
133
+ function saveConfig(config) {
134
+ ensureDir(PATHS.root);
135
+ fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + "\n", {
136
+ encoding: "utf-8",
137
+ mode: 384
138
+ });
139
+ }
140
+
141
+ // src/lib/output.ts
142
+ function formatOutput(data, format) {
143
+ if (format === "json") {
144
+ return JSON.stringify(data, replaceBigInt, 2);
145
+ }
146
+ return formatHuman(data);
147
+ }
148
+ function replaceBigInt(_key, value) {
149
+ if (typeof value === "bigint") {
150
+ return value.toString();
151
+ }
152
+ return value;
153
+ }
154
+ function formatHuman(data, indent = 0) {
155
+ if (data === null || data === void 0) {
156
+ return "null";
157
+ }
158
+ if (typeof data === "string" || typeof data === "number" || typeof data === "boolean" || typeof data === "bigint") {
159
+ return String(data);
160
+ }
161
+ if (Array.isArray(data)) {
162
+ if (data.length === 0) return "(empty)";
163
+ return data.map((item, i) => `${i + 1}. ${formatHuman(item, indent + 2)}`).join("\n");
164
+ }
165
+ if (typeof data === "object") {
166
+ const entries = Object.entries(data);
167
+ if (entries.length === 0) return "(empty)";
168
+ const pad = " ".repeat(indent);
169
+ const maxKeyLen = Math.max(...entries.map(([k]) => k.length));
170
+ return entries.map(([key, val]) => {
171
+ const paddedKey = key.padEnd(maxKeyLen);
172
+ const valStr = typeof val === "object" && val !== null ? "\n" + formatHuman(val, indent + 2) : String(val);
173
+ return `${pad}${paddedKey} ${valStr}`;
174
+ }).join("\n");
175
+ }
176
+ return String(data);
177
+ }
178
+
179
+ // src/base-command.ts
180
+ var BaseCommand = class extends Command {
181
+ static baseFlags = {
182
+ output: Flags.string({
183
+ char: "o",
184
+ description: "Output format",
185
+ options: ["json", "human"],
186
+ default: "human",
187
+ env: "JAW_OUTPUT"
188
+ }),
189
+ chain: Flags.integer({
190
+ char: "c",
191
+ description: "Chain ID",
192
+ env: "JAW_CHAIN_ID"
193
+ }),
194
+ "api-key": Flags.string({
195
+ description: "JAW API key",
196
+ env: "JAW_API_KEY"
197
+ }),
198
+ yes: Flags.boolean({
199
+ char: "y",
200
+ description: "Skip confirmations (for AI agents)",
201
+ default: false
202
+ }),
203
+ quiet: Flags.boolean({
204
+ char: "q",
205
+ description: "Suppress non-essential output",
206
+ default: false
207
+ })
208
+ };
209
+ resolveApiKey(flags) {
210
+ const apiKey = flags["api-key"] ?? loadConfig().apiKey;
211
+ if (!apiKey) {
212
+ this.error("API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`");
213
+ }
214
+ return apiKey;
215
+ }
216
+ resolveChainId(flags) {
217
+ const chainId = flags.chain ?? loadConfig().defaultChain;
218
+ if (!chainId) {
219
+ this.error("Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`");
220
+ }
221
+ return chainId;
222
+ }
223
+ outputResult(data, format) {
224
+ const output = formatOutput(data, format);
225
+ this.log(output);
226
+ }
227
+ };
228
+
229
+ // src/commands/config/write.ts
230
+ var ConfigWrite = class _ConfigWrite extends BaseCommand {
231
+ static description = "Write full config from inline JSON or a file path to ~/.jaw/config.json.";
232
+ static examples = [
233
+ `<%= config.bin %> config write '{"apiKey":"...","defaultChain":84532}'`,
234
+ "<%= config.bin %> config write ./jaw-config.json"
235
+ ];
236
+ static strict = false;
237
+ static flags = {
238
+ ...BaseCommand.baseFlags
239
+ };
240
+ async run() {
241
+ const { argv } = await this.parse(_ConfigWrite);
242
+ const rawArgs = argv;
243
+ if (rawArgs.length === 0) {
244
+ this.error(`Provide inline JSON or a file path.
245
+ Usage: jaw config write '{"apiKey":"..."}'`);
246
+ }
247
+ const input = rawArgs.join("").trim();
248
+ let raw;
249
+ if (input.startsWith("{")) {
250
+ raw = input;
251
+ } else {
252
+ if (!fs.existsSync(input)) {
253
+ this.error(`File not found: ${input}`);
254
+ }
255
+ raw = fs.readFileSync(input, "utf-8");
256
+ }
257
+ let config;
258
+ try {
259
+ config = JSON.parse(raw);
260
+ } catch {
261
+ try {
262
+ const compact = raw.replace(/\s+/g, "");
263
+ config = JSON.parse(compact);
264
+ } catch {
265
+ this.error("Invalid JSON");
266
+ }
267
+ }
268
+ if (config.keysUrl && !isValidKeysUrl(config.keysUrl)) {
269
+ this.error(`Untrusted keysUrl: ${config.keysUrl}. Must be a *.jaw.id domain (HTTPS) or localhost.`);
270
+ }
271
+ if (config.relayUrl && !isValidRelayUrl(config.relayUrl)) {
272
+ this.error(`Untrusted relayUrl: ${config.relayUrl}. Must be wss://*.jaw.id or ws://localhost.`);
273
+ }
274
+ if (config.permissions) {
275
+ try {
276
+ parsePermissionsConfig(config.permissions);
277
+ } catch (error) {
278
+ this.error(error.message);
279
+ }
280
+ }
281
+ saveConfig(config);
282
+ this.log("Config written to ~/.jaw/config.json");
283
+ }
284
+ };
285
+
286
+ export { ConfigWrite as default };
287
+ //# sourceMappingURL=write.js.map
288
+ //# sourceMappingURL=write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/paths.ts","../../../src/lib/validation.ts","../../../src/lib/config.ts","../../../src/lib/output.ts","../../../src/base-command.ts","../../../src/commands/config/write.ts"],"names":["fs2"],"mappings":"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","file":"write.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 type { PermissionsConfig } from './types.js';\n\nconst ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;\nconst SELECTOR_RE = /^0x[0-9a-fA-F]{8}$/;\nconst HEX_RE = /^0x[0-9a-fA-F]+$/;\nconst VALID_SPEND_UNITS = new Set(['minute', 'hour', 'day', 'week', 'month', 'year', 'forever']);\n\nexport function parsePermissionsConfig(raw: unknown): PermissionsConfig {\n const errors: string[] = [];\n\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new Error('Invalid permissions:\\n - Must be an object');\n }\n\n const obj = raw as Record<string, unknown>;\n const calls = obj.calls as unknown[] | undefined;\n const spends = obj.spends as unknown[] | undefined;\n\n if (!calls && !spends) {\n throw new Error('Invalid permissions:\\n - Must include at least \"calls\" or \"spends\"');\n }\n\n if (calls !== undefined) {\n if (!Array.isArray(calls) || calls.length === 0) {\n errors.push('calls: Must be a non-empty array');\n } else {\n for (let i = 0; i < calls.length; i++) {\n const c = calls[i] as Record<string, unknown>;\n if (!c || typeof c !== 'object') {\n errors.push(`calls.${i}: Must be an object`);\n continue;\n }\n if (typeof c.target !== 'string' || !ADDRESS_RE.test(c.target)) {\n errors.push(`calls.${i}.target: Must be a valid 0x address (40 hex chars)`);\n }\n if (c.selector !== undefined && (typeof c.selector !== 'string' || !SELECTOR_RE.test(c.selector))) {\n errors.push(`calls.${i}.selector: Must be a 4-byte hex selector (e.g. 0xa9059cbb)`);\n }\n }\n }\n }\n\n if (spends !== undefined) {\n if (!Array.isArray(spends) || spends.length === 0) {\n errors.push('spends: Must be a non-empty array');\n } else {\n for (let i = 0; i < spends.length; i++) {\n const s = spends[i] as Record<string, unknown>;\n if (!s || typeof s !== 'object') {\n errors.push(`spends.${i}: Must be an object`);\n continue;\n }\n if (typeof s.token !== 'string' || !ADDRESS_RE.test(s.token)) {\n errors.push(`spends.${i}.token: Must be a valid 0x address (40 hex chars)`);\n }\n if (typeof s.allowance !== 'string' || !HEX_RE.test(s.allowance)) {\n errors.push(`spends.${i}.allowance: Must be a non-empty 0x hex value`);\n }\n if (typeof s.unit !== 'string' || !VALID_SPEND_UNITS.has(s.unit)) {\n errors.push(`spends.${i}.unit: Must be one of: ${[...VALID_SPEND_UNITS].join(', ')}`);\n }\n if (s.multiplier !== undefined && (!Number.isInteger(s.multiplier) || (s.multiplier as number) < 1)) {\n errors.push(`spends.${i}.multiplier: Must be a positive integer`);\n }\n }\n }\n }\n\n if (errors.length > 0) {\n throw new Error(`Invalid permissions:\\n${errors.map((e) => ` - ${e}`).join('\\n')}`);\n }\n\n return raw as PermissionsConfig;\n}\n\nexport function isValidKeysUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n const isTrustedHost =\n parsed.hostname.endsWith('.jaw.id') ||\n parsed.hostname === 'jaw.id' ||\n parsed.hostname === 'localhost' ||\n parsed.hostname === '127.0.0.1';\n const isSecure = parsed.protocol === 'https:' || parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';\n return isTrustedHost && isSecure;\n } catch {\n return false;\n }\n}\n\nexport function isValidRelayUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n const isTrustedHost =\n parsed.hostname.endsWith('.jaw.id') ||\n parsed.hostname === 'jaw.id' ||\n parsed.hostname === 'localhost' ||\n parsed.hostname === '127.0.0.1';\n const isSecure = parsed.protocol === 'wss:' || parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';\n const isWebSocket = parsed.protocol === 'wss:' || parsed.protocol === 'ws:';\n return isTrustedHost && isSecure && isWebSocket;\n } catch {\n return false;\n }\n}\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\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\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 * as fs from 'node:fs';\nimport { BaseCommand } from '../../base-command.js';\nimport { saveConfig } from '../../lib/config.js';\nimport type { JawConfig } from '../../lib/types.js';\nimport { isValidKeysUrl, isValidRelayUrl, parsePermissionsConfig } from '../../lib/validation.js';\n\nexport default class ConfigWrite extends BaseCommand {\n static override description = 'Write full config from inline JSON or a file path to ~/.jaw/config.json.';\n\n static override examples = [\n '<%= config.bin %> config write \\'{\"apiKey\":\"...\",\"defaultChain\":84532}\\'',\n '<%= config.bin %> config write ./jaw-config.json',\n ];\n\n static override strict = false;\n\n static override flags = {\n ...BaseCommand.baseFlags,\n };\n\n async run(): Promise<void> {\n const { argv } = await this.parse(ConfigWrite);\n const rawArgs = argv as string[];\n\n if (rawArgs.length === 0) {\n this.error('Provide inline JSON or a file path.\\nUsage: jaw config write \\'{\"apiKey\":\"...\"}\\'');\n }\n\n // Join all args (handles terminal line wrapping)\n const input = rawArgs.join('').trim();\n\n let raw: string;\n if (input.startsWith('{')) {\n raw = input;\n } else {\n if (!fs.existsSync(input)) {\n this.error(`File not found: ${input}`);\n }\n raw = fs.readFileSync(input, 'utf-8');\n }\n\n let config: JawConfig;\n try {\n config = JSON.parse(raw) as JawConfig;\n } catch {\n // Terminal wrapping may insert spaces/newlines inside strings — try removing all whitespace\n try {\n const compact = raw.replace(/\\s+/g, '');\n config = JSON.parse(compact) as JawConfig;\n } catch {\n this.error('Invalid JSON');\n }\n }\n\n if (config.keysUrl && !isValidKeysUrl(config.keysUrl)) {\n this.error(`Untrusted keysUrl: ${config.keysUrl}. Must be a *.jaw.id domain (HTTPS) or localhost.`);\n }\n if (config.relayUrl && !isValidRelayUrl(config.relayUrl)) {\n this.error(`Untrusted relayUrl: ${config.relayUrl}. Must be wss://*.jaw.id or ws://localhost.`);\n }\n if (config.permissions) {\n try {\n parsePermissionsConfig(config.permissions);\n } catch (error) {\n this.error((error as Error).message);\n }\n }\n\n saveConfig(config);\n this.log('Config written to ~/.jaw/config.json');\n }\n}\n"]}
@@ -11,23 +11,46 @@ var PATHS = {
11
11
  root: JAW_DIR,
12
12
  config: path.join(JAW_DIR, "config.json"),
13
13
  session: path.join(JAW_DIR, "session.json"),
14
- relay: path.join(JAW_DIR, "relay.json")
14
+ relay: path.join(JAW_DIR, "relay.json"),
15
+ keystore: path.join(JAW_DIR, "keystore.json"),
16
+ sessionConfig: path.join(JAW_DIR, "session-config.json")
15
17
  };
16
18
 
17
19
  // src/lib/config.ts
20
+ function ensureDir(dir) {
21
+ fs2.mkdirSync(dir, { recursive: true, mode: 448 });
22
+ fs2.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
+ }
18
33
  function loadConfig() {
19
34
  if (!fs2.existsSync(PATHS.config)) {
20
35
  return {};
21
36
  }
22
37
  const raw = fs2.readFileSync(PATHS.config, "utf-8");
23
38
  try {
24
- return JSON.parse(raw);
39
+ const config = JSON.parse(raw);
40
+ return migrateConfig(config);
25
41
  } catch {
26
42
  throw new Error(
27
43
  `Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
28
44
  );
29
45
  }
30
46
  }
47
+ function saveConfig(config) {
48
+ ensureDir(PATHS.root);
49
+ fs2.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + "\n", {
50
+ encoding: "utf-8",
51
+ mode: 384
52
+ });
53
+ }
31
54
 
32
55
  // src/lib/output.ts
33
56
  function formatOutput(data, format) {
@@ -100,18 +123,14 @@ var BaseCommand = class extends Command {
100
123
  resolveApiKey(flags) {
101
124
  const apiKey = flags["api-key"] ?? loadConfig().apiKey;
102
125
  if (!apiKey) {
103
- this.error(
104
- "API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`"
105
- );
126
+ this.error("API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`");
106
127
  }
107
128
  return apiKey;
108
129
  }
109
130
  resolveChainId(flags) {
110
131
  const chainId = flags.chain ?? loadConfig().defaultChain;
111
132
  if (!chainId) {
112
- this.error(
113
- "Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`"
114
- );
133
+ this.error("Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`");
115
134
  }
116
135
  return chainId;
117
136
  }
@@ -135,11 +154,7 @@ async function deriveSharedSecret(privateKey, peerPublicKey) {
135
154
  async function encryptMessage(sharedSecret, payload) {
136
155
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
137
156
  const plaintext = new TextEncoder().encode(JSON.stringify(payload));
138
- const cipherBuf = await subtle.encrypt(
139
- { name: "AES-GCM", iv },
140
- sharedSecret,
141
- plaintext
142
- );
157
+ const cipherBuf = await subtle.encrypt({ name: "AES-GCM", iv }, sharedSecret, plaintext);
143
158
  return {
144
159
  iv: bufferToBase64(iv),
145
160
  ciphertext: bufferToBase64(new Uint8Array(cipherBuf))
@@ -148,11 +163,7 @@ async function encryptMessage(sharedSecret, payload) {
148
163
  async function decryptMessage(sharedSecret, envelope) {
149
164
  const iv = Buffer.from(envelope.iv, "base64");
150
165
  const ciphertext = Buffer.from(envelope.ciphertext, "base64");
151
- const plainBuf = await subtle.decrypt(
152
- { name: "AES-GCM", iv },
153
- sharedSecret,
154
- ciphertext
155
- );
166
+ const plainBuf = await subtle.decrypt({ name: "AES-GCM", iv }, sharedSecret, ciphertext);
156
167
  return JSON.parse(new TextDecoder().decode(plainBuf));
157
168
  }
158
169
  async function importKeyFromHex(type, hex) {
@@ -236,11 +247,7 @@ var WSBridge = class {
236
247
  let expectingKeyExchange = !this.peerPublicKeyHex;
237
248
  const timer = setTimeout(() => {
238
249
  ws.close();
239
- reject(
240
- new Error(
241
- "Browser did not connect in time.\nRun `jaw disconnect` then try again."
242
- )
243
- );
250
+ reject(new Error("Browser did not connect in time.\nRun `jaw disconnect` then try again."));
244
251
  }, 3e4);
245
252
  const sendEncryptedInit = async () => {
246
253
  if (!this.sharedSecret) return;
@@ -263,10 +270,7 @@ var WSBridge = class {
263
270
  if (!msg) return;
264
271
  if (msg.type === "encrypted" && this.sharedSecret) {
265
272
  try {
266
- const inner = await decryptMessage(
267
- this.sharedSecret,
268
- msg
269
- );
273
+ const inner = await decryptMessage(this.sharedSecret, msg);
270
274
  if (inner.type === "ready") {
271
275
  clearTimeout(readyTimer);
272
276
  ws.off("message", onMsg);
@@ -304,6 +308,10 @@ var WSBridge = class {
304
308
  expectingKeyExchange = true;
305
309
  onBrowserNeeded().catch(() => {
306
310
  });
311
+ } else if (!onBrowserNeeded) {
312
+ clearTimeout(timer);
313
+ ws.close();
314
+ reject(new Error("Browser not connected \u2014 relay session is stale."));
307
315
  }
308
316
  } else if (msg.type === "browser_connected") {
309
317
  expectingKeyExchange = true;
@@ -353,9 +361,7 @@ var WSBridge = class {
353
361
  return new Promise((resolve, reject) => {
354
362
  const timer = setTimeout(() => {
355
363
  reject(
356
- new Error(
357
- `Request timed out after ${this.timeout / 1e3}s. Did you complete the action in the browser?`
358
- )
364
+ new Error(`Request timed out after ${this.timeout / 1e3}s. Did you complete the action in the browser?`)
359
365
  );
360
366
  this.close();
361
367
  }, this.timeout);
@@ -363,10 +369,7 @@ var WSBridge = class {
363
369
  const msg = safeParse(data);
364
370
  if (!msg || msg.type !== "encrypted" || !this.sharedSecret) return;
365
371
  try {
366
- const inner = await decryptMessage(
367
- this.sharedSecret,
368
- msg
369
- );
372
+ const inner = await decryptMessage(this.sharedSecret, msg);
370
373
  if (inner.type === "rpc_response" && inner.id === id) {
371
374
  clearTimeout(timer);
372
375
  ws.off("message", onMessage);
@@ -374,11 +377,7 @@ var WSBridge = class {
374
377
  resolve(inner.data);
375
378
  } else {
376
379
  const err = inner.error;
377
- reject(
378
- new Error(
379
- err ? `[${err.code}] ${err.message}` : "Request failed"
380
- )
381
- );
380
+ reject(new Error(err ? `[${err.code}] ${err.message}` : "Request failed"));
382
381
  }
383
382
  }
384
383
  } catch {
@@ -388,9 +387,6 @@ var WSBridge = class {
388
387
  this.sendRaw(ws, serialized);
389
388
  });
390
389
  }
391
- isOpen() {
392
- return this.ws?.readyState === WebSocket.OPEN;
393
- }
394
390
  async shutdown() {
395
391
  this.disposed = true;
396
392
  if (this.ws?.readyState === WebSocket.OPEN && this.sharedSecret) {
@@ -398,10 +394,7 @@ var WSBridge = class {
398
394
  const envelope = await encryptMessage(this.sharedSecret, {
399
395
  type: "shutdown"
400
396
  });
401
- this.sendRaw(
402
- this.ws,
403
- JSON.stringify({ type: "encrypted", ...envelope })
404
- );
397
+ this.sendRaw(this.ws, JSON.stringify({ type: "encrypted", ...envelope }));
405
398
  } catch {
406
399
  }
407
400
  }
@@ -479,10 +472,8 @@ var WSBridge = class {
479
472
  this.reconnectAttempts++;
480
473
  setTimeout(() => {
481
474
  if (this.disposed) return;
482
- this.connectInternal(this.onBrowserNeeded, this.onPeerKeyChanged).catch(
483
- () => {
484
- }
485
- );
475
+ this.connectInternal(this.onBrowserNeeded, this.onPeerKeyChanged).catch(() => {
476
+ });
486
477
  }, delay);
487
478
  }
488
479
  /** Send a raw string over the WebSocket, enforcing message size limits. */
@@ -492,10 +483,7 @@ var WSBridge = class {
492
483
  async deriveSecret() {
493
484
  if (!this.peerPublicKeyHex) return;
494
485
  const privateKey = await importKeyFromHex("private", this.privateKeyHex);
495
- const peerPublicKey = await importKeyFromHex(
496
- "public",
497
- this.peerPublicKeyHex
498
- );
486
+ const peerPublicKey = await importKeyFromHex("public", this.peerPublicKeyHex);
499
487
  this.sharedSecret = await deriveSharedSecret(privateKey, peerPublicKey);
500
488
  }
501
489
  };