@jaw.id/cli 0.0.8 → 0.1.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.
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
@@ -143,7 +143,7 @@
143
143
  "description": "Set one or more configuration values. Accepts key=value pairs or a single key value.",
144
144
  "examples": [
145
145
  "<%= config.bin %> config set apiKey=your-api-key defaultChain=8453",
146
- "<%= config.bin %> config set ens=yourdomain.eth paymasterUrl=https://your-paymaster.com",
146
+ "<%= config.bin %> config set ens=yourdomain.eth sessionExpiry=14",
147
147
  "<%= config.bin %> config set apiKey your-api-key"
148
148
  ],
149
149
  "flags": {
@@ -280,6 +280,77 @@
280
280
  "show.js"
281
281
  ]
282
282
  },
283
+ "config:write": {
284
+ "aliases": [],
285
+ "args": {},
286
+ "description": "Write full config from inline JSON or a file path to ~/.jaw/config.json.",
287
+ "examples": [
288
+ "<%= config.bin %> config write '{\"apiKey\":\"...\",\"defaultChain\":84532}'",
289
+ "<%= config.bin %> config write ./jaw-config.json"
290
+ ],
291
+ "flags": {
292
+ "output": {
293
+ "char": "o",
294
+ "description": "Output format",
295
+ "env": "JAW_OUTPUT",
296
+ "name": "output",
297
+ "default": "human",
298
+ "hasDynamicHelp": false,
299
+ "multiple": false,
300
+ "options": [
301
+ "json",
302
+ "human"
303
+ ],
304
+ "type": "option"
305
+ },
306
+ "chain": {
307
+ "char": "c",
308
+ "description": "Chain ID",
309
+ "env": "JAW_CHAIN_ID",
310
+ "name": "chain",
311
+ "hasDynamicHelp": false,
312
+ "multiple": false,
313
+ "type": "option"
314
+ },
315
+ "api-key": {
316
+ "description": "JAW API key",
317
+ "env": "JAW_API_KEY",
318
+ "name": "api-key",
319
+ "hasDynamicHelp": false,
320
+ "multiple": false,
321
+ "type": "option"
322
+ },
323
+ "yes": {
324
+ "char": "y",
325
+ "description": "Skip confirmations (for AI agents)",
326
+ "name": "yes",
327
+ "allowNo": false,
328
+ "type": "boolean"
329
+ },
330
+ "quiet": {
331
+ "char": "q",
332
+ "description": "Suppress non-essential output",
333
+ "name": "quiet",
334
+ "allowNo": false,
335
+ "type": "boolean"
336
+ }
337
+ },
338
+ "hasDynamicHelp": false,
339
+ "hiddenAliases": [],
340
+ "id": "config:write",
341
+ "pluginAlias": "@jaw.id/cli",
342
+ "pluginName": "@jaw.id/cli",
343
+ "pluginType": "core",
344
+ "strict": false,
345
+ "enableJsonFlag": false,
346
+ "isESM": true,
347
+ "relativePath": [
348
+ "dist",
349
+ "commands",
350
+ "config",
351
+ "write.js"
352
+ ]
353
+ },
283
354
  "mcp": {
284
355
  "aliases": [],
285
356
  "args": {},
@@ -318,13 +389,14 @@
318
389
  "required": false
319
390
  }
320
391
  },
321
- "description": "Execute any JAW.id RPC method via the browser bridge.",
392
+ "description": "Execute any JAW.id RPC method via the browser bridge or local session key.",
322
393
  "examples": [
323
394
  "<%= config.bin %> rpc call wallet_sendCalls '{\"calls\":[{\"to\":\"0x...\",\"value\":\"0x0\"}]}'",
324
395
  "<%= config.bin %> rpc call personal_sign '\"Hello World\"'",
325
396
  "<%= config.bin %> rpc call wallet_getAssets",
326
397
  "<%= config.bin %> rpc call eth_requestAccounts",
327
- "<%= config.bin %> rpc call wallet_getCallsStatus '\"0x...\"'"
398
+ "<%= config.bin %> rpc call wallet_getCallsStatus '\"0x...\"'",
399
+ "<%= config.bin %> rpc call wallet_sendCalls '{\"calls\":[...]}' --session"
328
400
  ],
329
401
  "flags": {
330
402
  "output": {
@@ -380,6 +452,14 @@
380
452
  "hasDynamicHelp": false,
381
453
  "multiple": false,
382
454
  "type": "option"
455
+ },
456
+ "session": {
457
+ "char": "s",
458
+ "description": "Use local session key (auto mode)",
459
+ "env": "JAW_SESSION",
460
+ "name": "session",
461
+ "allowNo": false,
462
+ "type": "boolean"
383
463
  }
384
464
  },
385
465
  "hasDynamicHelp": false,
@@ -397,7 +477,227 @@
397
477
  "rpc",
398
478
  "call.js"
399
479
  ]
480
+ },
481
+ "session:revoke": {
482
+ "aliases": [],
483
+ "args": {},
484
+ "description": "Revoke on-chain permission and delete local session key.",
485
+ "flags": {
486
+ "output": {
487
+ "char": "o",
488
+ "description": "Output format",
489
+ "env": "JAW_OUTPUT",
490
+ "name": "output",
491
+ "default": "human",
492
+ "hasDynamicHelp": false,
493
+ "multiple": false,
494
+ "options": [
495
+ "json",
496
+ "human"
497
+ ],
498
+ "type": "option"
499
+ },
500
+ "chain": {
501
+ "char": "c",
502
+ "description": "Chain ID",
503
+ "env": "JAW_CHAIN_ID",
504
+ "name": "chain",
505
+ "hasDynamicHelp": false,
506
+ "multiple": false,
507
+ "type": "option"
508
+ },
509
+ "api-key": {
510
+ "description": "JAW API key",
511
+ "env": "JAW_API_KEY",
512
+ "name": "api-key",
513
+ "hasDynamicHelp": false,
514
+ "multiple": false,
515
+ "type": "option"
516
+ },
517
+ "yes": {
518
+ "char": "y",
519
+ "description": "Skip confirmations (for AI agents)",
520
+ "name": "yes",
521
+ "allowNo": false,
522
+ "type": "boolean"
523
+ },
524
+ "quiet": {
525
+ "char": "q",
526
+ "description": "Suppress non-essential output",
527
+ "name": "quiet",
528
+ "allowNo": false,
529
+ "type": "boolean"
530
+ }
531
+ },
532
+ "hasDynamicHelp": false,
533
+ "hiddenAliases": [],
534
+ "id": "session:revoke",
535
+ "pluginAlias": "@jaw.id/cli",
536
+ "pluginName": "@jaw.id/cli",
537
+ "pluginType": "core",
538
+ "strict": true,
539
+ "enableJsonFlag": false,
540
+ "isESM": true,
541
+ "relativePath": [
542
+ "dist",
543
+ "commands",
544
+ "session",
545
+ "revoke.js"
546
+ ]
547
+ },
548
+ "session:setup": {
549
+ "aliases": [],
550
+ "args": {},
551
+ "description": "Generate a session key and grant scoped on-chain permissions (one-time browser approval).",
552
+ "examples": [
553
+ "<%= config.bin %> session setup --chain 84532",
554
+ "<%= config.bin %> session setup --permissions '{\"calls\":[...]}' --expiry 14",
555
+ "<%= config.bin %> session setup --permissions ./permissions.json"
556
+ ],
557
+ "flags": {
558
+ "output": {
559
+ "char": "o",
560
+ "description": "Output format",
561
+ "env": "JAW_OUTPUT",
562
+ "name": "output",
563
+ "default": "human",
564
+ "hasDynamicHelp": false,
565
+ "multiple": false,
566
+ "options": [
567
+ "json",
568
+ "human"
569
+ ],
570
+ "type": "option"
571
+ },
572
+ "chain": {
573
+ "char": "c",
574
+ "description": "Chain ID",
575
+ "env": "JAW_CHAIN_ID",
576
+ "name": "chain",
577
+ "hasDynamicHelp": false,
578
+ "multiple": false,
579
+ "type": "option"
580
+ },
581
+ "api-key": {
582
+ "description": "JAW API key",
583
+ "env": "JAW_API_KEY",
584
+ "name": "api-key",
585
+ "hasDynamicHelp": false,
586
+ "multiple": false,
587
+ "type": "option"
588
+ },
589
+ "yes": {
590
+ "char": "y",
591
+ "description": "Skip confirmations (for AI agents)",
592
+ "name": "yes",
593
+ "allowNo": false,
594
+ "type": "boolean"
595
+ },
596
+ "quiet": {
597
+ "char": "q",
598
+ "description": "Suppress non-essential output",
599
+ "name": "quiet",
600
+ "allowNo": false,
601
+ "type": "boolean"
602
+ },
603
+ "permissions": {
604
+ "description": "Permission scope (inline JSON or file path). Overrides config.permissions.",
605
+ "name": "permissions",
606
+ "hasDynamicHelp": false,
607
+ "multiple": false,
608
+ "type": "option"
609
+ },
610
+ "expiry": {
611
+ "description": "Permission expiry in days. Overrides config.sessionExpiry.",
612
+ "name": "expiry",
613
+ "hasDynamicHelp": false,
614
+ "multiple": false,
615
+ "type": "option"
616
+ }
617
+ },
618
+ "hasDynamicHelp": false,
619
+ "hiddenAliases": [],
620
+ "id": "session:setup",
621
+ "pluginAlias": "@jaw.id/cli",
622
+ "pluginName": "@jaw.id/cli",
623
+ "pluginType": "core",
624
+ "strict": true,
625
+ "enableJsonFlag": false,
626
+ "isESM": true,
627
+ "relativePath": [
628
+ "dist",
629
+ "commands",
630
+ "session",
631
+ "setup.js"
632
+ ]
633
+ },
634
+ "session:status": {
635
+ "aliases": [],
636
+ "args": {},
637
+ "description": "Show current session status (address, permissions, expiry).",
638
+ "flags": {
639
+ "output": {
640
+ "char": "o",
641
+ "description": "Output format",
642
+ "env": "JAW_OUTPUT",
643
+ "name": "output",
644
+ "default": "human",
645
+ "hasDynamicHelp": false,
646
+ "multiple": false,
647
+ "options": [
648
+ "json",
649
+ "human"
650
+ ],
651
+ "type": "option"
652
+ },
653
+ "chain": {
654
+ "char": "c",
655
+ "description": "Chain ID",
656
+ "env": "JAW_CHAIN_ID",
657
+ "name": "chain",
658
+ "hasDynamicHelp": false,
659
+ "multiple": false,
660
+ "type": "option"
661
+ },
662
+ "api-key": {
663
+ "description": "JAW API key",
664
+ "env": "JAW_API_KEY",
665
+ "name": "api-key",
666
+ "hasDynamicHelp": false,
667
+ "multiple": false,
668
+ "type": "option"
669
+ },
670
+ "yes": {
671
+ "char": "y",
672
+ "description": "Skip confirmations (for AI agents)",
673
+ "name": "yes",
674
+ "allowNo": false,
675
+ "type": "boolean"
676
+ },
677
+ "quiet": {
678
+ "char": "q",
679
+ "description": "Suppress non-essential output",
680
+ "name": "quiet",
681
+ "allowNo": false,
682
+ "type": "boolean"
683
+ }
684
+ },
685
+ "hasDynamicHelp": false,
686
+ "hiddenAliases": [],
687
+ "id": "session:status",
688
+ "pluginAlias": "@jaw.id/cli",
689
+ "pluginName": "@jaw.id/cli",
690
+ "pluginType": "core",
691
+ "strict": true,
692
+ "enableJsonFlag": false,
693
+ "isESM": true,
694
+ "relativePath": [
695
+ "dist",
696
+ "commands",
697
+ "session",
698
+ "status.js"
699
+ ]
400
700
  }
401
701
  },
402
- "version": "0.0.8"
702
+ "version": "0.1.1"
403
703
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jaw.id/cli",
3
3
  "description": "CLI + MCP Server for JAW.id smart accounts — for humans and AI agents",
4
- "version": "0.0.8",
4
+ "version": "0.1.1",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -50,11 +50,26 @@
50
50
  },
51
51
  "mcp": {
52
52
  "description": "Start the MCP server for AI agents"
53
+ },
54
+ "session": {
55
+ "description": "Manage session keys for autonomous AI agents"
53
56
  }
54
57
  }
55
58
  },
56
59
  "nx": {
57
60
  "targets": {
61
+ "typecheck": {
62
+ "executor": "nx:run-commands",
63
+ "options": {
64
+ "cwd": "{projectRoot}",
65
+ "command": "NODE_OPTIONS='--max-old-space-size=8192' tsc --noEmit -p tsconfig.lib.json"
66
+ },
67
+ "cache": true,
68
+ "inputs": [
69
+ "default",
70
+ "^production"
71
+ ]
72
+ },
58
73
  "build": {
59
74
  "executor": "nx:run-commands",
60
75
  "options": {
@@ -89,9 +104,11 @@
89
104
  }
90
105
  },
91
106
  "dependencies": {
107
+ "@jaw.id/core": "0.4.0",
92
108
  "@modelcontextprotocol/sdk": "^1.12.1",
93
109
  "@oclif/core": "^4.3.0",
94
110
  "open": "^10.1.0",
111
+ "viem": "^2.38.2",
95
112
  "ws": "^8.18.0",
96
113
  "zod": "^3.24.0"
97
114
  },
@@ -1,80 +0,0 @@
1
- import * as fs2 from 'fs';
2
- import * as path from 'path';
3
- import * as os from 'os';
4
-
5
- // src/lib/session-store.ts
6
- var JAW_DIR = path.join(os.homedir(), ".jaw");
7
- var PATHS = {
8
- root: JAW_DIR,
9
- config: path.join(JAW_DIR, "config.json"),
10
- session: path.join(JAW_DIR, "session.json"),
11
- relay: path.join(JAW_DIR, "relay.json")
12
- };
13
-
14
- // src/lib/validation.ts
15
- function isValidAddress(value) {
16
- return /^0x[0-9a-fA-F]{40}$/.test(value);
17
- }
18
-
19
- // src/lib/config.ts
20
- function ensureDir(dir) {
21
- fs2.mkdirSync(dir, { recursive: true, mode: 448 });
22
- fs2.chmodSync(dir, 448);
23
- }
24
-
25
- // src/lib/session-store.ts
26
- var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
27
- function isValidSessionShape(session) {
28
- if (!session || typeof session !== "object") return false;
29
- const s = session;
30
- return typeof s.address === "string" && isValidAddress(s.address) && typeof s.chainId === "number" && Number.isInteger(s.chainId) && s.chainId > 0 && typeof s.connectedAt === "string" && !Number.isNaN(Date.parse(s.connectedAt)) && typeof s.expiresAt === "string" && !Number.isNaN(Date.parse(s.expiresAt));
31
- }
32
- function loadSession() {
33
- try {
34
- if (!fs2.existsSync(PATHS.session)) return null;
35
- const raw = fs2.readFileSync(PATHS.session, "utf-8");
36
- const parsed = JSON.parse(raw);
37
- if (!isValidSessionShape(parsed)) {
38
- clearSession();
39
- return null;
40
- }
41
- if (!isSessionValid(parsed)) {
42
- clearSession();
43
- return null;
44
- }
45
- return parsed;
46
- } catch {
47
- return null;
48
- }
49
- }
50
- function saveSession(session) {
51
- ensureDir(PATHS.root);
52
- fs2.writeFileSync(PATHS.session, JSON.stringify(session, null, 2) + "\n", {
53
- encoding: "utf-8",
54
- mode: 384
55
- });
56
- }
57
- function clearSession() {
58
- try {
59
- if (fs2.existsSync(PATHS.session)) {
60
- fs2.unlinkSync(PATHS.session);
61
- }
62
- } catch {
63
- }
64
- }
65
- function isSessionValid(session) {
66
- return new Date(session.expiresAt).getTime() > Date.now();
67
- }
68
- function createSession(address, chainId) {
69
- const now = /* @__PURE__ */ new Date();
70
- return {
71
- address,
72
- chainId,
73
- connectedAt: now.toISOString(),
74
- expiresAt: new Date(now.getTime() + SESSION_TTL_MS).toISOString()
75
- };
76
- }
77
-
78
- export { clearSession, createSession, isSessionValid, loadSession, saveSession };
79
- //# sourceMappingURL=session-store.js.map
80
- //# sourceMappingURL=session-store.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/lib/paths.ts","../../src/lib/validation.ts","../../src/lib/config.ts","../../src/lib/session-store.ts"],"names":["fs"],"mappings":"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","file":"session-store.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} as const;\n","export function isValidAddress(value: string): boolean {\n return /^0x[0-9a-fA-F]{40}$/.test(value);\n}\n\nexport function isValidHex(value: string): boolean {\n return /^0x[0-9a-fA-F]*$/.test(value);\n}\n\nexport function isValidChainId(value: string | number): boolean {\n const num = typeof value === 'string' ? parseInt(value, 10) : value;\n return Number.isInteger(num) && num > 0;\n}\n\nexport function parseChainId(value: string): number {\n const num = parseInt(value, 10);\n if (!isValidChainId(num)) {\n throw new Error(`Invalid chain ID: ${value}`);\n }\n return num;\n}\n\nexport function assertAddress(value: string, label = 'address'): `0x${string}` {\n if (!isValidAddress(value)) {\n throw new Error(`Invalid ${label}: ${value}`);\n }\n return value as `0x${string}`;\n}\n\nexport function parseWei(raw: string, label = 'value'): bigint {\n try {\n return BigInt(raw);\n } catch {\n throw new Error(`Invalid ${label}: \"${raw}\" is not a valid wei amount`);\n }\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\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 return JSON.parse(raw) as JawConfig;\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 getConfigValue(key: keyof JawConfig): string | number | undefined {\n const config = loadConfig();\n return config[key];\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","/**\n * Persists CLI session state to ~/.jaw/session.json\n * Avoids re-authenticating on every CLI invocation.\n */\n\nimport * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport { ensureDir } from './config.js';\nimport { isValidAddress } from './validation.js';\n\nexport interface CLISession {\n readonly address: string;\n readonly chainId: number;\n readonly connectedAt: string;\n readonly expiresAt: string;\n}\n\nconst SESSION_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nfunction isValidSessionShape(session: unknown): session is CLISession {\n if (!session || typeof session !== 'object') return false;\n const s = session as Record<string, unknown>;\n return (\n typeof s.address === 'string' &&\n isValidAddress(s.address) &&\n typeof s.chainId === 'number' &&\n Number.isInteger(s.chainId) &&\n s.chainId > 0 &&\n typeof s.connectedAt === 'string' &&\n !Number.isNaN(Date.parse(s.connectedAt)) &&\n typeof s.expiresAt === 'string' &&\n !Number.isNaN(Date.parse(s.expiresAt))\n );\n}\n\nexport function loadSession(): CLISession | null {\n try {\n if (!fs.existsSync(PATHS.session)) return null;\n const raw = fs.readFileSync(PATHS.session, 'utf-8');\n const parsed = JSON.parse(raw) as unknown;\n if (!isValidSessionShape(parsed)) {\n clearSession();\n return null;\n }\n if (!isSessionValid(parsed)) {\n clearSession();\n return null;\n }\n return parsed;\n } catch {\n return null;\n }\n}\n\nexport function saveSession(session: CLISession): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.session, JSON.stringify(session, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n\nexport function clearSession(): void {\n try {\n if (fs.existsSync(PATHS.session)) {\n fs.unlinkSync(PATHS.session);\n }\n } catch {\n // ignore\n }\n}\n\nexport function isSessionValid(session: CLISession): boolean {\n return new Date(session.expiresAt).getTime() > Date.now();\n}\n\nexport function createSession(address: string, chainId: number): CLISession {\n const now = new Date();\n return {\n address,\n chainId,\n connectedAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + SESSION_TTL_MS).toISOString(),\n };\n}\n"]}