happy-coder 0.10.0-3 → 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.
@@ -7,7 +7,7 @@ import { join, basename } from 'node:path';
7
7
  import { readFile, open, stat, unlink, mkdir, writeFile, rename } from 'node:fs/promises';
8
8
  import * as z from 'zod';
9
9
  import { z as z$1 } from 'zod';
10
- import { randomBytes, randomUUID } from 'node:crypto';
10
+ import { randomBytes, createCipheriv, createDecipheriv, randomUUID } from 'node:crypto';
11
11
  import tweetnacl from 'tweetnacl';
12
12
  import { EventEmitter } from 'node:events';
13
13
  import { io } from 'socket.io-client';
@@ -21,7 +21,7 @@ import { platform } from 'os';
21
21
  import { Expo } from 'expo-server-sdk';
22
22
 
23
23
  var name = "happy-coder";
24
- var version = "0.10.0-3";
24
+ var version = "0.10.0-4";
25
25
  var description = "Mobile and Web client for Claude Code and Codex";
26
26
  var author = "Kirill Dubovitskiy";
27
27
  var license = "MIT";
@@ -81,7 +81,7 @@ var scripts = {
81
81
  build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
82
82
  test: "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
83
83
  start: "yarn build && ./bin/happy.mjs",
84
- dev: "yarn build && tsx --env-file .env.dev src/index.ts",
84
+ dev: "tsx --env-file .env.dev src/index.ts",
85
85
  "dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
86
86
  "dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
87
87
  prepublishOnly: "yarn build && yarn test",
@@ -93,6 +93,7 @@ var dependencies = {
93
93
  "@anthropic-ai/sdk": "^0.56.0",
94
94
  "@modelcontextprotocol/sdk": "^1.15.1",
95
95
  "@stablelib/base64": "^2.0.1",
96
+ "@stablelib/hex": "^2.0.1",
96
97
  "@types/cross-spawn": "^6.0.6",
97
98
  "@types/http-proxy": "^1.17.16",
98
99
  "@types/ps-list": "^6.2.1",
@@ -166,6 +167,7 @@ var packageJson = {
166
167
 
167
168
  class Configuration {
168
169
  serverUrl;
170
+ webappUrl;
169
171
  isDaemonProcess;
170
172
  // Directories and paths (from persistence)
171
173
  happyHomeDir;
@@ -178,6 +180,7 @@ class Configuration {
178
180
  isExperimentalEnabled;
179
181
  constructor() {
180
182
  this.serverUrl = process.env.HAPPY_SERVER_URL || "https://api.cluster-fluster.com";
183
+ this.webappUrl = process.env.HAPPY_WEBAPP_URL || "https://app.happy.engineering";
181
184
  const args = process.argv.slice(2);
182
185
  this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
183
186
  if (process.env.HAPPY_HOME_DIR) {
@@ -222,7 +225,17 @@ function decodeBase64(base64, variant = "base64") {
222
225
  function getRandomBytes(size) {
223
226
  return new Uint8Array(randomBytes(size));
224
227
  }
225
- function encrypt(data, secret) {
228
+ function libsodiumEncryptForPublicKey(data, recipientPublicKey) {
229
+ const ephemeralKeyPair = tweetnacl.box.keyPair();
230
+ const nonce = getRandomBytes(tweetnacl.box.nonceLength);
231
+ const encrypted = tweetnacl.box(data, nonce, recipientPublicKey, ephemeralKeyPair.secretKey);
232
+ const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
233
+ result.set(ephemeralKeyPair.publicKey, 0);
234
+ result.set(nonce, ephemeralKeyPair.publicKey.length);
235
+ result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
236
+ return result;
237
+ }
238
+ function encryptLegacy(data, secret) {
226
239
  const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
227
240
  const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
228
241
  const result = new Uint8Array(nonce.length + encrypted.length);
@@ -230,7 +243,7 @@ function encrypt(data, secret) {
230
243
  result.set(encrypted, nonce.length);
231
244
  return result;
232
245
  }
233
- function decrypt(data, secret) {
246
+ function decryptLegacy(data, secret) {
234
247
  const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
235
248
  const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
236
249
  const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
@@ -239,6 +252,61 @@ function decrypt(data, secret) {
239
252
  }
240
253
  return JSON.parse(new TextDecoder().decode(decrypted));
241
254
  }
255
+ function encryptWithDataKey(data, dataKey) {
256
+ const nonce = getRandomBytes(12);
257
+ const cipher = createCipheriv("aes-256-gcm", dataKey, nonce);
258
+ const plaintext = new TextEncoder().encode(JSON.stringify(data));
259
+ const encrypted = Buffer.concat([
260
+ cipher.update(plaintext),
261
+ cipher.final()
262
+ ]);
263
+ const authTag = cipher.getAuthTag();
264
+ const bundle = new Uint8Array(12 + encrypted.length + 16 + 1);
265
+ bundle.set([0], 0);
266
+ bundle.set(nonce, 1);
267
+ bundle.set(new Uint8Array(encrypted), 13);
268
+ bundle.set(new Uint8Array(authTag), 13 + encrypted.length);
269
+ return bundle;
270
+ }
271
+ function decryptWithDataKey(bundle, dataKey) {
272
+ if (bundle.length < 1) {
273
+ return null;
274
+ }
275
+ if (bundle[0] !== 0) {
276
+ return null;
277
+ }
278
+ if (bundle.length < 12 + 16 + 1) {
279
+ return null;
280
+ }
281
+ const nonce = bundle.slice(1, 13);
282
+ const authTag = bundle.slice(bundle.length - 16);
283
+ const ciphertext = bundle.slice(13, bundle.length - 16);
284
+ try {
285
+ const decipher = createDecipheriv("aes-256-gcm", dataKey, nonce);
286
+ decipher.setAuthTag(authTag);
287
+ const decrypted = Buffer.concat([
288
+ decipher.update(ciphertext),
289
+ decipher.final()
290
+ ]);
291
+ return JSON.parse(new TextDecoder().decode(decrypted));
292
+ } catch (error) {
293
+ return null;
294
+ }
295
+ }
296
+ function encrypt(key, variant, data) {
297
+ if (variant === "legacy") {
298
+ return encryptLegacy(data, key);
299
+ } else {
300
+ return encryptWithDataKey(data, key);
301
+ }
302
+ }
303
+ function decrypt(key, variant, data) {
304
+ if (variant === "legacy") {
305
+ return decryptLegacy(data, key);
306
+ } else {
307
+ return decryptWithDataKey(data, key);
308
+ }
309
+ }
242
310
 
243
311
  const defaultSettings = {
244
312
  onboardingCompleted: false
@@ -302,8 +370,13 @@ async function updateSettings(updater) {
302
370
  }
303
371
  }
304
372
  const credentialsSchema = z.object({
305
- secret: z.string().base64(),
306
- token: z.string()
373
+ token: z.string(),
374
+ secret: z.string().base64().nullish(),
375
+ // Legacy
376
+ encryption: z.object({
377
+ publicKey: z.string().base64(),
378
+ machineKey: z.string().base64()
379
+ }).nullish()
307
380
  });
308
381
  async function readCredentials() {
309
382
  if (!existsSync(configuration.privateKeyFile)) {
@@ -312,15 +385,30 @@ async function readCredentials() {
312
385
  try {
313
386
  const keyBase64 = await readFile(configuration.privateKeyFile, "utf8");
314
387
  const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
315
- return {
316
- secret: new Uint8Array(Buffer.from(credentials.secret, "base64")),
317
- token: credentials.token
318
- };
388
+ if (credentials.secret) {
389
+ return {
390
+ token: credentials.token,
391
+ encryption: {
392
+ type: "legacy",
393
+ secret: new Uint8Array(Buffer.from(credentials.secret, "base64"))
394
+ }
395
+ };
396
+ } else if (credentials.encryption) {
397
+ return {
398
+ token: credentials.token,
399
+ encryption: {
400
+ type: "dataKey",
401
+ publicKey: new Uint8Array(Buffer.from(credentials.encryption.publicKey, "base64")),
402
+ machineKey: new Uint8Array(Buffer.from(credentials.encryption.machineKey, "base64"))
403
+ }
404
+ };
405
+ }
319
406
  } catch {
320
407
  return null;
321
408
  }
409
+ return null;
322
410
  }
323
- async function writeCredentials(credentials) {
411
+ async function writeCredentialsLegacy(credentials) {
324
412
  if (!existsSync(configuration.happyHomeDir)) {
325
413
  await mkdir(configuration.happyHomeDir, { recursive: true });
326
414
  }
@@ -329,6 +417,15 @@ async function writeCredentials(credentials) {
329
417
  token: credentials.token
330
418
  }, null, 2));
331
419
  }
420
+ async function writeCredentialsDataKey(credentials) {
421
+ if (!existsSync(configuration.happyHomeDir)) {
422
+ await mkdir(configuration.happyHomeDir, { recursive: true });
423
+ }
424
+ await writeFile(configuration.privateKeyFile, JSON.stringify({
425
+ encryption: { publicKey: encodeBase64(credentials.publicKey), machineKey: encodeBase64(credentials.machineKey) },
426
+ token: credentials.token
427
+ }, null, 2));
428
+ }
332
429
  async function clearCredentials() {
333
430
  if (existsSync(configuration.privateKeyFile)) {
334
431
  await unlink(configuration.privateKeyFile);
@@ -663,32 +760,6 @@ z$1.object({
663
760
  ]),
664
761
  createdAt: z$1.number()
665
762
  });
666
- z$1.object({
667
- createdAt: z$1.number(),
668
- id: z$1.string(),
669
- seq: z$1.number(),
670
- updatedAt: z$1.number(),
671
- metadata: z$1.any(),
672
- metadataVersion: z$1.number(),
673
- agentState: z$1.any().nullable(),
674
- agentStateVersion: z$1.number(),
675
- // Connectivity tracking (from server)
676
- connectivityStatus: z$1.union([
677
- z$1.enum(["neverConnected", "online", "offline"]),
678
- z$1.string()
679
- // Forward compatibility
680
- ]).optional(),
681
- connectivityStatusSince: z$1.number().optional(),
682
- connectivityStatusReason: z$1.string().optional(),
683
- // State tracking (from server)
684
- state: z$1.union([
685
- z$1.enum(["running", "archiveRequested", "archived"]),
686
- z$1.string()
687
- // Forward compatibility
688
- ]).optional(),
689
- stateSince: z$1.number().optional(),
690
- stateReason: z$1.string().optional()
691
- });
692
763
  z$1.object({
693
764
  host: z$1.string(),
694
765
  platform: z$1.string(),
@@ -713,37 +784,6 @@ z$1.object({
713
784
  // Forward compatibility
714
785
  ]).optional()
715
786
  });
716
- z$1.object({
717
- id: z$1.string(),
718
- metadata: z$1.any(),
719
- // Decrypted MachineMetadata
720
- metadataVersion: z$1.number(),
721
- daemonState: z$1.any().nullable(),
722
- // Decrypted DaemonState
723
- daemonStateVersion: z$1.number(),
724
- // We don't really care about these on the CLI for now
725
- // ApiMachineClient will not sync these
726
- active: z$1.boolean(),
727
- activeAt: z$1.number(),
728
- createdAt: z$1.number(),
729
- updatedAt: z$1.number(),
730
- // Connectivity tracking (from server)
731
- connectivityStatus: z$1.union([
732
- z$1.enum(["neverConnected", "online", "offline"]),
733
- z$1.string()
734
- // Forward compatibility
735
- ]).optional(),
736
- connectivityStatusSince: z$1.number().optional(),
737
- connectivityStatusReason: z$1.string().optional(),
738
- // State tracking (from server)
739
- state: z$1.union([
740
- z$1.enum(["running", "archiveRequested", "archived"]),
741
- z$1.string()
742
- // Forward compatibility
743
- ]).optional(),
744
- stateSince: z$1.number().optional(),
745
- stateReason: z$1.string().optional()
746
- });
747
787
  z$1.object({
748
788
  content: SessionMessageContentSchema,
749
789
  createdAt: z$1.number(),
@@ -867,12 +907,14 @@ class AsyncLock {
867
907
  class RpcHandlerManager {
868
908
  handlers = /* @__PURE__ */ new Map();
869
909
  scopePrefix;
870
- secret;
910
+ encryptionKey;
911
+ encryptionVariant;
871
912
  logger;
872
913
  socket = null;
873
914
  constructor(config) {
874
915
  this.scopePrefix = config.scopePrefix;
875
- this.secret = config.secret;
916
+ this.encryptionKey = config.encryptionKey;
917
+ this.encryptionVariant = config.encryptionVariant;
876
918
  this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
877
919
  }
878
920
  /**
@@ -898,20 +940,19 @@ class RpcHandlerManager {
898
940
  if (!handler) {
899
941
  this.logger("[RPC] [ERROR] Method not found", { method: request.method });
900
942
  const errorResponse = { error: "Method not found" };
901
- const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
943
+ const encryptedError = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
902
944
  return encryptedError;
903
945
  }
904
- const decryptedParams = decrypt(decodeBase64(request.params), this.secret);
946
+ const decryptedParams = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(request.params));
905
947
  const result = await handler(decryptedParams);
906
- const encryptedResponse = encodeBase64(encrypt(result, this.secret));
948
+ const encryptedResponse = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, result));
907
949
  return encryptedResponse;
908
950
  } catch (error) {
909
951
  this.logger("[RPC] [ERROR] Error handling request", { error });
910
952
  const errorResponse = {
911
953
  error: error instanceof Error ? error.message : "Unknown error"
912
954
  };
913
- const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
914
- return encryptedError;
955
+ return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
915
956
  }
916
957
  }
917
958
  onSocketConnect(socket) {
@@ -1256,7 +1297,6 @@ function registerCommonHandlers(rpcHandlerManager) {
1256
1297
 
1257
1298
  class ApiSessionClient extends EventEmitter {
1258
1299
  token;
1259
- secret;
1260
1300
  sessionId;
1261
1301
  metadata;
1262
1302
  metadataVersion;
@@ -1268,18 +1308,22 @@ class ApiSessionClient extends EventEmitter {
1268
1308
  rpcHandlerManager;
1269
1309
  agentStateLock = new AsyncLock();
1270
1310
  metadataLock = new AsyncLock();
1271
- constructor(token, secret, session) {
1311
+ encryptionKey;
1312
+ encryptionVariant;
1313
+ constructor(token, session) {
1272
1314
  super();
1273
1315
  this.token = token;
1274
- this.secret = secret;
1275
1316
  this.sessionId = session.id;
1276
1317
  this.metadata = session.metadata;
1277
1318
  this.metadataVersion = session.metadataVersion;
1278
1319
  this.agentState = session.agentState;
1279
1320
  this.agentStateVersion = session.agentStateVersion;
1321
+ this.encryptionKey = session.encryptionKey;
1322
+ this.encryptionVariant = session.encryptionVariant;
1280
1323
  this.rpcHandlerManager = new RpcHandlerManager({
1281
1324
  scopePrefix: this.sessionId,
1282
- secret: this.secret,
1325
+ encryptionKey: this.encryptionKey,
1326
+ encryptionVariant: this.encryptionVariant,
1283
1327
  logger: (msg, data) => logger.debug(msg, data)
1284
1328
  });
1285
1329
  registerCommonHandlers(this.rpcHandlerManager);
@@ -1321,7 +1365,7 @@ class ApiSessionClient extends EventEmitter {
1321
1365
  return;
1322
1366
  }
1323
1367
  if (data.body.t === "new-message" && data.body.message.content.t === "encrypted") {
1324
- const body = decrypt(decodeBase64(data.body.message.content.c), this.secret);
1368
+ const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.message.content.c));
1325
1369
  logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
1326
1370
  const userResult = UserMessageSchema.safeParse(body);
1327
1371
  if (userResult.success) {
@@ -1335,11 +1379,11 @@ class ApiSessionClient extends EventEmitter {
1335
1379
  }
1336
1380
  } else if (data.body.t === "update-session") {
1337
1381
  if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
1338
- this.metadata = decrypt(decodeBase64(data.body.metadata.value), this.secret);
1382
+ this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.metadata.value));
1339
1383
  this.metadataVersion = data.body.metadata.version;
1340
1384
  }
1341
1385
  if (data.body.agentState && data.body.agentState.version > this.agentStateVersion) {
1342
- this.agentState = data.body.agentState.value ? decrypt(decodeBase64(data.body.agentState.value), this.secret) : null;
1386
+ this.agentState = data.body.agentState.value ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.agentState.value)) : null;
1343
1387
  this.agentStateVersion = data.body.agentState.version;
1344
1388
  }
1345
1389
  } else if (data.body.t === "update-machine") {
@@ -1393,7 +1437,7 @@ class ApiSessionClient extends EventEmitter {
1393
1437
  };
1394
1438
  }
1395
1439
  logger.debugLargeJson("[SOCKET] Sending message through socket:", content);
1396
- const encrypted = encodeBase64(encrypt(content, this.secret));
1440
+ const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
1397
1441
  this.socket.emit("message", {
1398
1442
  sid: this.sessionId,
1399
1443
  message: encrypted
@@ -1427,7 +1471,7 @@ class ApiSessionClient extends EventEmitter {
1427
1471
  sentFrom: "cli"
1428
1472
  }
1429
1473
  };
1430
- const encrypted = encodeBase64(encrypt(content, this.secret));
1474
+ const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
1431
1475
  this.socket.emit("message", {
1432
1476
  sid: this.sessionId,
1433
1477
  message: encrypted
@@ -1442,7 +1486,7 @@ class ApiSessionClient extends EventEmitter {
1442
1486
  data: event
1443
1487
  }
1444
1488
  };
1445
- const encrypted = encodeBase64(encrypt(content, this.secret));
1489
+ const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
1446
1490
  this.socket.emit("message", {
1447
1491
  sid: this.sessionId,
1448
1492
  message: encrypted
@@ -1502,14 +1546,14 @@ class ApiSessionClient extends EventEmitter {
1502
1546
  this.metadataLock.inLock(async () => {
1503
1547
  await backoff(async () => {
1504
1548
  let updated = handler(this.metadata);
1505
- const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(updated, this.secret)) });
1549
+ const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) });
1506
1550
  if (answer.result === "success") {
1507
- this.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
1551
+ this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
1508
1552
  this.metadataVersion = answer.version;
1509
1553
  } else if (answer.result === "version-mismatch") {
1510
1554
  if (answer.version > this.metadataVersion) {
1511
1555
  this.metadataVersion = answer.version;
1512
- this.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
1556
+ this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
1513
1557
  }
1514
1558
  throw new Error("Metadata version mismatch");
1515
1559
  } else if (answer.result === "error") ;
@@ -1525,15 +1569,15 @@ class ApiSessionClient extends EventEmitter {
1525
1569
  this.agentStateLock.inLock(async () => {
1526
1570
  await backoff(async () => {
1527
1571
  let updated = handler(this.agentState || {});
1528
- const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(updated, this.secret)) : null });
1572
+ const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
1529
1573
  if (answer.result === "success") {
1530
- this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState), this.secret) : null;
1574
+ this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
1531
1575
  this.agentStateVersion = answer.version;
1532
1576
  logger.debug("Agent state updated", this.agentState);
1533
1577
  } else if (answer.result === "version-mismatch") {
1534
1578
  if (answer.version > this.agentStateVersion) {
1535
1579
  this.agentStateVersion = answer.version;
1536
- this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState), this.secret) : null;
1580
+ this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
1537
1581
  }
1538
1582
  throw new Error("Agent state version mismatch");
1539
1583
  } else if (answer.result === "error") ;
@@ -1563,13 +1607,13 @@ class ApiSessionClient extends EventEmitter {
1563
1607
  }
1564
1608
 
1565
1609
  class ApiMachineClient {
1566
- constructor(token, secret, machine) {
1610
+ constructor(token, machine) {
1567
1611
  this.token = token;
1568
- this.secret = secret;
1569
1612
  this.machine = machine;
1570
1613
  this.rpcHandlerManager = new RpcHandlerManager({
1571
1614
  scopePrefix: this.machine.id,
1572
- secret: this.secret,
1615
+ encryptionKey: this.machine.encryptionKey,
1616
+ encryptionVariant: this.machine.encryptionVariant,
1573
1617
  logger: (msg, data) => logger.debug(msg, data)
1574
1618
  });
1575
1619
  registerCommonHandlers(this.rpcHandlerManager);
@@ -1630,17 +1674,17 @@ class ApiMachineClient {
1630
1674
  const updated = handler(this.machine.metadata);
1631
1675
  const answer = await this.socket.emitWithAck("machine-update-metadata", {
1632
1676
  machineId: this.machine.id,
1633
- metadata: encodeBase64(encrypt(updated, this.secret)),
1677
+ metadata: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
1634
1678
  expectedVersion: this.machine.metadataVersion
1635
1679
  });
1636
1680
  if (answer.result === "success") {
1637
- this.machine.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
1681
+ this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
1638
1682
  this.machine.metadataVersion = answer.version;
1639
1683
  logger.debug("[API MACHINE] Metadata updated successfully");
1640
1684
  } else if (answer.result === "version-mismatch") {
1641
1685
  if (answer.version > this.machine.metadataVersion) {
1642
1686
  this.machine.metadataVersion = answer.version;
1643
- this.machine.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
1687
+ this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
1644
1688
  }
1645
1689
  throw new Error("Metadata version mismatch");
1646
1690
  }
@@ -1655,17 +1699,17 @@ class ApiMachineClient {
1655
1699
  const updated = handler(this.machine.daemonState);
1656
1700
  const answer = await this.socket.emitWithAck("machine-update-state", {
1657
1701
  machineId: this.machine.id,
1658
- daemonState: encodeBase64(encrypt(updated, this.secret)),
1702
+ daemonState: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
1659
1703
  expectedVersion: this.machine.daemonStateVersion
1660
1704
  });
1661
1705
  if (answer.result === "success") {
1662
- this.machine.daemonState = decrypt(decodeBase64(answer.daemonState), this.secret);
1706
+ this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
1663
1707
  this.machine.daemonStateVersion = answer.version;
1664
1708
  logger.debug("[API MACHINE] Daemon state updated successfully");
1665
1709
  } else if (answer.result === "version-mismatch") {
1666
1710
  if (answer.version > this.machine.daemonStateVersion) {
1667
1711
  this.machine.daemonStateVersion = answer.version;
1668
- this.machine.daemonState = decrypt(decodeBase64(answer.daemonState), this.secret);
1712
+ this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
1669
1713
  }
1670
1714
  throw new Error("Daemon state version mismatch");
1671
1715
  }
@@ -1712,12 +1756,12 @@ class ApiMachineClient {
1712
1756
  const update = data.body;
1713
1757
  if (update.metadata) {
1714
1758
  logger.debug("[API MACHINE] Received external metadata update");
1715
- this.machine.metadata = decrypt(decodeBase64(update.metadata.value), this.secret);
1759
+ this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.metadata.value));
1716
1760
  this.machine.metadataVersion = update.metadata.version;
1717
1761
  }
1718
1762
  if (update.daemonState) {
1719
1763
  logger.debug("[API MACHINE] Received external daemon state update");
1720
- this.machine.daemonState = decrypt(decodeBase64(update.daemonState.value), this.secret);
1764
+ this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.daemonState.value));
1721
1765
  this.machine.daemonStateVersion = update.daemonState.version;
1722
1766
  }
1723
1767
  } else {
@@ -1889,46 +1933,62 @@ class PushNotificationClient {
1889
1933
  }
1890
1934
 
1891
1935
  class ApiClient {
1892
- token;
1893
- secret;
1936
+ static async create(credential) {
1937
+ return new ApiClient(credential);
1938
+ }
1939
+ credential;
1894
1940
  pushClient;
1895
- constructor(token, secret) {
1896
- this.token = token;
1897
- this.secret = secret;
1898
- this.pushClient = new PushNotificationClient(token);
1941
+ constructor(credential) {
1942
+ this.credential = credential;
1943
+ this.pushClient = new PushNotificationClient(credential.token, configuration.serverUrl);
1899
1944
  }
1900
1945
  /**
1901
1946
  * Create a new session or load existing one with the given tag
1902
1947
  */
1903
1948
  async getOrCreateSession(opts) {
1949
+ let dataEncryptionKey = null;
1950
+ let encryptionKey;
1951
+ let encryptionVariant;
1952
+ if (this.credential.encryption.type === "dataKey") {
1953
+ encryptionKey = getRandomBytes(32);
1954
+ encryptionVariant = "dataKey";
1955
+ let encryptedDataKey = libsodiumEncryptForPublicKey(encryptionKey, this.credential.encryption.publicKey);
1956
+ dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
1957
+ dataEncryptionKey.set([0], 0);
1958
+ dataEncryptionKey.set(encryptedDataKey, 1);
1959
+ } else {
1960
+ encryptionKey = this.credential.encryption.secret;
1961
+ encryptionVariant = "legacy";
1962
+ }
1904
1963
  try {
1905
1964
  const response = await axios.post(
1906
1965
  `${configuration.serverUrl}/v1/sessions`,
1907
1966
  {
1908
1967
  tag: opts.tag,
1909
- metadata: encodeBase64(encrypt(opts.metadata, this.secret)),
1910
- agentState: opts.state ? encodeBase64(encrypt(opts.state, this.secret)) : null
1968
+ metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
1969
+ agentState: opts.state ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.state)) : null,
1970
+ dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : null
1911
1971
  },
1912
1972
  {
1913
1973
  headers: {
1914
- "Authorization": `Bearer ${this.token}`,
1974
+ "Authorization": `Bearer ${this.credential.token}`,
1915
1975
  "Content-Type": "application/json"
1916
1976
  },
1917
- timeout: 5e3
1918
- // 5 second timeout
1977
+ timeout: 6e4
1978
+ // 1 minute timeout for very bad network connections
1919
1979
  }
1920
1980
  );
1921
1981
  logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
1922
1982
  let raw = response.data.session;
1923
1983
  let session = {
1924
1984
  id: raw.id,
1925
- createdAt: raw.createdAt,
1926
- updatedAt: raw.updatedAt,
1927
1985
  seq: raw.seq,
1928
- metadata: decrypt(decodeBase64(raw.metadata), this.secret),
1986
+ metadata: decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)),
1929
1987
  metadataVersion: raw.metadataVersion,
1930
- agentState: raw.agentState ? decrypt(decodeBase64(raw.agentState), this.secret) : null,
1931
- agentStateVersion: raw.agentStateVersion
1988
+ agentState: raw.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.agentState)) : null,
1989
+ agentStateVersion: raw.agentStateVersion,
1990
+ encryptionKey,
1991
+ encryptionVariant
1932
1992
  };
1933
1993
  return session;
1934
1994
  } catch (error) {
@@ -1936,54 +1996,40 @@ class ApiClient {
1936
1996
  throw new Error(`Failed to get or create session: ${error instanceof Error ? error.message : "Unknown error"}`);
1937
1997
  }
1938
1998
  }
1939
- /**
1940
- * Get machine by ID from the server
1941
- * Returns the current machine state from the server with decrypted metadata and daemonState
1942
- */
1943
- async getMachine(machineId) {
1944
- const response = await axios.get(`${configuration.serverUrl}/v1/machines/${machineId}`, {
1945
- headers: {
1946
- "Authorization": `Bearer ${this.token}`,
1947
- "Content-Type": "application/json"
1948
- },
1949
- timeout: 2e3
1950
- });
1951
- const raw = response.data.machine;
1952
- if (!raw) {
1953
- return null;
1954
- }
1955
- logger.debug(`[API] Machine ${machineId} fetched from server`);
1956
- const machine = {
1957
- id: raw.id,
1958
- metadata: raw.metadata ? decrypt(decodeBase64(raw.metadata), this.secret) : null,
1959
- metadataVersion: raw.metadataVersion || 0,
1960
- daemonState: raw.daemonState ? decrypt(decodeBase64(raw.daemonState), this.secret) : null,
1961
- daemonStateVersion: raw.daemonStateVersion || 0,
1962
- active: raw.active,
1963
- activeAt: raw.activeAt,
1964
- createdAt: raw.createdAt,
1965
- updatedAt: raw.updatedAt
1966
- };
1967
- return machine;
1968
- }
1969
1999
  /**
1970
2000
  * Register or update machine with the server
1971
2001
  * Returns the current machine state from the server with decrypted metadata and daemonState
1972
2002
  */
1973
2003
  async getOrCreateMachine(opts) {
2004
+ let dataEncryptionKey = null;
2005
+ let encryptionKey;
2006
+ let encryptionVariant;
2007
+ if (this.credential.encryption.type === "dataKey") {
2008
+ encryptionVariant = "dataKey";
2009
+ encryptionKey = this.credential.encryption.machineKey;
2010
+ let encryptedDataKey = libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, this.credential.encryption.publicKey);
2011
+ dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
2012
+ dataEncryptionKey.set([0], 0);
2013
+ dataEncryptionKey.set(encryptedDataKey, 1);
2014
+ } else {
2015
+ encryptionKey = this.credential.encryption.secret;
2016
+ encryptionVariant = "legacy";
2017
+ }
1974
2018
  const response = await axios.post(
1975
2019
  `${configuration.serverUrl}/v1/machines`,
1976
2020
  {
1977
2021
  id: opts.machineId,
1978
- metadata: encodeBase64(encrypt(opts.metadata, this.secret)),
1979
- daemonState: opts.daemonState ? encodeBase64(encrypt(opts.daemonState, this.secret)) : void 0
2022
+ metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
2023
+ daemonState: opts.daemonState ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.daemonState)) : void 0,
2024
+ dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : void 0
1980
2025
  },
1981
2026
  {
1982
2027
  headers: {
1983
- "Authorization": `Bearer ${this.token}`,
2028
+ "Authorization": `Bearer ${this.credential.token}`,
1984
2029
  "Content-Type": "application/json"
1985
2030
  },
1986
- timeout: 5e3
2031
+ timeout: 6e4
2032
+ // 1 minute timeout for very bad network connections
1987
2033
  }
1988
2034
  );
1989
2035
  if (response.status !== 200) {
@@ -1995,22 +2041,20 @@ class ApiClient {
1995
2041
  logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
1996
2042
  const machine = {
1997
2043
  id: raw.id,
1998
- metadata: raw.metadata ? decrypt(decodeBase64(raw.metadata), this.secret) : null,
2044
+ encryptionKey,
2045
+ encryptionVariant,
2046
+ metadata: raw.metadata ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)) : null,
1999
2047
  metadataVersion: raw.metadataVersion || 0,
2000
- daemonState: raw.daemonState ? decrypt(decodeBase64(raw.daemonState), this.secret) : null,
2001
- daemonStateVersion: raw.daemonStateVersion || 0,
2002
- active: raw.active,
2003
- activeAt: raw.activeAt,
2004
- createdAt: raw.createdAt,
2005
- updatedAt: raw.updatedAt
2048
+ daemonState: raw.daemonState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.daemonState)) : null,
2049
+ daemonStateVersion: raw.daemonStateVersion || 0
2006
2050
  };
2007
2051
  return machine;
2008
2052
  }
2009
2053
  sessionSyncClient(session) {
2010
- return new ApiSessionClient(this.token, this.secret, session);
2054
+ return new ApiSessionClient(this.credential.token, session);
2011
2055
  }
2012
2056
  machineSyncClient(machine) {
2013
- return new ApiMachineClient(this.token, this.secret, machine);
2057
+ return new ApiMachineClient(this.credential.token, machine);
2014
2058
  }
2015
2059
  push() {
2016
2060
  return this.pushClient;
@@ -2028,7 +2072,7 @@ class ApiClient {
2028
2072
  },
2029
2073
  {
2030
2074
  headers: {
2031
- "Authorization": `Bearer ${this.token}`,
2075
+ "Authorization": `Bearer ${this.credential.token}`,
2032
2076
  "Content-Type": "application/json"
2033
2077
  },
2034
2078
  timeout: 5e3
@@ -2093,4 +2137,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
2093
2137
  }).passthrough()
2094
2138
  ]);
2095
2139
 
2096
- export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, packageJson as b, configuration as c, backoff as d, delay as e, AsyncLock as f, readDaemonState as g, clearDaemonState as h, readCredentials as i, encodeBase64 as j, encodeBase64Url as k, logger as l, decodeBase64 as m, acquireDaemonLock as n, writeDaemonState as o, projectPath as p, releaseDaemonLock as q, readSettings as r, clearCredentials as s, clearMachineId as t, updateSettings as u, getLatestDaemonLog as v, writeCredentials as w };
2140
+ export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, packageJson as b, configuration as c, backoff as d, delay as e, AsyncLock as f, readDaemonState as g, clearDaemonState as h, readCredentials as i, encodeBase64 as j, encodeBase64Url as k, logger as l, decodeBase64 as m, writeCredentialsDataKey as n, acquireDaemonLock as o, projectPath as p, writeDaemonState as q, readSettings as r, releaseDaemonLock as s, clearCredentials as t, updateSettings as u, clearMachineId as v, writeCredentialsLegacy as w, getLatestDaemonLog as x };