codesail 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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/index.js');
@@ -0,0 +1,44 @@
1
+ import {
2
+ CODESAIL_DIR,
3
+ DAEMON_STATE_PATH
4
+ } from "./chunk-HG54UP2Y.js";
5
+
6
+ // src/daemon/state.ts
7
+ import { readFile, writeFile, unlink, mkdir } from "fs/promises";
8
+ async function readDaemonState() {
9
+ try {
10
+ const data = await readFile(DAEMON_STATE_PATH, "utf-8");
11
+ return JSON.parse(data);
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+ async function writeDaemonState(state) {
17
+ await mkdir(CODESAIL_DIR, { recursive: true });
18
+ await writeFile(DAEMON_STATE_PATH, JSON.stringify(state, null, 2));
19
+ }
20
+ async function removeDaemonState() {
21
+ try {
22
+ await unlink(DAEMON_STATE_PATH);
23
+ } catch {
24
+ }
25
+ }
26
+ async function isDaemonRunning() {
27
+ const state = await readDaemonState();
28
+ if (!state) return false;
29
+ try {
30
+ process.kill(state.pid, 0);
31
+ return true;
32
+ } catch {
33
+ await removeDaemonState();
34
+ return false;
35
+ }
36
+ }
37
+
38
+ export {
39
+ readDaemonState,
40
+ writeDaemonState,
41
+ removeDaemonState,
42
+ isDaemonRunning
43
+ };
44
+ //# sourceMappingURL=chunk-4OXDCPSF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/daemon/state.ts"],"sourcesContent":["import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';\nimport { CODESAIL_DIR, DAEMON_STATE_PATH } from '../core/config.js';\n\nexport interface DaemonState {\n httpPort: number;\n pid: number;\n}\n\nexport async function readDaemonState(): Promise<DaemonState | null> {\n try {\n const data = await readFile(DAEMON_STATE_PATH, 'utf-8');\n return JSON.parse(data) as DaemonState;\n } catch {\n return null;\n }\n}\n\nexport async function writeDaemonState(state: DaemonState): Promise<void> {\n await mkdir(CODESAIL_DIR, { recursive: true });\n await writeFile(DAEMON_STATE_PATH, JSON.stringify(state, null, 2));\n}\n\nexport async function removeDaemonState(): Promise<void> {\n try {\n await unlink(DAEMON_STATE_PATH);\n } catch {\n // Already removed\n }\n}\n\n/** Check if a daemon is currently running */\nexport async function isDaemonRunning(): Promise<boolean> {\n const state = await readDaemonState();\n if (!state) return false;\n try {\n process.kill(state.pid, 0); // signal 0 = check if process exists\n return true;\n } catch {\n // Process not running, clean up stale state\n await removeDaemonState();\n return false;\n }\n}\n"],"mappings":";;;;;;AAAA,SAAS,UAAU,WAAW,QAAQ,aAAa;AAQnD,eAAsB,kBAA+C;AACnE,MAAI;AACF,UAAM,OAAO,MAAM,SAAS,mBAAmB,OAAO;AACtD,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,iBAAiB,OAAmC;AACxE,QAAM,MAAM,cAAc,EAAE,WAAW,KAAK,CAAC;AAC7C,QAAM,UAAU,mBAAmB,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AACnE;AAEA,eAAsB,oBAAmC;AACvD,MAAI;AACF,UAAM,OAAO,iBAAiB;AAAA,EAChC,QAAQ;AAAA,EAER;AACF;AAGA,eAAsB,kBAAoC;AACxD,QAAM,QAAQ,MAAM,gBAAgB;AACpC,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,YAAQ,KAAK,MAAM,KAAK,CAAC;AACzB,WAAO;AAAA,EACT,QAAQ;AAEN,UAAM,kBAAkB;AACxB,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,25 @@
1
+ // src/core/config.ts
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ var DEFAULT_SERVER_URL = "https://api.cluster-fluster.com";
5
+ var SOCKET_PATH = "/v1/updates";
6
+ var CODESAIL_DIR = join(homedir(), ".codesail");
7
+ var CREDENTIALS_PATH = join(CODESAIL_DIR, "credentials.json");
8
+ var CONFIG_PATH = join(CODESAIL_DIR, "config.json");
9
+ var DAEMON_STATE_PATH = join(CODESAIL_DIR, "daemon.state.json");
10
+ var QR_PREFIX = "codesail://terminal?";
11
+ var MASTER_SEED_LABEL = "Happy EnCoder Master Seed";
12
+ var CONTENT_CHILD_LABEL = "content";
13
+
14
+ export {
15
+ DEFAULT_SERVER_URL,
16
+ SOCKET_PATH,
17
+ CODESAIL_DIR,
18
+ CREDENTIALS_PATH,
19
+ CONFIG_PATH,
20
+ DAEMON_STATE_PATH,
21
+ QR_PREFIX,
22
+ MASTER_SEED_LABEL,
23
+ CONTENT_CHILD_LABEL
24
+ };
25
+ //# sourceMappingURL=chunk-HG54UP2Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/config.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport const DEFAULT_SERVER_URL = 'https://api.cluster-fluster.com';\nexport const SOCKET_PATH = '/v1/updates';\n\nexport const CODESAIL_DIR = join(homedir(), '.codesail');\nexport const CREDENTIALS_PATH = join(CODESAIL_DIR, 'credentials.json');\nexport const CONFIG_PATH = join(CODESAIL_DIR, 'config.json');\nexport const DAEMON_STATE_PATH = join(CODESAIL_DIR, 'daemon.state.json');\n\nexport const QR_PREFIX = 'codesail://terminal?';\n\n// Protocol constant — must stay as \"Happy EnCoder Master Seed\" for compatibility\n// with the relay server's HMAC key derivation. Changing this breaks all derived keys.\nexport const MASTER_SEED_LABEL = 'Happy EnCoder Master Seed';\nexport const CONTENT_CHILD_LABEL = 'content';\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,IAAM,qBAAqB;AAC3B,IAAM,cAAc;AAEpB,IAAM,eAAe,KAAK,QAAQ,GAAG,WAAW;AAChD,IAAM,mBAAmB,KAAK,cAAc,kBAAkB;AAC9D,IAAM,cAAc,KAAK,cAAc,aAAa;AACpD,IAAM,oBAAoB,KAAK,cAAc,mBAAmB;AAEhE,IAAM,YAAY;AAIlB,IAAM,oBAAoB;AAC1B,IAAM,sBAAsB;","names":[]}
@@ -0,0 +1,180 @@
1
+ import {
2
+ CONTENT_CHILD_LABEL,
3
+ MASTER_SEED_LABEL
4
+ } from "./chunk-HG54UP2Y.js";
5
+
6
+ // src/core/crypto/nacl.ts
7
+ import nacl from "tweetnacl";
8
+ import tweetnaclUtil from "tweetnacl-util";
9
+ var { decodeBase64, encodeBase64 } = tweetnaclUtil;
10
+ function secretBoxSeal(message, key) {
11
+ const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
12
+ const ciphertext = nacl.secretbox(message, nonce, key);
13
+ if (!ciphertext) throw new Error("secretbox seal failed");
14
+ const result = new Uint8Array(nonce.length + ciphertext.length);
15
+ result.set(nonce);
16
+ result.set(ciphertext, nonce.length);
17
+ return result;
18
+ }
19
+ function secretBoxOpen(sealed, key) {
20
+ const nonce = sealed.slice(0, nacl.secretbox.nonceLength);
21
+ const ciphertext = sealed.slice(nacl.secretbox.nonceLength);
22
+ const plaintext = nacl.secretbox.open(ciphertext, nonce, key);
23
+ if (!plaintext) throw new Error("secretbox open failed \u2014 bad key or corrupted data");
24
+ return plaintext;
25
+ }
26
+ function boxOpen(sealed, recipientSecretKey) {
27
+ const ephemeralPK = sealed.slice(0, 32);
28
+ const nonce = sealed.slice(32, 32 + nacl.box.nonceLength);
29
+ const ciphertext = sealed.slice(32 + nacl.box.nonceLength);
30
+ const plaintext = nacl.box.open(ciphertext, nonce, ephemeralPK, recipientSecretKey);
31
+ if (!plaintext) throw new Error("box open failed \u2014 bad key or corrupted data");
32
+ return plaintext;
33
+ }
34
+ function generateBoxKeyPair() {
35
+ return nacl.box.keyPair();
36
+ }
37
+ function boxKeyPairFromSeed(seed) {
38
+ return nacl.box.keyPair.fromSecretKey(seed);
39
+ }
40
+ function base64urlEncode(data) {
41
+ return encodeBase64(data).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
42
+ }
43
+
44
+ // src/core/crypto/aes.ts
45
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
46
+ var AES_KEY_LENGTH = 32;
47
+ var NONCE_LENGTH = 12;
48
+ var TAG_LENGTH = 16;
49
+ var VERSION_BYTE = 0;
50
+ function aesGcmEncrypt(plaintext, key) {
51
+ if (key.length !== AES_KEY_LENGTH) {
52
+ throw new Error(`AES key must be ${AES_KEY_LENGTH} bytes, got ${key.length}`);
53
+ }
54
+ const nonce = randomBytes(NONCE_LENGTH);
55
+ const cipher = createCipheriv("aes-256-gcm", key, nonce);
56
+ const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
57
+ const tag = cipher.getAuthTag();
58
+ const result = new Uint8Array(1 + NONCE_LENGTH + encrypted.length + TAG_LENGTH);
59
+ result[0] = VERSION_BYTE;
60
+ result.set(nonce, 1);
61
+ result.set(encrypted, 1 + NONCE_LENGTH);
62
+ result.set(tag, 1 + NONCE_LENGTH + encrypted.length);
63
+ return result;
64
+ }
65
+ function aesGcmDecrypt(data, key) {
66
+ if (key.length !== AES_KEY_LENGTH) {
67
+ throw new Error(`AES key must be ${AES_KEY_LENGTH} bytes, got ${key.length}`);
68
+ }
69
+ if (data.length < 1 + NONCE_LENGTH + TAG_LENGTH) {
70
+ throw new Error("AES-GCM data too short");
71
+ }
72
+ const version = data[0];
73
+ if (version !== VERSION_BYTE) {
74
+ throw new Error(`Unsupported AES-GCM version: ${version}`);
75
+ }
76
+ const nonce = data.slice(1, 1 + NONCE_LENGTH);
77
+ const ciphertextEnd = data.length - TAG_LENGTH;
78
+ const ciphertext = data.slice(1 + NONCE_LENGTH, ciphertextEnd);
79
+ const tag = data.slice(ciphertextEnd);
80
+ const decipher = createDecipheriv("aes-256-gcm", key, nonce);
81
+ decipher.setAuthTag(tag);
82
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
83
+ return new Uint8Array(decrypted);
84
+ }
85
+
86
+ // src/core/crypto/keys.ts
87
+ import { createHmac } from "crypto";
88
+ import nacl2 from "tweetnacl";
89
+ function hmacSha512(key, data) {
90
+ const hmac = createHmac("sha512", key instanceof Uint8Array ? Buffer.from(key) : key);
91
+ hmac.update(Buffer.from(data));
92
+ const result = new Uint8Array(hmac.digest());
93
+ return {
94
+ key: result.slice(0, 32),
95
+ chainCode: result.slice(32)
96
+ };
97
+ }
98
+ function deriveSecretKeyTreeRoot(masterSecret) {
99
+ return hmacSha512(MASTER_SEED_LABEL, masterSecret);
100
+ }
101
+ function deriveSecretKeyTreeChild(chainCode, label) {
102
+ const labelBytes = new TextEncoder().encode(label);
103
+ const data = new Uint8Array(1 + labelBytes.length);
104
+ data[0] = 0;
105
+ data.set(labelBytes, 1);
106
+ return hmacSha512(chainCode, data);
107
+ }
108
+ function deriveContentDataKey(masterSecret) {
109
+ const root = deriveSecretKeyTreeRoot(masterSecret);
110
+ const child = deriveSecretKeyTreeChild(root.chainCode, CONTENT_CHILD_LABEL);
111
+ return child.key;
112
+ }
113
+ function deriveSigningKeyPair(masterSecret) {
114
+ return nacl2.sign.keyPair.fromSeed(masterSecret);
115
+ }
116
+
117
+ // src/core/crypto/encryption.ts
118
+ function encryptRaw(em, plaintext) {
119
+ if (em.variant === "legacy") {
120
+ return secretBoxSeal(plaintext, em.key);
121
+ }
122
+ return aesGcmEncrypt(plaintext, em.key);
123
+ }
124
+ function decryptRaw(em, ciphertext) {
125
+ if (em.variant === "legacy") {
126
+ return secretBoxOpen(ciphertext, em.key);
127
+ }
128
+ return aesGcmDecrypt(ciphertext, em.key);
129
+ }
130
+ function encryptValue(em, value) {
131
+ const json = JSON.stringify(value);
132
+ const plaintext = new TextEncoder().encode(json);
133
+ return encryptRaw(em, plaintext);
134
+ }
135
+ function decryptValue(em, ciphertext) {
136
+ const plaintext = decryptRaw(em, ciphertext);
137
+ const json = new TextDecoder().decode(plaintext);
138
+ return JSON.parse(json);
139
+ }
140
+ function encryptToBase64(em, value) {
141
+ const encrypted = encryptValue(em, value);
142
+ return encodeBase64(encrypted);
143
+ }
144
+ function decryptFromBase64(em, base64) {
145
+ const data = decodeBase64(base64);
146
+ return decryptValue(em, data);
147
+ }
148
+ function resolveSessionEncryption(dataEncryptionKey, masterSecret) {
149
+ if (dataEncryptionKey) {
150
+ try {
151
+ const dekData = decodeBase64(dataEncryptionKey);
152
+ if (dekData.length > 1 && dekData[0] === 0) {
153
+ const encryptedKey = dekData.slice(1);
154
+ const contentDataKey = deriveContentDataKey(masterSecret);
155
+ const contentKeyPair = boxKeyPairFromSeed(contentDataKey);
156
+ const sessionKey = boxOpen(encryptedKey, contentKeyPair.secretKey);
157
+ return { key: sessionKey, variant: "dataKey" };
158
+ }
159
+ } catch {
160
+ }
161
+ }
162
+ return { key: masterSecret, variant: "legacy" };
163
+ }
164
+
165
+ export {
166
+ decodeBase64,
167
+ encodeBase64,
168
+ boxOpen,
169
+ generateBoxKeyPair,
170
+ base64urlEncode,
171
+ deriveSigningKeyPair,
172
+ encryptRaw,
173
+ decryptRaw,
174
+ encryptValue,
175
+ decryptValue,
176
+ encryptToBase64,
177
+ decryptFromBase64,
178
+ resolveSessionEncryption
179
+ };
180
+ //# sourceMappingURL=chunk-LWJDKIJF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/crypto/nacl.ts","../src/core/crypto/aes.ts","../src/core/crypto/keys.ts","../src/core/crypto/encryption.ts"],"sourcesContent":["import nacl from 'tweetnacl';\nimport tweetnaclUtil from 'tweetnacl-util';\n\nconst { decodeBase64, encodeBase64 } = tweetnaclUtil;\nexport { nacl, decodeBase64, encodeBase64 };\n\n/** NaCl secretbox seal: returns nonce(24) + ciphertext */\nexport function secretBoxSeal(message: Uint8Array, key: Uint8Array): Uint8Array {\n const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);\n const ciphertext = nacl.secretbox(message, nonce, key);\n if (!ciphertext) throw new Error('secretbox seal failed');\n const result = new Uint8Array(nonce.length + ciphertext.length);\n result.set(nonce);\n result.set(ciphertext, nonce.length);\n return result;\n}\n\n/** NaCl secretbox open: input = nonce(24) + ciphertext */\nexport function secretBoxOpen(sealed: Uint8Array, key: Uint8Array): Uint8Array {\n const nonce = sealed.slice(0, nacl.secretbox.nonceLength);\n const ciphertext = sealed.slice(nacl.secretbox.nonceLength);\n const plaintext = nacl.secretbox.open(ciphertext, nonce, key);\n if (!plaintext) throw new Error('secretbox open failed — bad key or corrupted data');\n return plaintext;\n}\n\n/** NaCl box seal: returns ephemeralPK(32) + nonce(24) + ciphertext */\nexport function boxSeal(\n message: Uint8Array,\n recipientPublicKey: Uint8Array,\n): Uint8Array {\n const ephemeral = nacl.box.keyPair();\n const nonce = nacl.randomBytes(nacl.box.nonceLength);\n const ciphertext = nacl.box(message, nonce, recipientPublicKey, ephemeral.secretKey);\n if (!ciphertext) throw new Error('box seal failed');\n const result = new Uint8Array(32 + nonce.length + ciphertext.length);\n result.set(ephemeral.publicKey);\n result.set(nonce, 32);\n result.set(ciphertext, 32 + nonce.length);\n return result;\n}\n\n/** NaCl box open: input = ephemeralPK(32) + nonce(24) + ciphertext */\nexport function boxOpen(\n sealed: Uint8Array,\n recipientSecretKey: Uint8Array,\n): Uint8Array {\n const ephemeralPK = sealed.slice(0, 32);\n const nonce = sealed.slice(32, 32 + nacl.box.nonceLength);\n const ciphertext = sealed.slice(32 + nacl.box.nonceLength);\n const plaintext = nacl.box.open(ciphertext, nonce, ephemeralPK, recipientSecretKey);\n if (!plaintext) throw new Error('box open failed — bad key or corrupted data');\n return plaintext;\n}\n\n/** Generate a random NaCl box keypair */\nexport function generateBoxKeyPair(): nacl.BoxKeyPair {\n return nacl.box.keyPair();\n}\n\n/** Derive a box keypair from a 32-byte seed (uses fromSecretKey for curve25519) */\nexport function boxKeyPairFromSeed(seed: Uint8Array): nacl.BoxKeyPair {\n // NaCl box keypair from seed: the seed IS the secret key for curve25519\n // However, nacl.box.keyPair.fromSecretKey expects a full secret key\n // We need to use the seed directly as a scalar\n return nacl.box.keyPair.fromSecretKey(seed);\n}\n\n/** Base64url encode (for QR codes) */\nexport function base64urlEncode(data: Uint8Array): string {\n return encodeBase64(data)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/** Base64url decode */\nexport function base64urlDecode(str: string): Uint8Array {\n let b64 = str.replace(/-/g, '+').replace(/_/g, '/');\n while (b64.length % 4 !== 0) b64 += '=';\n return decodeBase64(b64);\n}\n","import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\n\nconst AES_KEY_LENGTH = 32; // 256 bits\nconst NONCE_LENGTH = 12;\nconst TAG_LENGTH = 16;\nconst VERSION_BYTE = 0x00;\n\n/** AES-256-GCM encrypt. Format: version(1) + nonce(12) + ciphertext + tag(16) */\nexport function aesGcmEncrypt(plaintext: Uint8Array, key: Uint8Array): Uint8Array {\n if (key.length !== AES_KEY_LENGTH) {\n throw new Error(`AES key must be ${AES_KEY_LENGTH} bytes, got ${key.length}`);\n }\n const nonce = randomBytes(NONCE_LENGTH);\n const cipher = createCipheriv('aes-256-gcm', key, nonce);\n const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);\n const tag = cipher.getAuthTag();\n\n const result = new Uint8Array(1 + NONCE_LENGTH + encrypted.length + TAG_LENGTH);\n result[0] = VERSION_BYTE;\n result.set(nonce, 1);\n result.set(encrypted, 1 + NONCE_LENGTH);\n result.set(tag, 1 + NONCE_LENGTH + encrypted.length);\n return result;\n}\n\n/** AES-256-GCM decrypt. Input: version(1) + nonce(12) + ciphertext + tag(16) */\nexport function aesGcmDecrypt(data: Uint8Array, key: Uint8Array): Uint8Array {\n if (key.length !== AES_KEY_LENGTH) {\n throw new Error(`AES key must be ${AES_KEY_LENGTH} bytes, got ${key.length}`);\n }\n if (data.length < 1 + NONCE_LENGTH + TAG_LENGTH) {\n throw new Error('AES-GCM data too short');\n }\n\n const version = data[0];\n if (version !== VERSION_BYTE) {\n throw new Error(`Unsupported AES-GCM version: ${version}`);\n }\n\n const nonce = data.slice(1, 1 + NONCE_LENGTH);\n const ciphertextEnd = data.length - TAG_LENGTH;\n const ciphertext = data.slice(1 + NONCE_LENGTH, ciphertextEnd);\n const tag = data.slice(ciphertextEnd);\n\n const decipher = createDecipheriv('aes-256-gcm', key, nonce);\n decipher.setAuthTag(tag);\n const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);\n return new Uint8Array(decrypted);\n}\n","import { createHmac } from 'node:crypto';\nimport nacl from 'tweetnacl';\nimport { MASTER_SEED_LABEL, CONTENT_CHILD_LABEL } from '../config.js';\n\ninterface DerivedKey {\n key: Uint8Array; // first 32 bytes\n chainCode: Uint8Array; // last 32 bytes\n}\n\n/** HMAC-SHA512 producing 64 bytes, split into key(32) + chainCode(32) */\nfunction hmacSha512(key: string | Uint8Array, data: Uint8Array): DerivedKey {\n const hmac = createHmac('sha512', key instanceof Uint8Array ? Buffer.from(key) : key);\n hmac.update(Buffer.from(data));\n const result = new Uint8Array(hmac.digest());\n return {\n key: result.slice(0, 32),\n chainCode: result.slice(32),\n };\n}\n\n/** Derive the root of the secret key tree from the master secret */\nexport function deriveSecretKeyTreeRoot(masterSecret: Uint8Array): DerivedKey {\n return hmacSha512(MASTER_SEED_LABEL, masterSecret);\n}\n\n/** Derive a child key: HMAC-SHA512(chainCode, 0x00 + label_bytes) */\nexport function deriveSecretKeyTreeChild(chainCode: Uint8Array, label: string): DerivedKey {\n const labelBytes = new TextEncoder().encode(label);\n const data = new Uint8Array(1 + labelBytes.length);\n data[0] = 0x00;\n data.set(labelBytes, 1);\n return hmacSha512(chainCode, data);\n}\n\n/** Full derivation: masterSecret → root → content child → contentDataKey(32) */\nexport function deriveContentDataKey(masterSecret: Uint8Array): Uint8Array {\n const root = deriveSecretKeyTreeRoot(masterSecret);\n const child = deriveSecretKeyTreeChild(root.chainCode, CONTENT_CHILD_LABEL);\n return child.key;\n}\n\n/** Derive the NaCl box keypair used for content encryption (from contentDataKey seed) */\nexport function deriveContentKeyPair(masterSecret: Uint8Array): nacl.BoxKeyPair {\n const contentDataKey = deriveContentDataKey(masterSecret);\n return nacl.box.keyPair.fromSecretKey(contentDataKey);\n}\n\n/** Derive the Ed25519 signing keypair from the master secret */\nexport function deriveSigningKeyPair(masterSecret: Uint8Array): nacl.SignKeyPair {\n return nacl.sign.keyPair.fromSeed(masterSecret);\n}\n","import { secretBoxSeal, secretBoxOpen, encodeBase64, decodeBase64 } from './nacl.js';\nimport { aesGcmEncrypt, aesGcmDecrypt } from './aes.js';\nimport { boxOpen, boxKeyPairFromSeed } from './nacl.js';\nimport { deriveContentDataKey } from './keys.js';\n\nexport type EncryptionVariant = 'legacy' | 'dataKey';\n\nexport interface EncryptionManager {\n key: Uint8Array;\n variant: EncryptionVariant;\n}\n\n/** Encrypt raw bytes (already JSON-encoded) */\nexport function encryptRaw(em: EncryptionManager, plaintext: Uint8Array): Uint8Array {\n if (em.variant === 'legacy') {\n return secretBoxSeal(plaintext, em.key);\n }\n return aesGcmEncrypt(plaintext, em.key);\n}\n\n/** Decrypt raw bytes to plaintext */\nexport function decryptRaw(em: EncryptionManager, ciphertext: Uint8Array): Uint8Array {\n if (em.variant === 'legacy') {\n return secretBoxOpen(ciphertext, em.key);\n }\n return aesGcmDecrypt(ciphertext, em.key);\n}\n\n/** Encrypt a JS value: JSON.stringify → encrypt → raw bytes */\nexport function encryptValue(em: EncryptionManager, value: unknown): Uint8Array {\n const json = JSON.stringify(value);\n const plaintext = new TextEncoder().encode(json);\n return encryptRaw(em, plaintext);\n}\n\n/** Decrypt raw bytes → JSON.parse → JS value */\nexport function decryptValue(em: EncryptionManager, ciphertext: Uint8Array): unknown {\n const plaintext = decryptRaw(em, ciphertext);\n const json = new TextDecoder().decode(plaintext);\n return JSON.parse(json);\n}\n\n/** Encrypt a value and return base64 string */\nexport function encryptToBase64(em: EncryptionManager, value: unknown): string {\n const encrypted = encryptValue(em, value);\n return encodeBase64(encrypted);\n}\n\n/** Decrypt a base64 string to a JS value */\nexport function decryptFromBase64(em: EncryptionManager, base64: string): unknown {\n const data = decodeBase64(base64);\n return decryptValue(em, data);\n}\n\n/**\n * Determine the encryption key and variant for a session.\n * Returns the EncryptionManager config for the session.\n */\nexport function resolveSessionEncryption(\n dataEncryptionKey: string | null | undefined,\n masterSecret: Uint8Array,\n): EncryptionManager {\n if (dataEncryptionKey) {\n try {\n const dekData = decodeBase64(dataEncryptionKey);\n if (dekData.length > 1 && dekData[0] === 0x00) {\n // dataKey variant: decrypt the per-session key\n const encryptedKey = dekData.slice(1);\n const contentDataKey = deriveContentDataKey(masterSecret);\n const contentKeyPair = boxKeyPairFromSeed(contentDataKey);\n const sessionKey = boxOpen(encryptedKey, contentKeyPair.secretKey);\n return { key: sessionKey, variant: 'dataKey' };\n }\n } catch {\n // Fall through to legacy\n }\n }\n return { key: masterSecret, variant: 'legacy' };\n}\n"],"mappings":"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","names":["nacl","nacl"]}
@@ -0,0 +1,236 @@
1
+ import {
2
+ deriveSigningKeyPair,
3
+ encodeBase64
4
+ } from "./chunk-LWJDKIJF.js";
5
+ import {
6
+ CODESAIL_DIR,
7
+ CONFIG_PATH,
8
+ CREDENTIALS_PATH,
9
+ DEFAULT_SERVER_URL,
10
+ SOCKET_PATH
11
+ } from "./chunk-HG54UP2Y.js";
12
+
13
+ // src/storage/credentials.ts
14
+ import { readFile, writeFile, mkdir } from "fs/promises";
15
+ async function loadCredentials() {
16
+ try {
17
+ const data = await readFile(CREDENTIALS_PATH, "utf-8");
18
+ return JSON.parse(data);
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ async function saveCredentials(creds) {
24
+ await mkdir(CODESAIL_DIR, { recursive: true });
25
+ await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 384 });
26
+ }
27
+
28
+ // src/storage/config.ts
29
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
30
+ async function loadConfig() {
31
+ try {
32
+ const data = await readFile2(CONFIG_PATH, "utf-8");
33
+ return JSON.parse(data);
34
+ } catch {
35
+ return {};
36
+ }
37
+ }
38
+ async function saveConfig(config) {
39
+ await mkdir2(CODESAIL_DIR, { recursive: true });
40
+ await writeFile2(CONFIG_PATH, JSON.stringify(config, null, 2));
41
+ }
42
+ async function setConfigValue(key, value) {
43
+ const config = await loadConfig();
44
+ config[key] = value;
45
+ await saveConfig(config);
46
+ }
47
+
48
+ // src/core/crypto/signing.ts
49
+ import nacl from "tweetnacl";
50
+ function generateChallenge() {
51
+ return nacl.randomBytes(32);
52
+ }
53
+ function signChallenge(challenge, secretKey) {
54
+ return nacl.sign.detached(challenge, secretKey);
55
+ }
56
+ function createAuthPayload(masterSecret) {
57
+ const keyPair = deriveSigningKeyPair(masterSecret);
58
+ const challenge = generateChallenge();
59
+ const signature = signChallenge(challenge, keyPair.secretKey);
60
+ return {
61
+ challenge,
62
+ publicKey: keyPair.publicKey,
63
+ signature
64
+ };
65
+ }
66
+
67
+ // src/core/api.ts
68
+ function getServerURL(override) {
69
+ return override ?? DEFAULT_SERVER_URL;
70
+ }
71
+ async function resolveServerURL() {
72
+ const creds = await loadCredentials();
73
+ if (creds?.serverURL) return creds.serverURL;
74
+ const config = await loadConfig();
75
+ if (config.serverURL && typeof config.serverURL === "string") return config.serverURL;
76
+ return DEFAULT_SERVER_URL;
77
+ }
78
+ async function apiFetch(path, opts = {}) {
79
+ const serverURL = opts.serverURL ?? await resolveServerURL();
80
+ let url = serverURL + path;
81
+ if (opts.queryParams) {
82
+ const params = new URLSearchParams();
83
+ for (const [k, v] of Object.entries(opts.queryParams)) {
84
+ params.set(k, v);
85
+ }
86
+ let qs = params.toString();
87
+ qs = qs.replace(/\+/g, "%2B");
88
+ url += "?" + qs;
89
+ }
90
+ const headers = {
91
+ "Content-Type": "application/json"
92
+ };
93
+ if (opts.token) {
94
+ headers["Authorization"] = `Bearer ${opts.token}`;
95
+ }
96
+ const fetchOpts = {
97
+ method: opts.method ?? "GET",
98
+ headers
99
+ };
100
+ if (opts.body) {
101
+ fetchOpts.body = JSON.stringify(opts.body);
102
+ }
103
+ const response = await fetch(url, fetchOpts);
104
+ if (!response.ok) {
105
+ const text = await response.text();
106
+ throw new Error(`HTTP ${response.status}: ${text}`);
107
+ }
108
+ const data = await response.json();
109
+ return data;
110
+ }
111
+ async function authenticate(masterSecret, serverURL) {
112
+ const payload = createAuthPayload(masterSecret);
113
+ const body = {
114
+ challenge: encodeBase64(payload.challenge),
115
+ publicKey: encodeBase64(payload.publicKey),
116
+ signature: encodeBase64(payload.signature)
117
+ };
118
+ const result = await apiFetch("/v1/auth", {
119
+ method: "POST",
120
+ body,
121
+ serverURL: getServerURL(serverURL)
122
+ });
123
+ if (!result.success || !result.token) {
124
+ throw new Error("Authentication failed");
125
+ }
126
+ return result.token;
127
+ }
128
+ async function createAuthRequest(publicKey, supportsV2, serverURL) {
129
+ return apiFetch("/v1/auth/request", {
130
+ method: "POST",
131
+ body: { publicKey, supportsV2 },
132
+ serverURL: getServerURL(serverURL)
133
+ });
134
+ }
135
+ async function fetchSessions(token, serverURL) {
136
+ const result = await apiFetch("/v1/sessions", {
137
+ token,
138
+ serverURL: getServerURL(serverURL)
139
+ });
140
+ return result.sessions;
141
+ }
142
+ async function fetchMessages(sessionId, token, serverURL) {
143
+ const result = await apiFetch(`/v1/sessions/${sessionId}/messages`, {
144
+ token,
145
+ serverURL: getServerURL(serverURL)
146
+ });
147
+ return result.messages;
148
+ }
149
+
150
+ // src/core/socket.ts
151
+ import { io } from "socket.io-client";
152
+ function createSocketClient(serverURL, token) {
153
+ const socket = io(serverURL, {
154
+ path: SOCKET_PATH,
155
+ transports: ["websocket"],
156
+ reconnection: true,
157
+ reconnectionDelay: 1e3,
158
+ reconnectionDelayMax: 5e3,
159
+ auth: {
160
+ token,
161
+ clientType: "user-scoped"
162
+ }
163
+ });
164
+ return {
165
+ socket,
166
+ connect() {
167
+ socket.connect();
168
+ },
169
+ disconnect() {
170
+ socket.disconnect();
171
+ },
172
+ onUpdate(handler) {
173
+ socket.on("update", handler);
174
+ },
175
+ onEphemeral(handler) {
176
+ socket.on("ephemeral", handler);
177
+ },
178
+ onConnect(handler) {
179
+ socket.on("connect", handler);
180
+ },
181
+ onDisconnect(handler) {
182
+ socket.on("disconnect", handler);
183
+ },
184
+ sendMessage(sessionId, encryptedBase64) {
185
+ socket.emit("message", { sid: sessionId, message: encryptedBase64 });
186
+ },
187
+ rpcCall(method, params) {
188
+ return new Promise((resolve) => {
189
+ socket.timeout(3e4).emit("rpc-call", { method, params }, (err, response) => {
190
+ if (err) {
191
+ resolve({ ok: false, error: "timeout" });
192
+ } else {
193
+ resolve(response ?? { ok: false, error: "empty response" });
194
+ }
195
+ });
196
+ });
197
+ },
198
+ updateMetadata(sessionId, expectedVersion, encryptedBase64) {
199
+ return new Promise((resolve) => {
200
+ socket.timeout(15e3).emit("update-metadata", {
201
+ sid: sessionId,
202
+ expectedVersion,
203
+ metadata: encryptedBase64
204
+ }, (err, response) => {
205
+ if (err) resolve({ result: "error" });
206
+ else resolve(response ?? { result: "error" });
207
+ });
208
+ });
209
+ },
210
+ updateState(sessionId, expectedVersion, encryptedBase64) {
211
+ return new Promise((resolve) => {
212
+ socket.timeout(15e3).emit("update-state", {
213
+ sid: sessionId,
214
+ expectedVersion,
215
+ agentState: encryptedBase64
216
+ }, (err, response) => {
217
+ if (err) resolve({ result: "error" });
218
+ else resolve(response ?? { result: "error" });
219
+ });
220
+ });
221
+ }
222
+ };
223
+ }
224
+
225
+ export {
226
+ loadCredentials,
227
+ saveCredentials,
228
+ loadConfig,
229
+ setConfigValue,
230
+ authenticate,
231
+ createAuthRequest,
232
+ fetchSessions,
233
+ fetchMessages,
234
+ createSocketClient
235
+ };
236
+ //# sourceMappingURL=chunk-SFXOQ3OG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage/credentials.ts","../src/storage/config.ts","../src/core/crypto/signing.ts","../src/core/api.ts","../src/core/socket.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { CODESAIL_DIR, CREDENTIALS_PATH } from '../core/config.js';\n\nexport interface StoredCredentials {\n token: string;\n masterSecret: string; // base64\n serverURL?: string;\n}\n\nexport async function loadCredentials(): Promise<StoredCredentials | null> {\n try {\n const data = await readFile(CREDENTIALS_PATH, 'utf-8');\n return JSON.parse(data) as StoredCredentials;\n } catch {\n return null;\n }\n}\n\nexport async function saveCredentials(creds: StoredCredentials): Promise<void> {\n await mkdir(CODESAIL_DIR, { recursive: true });\n await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 0o600 });\n}\n\nexport async function deleteCredentials(): Promise<void> {\n try {\n const { unlink } = await import('node:fs/promises');\n await unlink(CREDENTIALS_PATH);\n } catch {\n // Already deleted\n }\n}\n","import { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { CODESAIL_DIR, CONFIG_PATH } from '../core/config.js';\n\nexport interface AppConfig {\n serverURL?: string;\n [key: string]: unknown;\n}\n\nexport async function loadConfig(): Promise<AppConfig> {\n try {\n const data = await readFile(CONFIG_PATH, 'utf-8');\n return JSON.parse(data) as AppConfig;\n } catch {\n return {};\n }\n}\n\nexport async function saveConfig(config: AppConfig): Promise<void> {\n await mkdir(CODESAIL_DIR, { recursive: true });\n await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));\n}\n\nexport async function getConfigValue(key: string): Promise<unknown> {\n const config = await loadConfig();\n return config[key];\n}\n\nexport async function setConfigValue(key: string, value: unknown): Promise<void> {\n const config = await loadConfig();\n config[key] = value;\n await saveConfig(config);\n}\n","import nacl from 'tweetnacl';\nimport { deriveSigningKeyPair } from './keys.js';\n\n/** Generate a random 32-byte challenge */\nexport function generateChallenge(): Uint8Array {\n return nacl.randomBytes(32);\n}\n\n/** Sign a challenge with detached signature */\nexport function signChallenge(challenge: Uint8Array, secretKey: Uint8Array): Uint8Array {\n return nacl.sign.detached(challenge, secretKey);\n}\n\n/** Full auth flow: derive signing keypair, generate challenge, sign it */\nexport function createAuthPayload(masterSecret: Uint8Array): {\n challenge: Uint8Array;\n publicKey: Uint8Array;\n signature: Uint8Array;\n} {\n const keyPair = deriveSigningKeyPair(masterSecret);\n const challenge = generateChallenge();\n const signature = signChallenge(challenge, keyPair.secretKey);\n return {\n challenge,\n publicKey: keyPair.publicKey,\n signature,\n };\n}\n","import { encodeBase64 } from './crypto/nacl.js';\nimport { createAuthPayload } from './crypto/signing.js';\nimport { DEFAULT_SERVER_URL } from './config.js';\nimport { loadCredentials } from '../storage/credentials.js';\nimport { loadConfig } from '../storage/config.js';\nimport type { RawSession } from '../types/session.js';\nimport type { RawMessage } from '../types/message.js';\n\nfunction getServerURL(override?: string): string {\n return override ?? DEFAULT_SERVER_URL;\n}\n\nasync function resolveServerURL(): Promise<string> {\n const creds = await loadCredentials();\n if (creds?.serverURL) return creds.serverURL;\n const config = await loadConfig();\n if (config.serverURL && typeof config.serverURL === 'string') return config.serverURL;\n return DEFAULT_SERVER_URL;\n}\n\ninterface FetchOptions {\n method?: string;\n body?: Record<string, unknown>;\n token?: string;\n serverURL?: string;\n queryParams?: Record<string, string>;\n}\n\nasync function apiFetch<T>(path: string, opts: FetchOptions = {}): Promise<T> {\n const serverURL = opts.serverURL ?? await resolveServerURL();\n let url = serverURL + path;\n\n if (opts.queryParams) {\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(opts.queryParams)) {\n params.set(k, v);\n }\n // Manually encode + as %2B (Node URLSearchParams encodes + correctly, but be safe)\n let qs = params.toString();\n qs = qs.replace(/\\+/g, '%2B');\n url += '?' + qs;\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (opts.token) {\n headers['Authorization'] = `Bearer ${opts.token}`;\n }\n\n const fetchOpts: RequestInit = {\n method: opts.method ?? 'GET',\n headers,\n };\n if (opts.body) {\n fetchOpts.body = JSON.stringify(opts.body);\n }\n\n const response = await fetch(url, fetchOpts);\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text}`);\n }\n\n const data = await response.json();\n return data as T;\n}\n\n// ---- Auth ----\n\ninterface AuthResponse {\n success: boolean;\n token?: string;\n}\n\n/** Authenticate with master secret → get JWT token */\nexport async function authenticate(\n masterSecret: Uint8Array,\n serverURL?: string,\n): Promise<string> {\n const payload = createAuthPayload(masterSecret);\n const body = {\n challenge: encodeBase64(payload.challenge),\n publicKey: encodeBase64(payload.publicKey),\n signature: encodeBase64(payload.signature),\n };\n\n const result = await apiFetch<AuthResponse>('/v1/auth', {\n method: 'POST',\n body,\n serverURL: getServerURL(serverURL),\n });\n\n if (!result.success || !result.token) {\n throw new Error('Authentication failed');\n }\n return result.token;\n}\n\n// ---- Auth Request (QR pairing) ----\n\ninterface AuthRequestResponse {\n status: string;\n supportsV2?: boolean;\n response?: string; // base64 encrypted\n}\n\n/** Create or poll an auth request (CLI side of QR pairing) */\nexport async function createAuthRequest(\n publicKey: string, // base64\n supportsV2: boolean,\n serverURL?: string,\n): Promise<AuthRequestResponse> {\n return apiFetch<AuthRequestResponse>('/v1/auth/request', {\n method: 'POST',\n body: { publicKey, supportsV2 },\n serverURL: getServerURL(serverURL),\n });\n}\n\n/** Check auth request status */\nexport async function checkAuthRequestStatus(\n publicKey: string, // base64\n token: string,\n serverURL?: string,\n): Promise<{ status: string; supportsV2?: boolean }> {\n return apiFetch('/v1/auth/request/status', {\n token,\n queryParams: { publicKey },\n serverURL: getServerURL(serverURL),\n });\n}\n\n/** Send auth response (mobile side of QR pairing) */\nexport async function sendAuthResponse(\n publicKey: string, // base64\n response: string, // base64\n token: string,\n serverURL?: string,\n): Promise<void> {\n await apiFetch('/v1/auth/response', {\n method: 'POST',\n body: { publicKey, response },\n token,\n serverURL: getServerURL(serverURL),\n });\n}\n\n// ---- Sessions ----\n\ninterface SessionsResponse {\n sessions: RawSession[];\n}\n\n/** Fetch all sessions */\nexport async function fetchSessions(token: string, serverURL?: string): Promise<RawSession[]> {\n const result = await apiFetch<SessionsResponse>('/v1/sessions', {\n token,\n serverURL: getServerURL(serverURL),\n });\n return result.sessions;\n}\n\n// ---- Messages ----\n\ninterface MessagesResponse {\n messages: RawMessage[];\n}\n\n/** Fetch messages for a session */\nexport async function fetchMessages(\n sessionId: string,\n token: string,\n serverURL?: string,\n): Promise<RawMessage[]> {\n const result = await apiFetch<MessagesResponse>(`/v1/sessions/${sessionId}/messages`, {\n token,\n serverURL: getServerURL(serverURL),\n });\n return result.messages;\n}\n","import { io, Socket } from 'socket.io-client';\nimport { SOCKET_PATH } from './config.js';\n\nexport type UpdateHandler = (data: unknown) => void;\nexport type EphemeralHandler = (data: unknown) => void;\n\nexport interface SocketClient {\n socket: Socket;\n connect(): void;\n disconnect(): void;\n onUpdate(handler: UpdateHandler): void;\n onEphemeral(handler: EphemeralHandler): void;\n onConnect(handler: () => void): void;\n onDisconnect(handler: (reason: string) => void): void;\n sendMessage(sessionId: string, encryptedBase64: string): void;\n rpcCall(method: string, params: string): Promise<Record<string, unknown>>;\n updateMetadata(sessionId: string, expectedVersion: number, encryptedBase64: string): Promise<Record<string, unknown>>;\n updateState(sessionId: string, expectedVersion: number, encryptedBase64: string | null): Promise<Record<string, unknown>>;\n}\n\nexport function createSocketClient(serverURL: string, token: string): SocketClient {\n const socket = io(serverURL, {\n path: SOCKET_PATH,\n transports: ['websocket'],\n reconnection: true,\n reconnectionDelay: 1000,\n reconnectionDelayMax: 5000,\n auth: {\n token,\n clientType: 'user-scoped',\n },\n });\n\n return {\n socket,\n\n connect() {\n socket.connect();\n },\n\n disconnect() {\n socket.disconnect();\n },\n\n onUpdate(handler: UpdateHandler) {\n socket.on('update', handler);\n },\n\n onEphemeral(handler: EphemeralHandler) {\n socket.on('ephemeral', handler);\n },\n\n onConnect(handler: () => void) {\n socket.on('connect', handler);\n },\n\n onDisconnect(handler: (reason: string) => void) {\n socket.on('disconnect', handler);\n },\n\n sendMessage(sessionId: string, encryptedBase64: string) {\n socket.emit('message', { sid: sessionId, message: encryptedBase64 });\n },\n\n rpcCall(method: string, params: string): Promise<Record<string, unknown>> {\n return new Promise((resolve) => {\n socket.timeout(30000).emit('rpc-call', { method, params }, (err: Error | null, response: unknown) => {\n if (err) {\n resolve({ ok: false, error: 'timeout' });\n } else {\n resolve((response as Record<string, unknown>) ?? { ok: false, error: 'empty response' });\n }\n });\n });\n },\n\n updateMetadata(sessionId: string, expectedVersion: number, encryptedBase64: string): Promise<Record<string, unknown>> {\n return new Promise((resolve) => {\n socket.timeout(15000).emit('update-metadata', {\n sid: sessionId,\n expectedVersion,\n metadata: encryptedBase64,\n }, (err: Error | null, response: unknown) => {\n if (err) resolve({ result: 'error' });\n else resolve((response as Record<string, unknown>) ?? { result: 'error' });\n });\n });\n },\n\n updateState(sessionId: string, expectedVersion: number, encryptedBase64: string | null): Promise<Record<string, unknown>> {\n return new Promise((resolve) => {\n socket.timeout(15000).emit('update-state', {\n sid: sessionId,\n expectedVersion,\n agentState: encryptedBase64,\n }, (err: Error | null, response: unknown) => {\n if (err) resolve({ result: 'error' });\n else resolve((response as Record<string, unknown>) ?? { result: 'error' });\n });\n });\n },\n };\n}\n"],"mappings":"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","names":["readFile","writeFile","mkdir","readFile","mkdir","writeFile"]}