@jaw.id/cli 0.0.2

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 (50) hide show
  1. package/README.md +239 -0
  2. package/bin/run.js +5 -0
  3. package/dist/base-command.js +124 -0
  4. package/dist/base-command.js.map +1 -0
  5. package/dist/commands/config/set.js +244 -0
  6. package/dist/commands/config/set.js.map +1 -0
  7. package/dist/commands/config/show.js +147 -0
  8. package/dist/commands/config/show.js.map +1 -0
  9. package/dist/commands/disconnect.js +196 -0
  10. package/dist/commands/disconnect.js.map +1 -0
  11. package/dist/commands/mcp/index.js +670 -0
  12. package/dist/commands/mcp/index.js.map +1 -0
  13. package/dist/commands/rpc/call.js +531 -0
  14. package/dist/commands/rpc/call.js.map +1 -0
  15. package/dist/index.js +460 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/lib/bridge-singleton.js +362 -0
  18. package/dist/lib/bridge-singleton.js.map +1 -0
  19. package/dist/lib/config.js +75 -0
  20. package/dist/lib/config.js.map +1 -0
  21. package/dist/lib/output.js +56 -0
  22. package/dist/lib/output.js.map +1 -0
  23. package/dist/lib/paths.js +16 -0
  24. package/dist/lib/paths.js.map +1 -0
  25. package/dist/lib/session-store.js +81 -0
  26. package/dist/lib/session-store.js.map +1 -0
  27. package/dist/lib/types.js +3 -0
  28. package/dist/lib/types.js.map +1 -0
  29. package/dist/lib/validation.js +45 -0
  30. package/dist/lib/validation.js.map +1 -0
  31. package/dist/lib/ws-bridge.js +141 -0
  32. package/dist/lib/ws-bridge.js.map +1 -0
  33. package/dist/lib/ws-daemon.js +344 -0
  34. package/dist/lib/ws-daemon.js.map +1 -0
  35. package/dist/mcp/handlers/config.js +149 -0
  36. package/dist/mcp/handlers/config.js.map +1 -0
  37. package/dist/mcp/handlers/daemon.js +185 -0
  38. package/dist/mcp/handlers/daemon.js.map +1 -0
  39. package/dist/mcp/handlers/resources.js +60 -0
  40. package/dist/mcp/handlers/resources.js.map +1 -0
  41. package/dist/mcp/handlers/rpc.js +444 -0
  42. package/dist/mcp/handlers/rpc.js.map +1 -0
  43. package/dist/mcp/helpers.js +26 -0
  44. package/dist/mcp/helpers.js.map +1 -0
  45. package/dist/mcp/server.js +658 -0
  46. package/dist/mcp/server.js.map +1 -0
  47. package/dist/mcp/tools.js +22 -0
  48. package/dist/mcp/tools.js.map +1 -0
  49. package/oclif.manifest.json +335 -0
  50. package/package.json +100 -0
package/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # @jaw.id/cli
2
+
3
+ CLI + MCP Server for JAW.id smart accounts -- built for humans and AI agents.
4
+
5
+ Uses browser-based passkey authentication via keys.jaw.id -- no private key management needed. Exposes a single generic `jaw_rpc` MCP tool that supports all EIP-1193 wallet methods.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ # Run directly with npx
11
+ npx @jaw.id/cli <command>
12
+
13
+ # Install globally
14
+ npm install -g @jaw.id/cli
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # 1. Configure API key and default chain
21
+ jaw config set apiKey=YOUR_API_KEY defaultChain=8453
22
+
23
+ # 2. Connect your wallet (opens browser for passkey auth)
24
+ jaw rpc call wallet_connect
25
+
26
+ # 3. Send a transaction
27
+ jaw rpc call wallet_sendCalls '{"calls":[{"to":"0x...","value":"0x0"}]}'
28
+
29
+ # 4. Check transaction status
30
+ jaw rpc call wallet_getCallsStatus '{"id":"0xBatchId"}'
31
+
32
+ # 5. When done, stop the background daemon
33
+ jaw disconnect
34
+ ```
35
+
36
+ ## How It Works
37
+
38
+ The CLI uses a persistent background daemon with a WebSocket bridge to the browser. All RPC methods are routed through the same path:
39
+
40
+ 1. CLI spawns a background WebSocket daemon on `127.0.0.1` (if not already running)
41
+ 2. Daemon opens your browser to `keys.jaw.id/cli-bridge`
42
+ 3. Browser page runs the JAW SDK and connects back to the daemon via WebSocket
43
+ 4. CLI sends RPC requests to the daemon, which forwards them to the browser SDK
44
+ 5. Browser SDK executes the request (prompting for passkey signing when needed)
45
+ 6. Response is routed back through the daemon to the CLI
46
+
47
+ The daemon stays alive across CLI commands so you only authenticate once. It auto-shuts down after 30 minutes of inactivity, or you can stop it manually with `jaw disconnect`.
48
+
49
+ ## CLI Commands
50
+
51
+ ### `jaw rpc call <method> [params]`
52
+
53
+ Execute any JAW.id RPC method via the browser bridge.
54
+
55
+ ```bash
56
+ # Connect wallet
57
+ jaw rpc call wallet_connect
58
+
59
+ # Send transaction
60
+ jaw rpc call wallet_sendCalls '{"calls":[{"to":"0x...","value":"0x0","data":"0x..."}]}'
61
+
62
+ # Sign a message
63
+ jaw rpc call personal_sign '"Hello World"'
64
+
65
+ # Sign typed data (EIP-712)
66
+ jaw rpc call eth_signTypedData_v4 '["0xYOUR_ADDRESS", "{\"types\":{...},\"primaryType\":\"...\",\"domain\":{...},\"message\":{...}}"]'
67
+
68
+ # Grant permissions
69
+ jaw rpc call wallet_grantPermissions '{"expiry":1750000000,"spender":"0x...","permissions":{...}}'
70
+
71
+ # Get assets
72
+ jaw rpc call wallet_getAssets
73
+
74
+ # Get chain ID
75
+ jaw rpc call eth_chainId
76
+ ```
77
+
78
+ | Flag | Description | Default |
79
+ |------|-------------|---------|
80
+ | `-o, --output` | Output format: `json` or `human` | `human` |
81
+ | `-c, --chain` | Chain ID | config default |
82
+ | `--api-key` | JAW API key | config/env |
83
+ | `-t, --timeout` | Request timeout (seconds) | `120` |
84
+ | `-y, --yes` | Skip confirmations | `false` |
85
+ | `-q, --quiet` | Suppress non-essential output | `false` |
86
+
87
+ ### `jaw config show`
88
+
89
+ Display current configuration (API key redacted).
90
+
91
+ ```bash
92
+ jaw config show
93
+ jaw config show --output json
94
+ ```
95
+
96
+ ### `jaw config set`
97
+
98
+ Set one or more configuration values.
99
+
100
+ ```bash
101
+ jaw config set apiKey=your-api-key defaultChain=8453
102
+ jaw config set ens=yourdomain.eth paymasterUrl=https://paymaster.example.com
103
+ jaw config set keysUrl=https://keys.jaw.id
104
+
105
+ # Legacy syntax also supported
106
+ jaw config set apiKey your-api-key
107
+ ```
108
+
109
+ ### `jaw disconnect`
110
+
111
+ Stop the background bridge daemon and close the browser session.
112
+
113
+ ```bash
114
+ jaw disconnect
115
+ ```
116
+
117
+ ## Testing Guide
118
+
119
+ Full end-to-end testing sequence:
120
+
121
+ ```bash
122
+ # 0. Setup
123
+ jaw config set apiKey=YOUR_API_KEY defaultChain=1
124
+
125
+ # 1. Connect wallet (opens browser, daemon starts)
126
+ jaw rpc call wallet_connect
127
+
128
+ # 2. Verify connection
129
+ jaw rpc call eth_accounts
130
+ jaw rpc call eth_chainId
131
+ jaw rpc call net_version
132
+
133
+ # 3. Read-only calls
134
+ jaw rpc call wallet_getAssets
135
+ jaw rpc call wallet_getCapabilities
136
+ jaw rpc call wallet_getPermissions
137
+ jaw rpc call wallet_getCallsHistory
138
+
139
+ # 4. Sign a message
140
+ jaw rpc call personal_sign '"Hello from JAW CLI"'
141
+
142
+ # 5. Sign typed data
143
+ jaw rpc call eth_signTypedData_v4 '["0xYOUR_ADDRESS", "{\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"}]},\"primaryType\":\"Person\",\"domain\":{\"name\":\"Test\"},\"message\":{\"name\":\"Alice\"}}"]'
144
+
145
+ # 6. Send transaction
146
+ jaw rpc call wallet_sendCalls '{"calls":[{"to":"0x0000000000000000000000000000000000000000","value":"0x0","data":"0x"}]}'
147
+
148
+ # 7. Switch chain
149
+ jaw rpc call wallet_switchEthereumChain '[{"chainId":"0x2105"}]'
150
+ jaw rpc call eth_chainId # verify: should return 0x2105
151
+
152
+ # 8. Grant permissions
153
+ jaw rpc call wallet_grantPermissions '{"expiry":1750000000,"spender":"0x0000000000000000000000000000000000000001","permissions":{"calls":[{"target":"0x3232323232323232323232323232323232323232","selector":"0xe0e0e0e0"}],"spends":[{"token":"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE","allowance":"0x2386F26FC10000","unit":"day","multiplier":1}]}}'
154
+
155
+ # 9. Verify permissions
156
+ jaw rpc call wallet_getPermissions
157
+
158
+ # 10. Revoke permissions (use permissionId from step 9)
159
+ jaw rpc call wallet_revokePermissions '{"id":"0xPERMISSION_ID_FROM_STEP_9"}'
160
+
161
+ # 11. Disconnect
162
+ jaw rpc call wallet_disconnect
163
+ jaw rpc call eth_accounts # should return []
164
+
165
+ # 12. Stop daemon
166
+ jaw disconnect
167
+ ```
168
+
169
+ ## MCP Server (for AI Agents)
170
+
171
+ Start the MCP server:
172
+
173
+ ```bash
174
+ jaw mcp
175
+ ```
176
+
177
+ ### MCP Configuration
178
+
179
+ **Claude Code** (`~/.claude.json`):
180
+
181
+ ```json
182
+ {
183
+ "mcpServers": {
184
+ "jaw": {
185
+ "command": "npx",
186
+ "args": ["@jaw.id/cli", "mcp"],
187
+ "env": {
188
+ "JAW_API_KEY": "your-api-key"
189
+ }
190
+ }
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### MCP Tools
196
+
197
+ | Tool | Description |
198
+ |------|-------------|
199
+ | `jaw_rpc` | Execute any JAW.id wallet RPC method. Supports all EIP-1193 methods. Opens browser for passkey signing when needed. |
200
+ | `jaw_config_show` | Show current CLI configuration (API key redacted). |
201
+ | `jaw_config_set` | Set a CLI configuration value. |
202
+
203
+ ### Example Agent Usage
204
+
205
+ ```
206
+ Agent: jaw_rpc({ method: "wallet_sendCalls", params: { calls: [{ to: "0x...", value: "0x0" }] } })
207
+ -> Daemon forwards to browser -> user signs with passkey -> result returned to agent
208
+ ```
209
+
210
+ ## Environment Variables
211
+
212
+ | Variable | Description |
213
+ |----------|-------------|
214
+ | `JAW_API_KEY` | JAW API key |
215
+ | `JAW_CHAIN_ID` | Default chain ID |
216
+ | `JAW_OUTPUT` | Output format (`json` or `human`) |
217
+
218
+ ## Configuration
219
+
220
+ Config file: `~/.jaw/config.json`
221
+
222
+ ```json
223
+ {
224
+ "apiKey": "your-api-key",
225
+ "defaultChain": 8453,
226
+ "keysUrl": "https://keys.jaw.id",
227
+ "paymasterUrl": "https://paymaster.example.com",
228
+ "ens": "yourdomain.eth"
229
+ }
230
+ ```
231
+
232
+ The daemon also writes runtime state to `~/.jaw/`:
233
+
234
+ | File | Purpose |
235
+ |------|---------|
236
+ | `config.json` | User configuration (mode 0600) |
237
+ | `bridge.json` | Active daemon connection info (port, token, pid) |
238
+ | `session.json` | Cached session state |
239
+ | `daemon.log` | Daemon stdout/stderr for debugging |
package/bin/run.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execute } from '@oclif/core';
4
+
5
+ await execute({ dir: import.meta.url });
@@ -0,0 +1,124 @@
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/base-command.ts
7
+ var JAW_DIR = path.join(os.homedir(), ".jaw");
8
+ var PATHS = {
9
+ config: path.join(JAW_DIR, "config.json"),
10
+ session: path.join(JAW_DIR, "session.json"),
11
+ bridge: path.join(JAW_DIR, "bridge.json"),
12
+ daemonLog: path.join(JAW_DIR, "daemon.log")
13
+ };
14
+
15
+ // src/lib/config.ts
16
+ function loadConfig() {
17
+ if (!fs.existsSync(PATHS.config)) {
18
+ return {};
19
+ }
20
+ const raw = fs.readFileSync(PATHS.config, "utf-8");
21
+ try {
22
+ return JSON.parse(raw);
23
+ } catch {
24
+ throw new Error(
25
+ `Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
26
+ );
27
+ }
28
+ }
29
+
30
+ // src/lib/output.ts
31
+ function formatOutput(data, format) {
32
+ if (format === "json") {
33
+ return JSON.stringify(data, replaceBigInt, 2);
34
+ }
35
+ return formatHuman(data);
36
+ }
37
+ function replaceBigInt(_key, value) {
38
+ if (typeof value === "bigint") {
39
+ return value.toString();
40
+ }
41
+ return value;
42
+ }
43
+ function formatHuman(data, indent = 0) {
44
+ if (data === null || data === void 0) {
45
+ return "null";
46
+ }
47
+ if (typeof data === "string" || typeof data === "number" || typeof data === "boolean" || typeof data === "bigint") {
48
+ return String(data);
49
+ }
50
+ if (Array.isArray(data)) {
51
+ if (data.length === 0) return "(empty)";
52
+ return data.map((item, i) => `${i + 1}. ${formatHuman(item, indent + 2)}`).join("\n");
53
+ }
54
+ if (typeof data === "object") {
55
+ const entries = Object.entries(data);
56
+ if (entries.length === 0) return "(empty)";
57
+ const pad = " ".repeat(indent);
58
+ const maxKeyLen = Math.max(...entries.map(([k]) => k.length));
59
+ return entries.map(([key, val]) => {
60
+ const paddedKey = key.padEnd(maxKeyLen);
61
+ const valStr = typeof val === "object" && val !== null ? "\n" + formatHuman(val, indent + 2) : String(val);
62
+ return `${pad}${paddedKey} ${valStr}`;
63
+ }).join("\n");
64
+ }
65
+ return String(data);
66
+ }
67
+
68
+ // src/base-command.ts
69
+ var BaseCommand = class extends Command {
70
+ static baseFlags = {
71
+ output: Flags.string({
72
+ char: "o",
73
+ description: "Output format",
74
+ options: ["json", "human"],
75
+ default: "human",
76
+ env: "JAW_OUTPUT"
77
+ }),
78
+ chain: Flags.integer({
79
+ char: "c",
80
+ description: "Chain ID",
81
+ env: "JAW_CHAIN_ID"
82
+ }),
83
+ "api-key": Flags.string({
84
+ description: "JAW API key",
85
+ env: "JAW_API_KEY"
86
+ }),
87
+ yes: Flags.boolean({
88
+ char: "y",
89
+ description: "Skip confirmations (for AI agents)",
90
+ default: false
91
+ }),
92
+ quiet: Flags.boolean({
93
+ char: "q",
94
+ description: "Suppress non-essential output",
95
+ default: false
96
+ })
97
+ };
98
+ resolveApiKey(flags) {
99
+ const apiKey = flags["api-key"] ?? loadConfig().apiKey;
100
+ if (!apiKey) {
101
+ this.error(
102
+ "API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`"
103
+ );
104
+ }
105
+ return apiKey;
106
+ }
107
+ resolveChainId(flags) {
108
+ const chainId = flags.chain ?? loadConfig().defaultChain;
109
+ if (!chainId) {
110
+ this.error(
111
+ "Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`"
112
+ );
113
+ }
114
+ return chainId;
115
+ }
116
+ outputResult(data, format) {
117
+ const output = formatOutput(data, format);
118
+ this.log(output);
119
+ }
120
+ };
121
+
122
+ export { BaseCommand };
123
+ //# sourceMappingURL=base-command.js.map
124
+ //# sourceMappingURL=base-command.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"],"names":[],"mappings":"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","file":"base-command.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 bridge: path.join(JAW_DIR, \"bridge.json\"),\n daemonLog: path.join(JAW_DIR, \"daemon.log\"),\n} as const;\n","import * as fs from \"node:fs\";\nimport { PATHS } from \"./paths.js\";\nimport type { JawConfig } from \"./types.js\";\nimport { isValidKeysUrl } 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(\n key: keyof JawConfig,\n): string | number | undefined {\n const config = loadConfig();\n return config[key];\n}\n\nexport function setConfigValue(\n key: keyof JawConfig,\n value: string | number,\n): void {\n if (\n key === \"keysUrl\" &&\n typeof value === \"string\" &&\n !isValidKeysUrl(value)\n ) {\n throw new Error(\n `Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`,\n );\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 ? '\\n' + formatHuman(val, indent + 2)\n : String(val);\n return `${pad}${paddedKey} ${valStr}`;\n })\n .join('\\n');\n }\n\n return String(data);\n}\n\nexport function printTable(rows: Record<string, unknown>[]): string {\n if (rows.length === 0) return '(no results)';\n\n const keys = Object.keys(rows[0]);\n const widths = keys.map(k =>\n Math.max(k.length, ...rows.map(r => String(r[k] ?? '').length))\n );\n\n const header = keys.map((k, i) => k.padEnd(widths[i])).join(' ');\n const separator = widths.map(w => '-'.repeat(w)).join(' ');\n const body = rows.map(r =>\n keys.map((k, i) => String(r[k] ?? '').padEnd(widths[i])).join(' ')\n ).join('\\n');\n\n return `${header}\\n${separator}\\n${body}`;\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(\n \"API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`\",\n );\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(\n \"Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`\",\n );\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"]}
@@ -0,0 +1,244 @@
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/base-command.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
+ bridge: path.join(JAW_DIR, "bridge.json"),
13
+ daemonLog: path.join(JAW_DIR, "daemon.log")
14
+ };
15
+
16
+ // src/lib/validation.ts
17
+ function isValidKeysUrl(url) {
18
+ try {
19
+ const parsed = new URL(url);
20
+ const isTrustedHost = parsed.hostname.endsWith(".jaw.id") || parsed.hostname === "jaw.id" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
21
+ const isSecure = parsed.protocol === "https:" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
22
+ return isTrustedHost && isSecure;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ // src/lib/config.ts
29
+ function ensureDir(dir) {
30
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
31
+ fs.chmodSync(dir, 448);
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
+ return JSON.parse(raw);
40
+ } catch {
41
+ throw new Error(
42
+ `Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
43
+ );
44
+ }
45
+ }
46
+ function saveConfig(config) {
47
+ ensureDir(PATHS.root);
48
+ fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + "\n", {
49
+ encoding: "utf-8",
50
+ mode: 384
51
+ });
52
+ }
53
+ function setConfigValue(key, value) {
54
+ if (key === "keysUrl" && typeof value === "string" && !isValidKeysUrl(value)) {
55
+ throw new Error(
56
+ `Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`
57
+ );
58
+ }
59
+ const config = loadConfig();
60
+ const updated = { ...config, [key]: value };
61
+ saveConfig(updated);
62
+ }
63
+
64
+ // src/lib/output.ts
65
+ function formatOutput(data, format) {
66
+ if (format === "json") {
67
+ return JSON.stringify(data, replaceBigInt, 2);
68
+ }
69
+ return formatHuman(data);
70
+ }
71
+ function replaceBigInt(_key, value) {
72
+ if (typeof value === "bigint") {
73
+ return value.toString();
74
+ }
75
+ return value;
76
+ }
77
+ function formatHuman(data, indent = 0) {
78
+ if (data === null || data === void 0) {
79
+ return "null";
80
+ }
81
+ if (typeof data === "string" || typeof data === "number" || typeof data === "boolean" || typeof data === "bigint") {
82
+ return String(data);
83
+ }
84
+ if (Array.isArray(data)) {
85
+ if (data.length === 0) return "(empty)";
86
+ return data.map((item, i) => `${i + 1}. ${formatHuman(item, indent + 2)}`).join("\n");
87
+ }
88
+ if (typeof data === "object") {
89
+ const entries = Object.entries(data);
90
+ if (entries.length === 0) return "(empty)";
91
+ const pad = " ".repeat(indent);
92
+ const maxKeyLen = Math.max(...entries.map(([k]) => k.length));
93
+ return entries.map(([key, val]) => {
94
+ const paddedKey = key.padEnd(maxKeyLen);
95
+ const valStr = typeof val === "object" && val !== null ? "\n" + formatHuman(val, indent + 2) : String(val);
96
+ return `${pad}${paddedKey} ${valStr}`;
97
+ }).join("\n");
98
+ }
99
+ return String(data);
100
+ }
101
+
102
+ // src/base-command.ts
103
+ var BaseCommand = class extends Command {
104
+ static baseFlags = {
105
+ output: Flags.string({
106
+ char: "o",
107
+ description: "Output format",
108
+ options: ["json", "human"],
109
+ default: "human",
110
+ env: "JAW_OUTPUT"
111
+ }),
112
+ chain: Flags.integer({
113
+ char: "c",
114
+ description: "Chain ID",
115
+ env: "JAW_CHAIN_ID"
116
+ }),
117
+ "api-key": Flags.string({
118
+ description: "JAW API key",
119
+ env: "JAW_API_KEY"
120
+ }),
121
+ yes: Flags.boolean({
122
+ char: "y",
123
+ description: "Skip confirmations (for AI agents)",
124
+ default: false
125
+ }),
126
+ quiet: Flags.boolean({
127
+ char: "q",
128
+ description: "Suppress non-essential output",
129
+ default: false
130
+ })
131
+ };
132
+ resolveApiKey(flags) {
133
+ const apiKey = flags["api-key"] ?? loadConfig().apiKey;
134
+ if (!apiKey) {
135
+ this.error(
136
+ "API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`"
137
+ );
138
+ }
139
+ return apiKey;
140
+ }
141
+ resolveChainId(flags) {
142
+ const chainId = flags.chain ?? loadConfig().defaultChain;
143
+ if (!chainId) {
144
+ this.error(
145
+ "Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`"
146
+ );
147
+ }
148
+ return chainId;
149
+ }
150
+ outputResult(data, format) {
151
+ const output = formatOutput(data, format);
152
+ this.log(output);
153
+ }
154
+ };
155
+
156
+ // src/commands/config/set.ts
157
+ var VALID_KEYS = [
158
+ "apiKey",
159
+ "defaultChain",
160
+ "keysUrl",
161
+ "paymasterUrl",
162
+ "ens"
163
+ ];
164
+ function isValidKey(key) {
165
+ return VALID_KEYS.includes(key);
166
+ }
167
+ var ConfigSet = class _ConfigSet extends BaseCommand {
168
+ static description = "Set one or more configuration values. Accepts key=value pairs or a single key value.";
169
+ static examples = [
170
+ "<%= config.bin %> config set apiKey=your-api-key defaultChain=8453",
171
+ "<%= config.bin %> config set ens=yourdomain.eth paymasterUrl=https://your-paymaster.com",
172
+ "<%= config.bin %> config set apiKey your-api-key"
173
+ ];
174
+ static strict = false;
175
+ static args = {};
176
+ static flags = {
177
+ ...BaseCommand.baseFlags
178
+ };
179
+ async run() {
180
+ const { argv, flags } = await this.parse(_ConfigSet);
181
+ const rawArgs = argv;
182
+ const entries = this.parseEntries(rawArgs);
183
+ if (entries.length === 0) {
184
+ this.error(
185
+ `No valid key=value pairs provided.
186
+ Valid keys: ${VALID_KEYS.join(", ")}
187
+ Usage: jaw config set key=value [key=value ...]`
188
+ );
189
+ }
190
+ const results = [];
191
+ for (const { key, value } of entries) {
192
+ const parsed = key === "defaultChain" ? parseInt(value, 10) : value;
193
+ if (key === "defaultChain" && isNaN(parsed)) {
194
+ this.error(`Invalid chain ID: ${value}`);
195
+ }
196
+ setConfigValue(key, parsed);
197
+ const displayValue = key === "apiKey" && typeof value === "string" ? `${value.slice(0, 8)}...` : parsed;
198
+ results.push({ key, value: displayValue });
199
+ }
200
+ if (flags.output === "json") {
201
+ this.outputResult(
202
+ results.length === 1 ? results[0] : results,
203
+ "json"
204
+ );
205
+ } else {
206
+ for (const { key, value } of results) {
207
+ this.log(`Set ${key} = ${value}`);
208
+ }
209
+ }
210
+ }
211
+ parseEntries(rawArgs) {
212
+ const entries = [];
213
+ let i = 0;
214
+ while (i < rawArgs.length) {
215
+ const arg = rawArgs[i];
216
+ if (arg.includes("=")) {
217
+ const eqIndex = arg.indexOf("=");
218
+ const key = arg.slice(0, eqIndex);
219
+ const value = arg.slice(eqIndex + 1);
220
+ if (!isValidKey(key)) {
221
+ this.error(
222
+ `Invalid config key: ${key}
223
+ Valid keys: ${VALID_KEYS.join(", ")}`
224
+ );
225
+ }
226
+ entries.push({ key, value });
227
+ i++;
228
+ } else if (isValidKey(arg) && i + 1 < rawArgs.length) {
229
+ entries.push({ key: arg, value: rawArgs[i + 1] });
230
+ i += 2;
231
+ } else {
232
+ this.error(
233
+ `Unexpected argument: ${arg}
234
+ Usage: jaw config set key=value [key=value ...]`
235
+ );
236
+ }
237
+ }
238
+ return entries;
239
+ }
240
+ };
241
+
242
+ export { ConfigSet as default };
243
+ //# sourceMappingURL=set.js.map
244
+ //# sourceMappingURL=set.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/set.ts"],"names":[],"mappings":"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","file":"set.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 bridge: path.join(JAW_DIR, \"bridge.json\"),\n daemonLog: path.join(JAW_DIR, \"daemon.log\"),\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 =\n parsed.protocol === \"https:\" ||\n parsed.hostname === \"localhost\" ||\n parsed.hostname === \"127.0.0.1\";\n return isTrustedHost && isSecure;\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 } 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(\n key: keyof JawConfig,\n): string | number | undefined {\n const config = loadConfig();\n return config[key];\n}\n\nexport function setConfigValue(\n key: keyof JawConfig,\n value: string | number,\n): void {\n if (\n key === \"keysUrl\" &&\n typeof value === \"string\" &&\n !isValidKeysUrl(value)\n ) {\n throw new Error(\n `Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`,\n );\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 ? '\\n' + formatHuman(val, indent + 2)\n : String(val);\n return `${pad}${paddedKey} ${valStr}`;\n })\n .join('\\n');\n }\n\n return String(data);\n}\n\nexport function printTable(rows: Record<string, unknown>[]): string {\n if (rows.length === 0) return '(no results)';\n\n const keys = Object.keys(rows[0]);\n const widths = keys.map(k =>\n Math.max(k.length, ...rows.map(r => String(r[k] ?? '').length))\n );\n\n const header = keys.map((k, i) => k.padEnd(widths[i])).join(' ');\n const separator = widths.map(w => '-'.repeat(w)).join(' ');\n const body = rows.map(r =>\n keys.map((k, i) => String(r[k] ?? '').padEnd(widths[i])).join(' ')\n ).join('\\n');\n\n return `${header}\\n${separator}\\n${body}`;\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(\n \"API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`\",\n );\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(\n \"Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`\",\n );\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 { setConfigValue } from \"../../lib/config.js\";\n\nconst VALID_KEYS = [\n \"apiKey\",\n \"defaultChain\",\n \"keysUrl\",\n \"paymasterUrl\",\n \"ens\",\n] as const;\n\ntype ValidKey = (typeof VALID_KEYS)[number];\n\nfunction isValidKey(key: string): key is ValidKey {\n return VALID_KEYS.includes(key as ValidKey);\n}\n\nexport default class ConfigSet extends BaseCommand {\n static override description =\n \"Set one or more configuration values. Accepts key=value pairs or a single key value.\";\n\n static override examples = [\n \"<%= config.bin %> config set apiKey=your-api-key defaultChain=8453\",\n \"<%= config.bin %> config set ens=yourdomain.eth paymasterUrl=https://your-paymaster.com\",\n \"<%= config.bin %> config set apiKey your-api-key\",\n ];\n\n static override strict = false;\n\n static override args = {};\n\n static override flags = {\n ...BaseCommand.baseFlags,\n };\n\n async run(): Promise<void> {\n const { argv, flags } = await this.parse(ConfigSet);\n const rawArgs = argv as string[];\n\n // Parse key=value pairs from all args\n const entries = this.parseEntries(rawArgs);\n\n if (entries.length === 0) {\n this.error(\n `No valid key=value pairs provided.\\nValid keys: ${VALID_KEYS.join(\", \")}\\nUsage: jaw config set key=value [key=value ...]`,\n );\n }\n\n const results: { key: string; value: string | number }[] = [];\n\n for (const { key, value } of entries) {\n const parsed = key === \"defaultChain\" ? parseInt(value, 10) : value;\n\n if (key === \"defaultChain\" && isNaN(parsed as number)) {\n this.error(`Invalid chain ID: ${value}`);\n }\n\n setConfigValue(key, parsed as string | number);\n\n const displayValue =\n key === \"apiKey\" && typeof value === \"string\"\n ? `${value.slice(0, 8)}...`\n : parsed;\n\n results.push({ key, value: displayValue });\n }\n\n if (flags.output === \"json\") {\n this.outputResult(\n results.length === 1 ? results[0] : results,\n \"json\",\n );\n } else {\n for (const { key, value } of results) {\n this.log(`Set ${key} = ${value}`);\n }\n }\n }\n\n private parseEntries(\n rawArgs: string[],\n ): { key: ValidKey; value: string }[] {\n const entries: { key: ValidKey; value: string }[] = [];\n\n let i = 0;\n while (i < rawArgs.length) {\n const arg = rawArgs[i];\n\n if (arg.includes(\"=\")) {\n // key=value syntax\n const eqIndex = arg.indexOf(\"=\");\n const key = arg.slice(0, eqIndex);\n const value = arg.slice(eqIndex + 1);\n\n if (!isValidKey(key)) {\n this.error(\n `Invalid config key: ${key}\\nValid keys: ${VALID_KEYS.join(\", \")}`,\n );\n }\n entries.push({ key, value });\n i++;\n } else if (isValidKey(arg) && i + 1 < rawArgs.length) {\n // key value syntax (legacy)\n entries.push({ key: arg, value: rawArgs[i + 1] });\n i += 2;\n } else {\n this.error(\n `Unexpected argument: ${arg}\\nUsage: jaw config set key=value [key=value ...]`,\n );\n }\n }\n\n return entries;\n }\n}"]}