happy-coder 0.10.0-2 → 0.10.0-4
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 +10 -1
- package/bin/happy-mcp.mjs +32 -0
- package/dist/codex/happyMcpStdioBridge.cjs +80 -0
- package/dist/codex/happyMcpStdioBridge.d.cts +2 -0
- package/dist/codex/happyMcpStdioBridge.d.mts +2 -0
- package/dist/codex/happyMcpStdioBridge.mjs +78 -0
- package/dist/index-67rskwL7.cjs +6033 -0
- package/dist/index-Dw96QD4T.mjs +6025 -0
- package/dist/index.cjs +33 -6024
- package/dist/index.mjs +33 -6024
- package/dist/lib.cjs +2 -1
- package/dist/lib.d.cts +54 -112
- package/dist/lib.d.mts +54 -112
- package/dist/lib.mjs +2 -1
- package/dist/runCodex-BLNf5zb1.cjs +1155 -0
- package/dist/runCodex-BNH8w4O9.mjs +1153 -0
- package/dist/{types-xfXKJHdM.mjs → types-2wHnX7UW.mjs} +311 -170
- package/dist/{types-WP9wteZE.cjs → types-BcDnTXMg.cjs} +348 -206
- package/package.json +20 -6
- package/scripts/ripgrep_launcher.cjs +2 -26
- package/scripts/unpack-tools.cjs +163 -0
- package/tools/archives/difftastic-LICENSE +21 -0
- package/tools/archives/difftastic-arm64-darwin.tar.gz +0 -0
- package/tools/archives/difftastic-arm64-linux.tar.gz +0 -0
- package/tools/archives/difftastic-x64-darwin.tar.gz +0 -0
- package/tools/archives/difftastic-x64-linux.tar.gz +0 -0
- package/tools/archives/difftastic-x64-win32.tar.gz +0 -0
- package/tools/archives/ripgrep-arm64-darwin.tar.gz +0 -0
- package/tools/archives/ripgrep-arm64-linux.tar.gz +0 -0
- package/tools/archives/ripgrep-x64-darwin.tar.gz +0 -0
- package/tools/archives/ripgrep-x64-linux.tar.gz +0 -0
- package/tools/archives/ripgrep-x64-win32.tar.gz +0 -0
- package/tools/licenses/difftastic-LICENSE +21 -0
- package/tools/licenses/ripgrep-LICENSE +3 -0
- package/tools/unpacked/difft +0 -0
- package/ripgrep/arm64-linux/rg +0 -0
- package/ripgrep/arm64-linux/ripgrep.node +0 -0
- package/ripgrep/x64-darwin/rg +0 -0
- package/ripgrep/x64-darwin/ripgrep.node +0 -0
- package/ripgrep/x64-linux/rg +0 -0
- package/ripgrep/x64-linux/ripgrep.node +0 -0
- package/ripgrep/x64-win32/rg.exe +0 -0
- package/ripgrep/x64-win32/ripgrep.node +0 -0
- /package/{ripgrep/COPYING → tools/archives/ripgrep-LICENSE} +0 -0
- /package/{ripgrep/arm64-darwin → tools/unpacked}/rg +0 -0
- /package/{ripgrep/arm64-darwin → tools/unpacked}/ripgrep.node +0 -0
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
var axios = require('axios');
|
|
4
4
|
var chalk = require('chalk');
|
|
5
|
-
var fs = require('fs');
|
|
6
|
-
var
|
|
5
|
+
var fs$1 = require('fs');
|
|
6
|
+
var fs = require('node:fs');
|
|
7
7
|
var os = require('node:os');
|
|
8
8
|
var node_path = require('node:path');
|
|
9
9
|
var promises = require('node:fs/promises');
|
|
@@ -14,10 +14,11 @@ var node_events = require('node:events');
|
|
|
14
14
|
var socket_ioClient = require('socket.io-client');
|
|
15
15
|
var child_process = require('child_process');
|
|
16
16
|
var util = require('util');
|
|
17
|
-
var fs$
|
|
17
|
+
var fs$2 = require('fs/promises');
|
|
18
18
|
var crypto = require('crypto');
|
|
19
19
|
var path = require('path');
|
|
20
20
|
var url = require('url');
|
|
21
|
+
var os$1 = require('os');
|
|
21
22
|
var expoServerSdk = require('expo-server-sdk');
|
|
22
23
|
|
|
23
24
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -41,8 +42,8 @@ function _interopNamespaceDefault(e) {
|
|
|
41
42
|
var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
|
|
42
43
|
|
|
43
44
|
var name = "happy-coder";
|
|
44
|
-
var version = "0.10.0-
|
|
45
|
-
var description = "Claude Code
|
|
45
|
+
var version = "0.10.0-4";
|
|
46
|
+
var description = "Mobile and Web client for Claude Code and Codex";
|
|
46
47
|
var author = "Kirill Dubovitskiy";
|
|
47
48
|
var license = "MIT";
|
|
48
49
|
var type = "module";
|
|
@@ -50,7 +51,8 @@ var homepage = "https://github.com/slopus/happy-cli";
|
|
|
50
51
|
var bugs = "https://github.com/slopus/happy-cli/issues";
|
|
51
52
|
var repository = "slopus/happy-cli";
|
|
52
53
|
var bin = {
|
|
53
|
-
happy: "./bin/happy.mjs"
|
|
54
|
+
happy: "./bin/happy.mjs",
|
|
55
|
+
"happy-mcp": "./bin/happy-mcp.mjs"
|
|
54
56
|
};
|
|
55
57
|
var main = "./dist/index.cjs";
|
|
56
58
|
var module$1 = "./dist/index.mjs";
|
|
@@ -75,13 +77,23 @@ var exports$1 = {
|
|
|
75
77
|
types: "./dist/lib.d.mts",
|
|
76
78
|
"default": "./dist/lib.mjs"
|
|
77
79
|
}
|
|
80
|
+
},
|
|
81
|
+
"./codex/happyMcpStdioBridge": {
|
|
82
|
+
require: {
|
|
83
|
+
types: "./dist/codex/happyMcpStdioBridge.d.cts",
|
|
84
|
+
"default": "./dist/codex/happyMcpStdioBridge.cjs"
|
|
85
|
+
},
|
|
86
|
+
"import": {
|
|
87
|
+
types: "./dist/codex/happyMcpStdioBridge.d.mts",
|
|
88
|
+
"default": "./dist/codex/happyMcpStdioBridge.mjs"
|
|
89
|
+
}
|
|
78
90
|
}
|
|
79
91
|
};
|
|
80
92
|
var files = [
|
|
81
93
|
"dist",
|
|
82
94
|
"bin",
|
|
83
95
|
"scripts",
|
|
84
|
-
"
|
|
96
|
+
"tools",
|
|
85
97
|
"package.json"
|
|
86
98
|
];
|
|
87
99
|
var scripts = {
|
|
@@ -90,17 +102,19 @@ var scripts = {
|
|
|
90
102
|
build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
|
|
91
103
|
test: "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
|
|
92
104
|
start: "yarn build && ./bin/happy.mjs",
|
|
93
|
-
dev: "
|
|
105
|
+
dev: "tsx --env-file .env.dev src/index.ts",
|
|
94
106
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
95
107
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
96
108
|
prepublishOnly: "yarn build && yarn test",
|
|
97
|
-
release: "release-it"
|
|
109
|
+
release: "release-it",
|
|
110
|
+
postinstall: "node scripts/unpack-tools.cjs"
|
|
98
111
|
};
|
|
99
112
|
var dependencies = {
|
|
100
113
|
"@anthropic-ai/claude-code": "^1.0.102",
|
|
101
114
|
"@anthropic-ai/sdk": "^0.56.0",
|
|
102
115
|
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
103
116
|
"@stablelib/base64": "^2.0.1",
|
|
117
|
+
"@stablelib/hex": "^2.0.1",
|
|
104
118
|
"@types/cross-spawn": "^6.0.6",
|
|
105
119
|
"@types/http-proxy": "^1.17.16",
|
|
106
120
|
"@types/ps-list": "^6.2.1",
|
|
@@ -120,6 +134,7 @@ var dependencies = {
|
|
|
120
134
|
"qrcode-terminal": "^0.12.0",
|
|
121
135
|
react: "^19.1.1",
|
|
122
136
|
"socket.io-client": "^4.8.1",
|
|
137
|
+
tar: "^7.4.3",
|
|
123
138
|
tweetnacl: "^1.0.3",
|
|
124
139
|
zod: "^3.23.8"
|
|
125
140
|
};
|
|
@@ -173,6 +188,7 @@ var packageJson = {
|
|
|
173
188
|
|
|
174
189
|
class Configuration {
|
|
175
190
|
serverUrl;
|
|
191
|
+
webappUrl;
|
|
176
192
|
isDaemonProcess;
|
|
177
193
|
// Directories and paths (from persistence)
|
|
178
194
|
happyHomeDir;
|
|
@@ -185,6 +201,7 @@ class Configuration {
|
|
|
185
201
|
isExperimentalEnabled;
|
|
186
202
|
constructor() {
|
|
187
203
|
this.serverUrl = process.env.HAPPY_SERVER_URL || "https://api.cluster-fluster.com";
|
|
204
|
+
this.webappUrl = process.env.HAPPY_WEBAPP_URL || "https://app.happy.engineering";
|
|
188
205
|
const args = process.argv.slice(2);
|
|
189
206
|
this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
|
|
190
207
|
if (process.env.HAPPY_HOME_DIR) {
|
|
@@ -200,11 +217,11 @@ class Configuration {
|
|
|
200
217
|
this.daemonLockFile = node_path.join(this.happyHomeDir, "daemon.state.json.lock");
|
|
201
218
|
this.isExperimentalEnabled = ["true", "1", "yes"].includes(process.env.HAPPY_EXPERIMENTAL?.toLowerCase() || "");
|
|
202
219
|
this.currentCliVersion = packageJson.version;
|
|
203
|
-
if (!
|
|
204
|
-
|
|
220
|
+
if (!fs.existsSync(this.happyHomeDir)) {
|
|
221
|
+
fs.mkdirSync(this.happyHomeDir, { recursive: true });
|
|
205
222
|
}
|
|
206
|
-
if (!
|
|
207
|
-
|
|
223
|
+
if (!fs.existsSync(this.logsDir)) {
|
|
224
|
+
fs.mkdirSync(this.logsDir, { recursive: true });
|
|
208
225
|
}
|
|
209
226
|
}
|
|
210
227
|
}
|
|
@@ -229,7 +246,17 @@ function decodeBase64(base64, variant = "base64") {
|
|
|
229
246
|
function getRandomBytes(size) {
|
|
230
247
|
return new Uint8Array(node_crypto.randomBytes(size));
|
|
231
248
|
}
|
|
232
|
-
function
|
|
249
|
+
function libsodiumEncryptForPublicKey(data, recipientPublicKey) {
|
|
250
|
+
const ephemeralKeyPair = tweetnacl.box.keyPair();
|
|
251
|
+
const nonce = getRandomBytes(tweetnacl.box.nonceLength);
|
|
252
|
+
const encrypted = tweetnacl.box(data, nonce, recipientPublicKey, ephemeralKeyPair.secretKey);
|
|
253
|
+
const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
|
|
254
|
+
result.set(ephemeralKeyPair.publicKey, 0);
|
|
255
|
+
result.set(nonce, ephemeralKeyPair.publicKey.length);
|
|
256
|
+
result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
function encryptLegacy(data, secret) {
|
|
233
260
|
const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
|
|
234
261
|
const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
|
|
235
262
|
const result = new Uint8Array(nonce.length + encrypted.length);
|
|
@@ -237,7 +264,7 @@ function encrypt(data, secret) {
|
|
|
237
264
|
result.set(encrypted, nonce.length);
|
|
238
265
|
return result;
|
|
239
266
|
}
|
|
240
|
-
function
|
|
267
|
+
function decryptLegacy(data, secret) {
|
|
241
268
|
const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
|
|
242
269
|
const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
|
|
243
270
|
const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
|
|
@@ -246,12 +273,67 @@ function decrypt(data, secret) {
|
|
|
246
273
|
}
|
|
247
274
|
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
248
275
|
}
|
|
276
|
+
function encryptWithDataKey(data, dataKey) {
|
|
277
|
+
const nonce = getRandomBytes(12);
|
|
278
|
+
const cipher = node_crypto.createCipheriv("aes-256-gcm", dataKey, nonce);
|
|
279
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(data));
|
|
280
|
+
const encrypted = Buffer.concat([
|
|
281
|
+
cipher.update(plaintext),
|
|
282
|
+
cipher.final()
|
|
283
|
+
]);
|
|
284
|
+
const authTag = cipher.getAuthTag();
|
|
285
|
+
const bundle = new Uint8Array(12 + encrypted.length + 16 + 1);
|
|
286
|
+
bundle.set([0], 0);
|
|
287
|
+
bundle.set(nonce, 1);
|
|
288
|
+
bundle.set(new Uint8Array(encrypted), 13);
|
|
289
|
+
bundle.set(new Uint8Array(authTag), 13 + encrypted.length);
|
|
290
|
+
return bundle;
|
|
291
|
+
}
|
|
292
|
+
function decryptWithDataKey(bundle, dataKey) {
|
|
293
|
+
if (bundle.length < 1) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
if (bundle[0] !== 0) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
if (bundle.length < 12 + 16 + 1) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const nonce = bundle.slice(1, 13);
|
|
303
|
+
const authTag = bundle.slice(bundle.length - 16);
|
|
304
|
+
const ciphertext = bundle.slice(13, bundle.length - 16);
|
|
305
|
+
try {
|
|
306
|
+
const decipher = node_crypto.createDecipheriv("aes-256-gcm", dataKey, nonce);
|
|
307
|
+
decipher.setAuthTag(authTag);
|
|
308
|
+
const decrypted = Buffer.concat([
|
|
309
|
+
decipher.update(ciphertext),
|
|
310
|
+
decipher.final()
|
|
311
|
+
]);
|
|
312
|
+
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
313
|
+
} catch (error) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function encrypt(key, variant, data) {
|
|
318
|
+
if (variant === "legacy") {
|
|
319
|
+
return encryptLegacy(data, key);
|
|
320
|
+
} else {
|
|
321
|
+
return encryptWithDataKey(data, key);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function decrypt(key, variant, data) {
|
|
325
|
+
if (variant === "legacy") {
|
|
326
|
+
return decryptLegacy(data, key);
|
|
327
|
+
} else {
|
|
328
|
+
return decryptWithDataKey(data, key);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
249
331
|
|
|
250
332
|
const defaultSettings = {
|
|
251
333
|
onboardingCompleted: false
|
|
252
334
|
};
|
|
253
335
|
async function readSettings() {
|
|
254
|
-
if (!
|
|
336
|
+
if (!fs.existsSync(configuration.settingsFile)) {
|
|
255
337
|
return { ...defaultSettings };
|
|
256
338
|
}
|
|
257
339
|
try {
|
|
@@ -271,7 +353,7 @@ async function updateSettings(updater) {
|
|
|
271
353
|
let attempts = 0;
|
|
272
354
|
while (attempts < MAX_LOCK_ATTEMPTS) {
|
|
273
355
|
try {
|
|
274
|
-
fileHandle = await promises.open(lockFile,
|
|
356
|
+
fileHandle = await promises.open(lockFile, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
|
|
275
357
|
break;
|
|
276
358
|
} catch (err) {
|
|
277
359
|
if (err.code === "EEXIST") {
|
|
@@ -296,7 +378,7 @@ async function updateSettings(updater) {
|
|
|
296
378
|
try {
|
|
297
379
|
const current = await readSettings() || { ...defaultSettings };
|
|
298
380
|
const updated = await updater(current);
|
|
299
|
-
if (!
|
|
381
|
+
if (!fs.existsSync(configuration.happyHomeDir)) {
|
|
300
382
|
await promises.mkdir(configuration.happyHomeDir, { recursive: true });
|
|
301
383
|
}
|
|
302
384
|
await promises.writeFile(tmpFile, JSON.stringify(updated, null, 2));
|
|
@@ -309,26 +391,46 @@ async function updateSettings(updater) {
|
|
|
309
391
|
}
|
|
310
392
|
}
|
|
311
393
|
const credentialsSchema = z__namespace.object({
|
|
312
|
-
|
|
313
|
-
|
|
394
|
+
token: z__namespace.string(),
|
|
395
|
+
secret: z__namespace.string().base64().nullish(),
|
|
396
|
+
// Legacy
|
|
397
|
+
encryption: z__namespace.object({
|
|
398
|
+
publicKey: z__namespace.string().base64(),
|
|
399
|
+
machineKey: z__namespace.string().base64()
|
|
400
|
+
}).nullish()
|
|
314
401
|
});
|
|
315
402
|
async function readCredentials() {
|
|
316
|
-
if (!
|
|
403
|
+
if (!fs.existsSync(configuration.privateKeyFile)) {
|
|
317
404
|
return null;
|
|
318
405
|
}
|
|
319
406
|
try {
|
|
320
407
|
const keyBase64 = await promises.readFile(configuration.privateKeyFile, "utf8");
|
|
321
408
|
const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
409
|
+
if (credentials.secret) {
|
|
410
|
+
return {
|
|
411
|
+
token: credentials.token,
|
|
412
|
+
encryption: {
|
|
413
|
+
type: "legacy",
|
|
414
|
+
secret: new Uint8Array(Buffer.from(credentials.secret, "base64"))
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
} else if (credentials.encryption) {
|
|
418
|
+
return {
|
|
419
|
+
token: credentials.token,
|
|
420
|
+
encryption: {
|
|
421
|
+
type: "dataKey",
|
|
422
|
+
publicKey: new Uint8Array(Buffer.from(credentials.encryption.publicKey, "base64")),
|
|
423
|
+
machineKey: new Uint8Array(Buffer.from(credentials.encryption.machineKey, "base64"))
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
326
427
|
} catch {
|
|
327
428
|
return null;
|
|
328
429
|
}
|
|
430
|
+
return null;
|
|
329
431
|
}
|
|
330
|
-
async function
|
|
331
|
-
if (!
|
|
432
|
+
async function writeCredentialsLegacy(credentials) {
|
|
433
|
+
if (!fs.existsSync(configuration.happyHomeDir)) {
|
|
332
434
|
await promises.mkdir(configuration.happyHomeDir, { recursive: true });
|
|
333
435
|
}
|
|
334
436
|
await promises.writeFile(configuration.privateKeyFile, JSON.stringify({
|
|
@@ -336,8 +438,17 @@ async function writeCredentials(credentials) {
|
|
|
336
438
|
token: credentials.token
|
|
337
439
|
}, null, 2));
|
|
338
440
|
}
|
|
441
|
+
async function writeCredentialsDataKey(credentials) {
|
|
442
|
+
if (!fs.existsSync(configuration.happyHomeDir)) {
|
|
443
|
+
await promises.mkdir(configuration.happyHomeDir, { recursive: true });
|
|
444
|
+
}
|
|
445
|
+
await promises.writeFile(configuration.privateKeyFile, JSON.stringify({
|
|
446
|
+
encryption: { publicKey: encodeBase64(credentials.publicKey), machineKey: encodeBase64(credentials.machineKey) },
|
|
447
|
+
token: credentials.token
|
|
448
|
+
}, null, 2));
|
|
449
|
+
}
|
|
339
450
|
async function clearCredentials() {
|
|
340
|
-
if (
|
|
451
|
+
if (fs.existsSync(configuration.privateKeyFile)) {
|
|
341
452
|
await promises.unlink(configuration.privateKeyFile);
|
|
342
453
|
}
|
|
343
454
|
}
|
|
@@ -349,7 +460,7 @@ async function clearMachineId() {
|
|
|
349
460
|
}
|
|
350
461
|
async function readDaemonState() {
|
|
351
462
|
try {
|
|
352
|
-
if (!
|
|
463
|
+
if (!fs.existsSync(configuration.daemonStateFile)) {
|
|
353
464
|
return null;
|
|
354
465
|
}
|
|
355
466
|
const content = await promises.readFile(configuration.daemonStateFile, "utf-8");
|
|
@@ -360,13 +471,13 @@ async function readDaemonState() {
|
|
|
360
471
|
}
|
|
361
472
|
}
|
|
362
473
|
function writeDaemonState(state) {
|
|
363
|
-
|
|
474
|
+
fs.writeFileSync(configuration.daemonStateFile, JSON.stringify(state, null, 2), "utf-8");
|
|
364
475
|
}
|
|
365
476
|
async function clearDaemonState() {
|
|
366
|
-
if (
|
|
477
|
+
if (fs.existsSync(configuration.daemonStateFile)) {
|
|
367
478
|
await promises.unlink(configuration.daemonStateFile);
|
|
368
479
|
}
|
|
369
|
-
if (
|
|
480
|
+
if (fs.existsSync(configuration.daemonLockFile)) {
|
|
370
481
|
try {
|
|
371
482
|
await promises.unlink(configuration.daemonLockFile);
|
|
372
483
|
} catch {
|
|
@@ -378,19 +489,19 @@ async function acquireDaemonLock(maxAttempts = 5, delayIncrementMs = 200) {
|
|
|
378
489
|
try {
|
|
379
490
|
const fileHandle = await promises.open(
|
|
380
491
|
configuration.daemonLockFile,
|
|
381
|
-
|
|
492
|
+
fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY
|
|
382
493
|
);
|
|
383
494
|
await fileHandle.writeFile(String(process.pid));
|
|
384
495
|
return fileHandle;
|
|
385
496
|
} catch (error) {
|
|
386
497
|
if (error.code === "EEXIST") {
|
|
387
498
|
try {
|
|
388
|
-
const lockPid =
|
|
499
|
+
const lockPid = fs.readFileSync(configuration.daemonLockFile, "utf-8").trim();
|
|
389
500
|
if (lockPid && !isNaN(Number(lockPid))) {
|
|
390
501
|
try {
|
|
391
502
|
process.kill(Number(lockPid), 0);
|
|
392
503
|
} catch {
|
|
393
|
-
|
|
504
|
+
fs.unlinkSync(configuration.daemonLockFile);
|
|
394
505
|
continue;
|
|
395
506
|
}
|
|
396
507
|
}
|
|
@@ -412,8 +523,8 @@ async function releaseDaemonLock(lockHandle) {
|
|
|
412
523
|
} catch {
|
|
413
524
|
}
|
|
414
525
|
try {
|
|
415
|
-
if (
|
|
416
|
-
|
|
526
|
+
if (fs.existsSync(configuration.daemonLockFile)) {
|
|
527
|
+
fs.unlinkSync(configuration.daemonLockFile);
|
|
417
528
|
}
|
|
418
529
|
} catch {
|
|
419
530
|
}
|
|
@@ -503,6 +614,13 @@ class Logger {
|
|
|
503
614
|
this.logToConsole("info", "[DEV]", message, ...args);
|
|
504
615
|
}
|
|
505
616
|
}
|
|
617
|
+
warn(message, ...args) {
|
|
618
|
+
this.logToConsole("warn", "", message, ...args);
|
|
619
|
+
this.debug(`[WARN] ${message}`, ...args);
|
|
620
|
+
}
|
|
621
|
+
getLogPath() {
|
|
622
|
+
return this.logFilePath;
|
|
623
|
+
}
|
|
506
624
|
logToConsole(level, prefix, message, ...args) {
|
|
507
625
|
switch (level) {
|
|
508
626
|
case "debug": {
|
|
@@ -561,7 +679,7 @@ class Logger {
|
|
|
561
679
|
});
|
|
562
680
|
}
|
|
563
681
|
try {
|
|
564
|
-
fs.appendFileSync(this.logFilePath, logLine);
|
|
682
|
+
fs$1.appendFileSync(this.logFilePath, logLine);
|
|
565
683
|
} catch (appendError) {
|
|
566
684
|
if (process.env.DEBUG) {
|
|
567
685
|
console.error("[DEV MODE ONLY THROWING] Failed to append to log file:", appendError);
|
|
@@ -574,12 +692,12 @@ let logger = new Logger();
|
|
|
574
692
|
async function listDaemonLogFiles(limit = 50) {
|
|
575
693
|
try {
|
|
576
694
|
const logsDir = configuration.logsDir;
|
|
577
|
-
if (!
|
|
695
|
+
if (!fs.existsSync(logsDir)) {
|
|
578
696
|
return [];
|
|
579
697
|
}
|
|
580
|
-
const logs =
|
|
698
|
+
const logs = fs.readdirSync(logsDir).filter((file) => file.endsWith("-daemon.log")).map((file) => {
|
|
581
699
|
const fullPath = node_path.join(logsDir, file);
|
|
582
|
-
const stats =
|
|
700
|
+
const stats = fs.statSync(fullPath);
|
|
583
701
|
return { file, path: fullPath, modified: stats.mtime };
|
|
584
702
|
}).sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
585
703
|
try {
|
|
@@ -587,8 +705,8 @@ async function listDaemonLogFiles(limit = 50) {
|
|
|
587
705
|
if (!state) {
|
|
588
706
|
return logs;
|
|
589
707
|
}
|
|
590
|
-
if (state.daemonLogPath &&
|
|
591
|
-
const stats =
|
|
708
|
+
if (state.daemonLogPath && fs.existsSync(state.daemonLogPath)) {
|
|
709
|
+
const stats = fs.statSync(state.daemonLogPath);
|
|
592
710
|
const persisted = {
|
|
593
711
|
file: node_path.basename(state.daemonLogPath),
|
|
594
712
|
path: state.daemonLogPath,
|
|
@@ -663,38 +781,13 @@ z.z.object({
|
|
|
663
781
|
]),
|
|
664
782
|
createdAt: z.z.number()
|
|
665
783
|
});
|
|
666
|
-
z.z.object({
|
|
667
|
-
createdAt: z.z.number(),
|
|
668
|
-
id: z.z.string(),
|
|
669
|
-
seq: z.z.number(),
|
|
670
|
-
updatedAt: z.z.number(),
|
|
671
|
-
metadata: z.z.any(),
|
|
672
|
-
metadataVersion: z.z.number(),
|
|
673
|
-
agentState: z.z.any().nullable(),
|
|
674
|
-
agentStateVersion: z.z.number(),
|
|
675
|
-
// Connectivity tracking (from server)
|
|
676
|
-
connectivityStatus: z.z.union([
|
|
677
|
-
z.z.enum(["neverConnected", "online", "offline"]),
|
|
678
|
-
z.z.string()
|
|
679
|
-
// Forward compatibility
|
|
680
|
-
]).optional(),
|
|
681
|
-
connectivityStatusSince: z.z.number().optional(),
|
|
682
|
-
connectivityStatusReason: z.z.string().optional(),
|
|
683
|
-
// State tracking (from server)
|
|
684
|
-
state: z.z.union([
|
|
685
|
-
z.z.enum(["running", "archiveRequested", "archived"]),
|
|
686
|
-
z.z.string()
|
|
687
|
-
// Forward compatibility
|
|
688
|
-
]).optional(),
|
|
689
|
-
stateSince: z.z.number().optional(),
|
|
690
|
-
stateReason: z.z.string().optional()
|
|
691
|
-
});
|
|
692
784
|
z.z.object({
|
|
693
785
|
host: z.z.string(),
|
|
694
786
|
platform: z.z.string(),
|
|
695
787
|
happyCliVersion: z.z.string(),
|
|
696
788
|
homeDir: z.z.string(),
|
|
697
|
-
happyHomeDir: z.z.string()
|
|
789
|
+
happyHomeDir: z.z.string(),
|
|
790
|
+
happyLibDir: z.z.string()
|
|
698
791
|
});
|
|
699
792
|
z.z.object({
|
|
700
793
|
status: z.z.union([
|
|
@@ -712,37 +805,6 @@ z.z.object({
|
|
|
712
805
|
// Forward compatibility
|
|
713
806
|
]).optional()
|
|
714
807
|
});
|
|
715
|
-
z.z.object({
|
|
716
|
-
id: z.z.string(),
|
|
717
|
-
metadata: z.z.any(),
|
|
718
|
-
// Decrypted MachineMetadata
|
|
719
|
-
metadataVersion: z.z.number(),
|
|
720
|
-
daemonState: z.z.any().nullable(),
|
|
721
|
-
// Decrypted DaemonState
|
|
722
|
-
daemonStateVersion: z.z.number(),
|
|
723
|
-
// We don't really care about these on the CLI for now
|
|
724
|
-
// ApiMachineClient will not sync these
|
|
725
|
-
active: z.z.boolean(),
|
|
726
|
-
activeAt: z.z.number(),
|
|
727
|
-
createdAt: z.z.number(),
|
|
728
|
-
updatedAt: z.z.number(),
|
|
729
|
-
// Connectivity tracking (from server)
|
|
730
|
-
connectivityStatus: z.z.union([
|
|
731
|
-
z.z.enum(["neverConnected", "online", "offline"]),
|
|
732
|
-
z.z.string()
|
|
733
|
-
// Forward compatibility
|
|
734
|
-
]).optional(),
|
|
735
|
-
connectivityStatusSince: z.z.number().optional(),
|
|
736
|
-
connectivityStatusReason: z.z.string().optional(),
|
|
737
|
-
// State tracking (from server)
|
|
738
|
-
state: z.z.union([
|
|
739
|
-
z.z.enum(["running", "archiveRequested", "archived"]),
|
|
740
|
-
z.z.string()
|
|
741
|
-
// Forward compatibility
|
|
742
|
-
]).optional(),
|
|
743
|
-
stateSince: z.z.number().optional(),
|
|
744
|
-
stateReason: z.z.string().optional()
|
|
745
|
-
});
|
|
746
808
|
z.z.object({
|
|
747
809
|
content: SessionMessageContentSchema,
|
|
748
810
|
createdAt: z.z.number(),
|
|
@@ -866,12 +928,14 @@ class AsyncLock {
|
|
|
866
928
|
class RpcHandlerManager {
|
|
867
929
|
handlers = /* @__PURE__ */ new Map();
|
|
868
930
|
scopePrefix;
|
|
869
|
-
|
|
931
|
+
encryptionKey;
|
|
932
|
+
encryptionVariant;
|
|
870
933
|
logger;
|
|
871
934
|
socket = null;
|
|
872
935
|
constructor(config) {
|
|
873
936
|
this.scopePrefix = config.scopePrefix;
|
|
874
|
-
this.
|
|
937
|
+
this.encryptionKey = config.encryptionKey;
|
|
938
|
+
this.encryptionVariant = config.encryptionVariant;
|
|
875
939
|
this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
|
|
876
940
|
}
|
|
877
941
|
/**
|
|
@@ -897,20 +961,19 @@ class RpcHandlerManager {
|
|
|
897
961
|
if (!handler) {
|
|
898
962
|
this.logger("[RPC] [ERROR] Method not found", { method: request.method });
|
|
899
963
|
const errorResponse = { error: "Method not found" };
|
|
900
|
-
const encryptedError = encodeBase64(encrypt(
|
|
964
|
+
const encryptedError = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
|
|
901
965
|
return encryptedError;
|
|
902
966
|
}
|
|
903
|
-
const decryptedParams = decrypt(decodeBase64(request.params)
|
|
967
|
+
const decryptedParams = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(request.params));
|
|
904
968
|
const result = await handler(decryptedParams);
|
|
905
|
-
const encryptedResponse = encodeBase64(encrypt(
|
|
969
|
+
const encryptedResponse = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, result));
|
|
906
970
|
return encryptedResponse;
|
|
907
971
|
} catch (error) {
|
|
908
972
|
this.logger("[RPC] [ERROR] Error handling request", { error });
|
|
909
973
|
const errorResponse = {
|
|
910
974
|
error: error instanceof Error ? error.message : "Unknown error"
|
|
911
975
|
};
|
|
912
|
-
|
|
913
|
-
return encryptedError;
|
|
976
|
+
return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
|
|
914
977
|
}
|
|
915
978
|
}
|
|
916
979
|
onSocketConnect(socket) {
|
|
@@ -952,13 +1015,13 @@ class RpcHandlerManager {
|
|
|
952
1015
|
}
|
|
953
1016
|
}
|
|
954
1017
|
|
|
955
|
-
const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-
|
|
1018
|
+
const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-BcDnTXMg.cjs', document.baseURI).href))));
|
|
956
1019
|
function projectPath() {
|
|
957
1020
|
const path$1 = path.resolve(__dirname$1, "..");
|
|
958
1021
|
return path$1;
|
|
959
1022
|
}
|
|
960
1023
|
|
|
961
|
-
function run(args, options) {
|
|
1024
|
+
function run$1(args, options) {
|
|
962
1025
|
const RUNNER_PATH = path.resolve(path.join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
|
|
963
1026
|
return new Promise((resolve2, reject) => {
|
|
964
1027
|
const child = child_process.spawn("node", [RUNNER_PATH, JSON.stringify(args)], {
|
|
@@ -986,6 +1049,44 @@ function run(args, options) {
|
|
|
986
1049
|
});
|
|
987
1050
|
}
|
|
988
1051
|
|
|
1052
|
+
function getBinaryPath() {
|
|
1053
|
+
const platformName = os$1.platform();
|
|
1054
|
+
const binaryName = platformName === "win32" ? "difft.exe" : "difft";
|
|
1055
|
+
return path.resolve(path.join(projectPath(), "tools", "unpacked", binaryName));
|
|
1056
|
+
}
|
|
1057
|
+
function run(args, options) {
|
|
1058
|
+
const binaryPath = getBinaryPath();
|
|
1059
|
+
return new Promise((resolve2, reject) => {
|
|
1060
|
+
const child = child_process.spawn(binaryPath, args, {
|
|
1061
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1062
|
+
cwd: options?.cwd,
|
|
1063
|
+
env: {
|
|
1064
|
+
...process.env,
|
|
1065
|
+
// Force color output when needed
|
|
1066
|
+
FORCE_COLOR: "1"
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
let stdout = "";
|
|
1070
|
+
let stderr = "";
|
|
1071
|
+
child.stdout.on("data", (data) => {
|
|
1072
|
+
stdout += data.toString();
|
|
1073
|
+
});
|
|
1074
|
+
child.stderr.on("data", (data) => {
|
|
1075
|
+
stderr += data.toString();
|
|
1076
|
+
});
|
|
1077
|
+
child.on("close", (code) => {
|
|
1078
|
+
resolve2({
|
|
1079
|
+
exitCode: code || 0,
|
|
1080
|
+
stdout,
|
|
1081
|
+
stderr
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1084
|
+
child.on("error", (err) => {
|
|
1085
|
+
reject(err);
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
|
|
989
1090
|
const execAsync = util.promisify(child_process.exec);
|
|
990
1091
|
function registerCommonHandlers(rpcHandlerManager) {
|
|
991
1092
|
rpcHandlerManager.registerHandler("bash", async (data) => {
|
|
@@ -1026,7 +1127,7 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1026
1127
|
rpcHandlerManager.registerHandler("readFile", async (data) => {
|
|
1027
1128
|
logger.debug("Read file request:", data.path);
|
|
1028
1129
|
try {
|
|
1029
|
-
const buffer = await fs$
|
|
1130
|
+
const buffer = await fs$2.readFile(data.path);
|
|
1030
1131
|
const content = buffer.toString("base64");
|
|
1031
1132
|
return { success: true, content };
|
|
1032
1133
|
} catch (error) {
|
|
@@ -1039,7 +1140,7 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1039
1140
|
try {
|
|
1040
1141
|
if (data.expectedHash !== null && data.expectedHash !== void 0) {
|
|
1041
1142
|
try {
|
|
1042
|
-
const existingBuffer = await fs$
|
|
1143
|
+
const existingBuffer = await fs$2.readFile(data.path);
|
|
1043
1144
|
const existingHash = crypto.createHash("sha256").update(existingBuffer).digest("hex");
|
|
1044
1145
|
if (existingHash !== data.expectedHash) {
|
|
1045
1146
|
return {
|
|
@@ -1059,7 +1160,7 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1059
1160
|
}
|
|
1060
1161
|
} else {
|
|
1061
1162
|
try {
|
|
1062
|
-
await fs$
|
|
1163
|
+
await fs$2.stat(data.path);
|
|
1063
1164
|
return {
|
|
1064
1165
|
success: false,
|
|
1065
1166
|
error: "File already exists but was expected to be new"
|
|
@@ -1072,7 +1173,7 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1072
1173
|
}
|
|
1073
1174
|
}
|
|
1074
1175
|
const buffer = Buffer.from(data.content, "base64");
|
|
1075
|
-
await fs$
|
|
1176
|
+
await fs$2.writeFile(data.path, buffer);
|
|
1076
1177
|
const hash = crypto.createHash("sha256").update(buffer).digest("hex");
|
|
1077
1178
|
return { success: true, hash };
|
|
1078
1179
|
} catch (error) {
|
|
@@ -1083,7 +1184,7 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1083
1184
|
rpcHandlerManager.registerHandler("listDirectory", async (data) => {
|
|
1084
1185
|
logger.debug("List directory request:", data.path);
|
|
1085
1186
|
try {
|
|
1086
|
-
const entries = await fs$
|
|
1187
|
+
const entries = await fs$2.readdir(data.path, { withFileTypes: true });
|
|
1087
1188
|
const directoryEntries = await Promise.all(
|
|
1088
1189
|
entries.map(async (entry) => {
|
|
1089
1190
|
const fullPath = path.join(data.path, entry.name);
|
|
@@ -1096,7 +1197,7 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1096
1197
|
type = "file";
|
|
1097
1198
|
}
|
|
1098
1199
|
try {
|
|
1099
|
-
const stats = await fs$
|
|
1200
|
+
const stats = await fs$2.stat(fullPath);
|
|
1100
1201
|
size = stats.size;
|
|
1101
1202
|
modified = stats.mtime.getTime();
|
|
1102
1203
|
} catch (error) {
|
|
@@ -1125,7 +1226,7 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1125
1226
|
logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
|
|
1126
1227
|
async function buildTree(path$1, name, currentDepth) {
|
|
1127
1228
|
try {
|
|
1128
|
-
const stats = await fs$
|
|
1229
|
+
const stats = await fs$2.stat(path$1);
|
|
1129
1230
|
const node = {
|
|
1130
1231
|
name,
|
|
1131
1232
|
path: path$1,
|
|
@@ -1134,7 +1235,7 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1134
1235
|
modified: stats.mtime.getTime()
|
|
1135
1236
|
};
|
|
1136
1237
|
if (stats.isDirectory() && currentDepth < data.maxDepth) {
|
|
1137
|
-
const entries = await fs$
|
|
1238
|
+
const entries = await fs$2.readdir(path$1, { withFileTypes: true });
|
|
1138
1239
|
const children = [];
|
|
1139
1240
|
await Promise.all(
|
|
1140
1241
|
entries.map(async (entry) => {
|
|
@@ -1180,7 +1281,7 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1180
1281
|
rpcHandlerManager.registerHandler("ripgrep", async (data) => {
|
|
1181
1282
|
logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
|
|
1182
1283
|
try {
|
|
1183
|
-
const result = await run(data.args, { cwd: data.cwd });
|
|
1284
|
+
const result = await run$1(data.args, { cwd: data.cwd });
|
|
1184
1285
|
return {
|
|
1185
1286
|
success: true,
|
|
1186
1287
|
exitCode: result.exitCode,
|
|
@@ -1195,11 +1296,28 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1195
1296
|
};
|
|
1196
1297
|
}
|
|
1197
1298
|
});
|
|
1299
|
+
rpcHandlerManager.registerHandler("difftastic", async (data) => {
|
|
1300
|
+
logger.debug("Difftastic request with args:", data.args, "cwd:", data.cwd);
|
|
1301
|
+
try {
|
|
1302
|
+
const result = await run(data.args, { cwd: data.cwd });
|
|
1303
|
+
return {
|
|
1304
|
+
success: true,
|
|
1305
|
+
exitCode: result.exitCode,
|
|
1306
|
+
stdout: result.stdout.toString(),
|
|
1307
|
+
stderr: result.stderr.toString()
|
|
1308
|
+
};
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
logger.debug("Failed to run difftastic:", error);
|
|
1311
|
+
return {
|
|
1312
|
+
success: false,
|
|
1313
|
+
error: error instanceof Error ? error.message : "Failed to run difftastic"
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1198
1317
|
}
|
|
1199
1318
|
|
|
1200
1319
|
class ApiSessionClient extends node_events.EventEmitter {
|
|
1201
1320
|
token;
|
|
1202
|
-
secret;
|
|
1203
1321
|
sessionId;
|
|
1204
1322
|
metadata;
|
|
1205
1323
|
metadataVersion;
|
|
@@ -1211,18 +1329,22 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1211
1329
|
rpcHandlerManager;
|
|
1212
1330
|
agentStateLock = new AsyncLock();
|
|
1213
1331
|
metadataLock = new AsyncLock();
|
|
1214
|
-
|
|
1332
|
+
encryptionKey;
|
|
1333
|
+
encryptionVariant;
|
|
1334
|
+
constructor(token, session) {
|
|
1215
1335
|
super();
|
|
1216
1336
|
this.token = token;
|
|
1217
|
-
this.secret = secret;
|
|
1218
1337
|
this.sessionId = session.id;
|
|
1219
1338
|
this.metadata = session.metadata;
|
|
1220
1339
|
this.metadataVersion = session.metadataVersion;
|
|
1221
1340
|
this.agentState = session.agentState;
|
|
1222
1341
|
this.agentStateVersion = session.agentStateVersion;
|
|
1342
|
+
this.encryptionKey = session.encryptionKey;
|
|
1343
|
+
this.encryptionVariant = session.encryptionVariant;
|
|
1223
1344
|
this.rpcHandlerManager = new RpcHandlerManager({
|
|
1224
1345
|
scopePrefix: this.sessionId,
|
|
1225
|
-
|
|
1346
|
+
encryptionKey: this.encryptionKey,
|
|
1347
|
+
encryptionVariant: this.encryptionVariant,
|
|
1226
1348
|
logger: (msg, data) => logger.debug(msg, data)
|
|
1227
1349
|
});
|
|
1228
1350
|
registerCommonHandlers(this.rpcHandlerManager);
|
|
@@ -1264,7 +1386,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1264
1386
|
return;
|
|
1265
1387
|
}
|
|
1266
1388
|
if (data.body.t === "new-message" && data.body.message.content.t === "encrypted") {
|
|
1267
|
-
const body = decrypt(decodeBase64(data.body.message.content.c)
|
|
1389
|
+
const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.message.content.c));
|
|
1268
1390
|
logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
|
|
1269
1391
|
const userResult = UserMessageSchema.safeParse(body);
|
|
1270
1392
|
if (userResult.success) {
|
|
@@ -1278,11 +1400,11 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1278
1400
|
}
|
|
1279
1401
|
} else if (data.body.t === "update-session") {
|
|
1280
1402
|
if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
|
|
1281
|
-
this.metadata = decrypt(decodeBase64(data.body.metadata.value)
|
|
1403
|
+
this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.metadata.value));
|
|
1282
1404
|
this.metadataVersion = data.body.metadata.version;
|
|
1283
1405
|
}
|
|
1284
1406
|
if (data.body.agentState && data.body.agentState.version > this.agentStateVersion) {
|
|
1285
|
-
this.agentState = data.body.agentState.value ? decrypt(decodeBase64(data.body.agentState.value)
|
|
1407
|
+
this.agentState = data.body.agentState.value ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.agentState.value)) : null;
|
|
1286
1408
|
this.agentStateVersion = data.body.agentState.version;
|
|
1287
1409
|
}
|
|
1288
1410
|
} else if (data.body.t === "update-machine") {
|
|
@@ -1336,7 +1458,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1336
1458
|
};
|
|
1337
1459
|
}
|
|
1338
1460
|
logger.debugLargeJson("[SOCKET] Sending message through socket:", content);
|
|
1339
|
-
const encrypted = encodeBase64(encrypt(
|
|
1461
|
+
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1340
1462
|
this.socket.emit("message", {
|
|
1341
1463
|
sid: this.sessionId,
|
|
1342
1464
|
message: encrypted
|
|
@@ -1358,6 +1480,24 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1358
1480
|
}));
|
|
1359
1481
|
}
|
|
1360
1482
|
}
|
|
1483
|
+
sendCodexMessage(body) {
|
|
1484
|
+
let content = {
|
|
1485
|
+
role: "agent",
|
|
1486
|
+
content: {
|
|
1487
|
+
type: "codex",
|
|
1488
|
+
data: body
|
|
1489
|
+
// This wraps the entire Claude message
|
|
1490
|
+
},
|
|
1491
|
+
meta: {
|
|
1492
|
+
sentFrom: "cli"
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1496
|
+
this.socket.emit("message", {
|
|
1497
|
+
sid: this.sessionId,
|
|
1498
|
+
message: encrypted
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1361
1501
|
sendSessionEvent(event, id) {
|
|
1362
1502
|
let content = {
|
|
1363
1503
|
role: "agent",
|
|
@@ -1367,7 +1507,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1367
1507
|
data: event
|
|
1368
1508
|
}
|
|
1369
1509
|
};
|
|
1370
|
-
const encrypted = encodeBase64(encrypt(
|
|
1510
|
+
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1371
1511
|
this.socket.emit("message", {
|
|
1372
1512
|
sid: this.sessionId,
|
|
1373
1513
|
message: encrypted
|
|
@@ -1427,14 +1567,14 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1427
1567
|
this.metadataLock.inLock(async () => {
|
|
1428
1568
|
await backoff(async () => {
|
|
1429
1569
|
let updated = handler(this.metadata);
|
|
1430
|
-
const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(
|
|
1570
|
+
const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) });
|
|
1431
1571
|
if (answer.result === "success") {
|
|
1432
|
-
this.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1572
|
+
this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
|
|
1433
1573
|
this.metadataVersion = answer.version;
|
|
1434
1574
|
} else if (answer.result === "version-mismatch") {
|
|
1435
1575
|
if (answer.version > this.metadataVersion) {
|
|
1436
1576
|
this.metadataVersion = answer.version;
|
|
1437
|
-
this.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1577
|
+
this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
|
|
1438
1578
|
}
|
|
1439
1579
|
throw new Error("Metadata version mismatch");
|
|
1440
1580
|
} else if (answer.result === "error") ;
|
|
@@ -1450,15 +1590,15 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1450
1590
|
this.agentStateLock.inLock(async () => {
|
|
1451
1591
|
await backoff(async () => {
|
|
1452
1592
|
let updated = handler(this.agentState || {});
|
|
1453
|
-
const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(
|
|
1593
|
+
const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
|
|
1454
1594
|
if (answer.result === "success") {
|
|
1455
|
-
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState)
|
|
1595
|
+
this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
|
|
1456
1596
|
this.agentStateVersion = answer.version;
|
|
1457
1597
|
logger.debug("Agent state updated", this.agentState);
|
|
1458
1598
|
} else if (answer.result === "version-mismatch") {
|
|
1459
1599
|
if (answer.version > this.agentStateVersion) {
|
|
1460
1600
|
this.agentStateVersion = answer.version;
|
|
1461
|
-
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState)
|
|
1601
|
+
this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
|
|
1462
1602
|
}
|
|
1463
1603
|
throw new Error("Agent state version mismatch");
|
|
1464
1604
|
} else if (answer.result === "error") ;
|
|
@@ -1482,18 +1622,19 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1482
1622
|
});
|
|
1483
1623
|
}
|
|
1484
1624
|
async close() {
|
|
1625
|
+
logger.debug("[API] socket.close() called");
|
|
1485
1626
|
this.socket.close();
|
|
1486
1627
|
}
|
|
1487
1628
|
}
|
|
1488
1629
|
|
|
1489
1630
|
class ApiMachineClient {
|
|
1490
|
-
constructor(token,
|
|
1631
|
+
constructor(token, machine) {
|
|
1491
1632
|
this.token = token;
|
|
1492
|
-
this.secret = secret;
|
|
1493
1633
|
this.machine = machine;
|
|
1494
1634
|
this.rpcHandlerManager = new RpcHandlerManager({
|
|
1495
1635
|
scopePrefix: this.machine.id,
|
|
1496
|
-
|
|
1636
|
+
encryptionKey: this.machine.encryptionKey,
|
|
1637
|
+
encryptionVariant: this.machine.encryptionVariant,
|
|
1497
1638
|
logger: (msg, data) => logger.debug(msg, data)
|
|
1498
1639
|
});
|
|
1499
1640
|
registerCommonHandlers(this.rpcHandlerManager);
|
|
@@ -1554,17 +1695,17 @@ class ApiMachineClient {
|
|
|
1554
1695
|
const updated = handler(this.machine.metadata);
|
|
1555
1696
|
const answer = await this.socket.emitWithAck("machine-update-metadata", {
|
|
1556
1697
|
machineId: this.machine.id,
|
|
1557
|
-
metadata: encodeBase64(encrypt(
|
|
1698
|
+
metadata: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
|
|
1558
1699
|
expectedVersion: this.machine.metadataVersion
|
|
1559
1700
|
});
|
|
1560
1701
|
if (answer.result === "success") {
|
|
1561
|
-
this.machine.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1702
|
+
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
|
|
1562
1703
|
this.machine.metadataVersion = answer.version;
|
|
1563
1704
|
logger.debug("[API MACHINE] Metadata updated successfully");
|
|
1564
1705
|
} else if (answer.result === "version-mismatch") {
|
|
1565
1706
|
if (answer.version > this.machine.metadataVersion) {
|
|
1566
1707
|
this.machine.metadataVersion = answer.version;
|
|
1567
|
-
this.machine.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1708
|
+
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
|
|
1568
1709
|
}
|
|
1569
1710
|
throw new Error("Metadata version mismatch");
|
|
1570
1711
|
}
|
|
@@ -1579,17 +1720,17 @@ class ApiMachineClient {
|
|
|
1579
1720
|
const updated = handler(this.machine.daemonState);
|
|
1580
1721
|
const answer = await this.socket.emitWithAck("machine-update-state", {
|
|
1581
1722
|
machineId: this.machine.id,
|
|
1582
|
-
daemonState: encodeBase64(encrypt(
|
|
1723
|
+
daemonState: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
|
|
1583
1724
|
expectedVersion: this.machine.daemonStateVersion
|
|
1584
1725
|
});
|
|
1585
1726
|
if (answer.result === "success") {
|
|
1586
|
-
this.machine.daemonState = decrypt(decodeBase64(answer.daemonState)
|
|
1727
|
+
this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
|
|
1587
1728
|
this.machine.daemonStateVersion = answer.version;
|
|
1588
1729
|
logger.debug("[API MACHINE] Daemon state updated successfully");
|
|
1589
1730
|
} else if (answer.result === "version-mismatch") {
|
|
1590
1731
|
if (answer.version > this.machine.daemonStateVersion) {
|
|
1591
1732
|
this.machine.daemonStateVersion = answer.version;
|
|
1592
|
-
this.machine.daemonState = decrypt(decodeBase64(answer.daemonState)
|
|
1733
|
+
this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
|
|
1593
1734
|
}
|
|
1594
1735
|
throw new Error("Daemon state version mismatch");
|
|
1595
1736
|
}
|
|
@@ -1636,12 +1777,12 @@ class ApiMachineClient {
|
|
|
1636
1777
|
const update = data.body;
|
|
1637
1778
|
if (update.metadata) {
|
|
1638
1779
|
logger.debug("[API MACHINE] Received external metadata update");
|
|
1639
|
-
this.machine.metadata = decrypt(decodeBase64(update.metadata.value)
|
|
1780
|
+
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.metadata.value));
|
|
1640
1781
|
this.machine.metadataVersion = update.metadata.version;
|
|
1641
1782
|
}
|
|
1642
1783
|
if (update.daemonState) {
|
|
1643
1784
|
logger.debug("[API MACHINE] Received external daemon state update");
|
|
1644
|
-
this.machine.daemonState = decrypt(decodeBase64(update.daemonState.value)
|
|
1785
|
+
this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.daemonState.value));
|
|
1645
1786
|
this.machine.daemonStateVersion = update.daemonState.version;
|
|
1646
1787
|
}
|
|
1647
1788
|
} else {
|
|
@@ -1813,46 +1954,62 @@ class PushNotificationClient {
|
|
|
1813
1954
|
}
|
|
1814
1955
|
|
|
1815
1956
|
class ApiClient {
|
|
1816
|
-
|
|
1817
|
-
|
|
1957
|
+
static async create(credential) {
|
|
1958
|
+
return new ApiClient(credential);
|
|
1959
|
+
}
|
|
1960
|
+
credential;
|
|
1818
1961
|
pushClient;
|
|
1819
|
-
constructor(
|
|
1820
|
-
this.
|
|
1821
|
-
this.
|
|
1822
|
-
this.pushClient = new PushNotificationClient(token);
|
|
1962
|
+
constructor(credential) {
|
|
1963
|
+
this.credential = credential;
|
|
1964
|
+
this.pushClient = new PushNotificationClient(credential.token, configuration.serverUrl);
|
|
1823
1965
|
}
|
|
1824
1966
|
/**
|
|
1825
1967
|
* Create a new session or load existing one with the given tag
|
|
1826
1968
|
*/
|
|
1827
1969
|
async getOrCreateSession(opts) {
|
|
1970
|
+
let dataEncryptionKey = null;
|
|
1971
|
+
let encryptionKey;
|
|
1972
|
+
let encryptionVariant;
|
|
1973
|
+
if (this.credential.encryption.type === "dataKey") {
|
|
1974
|
+
encryptionKey = getRandomBytes(32);
|
|
1975
|
+
encryptionVariant = "dataKey";
|
|
1976
|
+
let encryptedDataKey = libsodiumEncryptForPublicKey(encryptionKey, this.credential.encryption.publicKey);
|
|
1977
|
+
dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
|
|
1978
|
+
dataEncryptionKey.set([0], 0);
|
|
1979
|
+
dataEncryptionKey.set(encryptedDataKey, 1);
|
|
1980
|
+
} else {
|
|
1981
|
+
encryptionKey = this.credential.encryption.secret;
|
|
1982
|
+
encryptionVariant = "legacy";
|
|
1983
|
+
}
|
|
1828
1984
|
try {
|
|
1829
1985
|
const response = await axios.post(
|
|
1830
1986
|
`${configuration.serverUrl}/v1/sessions`,
|
|
1831
1987
|
{
|
|
1832
1988
|
tag: opts.tag,
|
|
1833
|
-
metadata: encodeBase64(encrypt(opts.metadata
|
|
1834
|
-
agentState: opts.state ? encodeBase64(encrypt(opts.state
|
|
1989
|
+
metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
|
|
1990
|
+
agentState: opts.state ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.state)) : null,
|
|
1991
|
+
dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : null
|
|
1835
1992
|
},
|
|
1836
1993
|
{
|
|
1837
1994
|
headers: {
|
|
1838
|
-
"Authorization": `Bearer ${this.token}`,
|
|
1995
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
1839
1996
|
"Content-Type": "application/json"
|
|
1840
1997
|
},
|
|
1841
|
-
timeout:
|
|
1842
|
-
//
|
|
1998
|
+
timeout: 6e4
|
|
1999
|
+
// 1 minute timeout for very bad network connections
|
|
1843
2000
|
}
|
|
1844
2001
|
);
|
|
1845
2002
|
logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
|
|
1846
2003
|
let raw = response.data.session;
|
|
1847
2004
|
let session = {
|
|
1848
2005
|
id: raw.id,
|
|
1849
|
-
createdAt: raw.createdAt,
|
|
1850
|
-
updatedAt: raw.updatedAt,
|
|
1851
2006
|
seq: raw.seq,
|
|
1852
|
-
metadata: decrypt(decodeBase64(raw.metadata)
|
|
2007
|
+
metadata: decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)),
|
|
1853
2008
|
metadataVersion: raw.metadataVersion,
|
|
1854
|
-
agentState: raw.agentState ? decrypt(decodeBase64(raw.agentState)
|
|
1855
|
-
agentStateVersion: raw.agentStateVersion
|
|
2009
|
+
agentState: raw.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.agentState)) : null,
|
|
2010
|
+
agentStateVersion: raw.agentStateVersion,
|
|
2011
|
+
encryptionKey,
|
|
2012
|
+
encryptionVariant
|
|
1856
2013
|
};
|
|
1857
2014
|
return session;
|
|
1858
2015
|
} catch (error) {
|
|
@@ -1860,54 +2017,40 @@ class ApiClient {
|
|
|
1860
2017
|
throw new Error(`Failed to get or create session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1861
2018
|
}
|
|
1862
2019
|
}
|
|
1863
|
-
/**
|
|
1864
|
-
* Get machine by ID from the server
|
|
1865
|
-
* Returns the current machine state from the server with decrypted metadata and daemonState
|
|
1866
|
-
*/
|
|
1867
|
-
async getMachine(machineId) {
|
|
1868
|
-
const response = await axios.get(`${configuration.serverUrl}/v1/machines/${machineId}`, {
|
|
1869
|
-
headers: {
|
|
1870
|
-
"Authorization": `Bearer ${this.token}`,
|
|
1871
|
-
"Content-Type": "application/json"
|
|
1872
|
-
},
|
|
1873
|
-
timeout: 2e3
|
|
1874
|
-
});
|
|
1875
|
-
const raw = response.data.machine;
|
|
1876
|
-
if (!raw) {
|
|
1877
|
-
return null;
|
|
1878
|
-
}
|
|
1879
|
-
logger.debug(`[API] Machine ${machineId} fetched from server`);
|
|
1880
|
-
const machine = {
|
|
1881
|
-
id: raw.id,
|
|
1882
|
-
metadata: raw.metadata ? decrypt(decodeBase64(raw.metadata), this.secret) : null,
|
|
1883
|
-
metadataVersion: raw.metadataVersion || 0,
|
|
1884
|
-
daemonState: raw.daemonState ? decrypt(decodeBase64(raw.daemonState), this.secret) : null,
|
|
1885
|
-
daemonStateVersion: raw.daemonStateVersion || 0,
|
|
1886
|
-
active: raw.active,
|
|
1887
|
-
activeAt: raw.activeAt,
|
|
1888
|
-
createdAt: raw.createdAt,
|
|
1889
|
-
updatedAt: raw.updatedAt
|
|
1890
|
-
};
|
|
1891
|
-
return machine;
|
|
1892
|
-
}
|
|
1893
2020
|
/**
|
|
1894
2021
|
* Register or update machine with the server
|
|
1895
2022
|
* Returns the current machine state from the server with decrypted metadata and daemonState
|
|
1896
2023
|
*/
|
|
1897
2024
|
async getOrCreateMachine(opts) {
|
|
2025
|
+
let dataEncryptionKey = null;
|
|
2026
|
+
let encryptionKey;
|
|
2027
|
+
let encryptionVariant;
|
|
2028
|
+
if (this.credential.encryption.type === "dataKey") {
|
|
2029
|
+
encryptionVariant = "dataKey";
|
|
2030
|
+
encryptionKey = this.credential.encryption.machineKey;
|
|
2031
|
+
let encryptedDataKey = libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, this.credential.encryption.publicKey);
|
|
2032
|
+
dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
|
|
2033
|
+
dataEncryptionKey.set([0], 0);
|
|
2034
|
+
dataEncryptionKey.set(encryptedDataKey, 1);
|
|
2035
|
+
} else {
|
|
2036
|
+
encryptionKey = this.credential.encryption.secret;
|
|
2037
|
+
encryptionVariant = "legacy";
|
|
2038
|
+
}
|
|
1898
2039
|
const response = await axios.post(
|
|
1899
2040
|
`${configuration.serverUrl}/v1/machines`,
|
|
1900
2041
|
{
|
|
1901
2042
|
id: opts.machineId,
|
|
1902
|
-
metadata: encodeBase64(encrypt(opts.metadata
|
|
1903
|
-
daemonState: opts.daemonState ? encodeBase64(encrypt(opts.daemonState
|
|
2043
|
+
metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
|
|
2044
|
+
daemonState: opts.daemonState ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.daemonState)) : void 0,
|
|
2045
|
+
dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : void 0
|
|
1904
2046
|
},
|
|
1905
2047
|
{
|
|
1906
2048
|
headers: {
|
|
1907
|
-
"Authorization": `Bearer ${this.token}`,
|
|
2049
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
1908
2050
|
"Content-Type": "application/json"
|
|
1909
2051
|
},
|
|
1910
|
-
timeout:
|
|
2052
|
+
timeout: 6e4
|
|
2053
|
+
// 1 minute timeout for very bad network connections
|
|
1911
2054
|
}
|
|
1912
2055
|
);
|
|
1913
2056
|
if (response.status !== 200) {
|
|
@@ -1919,22 +2062,20 @@ class ApiClient {
|
|
|
1919
2062
|
logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
|
|
1920
2063
|
const machine = {
|
|
1921
2064
|
id: raw.id,
|
|
1922
|
-
|
|
2065
|
+
encryptionKey,
|
|
2066
|
+
encryptionVariant,
|
|
2067
|
+
metadata: raw.metadata ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)) : null,
|
|
1923
2068
|
metadataVersion: raw.metadataVersion || 0,
|
|
1924
|
-
daemonState: raw.daemonState ? decrypt(decodeBase64(raw.daemonState)
|
|
1925
|
-
daemonStateVersion: raw.daemonStateVersion || 0
|
|
1926
|
-
active: raw.active,
|
|
1927
|
-
activeAt: raw.activeAt,
|
|
1928
|
-
createdAt: raw.createdAt,
|
|
1929
|
-
updatedAt: raw.updatedAt
|
|
2069
|
+
daemonState: raw.daemonState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.daemonState)) : null,
|
|
2070
|
+
daemonStateVersion: raw.daemonStateVersion || 0
|
|
1930
2071
|
};
|
|
1931
2072
|
return machine;
|
|
1932
2073
|
}
|
|
1933
2074
|
sessionSyncClient(session) {
|
|
1934
|
-
return new ApiSessionClient(this.token,
|
|
2075
|
+
return new ApiSessionClient(this.credential.token, session);
|
|
1935
2076
|
}
|
|
1936
2077
|
machineSyncClient(machine) {
|
|
1937
|
-
return new ApiMachineClient(this.token,
|
|
2078
|
+
return new ApiMachineClient(this.credential.token, machine);
|
|
1938
2079
|
}
|
|
1939
2080
|
push() {
|
|
1940
2081
|
return this.pushClient;
|
|
@@ -1952,7 +2093,7 @@ class ApiClient {
|
|
|
1952
2093
|
},
|
|
1953
2094
|
{
|
|
1954
2095
|
headers: {
|
|
1955
|
-
"Authorization": `Bearer ${this.token}`,
|
|
2096
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
1956
2097
|
"Content-Type": "application/json"
|
|
1957
2098
|
},
|
|
1958
2099
|
timeout: 5e3
|
|
@@ -2040,5 +2181,6 @@ exports.readDaemonState = readDaemonState;
|
|
|
2040
2181
|
exports.readSettings = readSettings;
|
|
2041
2182
|
exports.releaseDaemonLock = releaseDaemonLock;
|
|
2042
2183
|
exports.updateSettings = updateSettings;
|
|
2043
|
-
exports.
|
|
2184
|
+
exports.writeCredentialsDataKey = writeCredentialsDataKey;
|
|
2185
|
+
exports.writeCredentialsLegacy = writeCredentialsLegacy;
|
|
2044
2186
|
exports.writeDaemonState = writeDaemonState;
|