@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.
- package/README.md +239 -0
- package/bin/run.js +5 -0
- package/dist/base-command.js +124 -0
- package/dist/base-command.js.map +1 -0
- package/dist/commands/config/set.js +244 -0
- package/dist/commands/config/set.js.map +1 -0
- package/dist/commands/config/show.js +147 -0
- package/dist/commands/config/show.js.map +1 -0
- package/dist/commands/disconnect.js +196 -0
- package/dist/commands/disconnect.js.map +1 -0
- package/dist/commands/mcp/index.js +670 -0
- package/dist/commands/mcp/index.js.map +1 -0
- package/dist/commands/rpc/call.js +531 -0
- package/dist/commands/rpc/call.js.map +1 -0
- package/dist/index.js +460 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/bridge-singleton.js +362 -0
- package/dist/lib/bridge-singleton.js.map +1 -0
- package/dist/lib/config.js +75 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/output.js +56 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/paths.js +16 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/session-store.js +81 -0
- package/dist/lib/session-store.js.map +1 -0
- package/dist/lib/types.js +3 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/validation.js +45 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/lib/ws-bridge.js +141 -0
- package/dist/lib/ws-bridge.js.map +1 -0
- package/dist/lib/ws-daemon.js +344 -0
- package/dist/lib/ws-daemon.js.map +1 -0
- package/dist/mcp/handlers/config.js +149 -0
- package/dist/mcp/handlers/config.js.map +1 -0
- package/dist/mcp/handlers/daemon.js +185 -0
- package/dist/mcp/handlers/daemon.js.map +1 -0
- package/dist/mcp/handlers/resources.js +60 -0
- package/dist/mcp/handlers/resources.js.map +1 -0
- package/dist/mcp/handlers/rpc.js +444 -0
- package/dist/mcp/handlers/rpc.js.map +1 -0
- package/dist/mcp/helpers.js +26 -0
- package/dist/mcp/helpers.js.map +1 -0
- package/dist/mcp/server.js +658 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.js +22 -0
- package/dist/mcp/tools.js.map +1 -0
- package/oclif.manifest.json +335 -0
- 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,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}"]}
|