@jaw.id/cli 0.0.7 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -24
- package/dist/base-command.js +28 -8
- package/dist/base-command.js.map +1 -1
- package/dist/commands/config/set.js +28 -38
- package/dist/commands/config/set.js.map +1 -1
- package/dist/commands/config/show.js +29 -12
- package/dist/commands/config/show.js.map +1 -1
- package/dist/commands/config/write.js +288 -0
- package/dist/commands/config/write.js.map +1 -0
- package/dist/commands/disconnect.js +42 -54
- package/dist/commands/disconnect.js.map +1 -1
- package/dist/commands/mcp/index.js +62 -102
- package/dist/commands/mcp/index.js.map +1 -1
- package/dist/commands/rpc/call.js +218 -92
- package/dist/commands/rpc/call.js.map +1 -1
- package/dist/commands/session/revoke.js +727 -0
- package/dist/commands/session/revoke.js.map +1 -0
- package/dist/commands/session/setup.js +960 -0
- package/dist/commands/session/setup.js.map +1 -0
- package/dist/commands/session/status.js +206 -0
- package/dist/commands/session/status.js.map +1 -0
- package/dist/commands/version.js +28 -8
- package/dist/commands/version.js.map +1 -1
- package/dist/index.js +42 -67
- package/dist/index.js.map +1 -1
- package/dist/lib/bridge-singleton.js +47 -61
- package/dist/lib/bridge-singleton.js.map +1 -1
- package/dist/lib/config.js +17 -13
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/crypto.js +3 -15
- package/dist/lib/crypto.js.map +1 -1
- package/dist/lib/keystore.js +64 -0
- package/dist/lib/keystore.js.map +1 -0
- package/dist/lib/output.js +1 -16
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/paths.js +3 -1
- package/dist/lib/paths.js.map +1 -1
- package/dist/lib/session-bridge.js +168 -0
- package/dist/lib/session-bridge.js.map +1 -0
- package/dist/lib/session-config.js +52 -0
- package/dist/lib/session-config.js.map +1 -0
- package/dist/lib/validation.js +62 -27
- package/dist/lib/validation.js.map +1 -1
- package/dist/lib/ws-bridge.js +15 -46
- package/dist/lib/ws-bridge.js.map +1 -1
- package/dist/mcp/handlers/config.js +28 -29
- package/dist/mcp/handlers/config.js.map +1 -1
- package/dist/mcp/handlers/daemon.js +41 -51
- package/dist/mcp/handlers/daemon.js.map +1 -1
- package/dist/mcp/handlers/resources.js +1 -3
- package/dist/mcp/handlers/resources.js.map +1 -1
- package/dist/mcp/handlers/rpc.js +63 -79
- package/dist/mcp/handlers/rpc.js.map +1 -1
- package/dist/mcp/helpers.js.map +1 -1
- package/dist/mcp/server.js +61 -99
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.js +2 -4
- package/dist/mcp/tools.js.map +1 -1
- package/oclif.manifest.json +304 -4
- package/package.json +19 -1
- package/dist/lib/session-store.js +0 -80
- package/dist/lib/session-store.js.map +0 -1
package/dist/lib/crypto.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
// src/lib/crypto.ts
|
|
2
2
|
var subtle = globalThis.crypto.subtle;
|
|
3
3
|
async function generateKeyPair() {
|
|
4
|
-
return subtle.generateKey(
|
|
5
|
-
{ name: "ECDH", namedCurve: "P-256" },
|
|
6
|
-
true,
|
|
7
|
-
["deriveKey"]
|
|
8
|
-
);
|
|
4
|
+
return subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveKey"]);
|
|
9
5
|
}
|
|
10
6
|
async function deriveSharedSecret(privateKey, peerPublicKey) {
|
|
11
7
|
return subtle.deriveKey(
|
|
@@ -19,11 +15,7 @@ async function deriveSharedSecret(privateKey, peerPublicKey) {
|
|
|
19
15
|
async function encryptMessage(sharedSecret, payload) {
|
|
20
16
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
21
17
|
const plaintext = new TextEncoder().encode(JSON.stringify(payload));
|
|
22
|
-
const cipherBuf = await subtle.encrypt(
|
|
23
|
-
{ name: "AES-GCM", iv },
|
|
24
|
-
sharedSecret,
|
|
25
|
-
plaintext
|
|
26
|
-
);
|
|
18
|
+
const cipherBuf = await subtle.encrypt({ name: "AES-GCM", iv }, sharedSecret, plaintext);
|
|
27
19
|
return {
|
|
28
20
|
iv: bufferToBase64(iv),
|
|
29
21
|
ciphertext: bufferToBase64(new Uint8Array(cipherBuf))
|
|
@@ -32,11 +24,7 @@ async function encryptMessage(sharedSecret, payload) {
|
|
|
32
24
|
async function decryptMessage(sharedSecret, envelope) {
|
|
33
25
|
const iv = Buffer.from(envelope.iv, "base64");
|
|
34
26
|
const ciphertext = Buffer.from(envelope.ciphertext, "base64");
|
|
35
|
-
const plainBuf = await subtle.decrypt(
|
|
36
|
-
{ name: "AES-GCM", iv },
|
|
37
|
-
sharedSecret,
|
|
38
|
-
ciphertext
|
|
39
|
-
);
|
|
27
|
+
const plainBuf = await subtle.decrypt({ name: "AES-GCM", iv }, sharedSecret, ciphertext);
|
|
40
28
|
return JSON.parse(new TextDecoder().decode(plainBuf));
|
|
41
29
|
}
|
|
42
30
|
async function exportKeyToHex(type, key) {
|
package/dist/lib/crypto.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/crypto.ts"],"names":[],"mappings":";AAaA,IAAM,MAAA,GAAS,WAAW,MAAA,CAAO,MAAA;AAIjC,eAAsB,eAAA,GAAqC;AACzD,EAAA,OAAO,MAAA,CAAO,WAAA
|
|
1
|
+
{"version":3,"sources":["../../src/lib/crypto.ts"],"names":[],"mappings":"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","file":"crypto.js","sourcesContent":["/**\n * E2E encryption primitives for CLI ↔ browser communication.\n *\n * Uses ECDH P-256 for key exchange and AES-256-GCM for message encryption.\n * Mirrors the same crypto operations in @jaw.id/core but uses only\n * Node.js built-in crypto.subtle — no external dependencies.\n */\n\nimport type { webcrypto } from 'node:crypto';\n\ntype CKey = webcrypto.CryptoKey;\ntype CKeyPair = webcrypto.CryptoKeyPair;\n\nconst subtle = globalThis.crypto.subtle;\n\n// ── Key generation ───────────────────────────────────────────────\n\nexport async function generateKeyPair(): Promise<CKeyPair> {\n return subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']) as Promise<CKeyPair>;\n}\n\nexport async function deriveSharedSecret(privateKey: CKey, peerPublicKey: CKey): Promise<CKey> {\n return subtle.deriveKey(\n { name: 'ECDH', public: peerPublicKey },\n privateKey,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt']\n );\n}\n\n// ── Encrypt / Decrypt ────────────────────────────────────────────\n\nexport interface EncryptedEnvelope {\n iv: string; // base64\n ciphertext: string; // base64\n}\n\nexport async function encryptMessage(sharedSecret: CKey, payload: Record<string, unknown>): Promise<EncryptedEnvelope> {\n const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));\n const plaintext = new TextEncoder().encode(JSON.stringify(payload));\n const cipherBuf = await subtle.encrypt({ name: 'AES-GCM', iv }, sharedSecret, plaintext);\n return {\n iv: bufferToBase64(iv),\n ciphertext: bufferToBase64(new Uint8Array(cipherBuf)),\n };\n}\n\nexport async function decryptMessage(\n sharedSecret: CKey,\n envelope: EncryptedEnvelope\n): Promise<Record<string, unknown>> {\n const iv = Buffer.from(envelope.iv, 'base64');\n const ciphertext = Buffer.from(envelope.ciphertext, 'base64');\n const plainBuf = await subtle.decrypt({ name: 'AES-GCM', iv }, sharedSecret, ciphertext);\n return JSON.parse(new TextDecoder().decode(plainBuf));\n}\n\n// ── Key import / export (hex) ────────────────────────────────────\n\nexport async function exportKeyToHex(type: 'private' | 'public', key: CKey): Promise<string> {\n const format = type === 'private' ? 'pkcs8' : 'spki';\n const buf = await subtle.exportKey(format, key);\n return bytesToHex(new Uint8Array(buf));\n}\n\nexport async function importKeyFromHex(type: 'private' | 'public', hex: string): Promise<CKey> {\n const format = type === 'private' ? 'pkcs8' : 'spki';\n return subtle.importKey(\n format,\n Buffer.from(hexToBytes(hex)),\n { name: 'ECDH', namedCurve: 'P-256' },\n true,\n type === 'private' ? ['deriveKey'] : []\n );\n}\n\n// ── Encoding helpers ─────────────────────────────────────────────\n\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('Invalid hex: odd length');\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);\n }\n return bytes;\n}\n\nfunction bufferToBase64(buf: Uint8Array): string {\n return Buffer.from(buf).toString('base64');\n}\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import * as fs2 from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
|
|
6
|
+
// src/lib/keystore.ts
|
|
7
|
+
var JAW_DIR = path.join(os.homedir(), ".jaw");
|
|
8
|
+
var PATHS = {
|
|
9
|
+
root: JAW_DIR,
|
|
10
|
+
config: path.join(JAW_DIR, "config.json"),
|
|
11
|
+
session: path.join(JAW_DIR, "session.json"),
|
|
12
|
+
relay: path.join(JAW_DIR, "relay.json"),
|
|
13
|
+
keystore: path.join(JAW_DIR, "keystore.json"),
|
|
14
|
+
sessionConfig: path.join(JAW_DIR, "session-config.json")
|
|
15
|
+
};
|
|
16
|
+
function ensureDir(dir) {
|
|
17
|
+
fs2.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
18
|
+
fs2.chmodSync(dir, 448);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/lib/keystore.ts
|
|
22
|
+
function generateSessionKey() {
|
|
23
|
+
const bytes = crypto.randomBytes(32);
|
|
24
|
+
return `0x${bytes.toString("hex")}`;
|
|
25
|
+
}
|
|
26
|
+
function saveKeystore(privateKeyHex, address) {
|
|
27
|
+
const keystore = {
|
|
28
|
+
version: 2,
|
|
29
|
+
privateKey: privateKeyHex,
|
|
30
|
+
address,
|
|
31
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
32
|
+
};
|
|
33
|
+
ensureDir(PATHS.root);
|
|
34
|
+
fs2.writeFileSync(PATHS.keystore, JSON.stringify(keystore, null, 2) + "\n", {
|
|
35
|
+
encoding: "utf-8",
|
|
36
|
+
mode: 384
|
|
37
|
+
});
|
|
38
|
+
fs2.chmodSync(PATHS.keystore, 384);
|
|
39
|
+
}
|
|
40
|
+
function loadSessionKey() {
|
|
41
|
+
if (!fs2.existsSync(PATHS.keystore)) {
|
|
42
|
+
throw new Error("No session configured. Run `jaw session setup` first.");
|
|
43
|
+
}
|
|
44
|
+
const contents = fs2.readFileSync(PATHS.keystore, "utf-8");
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(contents);
|
|
48
|
+
} catch {
|
|
49
|
+
throw new Error(`Keystore at ${PATHS.keystore} is corrupted. Run \`jaw session setup\` to recreate it.`);
|
|
50
|
+
}
|
|
51
|
+
return parsed.privateKey;
|
|
52
|
+
}
|
|
53
|
+
function deleteKeystore() {
|
|
54
|
+
if (fs2.existsSync(PATHS.keystore)) {
|
|
55
|
+
fs2.unlinkSync(PATHS.keystore);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function keystoreExists() {
|
|
59
|
+
return fs2.existsSync(PATHS.keystore);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { deleteKeystore, generateSessionKey, keystoreExists, loadSessionKey, saveKeystore };
|
|
63
|
+
//# sourceMappingURL=keystore.js.map
|
|
64
|
+
//# sourceMappingURL=keystore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/paths.ts","../../src/lib/config.ts","../../src/lib/keystore.ts"],"names":["fs"],"mappings":";;;;;;AAGA,IAAM,OAAA,GAAe,IAAA,CAAA,IAAA,CAAQ,EAAA,CAAA,OAAA,EAAQ,EAAG,MAAM,CAAA;AAEvC,IAAM,KAAA,GAAQ;AAAA,EACnB,IAAA,EAAM,OAAA;AAAA,EACN,MAAA,EAAa,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,aAAa,CAAA;AAAA,EACxC,OAAA,EAAc,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,cAAc,CAAA;AAAA,EAC1C,KAAA,EAAY,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,YAAY,CAAA;AAAA,EACtC,QAAA,EAAe,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,eAAe,CAAA;AAAA,EAC5C,aAAA,EAAoB,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,qBAAqB;AACzD,CAAA;ACPO,SAAS,UAAU,GAAA,EAAmB;AAC3C,EAAGA,cAAU,GAAA,EAAK,EAAE,WAAW,IAAA,EAAM,IAAA,EAAM,KAAO,CAAA;AAClD,EAAGA,GAAA,CAAA,SAAA,CAAU,KAAK,GAAK,CAAA;AACzB;;;ACOO,SAAS,kBAAA,GAAoC;AAClD,EAAA,MAAM,KAAA,GAAe,mBAAY,EAAE,CAAA;AACnC,EAAA,OAAO,CAAA,EAAA,EAAK,KAAA,CAAM,QAAA,CAAS,KAAK,CAAC,CAAA,CAAA;AACnC;AAOO,SAAS,YAAA,CAAa,eAAuB,OAAA,EAAuB;AACzE,EAAA,MAAM,QAAA,GAAyB;AAAA,IAC7B,OAAA,EAAS,CAAA;AAAA,IACT,UAAA,EAAY,aAAA;AAAA,IACZ,OAAA;AAAA,IACA,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,GACpC;AAEA,EAAA,SAAA,CAAU,MAAM,IAAI,CAAA;AACpB,EAAG,GAAA,CAAA,aAAA,CAAc,MAAM,QAAA,EAAU,IAAA,CAAK,UAAU,QAAA,EAAU,IAAA,EAAM,CAAC,CAAA,GAAI,IAAA,EAAM;AAAA,IACzE,QAAA,EAAU,OAAA;AAAA,IACV,IAAA,EAAM;AAAA,GACP,CAAA;AAED,EAAG,GAAA,CAAA,SAAA,CAAU,KAAA,CAAM,QAAA,EAAU,GAAK,CAAA;AACpC;AAKO,SAAS,cAAA,GAAyB;AACvC,EAAA,IAAI,CAAI,GAAA,CAAA,UAAA,CAAW,KAAA,CAAM,QAAQ,CAAA,EAAG;AAClC,IAAA,MAAM,IAAI,MAAM,uDAAuD,CAAA;AAAA,EACzE;AAEA,EAAA,MAAM,QAAA,GAAc,GAAA,CAAA,YAAA,CAAa,KAAA,CAAM,QAAA,EAAU,OAAO,CAAA;AACxD,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,QAAQ,CAAA;AAAA,EAC9B,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,KAAA,CAAM,QAAQ,CAAA,wDAAA,CAA0D,CAAA;AAAA,EACzG;AACA,EAAA,OAAO,MAAA,CAAO,UAAA;AAChB;AAKO,SAAS,cAAA,GAAuB;AACrC,EAAA,IAAO,GAAA,CAAA,UAAA,CAAW,KAAA,CAAM,QAAQ,CAAA,EAAG;AACjC,IAAG,GAAA,CAAA,UAAA,CAAW,MAAM,QAAQ,CAAA;AAAA,EAC9B;AACF;AAKO,SAAS,cAAA,GAA0B;AACxC,EAAA,OAAU,GAAA,CAAA,UAAA,CAAW,MAAM,QAAQ,CAAA;AACrC","file":"keystore.js","sourcesContent":["import * as path from 'node:path';\nimport * as os from 'node:os';\n\nconst JAW_DIR = path.join(os.homedir(), '.jaw');\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, 'config.json'),\n session: path.join(JAW_DIR, 'session.json'),\n relay: path.join(JAW_DIR, 'relay.json'),\n keystore: path.join(JAW_DIR, 'keystore.json'),\n sessionConfig: path.join(JAW_DIR, 'session-config.json'),\n} as const;\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport type { JawConfig } from './types.js';\nimport { isValidKeysUrl, isValidRelayUrl } from './validation.js';\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nfunction migrateConfig(config: JawConfig): JawConfig {\n if (config.paymasterUrl && !config.paymasters) {\n const chainId = config.defaultChain ?? 1;\n config.paymasters = { [chainId]: { url: config.paymasterUrl } };\n delete config.paymasterUrl;\n saveConfig(config);\n }\n return config;\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, 'utf-8');\n try {\n const config = JSON.parse(raw) as JawConfig;\n return migrateConfig(config);\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n };\n}\n\nexport function setConfigValue(key: keyof JawConfig, value: string | number): void {\n if (key === 'keysUrl' && typeof value === 'string' && !isValidKeysUrl(value)) {\n throw new Error(`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`);\n }\n if (key === 'relayUrl' && typeof value === 'string' && !isValidRelayUrl(value)) {\n throw new Error(`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`);\n }\n const config = loadConfig();\n const updated = { ...config, [key]: value };\n saveConfig(updated);\n}\n","import * as crypto from 'node:crypto';\nimport * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport { ensureDir } from './config.js';\n\ninterface KeystoreFile {\n version: 2;\n privateKey: string;\n address: string;\n createdAt: string;\n}\n\n/**\n * Generate a random secp256k1 private key as 0x-prefixed hex.\n */\nexport function generateSessionKey(): `0x${string}` {\n const bytes = crypto.randomBytes(32);\n return `0x${bytes.toString('hex')}` as `0x${string}`;\n}\n\n/**\n * Save private key to keystore.json.\n * On-chain PermissionManager is the real security boundary — the session key\n * can only act within its granted permission scope regardless of local access.\n */\nexport function saveKeystore(privateKeyHex: string, address: string): void {\n const keystore: KeystoreFile = {\n version: 2,\n privateKey: privateKeyHex,\n address,\n createdAt: new Date().toISOString(),\n };\n\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.keystore, JSON.stringify(keystore, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n // `mode` is only honored on file creation, not overwrite — re-apply explicitly.\n fs.chmodSync(PATHS.keystore, 0o600);\n}\n\n/**\n * Load private key hex from keystore.json.\n */\nexport function loadSessionKey(): string {\n if (!fs.existsSync(PATHS.keystore)) {\n throw new Error('No session configured. Run `jaw session setup` first.');\n }\n\n const contents = fs.readFileSync(PATHS.keystore, 'utf-8');\n let parsed: KeystoreFile;\n try {\n parsed = JSON.parse(contents) as KeystoreFile;\n } catch {\n throw new Error(`Keystore at ${PATHS.keystore} is corrupted. Run \\`jaw session setup\\` to recreate it.`);\n }\n return parsed.privateKey;\n}\n\n/**\n * Delete keystore.json.\n */\nexport function deleteKeystore(): void {\n if (fs.existsSync(PATHS.keystore)) {\n fs.unlinkSync(PATHS.keystore);\n }\n}\n\n/**\n * Check if keystore.json exists.\n */\nexport function keystoreExists(): boolean {\n return fs.existsSync(PATHS.keystore);\n}\n"]}
|
package/dist/lib/output.js
CHANGED
|
@@ -35,22 +35,7 @@ function formatHuman(data, indent = 0) {
|
|
|
35
35
|
}
|
|
36
36
|
return String(data);
|
|
37
37
|
}
|
|
38
|
-
function printTable(rows) {
|
|
39
|
-
if (rows.length === 0) return "(no results)";
|
|
40
|
-
const keys = Object.keys(rows[0]);
|
|
41
|
-
const widths = keys.map(
|
|
42
|
-
(k) => Math.max(k.length, ...rows.map((r) => String(r[k] ?? "").length))
|
|
43
|
-
);
|
|
44
|
-
const header = keys.map((k, i) => k.padEnd(widths[i])).join(" ");
|
|
45
|
-
const separator = widths.map((w) => "-".repeat(w)).join(" ");
|
|
46
|
-
const body = rows.map(
|
|
47
|
-
(r) => keys.map((k, i) => String(r[k] ?? "").padEnd(widths[i])).join(" ")
|
|
48
|
-
).join("\n");
|
|
49
|
-
return `${header}
|
|
50
|
-
${separator}
|
|
51
|
-
${body}`;
|
|
52
|
-
}
|
|
53
38
|
|
|
54
|
-
export { formatOutput
|
|
39
|
+
export { formatOutput };
|
|
55
40
|
//# sourceMappingURL=output.js.map
|
|
56
41
|
//# sourceMappingURL=output.js.map
|
package/dist/lib/output.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/output.ts"],"names":[],"mappings":";AAEO,SAAS,YAAA,CAAa,MAAe,MAAA,EAA8B;AACxE,EAAA,IAAI,WAAW,MAAA,EAAQ;AACrB,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,aAAA,EAAe,CAAC,CAAA;AAAA,EAC9C;AACA,EAAA,OAAO,YAAY,IAAI,CAAA;AACzB;AAEA,SAAS,aAAA,CAAc,MAAc,KAAA,EAAyB;AAC5D,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,MAAM,QAAA,EAAS;AAAA,EACxB;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,WAAA,CAAY,IAAA,EAAe,MAAA,GAAS,CAAA,EAAW;AACtD,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,MAAA,EAAW;AACvC,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,OAAO,IAAA,KAAS,QAAA,IAAY,OAAO,IAAA,KAAS,SAAA,IAAa,OAAO,IAAA,KAAS,QAAA,EAAU;AACjH,IAAA,OAAO,OAAO,IAAI,CAAA;AAAA,EACpB;AAEA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACvB,IAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,SAAA;AAC9B,IAAA,OAAO,KAAK,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,KAAM,GAAG,CAAA,GAAI,CAAC,CAAA,EAAA,EAAK,WAAA,CAAY,MAAM,MAAA,GAAS,CAAC,CAAC,CAAA,CAAE,CAAA,CAAE,KAAK,IAAI,CAAA;AAAA,EACtF;AAEA,EAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,IAA+B,CAAA;AAC9D,IAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,SAAA;AACjC,IAAA,MAAM,GAAA,GAAM,GAAA,CAAI,MAAA,CAAO,MAAM,CAAA;AAC7B,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,GAAG,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAC,CAAC,CAAA,KAAM,CAAA,CAAE,MAAM,CAAC,CAAA;AAC5D,IAAA,OAAO,QACJ,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,GAAG,CAAA,KAAM;AACnB,MAAA,MAAM,SAAA,GAAY,GAAA,CAAI,MAAA,CAAO,SAAS,CAAA;AACtC,MAAA,MAAM,MAAA,GAAS,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,IAAA,
|
|
1
|
+
{"version":3,"sources":["../../src/lib/output.ts"],"names":[],"mappings":";AAEO,SAAS,YAAA,CAAa,MAAe,MAAA,EAA8B;AACxE,EAAA,IAAI,WAAW,MAAA,EAAQ;AACrB,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,aAAA,EAAe,CAAC,CAAA;AAAA,EAC9C;AACA,EAAA,OAAO,YAAY,IAAI,CAAA;AACzB;AAEA,SAAS,aAAA,CAAc,MAAc,KAAA,EAAyB;AAC5D,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,MAAM,QAAA,EAAS;AAAA,EACxB;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,WAAA,CAAY,IAAA,EAAe,MAAA,GAAS,CAAA,EAAW;AACtD,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,MAAA,EAAW;AACvC,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,OAAO,IAAA,KAAS,QAAA,IAAY,OAAO,IAAA,KAAS,SAAA,IAAa,OAAO,IAAA,KAAS,QAAA,EAAU;AACjH,IAAA,OAAO,OAAO,IAAI,CAAA;AAAA,EACpB;AAEA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACvB,IAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,SAAA;AAC9B,IAAA,OAAO,KAAK,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,KAAM,GAAG,CAAA,GAAI,CAAC,CAAA,EAAA,EAAK,WAAA,CAAY,MAAM,MAAA,GAAS,CAAC,CAAC,CAAA,CAAE,CAAA,CAAE,KAAK,IAAI,CAAA;AAAA,EACtF;AAEA,EAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,IAA+B,CAAA;AAC9D,IAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,SAAA;AACjC,IAAA,MAAM,GAAA,GAAM,GAAA,CAAI,MAAA,CAAO,MAAM,CAAA;AAC7B,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,GAAG,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAC,CAAC,CAAA,KAAM,CAAA,CAAE,MAAM,CAAC,CAAA;AAC5D,IAAA,OAAO,QACJ,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,GAAG,CAAA,KAAM;AACnB,MAAA,MAAM,SAAA,GAAY,GAAA,CAAI,MAAA,CAAO,SAAS,CAAA;AACtC,MAAA,MAAM,MAAA,GAAS,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,IAAA,GAAO,IAAA,GAAO,WAAA,CAAY,GAAA,EAAK,MAAA,GAAS,CAAC,CAAA,GAAI,OAAO,GAAG,CAAA;AACzG,MAAA,OAAO,CAAA,EAAG,GAAG,CAAA,EAAG,SAAS,KAAK,MAAM,CAAA,CAAA;AAAA,IACtC,CAAC,CAAA,CACA,IAAA,CAAK,IAAI,CAAA;AAAA,EACd;AAEA,EAAA,OAAO,OAAO,IAAI,CAAA;AACpB","file":"output.js","sourcesContent":["import type { OutputFormat } from './types.js';\n\nexport function formatOutput(data: unknown, format: OutputFormat): string {\n if (format === 'json') {\n return JSON.stringify(data, replaceBigInt, 2);\n }\n return formatHuman(data);\n}\n\nfunction replaceBigInt(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') {\n return value.toString();\n }\n return value;\n}\n\nfunction formatHuman(data: unknown, indent = 0): string {\n if (data === null || data === undefined) {\n return 'null';\n }\n\n if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || typeof data === 'bigint') {\n return String(data);\n }\n\n if (Array.isArray(data)) {\n if (data.length === 0) return '(empty)';\n return data.map((item, i) => `${i + 1}. ${formatHuman(item, indent + 2)}`).join('\\n');\n }\n\n if (typeof data === 'object') {\n const entries = Object.entries(data as Record<string, unknown>);\n if (entries.length === 0) return '(empty)';\n const pad = ' '.repeat(indent);\n const maxKeyLen = Math.max(...entries.map(([k]) => k.length));\n return entries\n .map(([key, val]) => {\n const paddedKey = key.padEnd(maxKeyLen);\n const valStr = typeof val === 'object' && val !== null ? '\\n' + formatHuman(val, indent + 2) : String(val);\n return `${pad}${paddedKey} ${valStr}`;\n })\n .join('\\n');\n }\n\n return String(data);\n}\n"]}
|
package/dist/lib/paths.js
CHANGED
|
@@ -7,7 +7,9 @@ var PATHS = {
|
|
|
7
7
|
root: JAW_DIR,
|
|
8
8
|
config: path.join(JAW_DIR, "config.json"),
|
|
9
9
|
session: path.join(JAW_DIR, "session.json"),
|
|
10
|
-
relay: path.join(JAW_DIR, "relay.json")
|
|
10
|
+
relay: path.join(JAW_DIR, "relay.json"),
|
|
11
|
+
keystore: path.join(JAW_DIR, "keystore.json"),
|
|
12
|
+
sessionConfig: path.join(JAW_DIR, "session-config.json")
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
export { PATHS };
|
package/dist/lib/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/paths.ts"],"names":[],"mappings":";;;;AAGA,IAAM,OAAA,GAAe,IAAA,CAAA,IAAA,CAAQ,EAAA,CAAA,OAAA,EAAQ,EAAG,MAAM,CAAA;AAEvC,IAAM,KAAA,GAAQ;AAAA,EACnB,IAAA,EAAM,OAAA;AAAA,EACN,MAAA,EAAa,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,aAAa,CAAA;AAAA,EACxC,OAAA,EAAc,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,cAAc,CAAA;AAAA,EAC1C,KAAA,EAAY,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,YAAY;
|
|
1
|
+
{"version":3,"sources":["../../src/lib/paths.ts"],"names":[],"mappings":";;;;AAGA,IAAM,OAAA,GAAe,IAAA,CAAA,IAAA,CAAQ,EAAA,CAAA,OAAA,EAAQ,EAAG,MAAM,CAAA;AAEvC,IAAM,KAAA,GAAQ;AAAA,EACnB,IAAA,EAAM,OAAA;AAAA,EACN,MAAA,EAAa,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,aAAa,CAAA;AAAA,EACxC,OAAA,EAAc,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,cAAc,CAAA;AAAA,EAC1C,KAAA,EAAY,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,YAAY,CAAA;AAAA,EACtC,QAAA,EAAe,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,eAAe,CAAA;AAAA,EAC5C,aAAA,EAAoB,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,qBAAqB;AACzD","file":"paths.js","sourcesContent":["import * as path from 'node:path';\nimport * as os from 'node:os';\n\nconst JAW_DIR = path.join(os.homedir(), '.jaw');\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, 'config.json'),\n session: path.join(JAW_DIR, 'session.json'),\n relay: path.join(JAW_DIR, 'relay.json'),\n keystore: path.join(JAW_DIR, 'keystore.json'),\n sessionConfig: path.join(JAW_DIR, 'session-config.json'),\n} as const;\n"]}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import 'crypto';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
|
|
6
|
+
// src/lib/keystore.ts
|
|
7
|
+
var JAW_DIR = path.join(os.homedir(), ".jaw");
|
|
8
|
+
var PATHS = {
|
|
9
|
+
root: JAW_DIR,
|
|
10
|
+
config: path.join(JAW_DIR, "config.json"),
|
|
11
|
+
session: path.join(JAW_DIR, "session.json"),
|
|
12
|
+
relay: path.join(JAW_DIR, "relay.json"),
|
|
13
|
+
keystore: path.join(JAW_DIR, "keystore.json"),
|
|
14
|
+
sessionConfig: path.join(JAW_DIR, "session-config.json")
|
|
15
|
+
};
|
|
16
|
+
function ensureDir(dir) {
|
|
17
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
18
|
+
fs.chmodSync(dir, 448);
|
|
19
|
+
}
|
|
20
|
+
function migrateConfig(config) {
|
|
21
|
+
if (config.paymasterUrl && !config.paymasters) {
|
|
22
|
+
const chainId = config.defaultChain ?? 1;
|
|
23
|
+
config.paymasters = { [chainId]: { url: config.paymasterUrl } };
|
|
24
|
+
delete config.paymasterUrl;
|
|
25
|
+
saveConfig(config);
|
|
26
|
+
}
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
function loadConfig() {
|
|
30
|
+
if (!fs.existsSync(PATHS.config)) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
const raw = fs.readFileSync(PATHS.config, "utf-8");
|
|
34
|
+
try {
|
|
35
|
+
const config = JSON.parse(raw);
|
|
36
|
+
return migrateConfig(config);
|
|
37
|
+
} catch {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function saveConfig(config) {
|
|
44
|
+
ensureDir(PATHS.root);
|
|
45
|
+
fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + "\n", {
|
|
46
|
+
encoding: "utf-8",
|
|
47
|
+
mode: 384
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/lib/keystore.ts
|
|
52
|
+
function loadSessionKey() {
|
|
53
|
+
if (!fs.existsSync(PATHS.keystore)) {
|
|
54
|
+
throw new Error("No session configured. Run `jaw session setup` first.");
|
|
55
|
+
}
|
|
56
|
+
const contents = fs.readFileSync(PATHS.keystore, "utf-8");
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(contents);
|
|
60
|
+
} catch {
|
|
61
|
+
throw new Error(`Keystore at ${PATHS.keystore} is corrupted. Run \`jaw session setup\` to recreate it.`);
|
|
62
|
+
}
|
|
63
|
+
return parsed.privateKey;
|
|
64
|
+
}
|
|
65
|
+
function loadSessionConfig() {
|
|
66
|
+
if (!fs.existsSync(PATHS.sessionConfig)) {
|
|
67
|
+
throw new Error("No session configured. Run `jaw session setup` first.");
|
|
68
|
+
}
|
|
69
|
+
const raw = fs.readFileSync(PATHS.sessionConfig, "utf-8");
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(raw);
|
|
72
|
+
} catch {
|
|
73
|
+
throw new Error(`Session config at ${PATHS.sessionConfig} is corrupted. Run \`jaw session setup\` to recreate it.`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/lib/session-bridge.ts
|
|
78
|
+
var SessionBridge = class {
|
|
79
|
+
options;
|
|
80
|
+
session = null;
|
|
81
|
+
constructor(options) {
|
|
82
|
+
this.options = { ...options };
|
|
83
|
+
if (!this.options.paymasterUrl) {
|
|
84
|
+
const config = loadConfig();
|
|
85
|
+
const pm = config.paymasters?.[this.options.chainId];
|
|
86
|
+
if (pm) {
|
|
87
|
+
this.options.paymasterUrl = pm.url;
|
|
88
|
+
this.options.paymasterContext = pm.context;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async getSession() {
|
|
93
|
+
if (this.session) {
|
|
94
|
+
this.checkExpiry(this.session.config);
|
|
95
|
+
return this.session;
|
|
96
|
+
}
|
|
97
|
+
const config = loadSessionConfig();
|
|
98
|
+
this.checkExpiry(config);
|
|
99
|
+
if (config.chainId !== this.options.chainId) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Session was created for chain ${config.chainId}, but --chain ${this.options.chainId} was requested. Run \`jaw session setup --chain ${this.options.chainId}\` to create a session for that chain.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
let privateKeyHex = loadSessionKey();
|
|
105
|
+
const { privateKeyToAccount } = await import('viem/accounts');
|
|
106
|
+
const localAccount = privateKeyToAccount(privateKeyHex);
|
|
107
|
+
privateKeyHex = null;
|
|
108
|
+
const { Account } = await import('@jaw.id/core');
|
|
109
|
+
const account = await Account.fromLocalAccount(
|
|
110
|
+
{
|
|
111
|
+
chainId: this.options.chainId,
|
|
112
|
+
apiKey: this.options.apiKey,
|
|
113
|
+
paymasterUrl: this.options.paymasterUrl,
|
|
114
|
+
paymasterContext: this.options.paymasterContext
|
|
115
|
+
},
|
|
116
|
+
localAccount
|
|
117
|
+
);
|
|
118
|
+
this.session = { account, config };
|
|
119
|
+
return this.session;
|
|
120
|
+
}
|
|
121
|
+
checkExpiry(config) {
|
|
122
|
+
if (config.expiry <= Date.now() / 1e3) {
|
|
123
|
+
const expiryDate = new Date(config.expiry * 1e3).toISOString();
|
|
124
|
+
throw new Error(`Session expired on ${expiryDate}. Run \`jaw session setup\` to create a new session.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async request(method, params) {
|
|
128
|
+
const { account, config } = await this.getSession();
|
|
129
|
+
switch (method) {
|
|
130
|
+
case "eth_requestAccounts":
|
|
131
|
+
case "eth_accounts":
|
|
132
|
+
return [config.sessionAddress];
|
|
133
|
+
case "wallet_sendCalls": {
|
|
134
|
+
const payload = Array.isArray(params) ? params[0] : params;
|
|
135
|
+
const { calls } = payload;
|
|
136
|
+
return account.sendCalls(calls, {
|
|
137
|
+
permissionId: config.permissionId
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
case "wallet_getCallsStatus": {
|
|
141
|
+
const batchId = Array.isArray(params) ? params[0] : params;
|
|
142
|
+
return account.getCallStatus(batchId);
|
|
143
|
+
}
|
|
144
|
+
case "personal_sign": {
|
|
145
|
+
const message = Array.isArray(params) ? params[0] : params;
|
|
146
|
+
return account.signMessage(message);
|
|
147
|
+
}
|
|
148
|
+
case "eth_signTypedData_v4": {
|
|
149
|
+
const asArray = Array.isArray(params) ? params : [params];
|
|
150
|
+
const raw = asArray.length > 1 ? asArray[1] : asArray[0];
|
|
151
|
+
const typedData = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
152
|
+
return account.signTypedData(typedData);
|
|
153
|
+
}
|
|
154
|
+
case "wallet_grantPermissions":
|
|
155
|
+
throw new Error("Requires browser \u2014 run `jaw session setup`.");
|
|
156
|
+
case "wallet_revokePermissions":
|
|
157
|
+
throw new Error("Requires browser \u2014 run `jaw session revoke`.");
|
|
158
|
+
default:
|
|
159
|
+
throw new Error(`Method ${method} is not supported in auto mode.`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
close() {
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export { SessionBridge };
|
|
167
|
+
//# sourceMappingURL=session-bridge.js.map
|
|
168
|
+
//# sourceMappingURL=session-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/paths.ts","../../src/lib/config.ts","../../src/lib/keystore.ts","../../src/lib/session-config.ts","../../src/lib/session-bridge.ts"],"names":["fs2","fs3"],"mappings":";;;;;;AAGA,IAAM,OAAA,GAAe,IAAA,CAAA,IAAA,CAAQ,EAAA,CAAA,OAAA,EAAQ,EAAG,MAAM,CAAA;AAEvC,IAAM,KAAA,GAAQ;AAAA,EACnB,IAAA,EAAM,OAAA;AAAA,EACN,MAAA,EAAa,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,aAAa,CAAA;AAAA,EACxC,OAAA,EAAc,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,cAAc,CAAA;AAAA,EAC1C,KAAA,EAAY,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,YAAY,CAAA;AAAA,EACtC,QAAA,EAAe,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,eAAe,CAAA;AAAA,EAC5C,aAAA,EAAoB,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,qBAAqB;AACzD,CAAA;ACPO,SAAS,UAAU,GAAA,EAAmB;AAC3C,EAAG,aAAU,GAAA,EAAK,EAAE,WAAW,IAAA,EAAM,IAAA,EAAM,KAAO,CAAA;AAClD,EAAG,EAAA,CAAA,SAAA,CAAU,KAAK,GAAK,CAAA;AACzB;AAEA,SAAS,cAAc,MAAA,EAA8B;AACnD,EAAA,IAAI,MAAA,CAAO,YAAA,IAAgB,CAAC,MAAA,CAAO,UAAA,EAAY;AAC7C,IAAA,MAAM,OAAA,GAAU,OAAO,YAAA,IAAgB,CAAA;AACvC,IAAA,MAAA,CAAO,UAAA,GAAa,EAAE,CAAC,OAAO,GAAG,EAAE,GAAA,EAAK,MAAA,CAAO,YAAA,EAAa,EAAE;AAC9D,IAAA,OAAO,MAAA,CAAO,YAAA;AACd,IAAA,UAAA,CAAW,MAAM,CAAA;AAAA,EACnB;AACA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,UAAA,GAAwB;AACtC,EAAA,IAAI,CAAI,EAAA,CAAA,UAAA,CAAW,KAAA,CAAM,MAAM,CAAA,EAAG;AAChC,IAAA,OAAO,EAAC;AAAA,EACV;AACA,EAAA,MAAM,GAAA,GAAS,EAAA,CAAA,YAAA,CAAa,KAAA,CAAM,MAAA,EAAQ,OAAO,CAAA;AACjD,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC7B,IAAA,OAAO,cAAc,MAAM,CAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,eAAA,EAAkB,MAAM,MAAM,CAAA,oEAAA;AAAA,KAChC;AAAA,EACF;AACF;AAEO,SAAS,WAAW,MAAA,EAAyB;AAClD,EAAA,SAAA,CAAU,MAAM,IAAI,CAAA;AACpB,EAAG,EAAA,CAAA,aAAA,CAAc,MAAM,MAAA,EAAQ,IAAA,CAAK,UAAU,MAAA,EAAQ,IAAA,EAAM,CAAC,CAAA,GAAI,IAAA,EAAM;AAAA,IACrE,QAAA,EAAU,OAAA;AAAA,IACV,IAAA,EAAM;AAAA,GACP,CAAA;AACH;;;ACIO,SAAS,cAAA,GAAyB;AACvC,EAAA,IAAI,CAAIA,EAAA,CAAA,UAAA,CAAW,KAAA,CAAM,QAAQ,CAAA,EAAG;AAClC,IAAA,MAAM,IAAI,MAAM,uDAAuD,CAAA;AAAA,EACzE;AAEA,EAAA,MAAM,QAAA,GAAcA,EAAA,CAAA,YAAA,CAAa,KAAA,CAAM,QAAA,EAAU,OAAO,CAAA;AACxD,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,QAAQ,CAAA;AAAA,EAC9B,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,KAAA,CAAM,QAAQ,CAAA,wDAAA,CAA0D,CAAA;AAAA,EACzG;AACA,EAAA,OAAO,MAAA,CAAO,UAAA;AAChB;AChCO,SAAS,iBAAA,GAAmC;AACjD,EAAA,IAAI,CAAIC,EAAA,CAAA,UAAA,CAAW,KAAA,CAAM,aAAa,CAAA,EAAG;AACvC,IAAA,MAAM,IAAI,MAAM,uDAAuD,CAAA;AAAA,EACzE;AACA,EAAA,MAAM,GAAA,GAASA,EAAA,CAAA,YAAA,CAAa,KAAA,CAAM,aAAA,EAAe,OAAO,CAAA;AACxD,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,KAAA,CAAM,aAAa,CAAA,wDAAA,CAA0D,CAAA;AAAA,EACpH;AACF;;;ACbO,IAAM,gBAAN,MAAoB;AAAA,EACR,OAAA;AAAA,EACT,OAAA,GAAqC,IAAA;AAAA,EAE7C,YAAY,OAAA,EAA+B;AACzC,IAAA,IAAA,CAAK,OAAA,GAAU,EAAE,GAAG,OAAA,EAAQ;AAE5B,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,CAAQ,YAAA,EAAc;AAC9B,MAAA,MAAM,SAAS,UAAA,EAAW;AAC1B,MAAA,MAAM,EAAA,GAAK,MAAA,CAAO,UAAA,GAAa,IAAA,CAAK,QAAQ,OAAO,CAAA;AACnD,MAAA,IAAI,EAAA,EAAI;AACN,QAAA,IAAA,CAAK,OAAA,CAAQ,eAAe,EAAA,CAAG,GAAA;AAC/B,QAAA,IAAA,CAAK,OAAA,CAAQ,mBAAmB,EAAA,CAAG,OAAA;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,UAAA,GAA0C;AACtD,IAAA,IAAI,KAAK,OAAA,EAAS;AAChB,MAAA,IAAA,CAAK,WAAA,CAAY,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAA;AACpC,MAAA,OAAO,IAAA,CAAK,OAAA;AAAA,IACd;AAEA,IAAA,MAAM,SAAS,iBAAA,EAAkB;AACjC,IAAA,IAAA,CAAK,YAAY,MAAM,CAAA;AAEvB,IAAA,IAAI,MAAA,CAAO,OAAA,KAAY,IAAA,CAAK,OAAA,CAAQ,OAAA,EAAS;AAC3C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,8BAAA,EAAiC,MAAA,CAAO,OAAO,CAAA,cAAA,EAAiB,IAAA,CAAK,QAAQ,OAAO,CAAA,gDAAA,EAC/C,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA,sCAAA;AAAA,OAC3D;AAAA,IACF;AAEA,IAAA,IAAI,gBAA+B,cAAA,EAAe;AAElD,IAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,MAAM,OAAO,eAAe,CAAA;AAC5D,IAAA,MAAM,YAAA,GAAe,oBAAoB,aAA8B,CAAA;AACvE,IAAA,aAAA,GAAgB,IAAA;AAEhB,IAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,MAAM,OAAO,cAAc,CAAA;AAC/C,IAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,gBAAA;AAAA,MAC5B;AAAA,QACE,OAAA,EAAS,KAAK,OAAA,CAAQ,OAAA;AAAA,QACtB,MAAA,EAAQ,KAAK,OAAA,CAAQ,MAAA;AAAA,QACrB,YAAA,EAAc,KAAK,OAAA,CAAQ,YAAA;AAAA,QAC3B,gBAAA,EAAkB,KAAK,OAAA,CAAQ;AAAA,OACjC;AAAA,MACA;AAAA,KACF;AAEA,IAAA,IAAA,CAAK,OAAA,GAAU,EAAE,OAAA,EAAmD,MAAA,EAAO;AAC3E,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd;AAAA,EAEQ,YAAY,MAAA,EAA6B;AAC/C,IAAA,IAAI,MAAA,CAAO,MAAA,IAAU,IAAA,CAAK,GAAA,KAAQ,GAAA,EAAM;AACtC,MAAA,MAAM,aAAa,IAAI,IAAA,CAAK,OAAO,MAAA,GAAS,GAAI,EAAE,WAAA,EAAY;AAC9D,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,UAAU,CAAA,oDAAA,CAAsD,CAAA;AAAA,IACxG;AAAA,EACF;AAAA,EAEA,MAAM,OAAA,CAAQ,MAAA,EAAgB,MAAA,EAAoC;AAChE,IAAA,MAAM,EAAE,OAAA,EAAS,MAAA,EAAO,GAAI,MAAM,KAAK,UAAA,EAAW;AAElD,IAAA,QAAQ,MAAA;AAAQ,MACd,KAAK,qBAAA;AAAA,MACL,KAAK,cAAA;AACH,QAAA,OAAO,CAAC,OAAO,cAAc,CAAA;AAAA,MAE/B,KAAK,kBAAA,EAAoB;AACvB,QAAA,MAAM,UAAU,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA,GAAI,MAAA;AACpD,QAAA,MAAM,EAAE,OAAM,GAAI,OAAA;AAGlB,QAAA,OAAO,OAAA,CAAQ,UAAU,KAAA,EAAO;AAAA,UAC9B,cAAc,MAAA,CAAO;AAAA,SACtB,CAAA;AAAA,MACH;AAAA,MAEA,KAAK,uBAAA,EAAyB;AAC5B,QAAA,MAAM,UAAU,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA,GAAI,MAAA;AACpD,QAAA,OAAO,OAAA,CAAQ,cAAc,OAAwB,CAAA;AAAA,MACvD;AAAA,MAEA,KAAK,eAAA,EAAiB;AACpB,QAAA,MAAM,UAAU,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA,GAAI,MAAA;AACpD,QAAA,OAAO,OAAA,CAAQ,YAAY,OAAiB,CAAA;AAAA,MAC9C;AAAA,MAEA,KAAK,sBAAA,EAAwB;AAC3B,QAAA,MAAM,UAAU,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,GAAI,MAAA,GAAS,CAAC,MAAM,CAAA;AACxD,QAAA,MAAM,GAAA,GAAM,QAAQ,MAAA,GAAS,CAAA,GAAI,QAAQ,CAAC,CAAA,GAAI,QAAQ,CAAC,CAAA;AACvD,QAAA,MAAM,YAAY,OAAO,GAAA,KAAQ,WAAW,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,GAAI,GAAA;AAC9D,QAAA,OAAO,OAAA,CAAQ,cAAc,SAAS,CAAA;AAAA,MACxC;AAAA,MAEA,KAAK,yBAAA;AACH,QAAA,MAAM,IAAI,MAAM,kDAA6C,CAAA;AAAA,MAE/D,KAAK,0BAAA;AACH,QAAA,MAAM,IAAI,MAAM,mDAA8C,CAAA;AAAA,MAEhE;AACE,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,OAAA,EAAU,MAAM,CAAA,+BAAA,CAAiC,CAAA;AAAA;AACrE,EACF;AAAA,EAEA,KAAA,GAAc;AAAA,EAEd;AACF","file":"session-bridge.js","sourcesContent":["import * as path from 'node:path';\nimport * as os from 'node:os';\n\nconst JAW_DIR = path.join(os.homedir(), '.jaw');\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, 'config.json'),\n session: path.join(JAW_DIR, 'session.json'),\n relay: path.join(JAW_DIR, 'relay.json'),\n keystore: path.join(JAW_DIR, 'keystore.json'),\n sessionConfig: path.join(JAW_DIR, 'session-config.json'),\n} as const;\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport type { JawConfig } from './types.js';\nimport { isValidKeysUrl, isValidRelayUrl } from './validation.js';\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nfunction migrateConfig(config: JawConfig): JawConfig {\n if (config.paymasterUrl && !config.paymasters) {\n const chainId = config.defaultChain ?? 1;\n config.paymasters = { [chainId]: { url: config.paymasterUrl } };\n delete config.paymasterUrl;\n saveConfig(config);\n }\n return config;\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, 'utf-8');\n try {\n const config = JSON.parse(raw) as JawConfig;\n return migrateConfig(config);\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n };\n}\n\nexport function setConfigValue(key: keyof JawConfig, value: string | number): void {\n if (key === 'keysUrl' && typeof value === 'string' && !isValidKeysUrl(value)) {\n throw new Error(`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`);\n }\n if (key === 'relayUrl' && typeof value === 'string' && !isValidRelayUrl(value)) {\n throw new Error(`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`);\n }\n const config = loadConfig();\n const updated = { ...config, [key]: value };\n saveConfig(updated);\n}\n","import * as crypto from 'node:crypto';\nimport * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport { ensureDir } from './config.js';\n\ninterface KeystoreFile {\n version: 2;\n privateKey: string;\n address: string;\n createdAt: string;\n}\n\n/**\n * Generate a random secp256k1 private key as 0x-prefixed hex.\n */\nexport function generateSessionKey(): `0x${string}` {\n const bytes = crypto.randomBytes(32);\n return `0x${bytes.toString('hex')}` as `0x${string}`;\n}\n\n/**\n * Save private key to keystore.json.\n * On-chain PermissionManager is the real security boundary — the session key\n * can only act within its granted permission scope regardless of local access.\n */\nexport function saveKeystore(privateKeyHex: string, address: string): void {\n const keystore: KeystoreFile = {\n version: 2,\n privateKey: privateKeyHex,\n address,\n createdAt: new Date().toISOString(),\n };\n\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.keystore, JSON.stringify(keystore, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n // `mode` is only honored on file creation, not overwrite — re-apply explicitly.\n fs.chmodSync(PATHS.keystore, 0o600);\n}\n\n/**\n * Load private key hex from keystore.json.\n */\nexport function loadSessionKey(): string {\n if (!fs.existsSync(PATHS.keystore)) {\n throw new Error('No session configured. Run `jaw session setup` first.');\n }\n\n const contents = fs.readFileSync(PATHS.keystore, 'utf-8');\n let parsed: KeystoreFile;\n try {\n parsed = JSON.parse(contents) as KeystoreFile;\n } catch {\n throw new Error(`Keystore at ${PATHS.keystore} is corrupted. Run \\`jaw session setup\\` to recreate it.`);\n }\n return parsed.privateKey;\n}\n\n/**\n * Delete keystore.json.\n */\nexport function deleteKeystore(): void {\n if (fs.existsSync(PATHS.keystore)) {\n fs.unlinkSync(PATHS.keystore);\n }\n}\n\n/**\n * Check if keystore.json exists.\n */\nexport function keystoreExists(): boolean {\n return fs.existsSync(PATHS.keystore);\n}\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport { ensureDir } from './config.js';\n\nexport interface SessionConfig {\n ownerAddress: string;\n sessionAddress: string;\n permissionId: string;\n chainId: number;\n expiry: number;\n createdAt: string;\n}\n\nexport function saveSessionConfig(input: Omit<SessionConfig, 'createdAt'>): void {\n const config: SessionConfig = {\n ...input,\n createdAt: new Date().toISOString(),\n };\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.sessionConfig, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n fs.chmodSync(PATHS.sessionConfig, 0o600);\n}\n\nexport function loadSessionConfig(): SessionConfig {\n if (!fs.existsSync(PATHS.sessionConfig)) {\n throw new Error('No session configured. Run `jaw session setup` first.');\n }\n const raw = fs.readFileSync(PATHS.sessionConfig, 'utf-8');\n try {\n return JSON.parse(raw) as SessionConfig;\n } catch {\n throw new Error(`Session config at ${PATHS.sessionConfig} is corrupted. Run \\`jaw session setup\\` to recreate it.`);\n }\n}\n\nexport function deleteSessionConfig(): void {\n if (fs.existsSync(PATHS.sessionConfig)) {\n fs.unlinkSync(PATHS.sessionConfig);\n }\n}\n","import { loadSessionKey } from './keystore.js';\nimport { loadSessionConfig, type SessionConfig } from './session-config.js';\nimport { loadConfig } from './config.js';\n\nexport interface SessionBridgeOptions {\n apiKey: string;\n chainId: number;\n paymasterUrl?: string;\n paymasterContext?: Record<string, unknown>;\n}\n\n/** Lazily resolved Account instance + session config */\ninterface InitializedSession {\n account: {\n address: string;\n sendCalls: (...args: unknown[]) => Promise<unknown>;\n getCallStatus: (batchId: `0x${string}`) => Promise<unknown>;\n signMessage: (message: string) => Promise<`0x${string}`>;\n signTypedData: (typedData: unknown) => Promise<`0x${string}`>;\n };\n config: SessionConfig;\n}\n\nexport class SessionBridge {\n private readonly options: SessionBridgeOptions;\n private session: InitializedSession | null = null;\n\n constructor(options: SessionBridgeOptions) {\n this.options = { ...options };\n\n if (!this.options.paymasterUrl) {\n const config = loadConfig();\n const pm = config.paymasters?.[this.options.chainId];\n if (pm) {\n this.options.paymasterUrl = pm.url;\n this.options.paymasterContext = pm.context;\n }\n }\n }\n\n private async getSession(): Promise<InitializedSession> {\n if (this.session) {\n this.checkExpiry(this.session.config);\n return this.session;\n }\n\n const config = loadSessionConfig();\n this.checkExpiry(config);\n\n if (config.chainId !== this.options.chainId) {\n throw new Error(\n `Session was created for chain ${config.chainId}, but --chain ${this.options.chainId} was requested. ` +\n `Run \\`jaw session setup --chain ${this.options.chainId}\\` to create a session for that chain.`\n );\n }\n\n let privateKeyHex: string | null = loadSessionKey();\n\n const { privateKeyToAccount } = await import('viem/accounts');\n const localAccount = privateKeyToAccount(privateKeyHex as `0x${string}`);\n privateKeyHex = null;\n\n const { Account } = await import('@jaw.id/core');\n const account = await Account.fromLocalAccount(\n {\n chainId: this.options.chainId,\n apiKey: this.options.apiKey,\n paymasterUrl: this.options.paymasterUrl,\n paymasterContext: this.options.paymasterContext,\n },\n localAccount\n );\n\n this.session = { account: account as InitializedSession['account'], config };\n return this.session;\n }\n\n private checkExpiry(config: SessionConfig): void {\n if (config.expiry <= Date.now() / 1000) {\n const expiryDate = new Date(config.expiry * 1000).toISOString();\n throw new Error(`Session expired on ${expiryDate}. Run \\`jaw session setup\\` to create a new session.`);\n }\n }\n\n async request(method: string, params?: unknown): Promise<unknown> {\n const { account, config } = await this.getSession();\n\n switch (method) {\n case 'eth_requestAccounts':\n case 'eth_accounts':\n return [config.sessionAddress];\n\n case 'wallet_sendCalls': {\n const payload = Array.isArray(params) ? params[0] : params;\n const { calls } = payload as {\n calls: Array<{ to: string; value?: string; data?: string }>;\n };\n return account.sendCalls(calls, {\n permissionId: config.permissionId as `0x${string}`,\n });\n }\n\n case 'wallet_getCallsStatus': {\n const batchId = Array.isArray(params) ? params[0] : params;\n return account.getCallStatus(batchId as `0x${string}`);\n }\n\n case 'personal_sign': {\n const message = Array.isArray(params) ? params[0] : params;\n return account.signMessage(message as string);\n }\n\n case 'eth_signTypedData_v4': {\n const asArray = Array.isArray(params) ? params : [params];\n const raw = asArray.length > 1 ? asArray[1] : asArray[0];\n const typedData = typeof raw === 'string' ? JSON.parse(raw) : raw;\n return account.signTypedData(typedData);\n }\n\n case 'wallet_grantPermissions':\n throw new Error('Requires browser — run `jaw session setup`.');\n\n case 'wallet_revokePermissions':\n throw new Error('Requires browser — run `jaw session revoke`.');\n\n default:\n throw new Error(`Method ${method} is not supported in auto mode.`);\n }\n }\n\n close(): void {\n // No-op — no WebSocket to close\n }\n}\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as fs2 from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
|
|
5
|
+
// src/lib/session-config.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
|
+
keystore: path.join(JAW_DIR, "keystore.json"),
|
|
13
|
+
sessionConfig: path.join(JAW_DIR, "session-config.json")
|
|
14
|
+
};
|
|
15
|
+
function ensureDir(dir) {
|
|
16
|
+
fs2.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
17
|
+
fs2.chmodSync(dir, 448);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/lib/session-config.ts
|
|
21
|
+
function saveSessionConfig(input) {
|
|
22
|
+
const config = {
|
|
23
|
+
...input,
|
|
24
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25
|
+
};
|
|
26
|
+
ensureDir(PATHS.root);
|
|
27
|
+
fs2.writeFileSync(PATHS.sessionConfig, JSON.stringify(config, null, 2) + "\n", {
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
mode: 384
|
|
30
|
+
});
|
|
31
|
+
fs2.chmodSync(PATHS.sessionConfig, 384);
|
|
32
|
+
}
|
|
33
|
+
function loadSessionConfig() {
|
|
34
|
+
if (!fs2.existsSync(PATHS.sessionConfig)) {
|
|
35
|
+
throw new Error("No session configured. Run `jaw session setup` first.");
|
|
36
|
+
}
|
|
37
|
+
const raw = fs2.readFileSync(PATHS.sessionConfig, "utf-8");
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(raw);
|
|
40
|
+
} catch {
|
|
41
|
+
throw new Error(`Session config at ${PATHS.sessionConfig} is corrupted. Run \`jaw session setup\` to recreate it.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function deleteSessionConfig() {
|
|
45
|
+
if (fs2.existsSync(PATHS.sessionConfig)) {
|
|
46
|
+
fs2.unlinkSync(PATHS.sessionConfig);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { deleteSessionConfig, loadSessionConfig, saveSessionConfig };
|
|
51
|
+
//# sourceMappingURL=session-config.js.map
|
|
52
|
+
//# sourceMappingURL=session-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/paths.ts","../../src/lib/config.ts","../../src/lib/session-config.ts"],"names":["fs"],"mappings":";;;;;AAGA,IAAM,OAAA,GAAe,IAAA,CAAA,IAAA,CAAQ,EAAA,CAAA,OAAA,EAAQ,EAAG,MAAM,CAAA;AAEvC,IAAM,KAAA,GAAQ;AAAA,EACnB,IAAA,EAAM,OAAA;AAAA,EACN,MAAA,EAAa,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,aAAa,CAAA;AAAA,EACxC,OAAA,EAAc,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,cAAc,CAAA;AAAA,EAC1C,KAAA,EAAY,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,YAAY,CAAA;AAAA,EACtC,QAAA,EAAe,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,eAAe,CAAA;AAAA,EAC5C,aAAA,EAAoB,IAAA,CAAA,IAAA,CAAK,OAAA,EAAS,qBAAqB;AACzD,CAAA;ACPO,SAAS,UAAU,GAAA,EAAmB;AAC3C,EAAGA,cAAU,GAAA,EAAK,EAAE,WAAW,IAAA,EAAM,IAAA,EAAM,KAAO,CAAA;AAClD,EAAGA,GAAA,CAAA,SAAA,CAAU,KAAK,GAAK,CAAA;AACzB;;;ACKO,SAAS,kBAAkB,KAAA,EAA+C;AAC/E,EAAA,MAAM,MAAA,GAAwB;AAAA,IAC5B,GAAG,KAAA;AAAA,IACH,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,GACpC;AACA,EAAA,SAAA,CAAU,MAAM,IAAI,CAAA;AACpB,EAAG,GAAA,CAAA,aAAA,CAAc,MAAM,aAAA,EAAe,IAAA,CAAK,UAAU,MAAA,EAAQ,IAAA,EAAM,CAAC,CAAA,GAAI,IAAA,EAAM;AAAA,IAC5E,QAAA,EAAU,OAAA;AAAA,IACV,IAAA,EAAM;AAAA,GACP,CAAA;AACD,EAAG,GAAA,CAAA,SAAA,CAAU,KAAA,CAAM,aAAA,EAAe,GAAK,CAAA;AACzC;AAEO,SAAS,iBAAA,GAAmC;AACjD,EAAA,IAAI,CAAI,GAAA,CAAA,UAAA,CAAW,KAAA,CAAM,aAAa,CAAA,EAAG;AACvC,IAAA,MAAM,IAAI,MAAM,uDAAuD,CAAA;AAAA,EACzE;AACA,EAAA,MAAM,GAAA,GAAS,GAAA,CAAA,YAAA,CAAa,KAAA,CAAM,aAAA,EAAe,OAAO,CAAA;AACxD,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,KAAA,CAAM,aAAa,CAAA,wDAAA,CAA0D,CAAA;AAAA,EACpH;AACF;AAEO,SAAS,mBAAA,GAA4B;AAC1C,EAAA,IAAO,GAAA,CAAA,UAAA,CAAW,KAAA,CAAM,aAAa,CAAA,EAAG;AACtC,IAAG,GAAA,CAAA,UAAA,CAAW,MAAM,aAAa,CAAA;AAAA,EACnC;AACF","file":"session-config.js","sourcesContent":["import * as path from 'node:path';\nimport * as os from 'node:os';\n\nconst JAW_DIR = path.join(os.homedir(), '.jaw');\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, 'config.json'),\n session: path.join(JAW_DIR, 'session.json'),\n relay: path.join(JAW_DIR, 'relay.json'),\n keystore: path.join(JAW_DIR, 'keystore.json'),\n sessionConfig: path.join(JAW_DIR, 'session-config.json'),\n} as const;\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport type { JawConfig } from './types.js';\nimport { isValidKeysUrl, isValidRelayUrl } from './validation.js';\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nfunction migrateConfig(config: JawConfig): JawConfig {\n if (config.paymasterUrl && !config.paymasters) {\n const chainId = config.defaultChain ?? 1;\n config.paymasters = { [chainId]: { url: config.paymasterUrl } };\n delete config.paymasterUrl;\n saveConfig(config);\n }\n return config;\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, 'utf-8');\n try {\n const config = JSON.parse(raw) as JawConfig;\n return migrateConfig(config);\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n };\n}\n\nexport function setConfigValue(key: keyof JawConfig, value: string | number): void {\n if (key === 'keysUrl' && typeof value === 'string' && !isValidKeysUrl(value)) {\n throw new Error(`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`);\n }\n if (key === 'relayUrl' && typeof value === 'string' && !isValidRelayUrl(value)) {\n throw new Error(`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`);\n }\n const config = loadConfig();\n const updated = { ...config, [key]: value };\n saveConfig(updated);\n}\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport { ensureDir } from './config.js';\n\nexport interface SessionConfig {\n ownerAddress: string;\n sessionAddress: string;\n permissionId: string;\n chainId: number;\n expiry: number;\n createdAt: string;\n}\n\nexport function saveSessionConfig(input: Omit<SessionConfig, 'createdAt'>): void {\n const config: SessionConfig = {\n ...input,\n createdAt: new Date().toISOString(),\n };\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.sessionConfig, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n fs.chmodSync(PATHS.sessionConfig, 0o600);\n}\n\nexport function loadSessionConfig(): SessionConfig {\n if (!fs.existsSync(PATHS.sessionConfig)) {\n throw new Error('No session configured. Run `jaw session setup` first.');\n }\n const raw = fs.readFileSync(PATHS.sessionConfig, 'utf-8');\n try {\n return JSON.parse(raw) as SessionConfig;\n } catch {\n throw new Error(`Session config at ${PATHS.sessionConfig} is corrupted. Run \\`jaw session setup\\` to recreate it.`);\n }\n}\n\nexport function deleteSessionConfig(): void {\n if (fs.existsSync(PATHS.sessionConfig)) {\n fs.unlinkSync(PATHS.sessionConfig);\n }\n}\n"]}
|
package/dist/lib/validation.js
CHANGED
|
@@ -1,33 +1,68 @@
|
|
|
1
1
|
// src/lib/validation.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return Number.isInteger(num) && num > 0;
|
|
11
|
-
}
|
|
12
|
-
function parseChainId(value) {
|
|
13
|
-
const num = parseInt(value, 10);
|
|
14
|
-
if (!isValidChainId(num)) {
|
|
15
|
-
throw new Error(`Invalid chain ID: ${value}`);
|
|
2
|
+
var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
3
|
+
var SELECTOR_RE = /^0x[0-9a-fA-F]{8}$/;
|
|
4
|
+
var HEX_RE = /^0x[0-9a-fA-F]+$/;
|
|
5
|
+
var VALID_SPEND_UNITS = /* @__PURE__ */ new Set(["minute", "hour", "day", "week", "month", "year", "forever"]);
|
|
6
|
+
function parsePermissionsConfig(raw) {
|
|
7
|
+
const errors = [];
|
|
8
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
9
|
+
throw new Error("Invalid permissions:\n - Must be an object");
|
|
16
10
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!
|
|
21
|
-
throw new Error(
|
|
11
|
+
const obj = raw;
|
|
12
|
+
const calls = obj.calls;
|
|
13
|
+
const spends = obj.spends;
|
|
14
|
+
if (!calls && !spends) {
|
|
15
|
+
throw new Error('Invalid permissions:\n - Must include at least "calls" or "spends"');
|
|
22
16
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
17
|
+
if (calls !== void 0) {
|
|
18
|
+
if (!Array.isArray(calls) || calls.length === 0) {
|
|
19
|
+
errors.push("calls: Must be a non-empty array");
|
|
20
|
+
} else {
|
|
21
|
+
for (let i = 0; i < calls.length; i++) {
|
|
22
|
+
const c = calls[i];
|
|
23
|
+
if (!c || typeof c !== "object") {
|
|
24
|
+
errors.push(`calls.${i}: Must be an object`);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (typeof c.target !== "string" || !ADDRESS_RE.test(c.target)) {
|
|
28
|
+
errors.push(`calls.${i}.target: Must be a valid 0x address (40 hex chars)`);
|
|
29
|
+
}
|
|
30
|
+
if (c.selector !== void 0 && (typeof c.selector !== "string" || !SELECTOR_RE.test(c.selector))) {
|
|
31
|
+
errors.push(`calls.${i}.selector: Must be a 4-byte hex selector (e.g. 0xa9059cbb)`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (spends !== void 0) {
|
|
37
|
+
if (!Array.isArray(spends) || spends.length === 0) {
|
|
38
|
+
errors.push("spends: Must be a non-empty array");
|
|
39
|
+
} else {
|
|
40
|
+
for (let i = 0; i < spends.length; i++) {
|
|
41
|
+
const s = spends[i];
|
|
42
|
+
if (!s || typeof s !== "object") {
|
|
43
|
+
errors.push(`spends.${i}: Must be an object`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (typeof s.token !== "string" || !ADDRESS_RE.test(s.token)) {
|
|
47
|
+
errors.push(`spends.${i}.token: Must be a valid 0x address (40 hex chars)`);
|
|
48
|
+
}
|
|
49
|
+
if (typeof s.allowance !== "string" || !HEX_RE.test(s.allowance)) {
|
|
50
|
+
errors.push(`spends.${i}.allowance: Must be a non-empty 0x hex value`);
|
|
51
|
+
}
|
|
52
|
+
if (typeof s.unit !== "string" || !VALID_SPEND_UNITS.has(s.unit)) {
|
|
53
|
+
errors.push(`spends.${i}.unit: Must be one of: ${[...VALID_SPEND_UNITS].join(", ")}`);
|
|
54
|
+
}
|
|
55
|
+
if (s.multiplier !== void 0 && (!Number.isInteger(s.multiplier) || s.multiplier < 1)) {
|
|
56
|
+
errors.push(`spends.${i}.multiplier: Must be a positive integer`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (errors.length > 0) {
|
|
62
|
+
throw new Error(`Invalid permissions:
|
|
63
|
+
${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
30
64
|
}
|
|
65
|
+
return raw;
|
|
31
66
|
}
|
|
32
67
|
function isValidKeysUrl(url) {
|
|
33
68
|
try {
|
|
@@ -51,6 +86,6 @@ function isValidRelayUrl(url) {
|
|
|
51
86
|
}
|
|
52
87
|
}
|
|
53
88
|
|
|
54
|
-
export {
|
|
89
|
+
export { isValidKeysUrl, isValidRelayUrl, parsePermissionsConfig };
|
|
55
90
|
//# sourceMappingURL=validation.js.map
|
|
56
91
|
//# sourceMappingURL=validation.js.map
|