@jaw.id/cli 0.0.8 → 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 (56) hide show
  1. package/dist/base-command.js +26 -2
  2. package/dist/base-command.js.map +1 -1
  3. package/dist/commands/config/set.js +19 -7
  4. package/dist/commands/config/set.js.map +1 -1
  5. package/dist/commands/config/show.js +26 -2
  6. package/dist/commands/config/show.js.map +1 -1
  7. package/dist/commands/config/write.js +288 -0
  8. package/dist/commands/config/write.js.map +1 -0
  9. package/dist/commands/disconnect.js +29 -5
  10. package/dist/commands/disconnect.js.map +1 -1
  11. package/dist/commands/mcp/index.js +34 -17
  12. package/dist/commands/mcp/index.js.map +1 -1
  13. package/dist/commands/rpc/call.js +205 -37
  14. package/dist/commands/rpc/call.js.map +1 -1
  15. package/dist/commands/session/revoke.js +727 -0
  16. package/dist/commands/session/revoke.js.map +1 -0
  17. package/dist/commands/session/setup.js +960 -0
  18. package/dist/commands/session/setup.js.map +1 -0
  19. package/dist/commands/session/status.js +206 -0
  20. package/dist/commands/session/status.js.map +1 -0
  21. package/dist/commands/version.js +26 -2
  22. package/dist/commands/version.js.map +1 -1
  23. package/dist/index.js +28 -13
  24. package/dist/index.js.map +1 -1
  25. package/dist/lib/bridge-singleton.js +35 -13
  26. package/dist/lib/bridge-singleton.js.map +1 -1
  27. package/dist/lib/config.js +15 -7
  28. package/dist/lib/config.js.map +1 -1
  29. package/dist/lib/keystore.js +64 -0
  30. package/dist/lib/keystore.js.map +1 -0
  31. package/dist/lib/output.js +1 -12
  32. package/dist/lib/output.js.map +1 -1
  33. package/dist/lib/paths.js +3 -1
  34. package/dist/lib/paths.js.map +1 -1
  35. package/dist/lib/session-bridge.js +168 -0
  36. package/dist/lib/session-bridge.js.map +1 -0
  37. package/dist/lib/session-config.js +52 -0
  38. package/dist/lib/session-config.js.map +1 -0
  39. package/dist/lib/validation.js +62 -27
  40. package/dist/lib/validation.js.map +1 -1
  41. package/dist/lib/ws-bridge.js +4 -3
  42. package/dist/lib/ws-bridge.js.map +1 -1
  43. package/dist/mcp/handlers/config.js +16 -4
  44. package/dist/mcp/handlers/config.js.map +1 -1
  45. package/dist/mcp/handlers/daemon.js +29 -5
  46. package/dist/mcp/handlers/daemon.js.map +1 -1
  47. package/dist/mcp/handlers/rpc.js +49 -25
  48. package/dist/mcp/handlers/rpc.js.map +1 -1
  49. package/dist/mcp/server.js +34 -17
  50. package/dist/mcp/server.js.map +1 -1
  51. package/dist/mcp/tools.js +1 -1
  52. package/dist/mcp/tools.js.map +1 -1
  53. package/oclif.manifest.json +304 -4
  54. package/package.json +18 -1
  55. package/dist/lib/session-store.js +0 -80
  56. 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) {
@@ -285,6 +308,10 @@ var WSBridge = class {
285
308
  expectingKeyExchange = true;
286
309
  onBrowserNeeded().catch(() => {
287
310
  });
311
+ } else if (!onBrowserNeeded) {
312
+ clearTimeout(timer);
313
+ ws.close();
314
+ reject(new Error("Browser not connected \u2014 relay session is stale."));
288
315
  }
289
316
  } else if (msg.type === "browser_connected") {
290
317
  expectingKeyExchange = true;
@@ -360,9 +387,6 @@ var WSBridge = class {
360
387
  this.sendRaw(ws, serialized);
361
388
  });
362
389
  }
363
- isOpen() {
364
- return this.ws?.readyState === WebSocket.OPEN;
365
- }
366
390
  async shutdown() {
367
391
  this.disposed = true;
368
392
  if (this.ws?.readyState === WebSocket.OPEN && this.sharedSecret) {