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.
Files changed (46) hide show
  1. package/README.md +10 -1
  2. package/bin/happy-mcp.mjs +32 -0
  3. package/dist/codex/happyMcpStdioBridge.cjs +80 -0
  4. package/dist/codex/happyMcpStdioBridge.d.cts +2 -0
  5. package/dist/codex/happyMcpStdioBridge.d.mts +2 -0
  6. package/dist/codex/happyMcpStdioBridge.mjs +78 -0
  7. package/dist/index-67rskwL7.cjs +6033 -0
  8. package/dist/index-Dw96QD4T.mjs +6025 -0
  9. package/dist/index.cjs +33 -6024
  10. package/dist/index.mjs +33 -6024
  11. package/dist/lib.cjs +2 -1
  12. package/dist/lib.d.cts +54 -112
  13. package/dist/lib.d.mts +54 -112
  14. package/dist/lib.mjs +2 -1
  15. package/dist/runCodex-BLNf5zb1.cjs +1155 -0
  16. package/dist/runCodex-BNH8w4O9.mjs +1153 -0
  17. package/dist/{types-xfXKJHdM.mjs → types-2wHnX7UW.mjs} +311 -170
  18. package/dist/{types-WP9wteZE.cjs → types-BcDnTXMg.cjs} +348 -206
  19. package/package.json +20 -6
  20. package/scripts/ripgrep_launcher.cjs +2 -26
  21. package/scripts/unpack-tools.cjs +163 -0
  22. package/tools/archives/difftastic-LICENSE +21 -0
  23. package/tools/archives/difftastic-arm64-darwin.tar.gz +0 -0
  24. package/tools/archives/difftastic-arm64-linux.tar.gz +0 -0
  25. package/tools/archives/difftastic-x64-darwin.tar.gz +0 -0
  26. package/tools/archives/difftastic-x64-linux.tar.gz +0 -0
  27. package/tools/archives/difftastic-x64-win32.tar.gz +0 -0
  28. package/tools/archives/ripgrep-arm64-darwin.tar.gz +0 -0
  29. package/tools/archives/ripgrep-arm64-linux.tar.gz +0 -0
  30. package/tools/archives/ripgrep-x64-darwin.tar.gz +0 -0
  31. package/tools/archives/ripgrep-x64-linux.tar.gz +0 -0
  32. package/tools/archives/ripgrep-x64-win32.tar.gz +0 -0
  33. package/tools/licenses/difftastic-LICENSE +21 -0
  34. package/tools/licenses/ripgrep-LICENSE +3 -0
  35. package/tools/unpacked/difft +0 -0
  36. package/ripgrep/arm64-linux/rg +0 -0
  37. package/ripgrep/arm64-linux/ripgrep.node +0 -0
  38. package/ripgrep/x64-darwin/rg +0 -0
  39. package/ripgrep/x64-darwin/ripgrep.node +0 -0
  40. package/ripgrep/x64-linux/rg +0 -0
  41. package/ripgrep/x64-linux/ripgrep.node +0 -0
  42. package/ripgrep/x64-win32/rg.exe +0 -0
  43. package/ripgrep/x64-win32/ripgrep.node +0 -0
  44. /package/{ripgrep/COPYING → tools/archives/ripgrep-LICENSE} +0 -0
  45. /package/{ripgrep/arm64-darwin → tools/unpacked}/rg +0 -0
  46. /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 node_fs = require('node:fs');
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$1 = require('fs/promises');
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-2";
45
- var description = "Claude Code session sharing CLI";
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
- "ripgrep",
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: "yarn build && tsx --env-file .env.dev src/index.ts",
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 (!node_fs.existsSync(this.happyHomeDir)) {
204
- node_fs.mkdirSync(this.happyHomeDir, { recursive: true });
220
+ if (!fs.existsSync(this.happyHomeDir)) {
221
+ fs.mkdirSync(this.happyHomeDir, { recursive: true });
205
222
  }
206
- if (!node_fs.existsSync(this.logsDir)) {
207
- node_fs.mkdirSync(this.logsDir, { recursive: true });
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 encrypt(data, secret) {
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 decrypt(data, secret) {
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 (!node_fs.existsSync(configuration.settingsFile)) {
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, node_fs.constants.O_CREAT | node_fs.constants.O_EXCL | node_fs.constants.O_WRONLY);
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 (!node_fs.existsSync(configuration.happyHomeDir)) {
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
- secret: z__namespace.string().base64(),
313
- token: z__namespace.string()
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 (!node_fs.existsSync(configuration.privateKeyFile)) {
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
- return {
323
- secret: new Uint8Array(Buffer.from(credentials.secret, "base64")),
324
- token: credentials.token
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 writeCredentials(credentials) {
331
- if (!node_fs.existsSync(configuration.happyHomeDir)) {
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 (node_fs.existsSync(configuration.privateKeyFile)) {
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 (!node_fs.existsSync(configuration.daemonStateFile)) {
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
- node_fs.writeFileSync(configuration.daemonStateFile, JSON.stringify(state, null, 2), "utf-8");
474
+ fs.writeFileSync(configuration.daemonStateFile, JSON.stringify(state, null, 2), "utf-8");
364
475
  }
365
476
  async function clearDaemonState() {
366
- if (node_fs.existsSync(configuration.daemonStateFile)) {
477
+ if (fs.existsSync(configuration.daemonStateFile)) {
367
478
  await promises.unlink(configuration.daemonStateFile);
368
479
  }
369
- if (node_fs.existsSync(configuration.daemonLockFile)) {
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
- node_fs.constants.O_CREAT | node_fs.constants.O_EXCL | node_fs.constants.O_WRONLY
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 = node_fs.readFileSync(configuration.daemonLockFile, "utf-8").trim();
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
- node_fs.unlinkSync(configuration.daemonLockFile);
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 (node_fs.existsSync(configuration.daemonLockFile)) {
416
- node_fs.unlinkSync(configuration.daemonLockFile);
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 (!node_fs.existsSync(logsDir)) {
695
+ if (!fs.existsSync(logsDir)) {
578
696
  return [];
579
697
  }
580
- const logs = node_fs.readdirSync(logsDir).filter((file) => file.endsWith("-daemon.log")).map((file) => {
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 = node_fs.statSync(fullPath);
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 && node_fs.existsSync(state.daemonLogPath)) {
591
- const stats = node_fs.statSync(state.daemonLogPath);
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
- secret;
931
+ encryptionKey;
932
+ encryptionVariant;
870
933
  logger;
871
934
  socket = null;
872
935
  constructor(config) {
873
936
  this.scopePrefix = config.scopePrefix;
874
- this.secret = config.secret;
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(errorResponse, this.secret));
964
+ const encryptedError = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
901
965
  return encryptedError;
902
966
  }
903
- const decryptedParams = decrypt(decodeBase64(request.params), this.secret);
967
+ const decryptedParams = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(request.params));
904
968
  const result = await handler(decryptedParams);
905
- const encryptedResponse = encodeBase64(encrypt(result, this.secret));
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
- const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
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-WP9wteZE.cjs', document.baseURI).href))));
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$1.readFile(data.path);
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$1.readFile(data.path);
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$1.stat(data.path);
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$1.writeFile(data.path, buffer);
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$1.readdir(data.path, { withFileTypes: true });
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$1.stat(fullPath);
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$1.stat(path$1);
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$1.readdir(path$1, { withFileTypes: true });
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
- constructor(token, secret, session) {
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
- secret: this.secret,
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), this.secret);
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), this.secret);
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), this.secret) : null;
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(content, this.secret));
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(content, this.secret));
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(updated, this.secret)) });
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), this.secret);
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), this.secret);
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(updated, this.secret)) : null });
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), this.secret) : null;
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), this.secret) : null;
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, secret, machine) {
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
- secret: this.secret,
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(updated, this.secret)),
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), this.secret);
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), this.secret);
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(updated, this.secret)),
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), this.secret);
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), this.secret);
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), this.secret);
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), this.secret);
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
- token;
1817
- secret;
1957
+ static async create(credential) {
1958
+ return new ApiClient(credential);
1959
+ }
1960
+ credential;
1818
1961
  pushClient;
1819
- constructor(token, secret) {
1820
- this.token = token;
1821
- this.secret = secret;
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, this.secret)),
1834
- agentState: opts.state ? encodeBase64(encrypt(opts.state, this.secret)) : null
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: 5e3
1842
- // 5 second timeout
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), this.secret),
2007
+ metadata: decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)),
1853
2008
  metadataVersion: raw.metadataVersion,
1854
- agentState: raw.agentState ? decrypt(decodeBase64(raw.agentState), this.secret) : null,
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, this.secret)),
1903
- daemonState: opts.daemonState ? encodeBase64(encrypt(opts.daemonState, this.secret)) : void 0
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: 5e3
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
- metadata: raw.metadata ? decrypt(decodeBase64(raw.metadata), this.secret) : null,
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), this.secret) : null,
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, this.secret, session);
2075
+ return new ApiSessionClient(this.credential.token, session);
1935
2076
  }
1936
2077
  machineSyncClient(machine) {
1937
- return new ApiMachineClient(this.token, this.secret, machine);
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.writeCredentials = writeCredentials;
2184
+ exports.writeCredentialsDataKey = writeCredentialsDataKey;
2185
+ exports.writeCredentialsLegacy = writeCredentialsLegacy;
2044
2186
  exports.writeDaemonState = writeDaemonState;