@zhigang1992/happy-cli 0.12.1

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 (47) hide show
  1. package/README.md +60 -0
  2. package/bin/happy-mcp.mjs +32 -0
  3. package/bin/happy.mjs +35 -0
  4. package/dist/codex/happyMcpStdioBridge.cjs +80 -0
  5. package/dist/codex/happyMcpStdioBridge.d.cts +2 -0
  6. package/dist/codex/happyMcpStdioBridge.d.mts +2 -0
  7. package/dist/codex/happyMcpStdioBridge.mjs +78 -0
  8. package/dist/index-BOBrKhX5.cjs +6655 -0
  9. package/dist/index-DsHtmQqP.mjs +6624 -0
  10. package/dist/index.cjs +42 -0
  11. package/dist/index.d.cts +1 -0
  12. package/dist/index.d.mts +1 -0
  13. package/dist/index.mjs +39 -0
  14. package/dist/lib.cjs +31 -0
  15. package/dist/lib.d.cts +817 -0
  16. package/dist/lib.d.mts +817 -0
  17. package/dist/lib.mjs +21 -0
  18. package/dist/list-BW6QBLa1.cjs +328 -0
  19. package/dist/list-hET5tyMc.mjs +326 -0
  20. package/dist/prompt-DXkgjktW.cjs +203 -0
  21. package/dist/prompt-Dz7G8yGx.mjs +201 -0
  22. package/dist/runCodex-CLGYMNs2.mjs +1335 -0
  23. package/dist/runCodex-CylcX5Ug.cjs +1338 -0
  24. package/dist/types-BsjUgWOx.cjs +2264 -0
  25. package/dist/types-CGco5Y-r.mjs +2213 -0
  26. package/package.json +126 -0
  27. package/scripts/claude_local_launcher.cjs +98 -0
  28. package/scripts/claude_remote_launcher.cjs +13 -0
  29. package/scripts/ripgrep_launcher.cjs +33 -0
  30. package/scripts/unpack-tools.cjs +163 -0
  31. package/tools/archives/difftastic-LICENSE +21 -0
  32. package/tools/archives/difftastic-arm64-darwin.tar.gz +0 -0
  33. package/tools/archives/difftastic-arm64-linux.tar.gz +0 -0
  34. package/tools/archives/difftastic-x64-darwin.tar.gz +0 -0
  35. package/tools/archives/difftastic-x64-linux.tar.gz +0 -0
  36. package/tools/archives/difftastic-x64-win32.tar.gz +0 -0
  37. package/tools/archives/ripgrep-LICENSE +3 -0
  38. package/tools/archives/ripgrep-arm64-darwin.tar.gz +0 -0
  39. package/tools/archives/ripgrep-arm64-linux.tar.gz +0 -0
  40. package/tools/archives/ripgrep-x64-darwin.tar.gz +0 -0
  41. package/tools/archives/ripgrep-x64-linux.tar.gz +0 -0
  42. package/tools/archives/ripgrep-x64-win32.tar.gz +0 -0
  43. package/tools/licenses/difftastic-LICENSE +21 -0
  44. package/tools/licenses/ripgrep-LICENSE +3 -0
  45. package/tools/unpacked/difft +0 -0
  46. package/tools/unpacked/rg +0 -0
  47. package/tools/unpacked/ripgrep.node +0 -0
@@ -0,0 +1,2264 @@
1
+ 'use strict';
2
+
3
+ var axios = require('axios');
4
+ var chalk = require('chalk');
5
+ var fs$1 = require('fs');
6
+ var fs = require('node:fs');
7
+ var os = require('node:os');
8
+ var node_path = require('node:path');
9
+ var promises = require('node:fs/promises');
10
+ var z = require('zod');
11
+ var node_crypto = require('node:crypto');
12
+ var tweetnacl = require('tweetnacl');
13
+ var node_events = require('node:events');
14
+ var socket_ioClient = require('socket.io-client');
15
+ var child_process = require('child_process');
16
+ var util = require('util');
17
+ var fs$2 = require('fs/promises');
18
+ var crypto = require('crypto');
19
+ var path = require('path');
20
+ var url = require('url');
21
+ var os$1 = require('os');
22
+ var expoServerSdk = require('expo-server-sdk');
23
+
24
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
25
+ function _interopNamespaceDefault(e) {
26
+ var n = Object.create(null);
27
+ if (e) {
28
+ Object.keys(e).forEach(function (k) {
29
+ if (k !== 'default') {
30
+ var d = Object.getOwnPropertyDescriptor(e, k);
31
+ Object.defineProperty(n, k, d.get ? d : {
32
+ enumerable: true,
33
+ get: function () { return e[k]; }
34
+ });
35
+ }
36
+ });
37
+ }
38
+ n.default = e;
39
+ return Object.freeze(n);
40
+ }
41
+
42
+ var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
43
+
44
+ var name = "@zhigang1992/happy-cli";
45
+ var version = "0.12.1";
46
+ var description = "Mobile and Web client for Claude Code and Codex";
47
+ var author = "Kirill Dubovitskiy";
48
+ var license = "MIT";
49
+ var type = "module";
50
+ var homepage = "https://github.com/slopus/happy-cli";
51
+ var bugs = "https://github.com/slopus/happy-cli/issues";
52
+ var repository = "slopus/happy-cli";
53
+ var bin = {
54
+ happy: "./bin/happy.mjs",
55
+ "happy-mcp": "./bin/happy-mcp.mjs"
56
+ };
57
+ var main = "./dist/index.cjs";
58
+ var module$1 = "./dist/index.mjs";
59
+ var types = "./dist/index.d.cts";
60
+ var exports$1 = {
61
+ ".": {
62
+ require: {
63
+ types: "./dist/index.d.cts",
64
+ "default": "./dist/index.cjs"
65
+ },
66
+ "import": {
67
+ types: "./dist/index.d.mts",
68
+ "default": "./dist/index.mjs"
69
+ }
70
+ },
71
+ "./lib": {
72
+ require: {
73
+ types: "./dist/lib.d.cts",
74
+ "default": "./dist/lib.cjs"
75
+ },
76
+ "import": {
77
+ types: "./dist/lib.d.mts",
78
+ "default": "./dist/lib.mjs"
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
+ }
90
+ }
91
+ };
92
+ var files = [
93
+ "dist",
94
+ "bin",
95
+ "scripts",
96
+ "tools",
97
+ "package.json"
98
+ ];
99
+ var scripts = {
100
+ "why do we need to build before running tests / dev?": "We need the binary to be built so we run daemon commands which directly run the binary - we don't want them to go out of sync or have custom spawn logic depending how we started happy",
101
+ typecheck: "tsc --noEmit",
102
+ build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
103
+ test: "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
104
+ start: "yarn build && ./bin/happy.mjs",
105
+ dev: "tsx src/index.ts",
106
+ "dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
107
+ "dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
108
+ prepublishOnly: "yarn build && yarn test",
109
+ release: "release-it",
110
+ postinstall: "node scripts/unpack-tools.cjs"
111
+ };
112
+ var dependencies = {
113
+ "@anthropic-ai/claude-code": "2.0.55",
114
+ "@anthropic-ai/sdk": "0.65.0",
115
+ "@elevenlabs/elevenlabs-js": "^2.25.1",
116
+ "@modelcontextprotocol/sdk": "^1.15.1",
117
+ "@stablelib/base64": "^2.0.1",
118
+ "@stablelib/hex": "^2.0.1",
119
+ "@types/cross-spawn": "^6.0.6",
120
+ "@types/http-proxy": "^1.17.16",
121
+ "@types/ps-list": "^6.2.1",
122
+ "@types/qrcode-terminal": "^0.12.2",
123
+ "@types/react": "^19.1.9",
124
+ "@types/tmp": "^0.2.6",
125
+ axios: "^1.10.0",
126
+ chalk: "^5.4.1",
127
+ "cross-spawn": "^7.0.6",
128
+ "expo-server-sdk": "^3.15.0",
129
+ fastify: "^5.5.0",
130
+ "fastify-type-provider-zod": "4.0.2",
131
+ "http-proxy": "^1.18.1",
132
+ "http-proxy-middleware": "^3.0.5",
133
+ ink: "^6.1.0",
134
+ open: "^10.2.0",
135
+ "ps-list": "^8.1.1",
136
+ "qrcode-terminal": "^0.12.0",
137
+ react: "^19.1.1",
138
+ "socket.io-client": "^4.8.1",
139
+ tar: "^7.4.3",
140
+ tmp: "^0.2.5",
141
+ tweetnacl: "^1.0.3",
142
+ zod: "^3.23.8"
143
+ };
144
+ var devDependencies = {
145
+ "@eslint/compat": "^1",
146
+ "@types/node": ">=20",
147
+ "cross-env": "^10.0.0",
148
+ dotenv: "^16.6.1",
149
+ eslint: "^9",
150
+ "eslint-config-prettier": "^10",
151
+ pkgroll: "^2.14.2",
152
+ "release-it": "^19.0.4",
153
+ shx: "^0.3.3",
154
+ "ts-node": "^10",
155
+ tsx: "^4.20.3",
156
+ typescript: "^5",
157
+ vitest: "^3.2.4"
158
+ };
159
+ var resolutions = {
160
+ "whatwg-url": "14.2.0",
161
+ "parse-path": "7.0.3",
162
+ "@types/parse-path": "7.0.3"
163
+ };
164
+ var publishConfig = {
165
+ registry: "https://registry.npmjs.org"
166
+ };
167
+ var packageManager = "yarn@1.22.22";
168
+ var packageJson = {
169
+ name: name,
170
+ version: version,
171
+ description: description,
172
+ author: author,
173
+ license: license,
174
+ type: type,
175
+ homepage: homepage,
176
+ bugs: bugs,
177
+ repository: repository,
178
+ bin: bin,
179
+ main: main,
180
+ module: module$1,
181
+ types: types,
182
+ exports: exports$1,
183
+ files: files,
184
+ scripts: scripts,
185
+ dependencies: dependencies,
186
+ devDependencies: devDependencies,
187
+ resolutions: resolutions,
188
+ publishConfig: publishConfig,
189
+ packageManager: packageManager
190
+ };
191
+
192
+ class Configuration {
193
+ serverUrl;
194
+ webappUrl;
195
+ isDaemonProcess;
196
+ // Directories and paths (from persistence)
197
+ happyHomeDir;
198
+ logsDir;
199
+ settingsFile;
200
+ privateKeyFile;
201
+ daemonStateFile;
202
+ daemonLockFile;
203
+ currentCliVersion;
204
+ isExperimentalEnabled;
205
+ disableCaffeinate;
206
+ constructor() {
207
+ this.serverUrl = process.env.HAPPY_SERVER_URL || "https://api.cluster-fluster.com";
208
+ this.webappUrl = process.env.HAPPY_WEBAPP_URL || "https://app.happy.engineering";
209
+ const args = process.argv.slice(2);
210
+ this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
211
+ if (process.env.HAPPY_HOME_DIR) {
212
+ const expandedPath = process.env.HAPPY_HOME_DIR.replace(/^~/, os.homedir());
213
+ this.happyHomeDir = expandedPath;
214
+ } else {
215
+ this.happyHomeDir = node_path.join(os.homedir(), ".happy");
216
+ }
217
+ this.logsDir = node_path.join(this.happyHomeDir, "logs");
218
+ this.settingsFile = node_path.join(this.happyHomeDir, "settings.json");
219
+ this.privateKeyFile = node_path.join(this.happyHomeDir, "access.key");
220
+ this.daemonStateFile = node_path.join(this.happyHomeDir, "daemon.state.json");
221
+ this.daemonLockFile = node_path.join(this.happyHomeDir, "daemon.state.json.lock");
222
+ this.isExperimentalEnabled = ["true", "1", "yes"].includes(process.env.HAPPY_EXPERIMENTAL?.toLowerCase() || "");
223
+ this.disableCaffeinate = ["true", "1", "yes"].includes(process.env.HAPPY_DISABLE_CAFFEINATE?.toLowerCase() || "");
224
+ this.currentCliVersion = packageJson.version;
225
+ if (!fs.existsSync(this.happyHomeDir)) {
226
+ fs.mkdirSync(this.happyHomeDir, { recursive: true });
227
+ }
228
+ if (!fs.existsSync(this.logsDir)) {
229
+ fs.mkdirSync(this.logsDir, { recursive: true });
230
+ }
231
+ }
232
+ }
233
+ const configuration = new Configuration();
234
+
235
+ function encodeBase64(buffer, variant = "base64") {
236
+ if (variant === "base64url") {
237
+ return encodeBase64Url(buffer);
238
+ }
239
+ return Buffer.from(buffer).toString("base64");
240
+ }
241
+ function encodeBase64Url(buffer) {
242
+ return Buffer.from(buffer).toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
243
+ }
244
+ function decodeBase64(base64, variant = "base64") {
245
+ if (variant === "base64url") {
246
+ const base64Standard = base64.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - base64.length % 4) % 4);
247
+ return new Uint8Array(Buffer.from(base64Standard, "base64"));
248
+ }
249
+ return new Uint8Array(Buffer.from(base64, "base64"));
250
+ }
251
+ function getRandomBytes(size) {
252
+ return new Uint8Array(node_crypto.randomBytes(size));
253
+ }
254
+ function derivePublicKeyFromSeed(seed) {
255
+ const hash = node_crypto.createHash("sha512").update(seed).digest();
256
+ const secretKey = new Uint8Array(hash.slice(0, 32));
257
+ const keypair = tweetnacl.box.keyPair.fromSecretKey(secretKey);
258
+ return keypair.publicKey;
259
+ }
260
+ function libsodiumEncryptForPublicKey(data, recipientPublicKey) {
261
+ const ephemeralKeyPair = tweetnacl.box.keyPair();
262
+ const nonce = getRandomBytes(tweetnacl.box.nonceLength);
263
+ const encrypted = tweetnacl.box(data, nonce, recipientPublicKey, ephemeralKeyPair.secretKey);
264
+ const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
265
+ result.set(ephemeralKeyPair.publicKey, 0);
266
+ result.set(nonce, ephemeralKeyPair.publicKey.length);
267
+ result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
268
+ return result;
269
+ }
270
+ function libsodiumDecryptFromPublicKey(encryptedBundle, recipientSecretKey) {
271
+ if (encryptedBundle.length < 32 + 24) {
272
+ return null;
273
+ }
274
+ const ephemeralPublicKey = encryptedBundle.slice(0, 32);
275
+ const nonce = encryptedBundle.slice(32, 56);
276
+ const encrypted = encryptedBundle.slice(56);
277
+ const decrypted = tweetnacl.box.open(encrypted, nonce, ephemeralPublicKey, recipientSecretKey);
278
+ return decrypted || null;
279
+ }
280
+ function encryptLegacy(data, secret) {
281
+ const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
282
+ const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
283
+ const result = new Uint8Array(nonce.length + encrypted.length);
284
+ result.set(nonce);
285
+ result.set(encrypted, nonce.length);
286
+ return result;
287
+ }
288
+ function decryptLegacy(data, secret) {
289
+ const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
290
+ const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
291
+ const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
292
+ if (!decrypted) {
293
+ return null;
294
+ }
295
+ return JSON.parse(new TextDecoder().decode(decrypted));
296
+ }
297
+ function encryptWithDataKey(data, dataKey) {
298
+ const nonce = getRandomBytes(12);
299
+ const cipher = node_crypto.createCipheriv("aes-256-gcm", dataKey, nonce);
300
+ const plaintext = new TextEncoder().encode(JSON.stringify(data));
301
+ const encrypted = Buffer.concat([
302
+ cipher.update(plaintext),
303
+ cipher.final()
304
+ ]);
305
+ const authTag = cipher.getAuthTag();
306
+ const bundle = new Uint8Array(12 + encrypted.length + 16 + 1);
307
+ bundle.set([0], 0);
308
+ bundle.set(nonce, 1);
309
+ bundle.set(new Uint8Array(encrypted), 13);
310
+ bundle.set(new Uint8Array(authTag), 13 + encrypted.length);
311
+ return bundle;
312
+ }
313
+ function decryptWithDataKey(bundle, dataKey) {
314
+ if (bundle.length < 1) {
315
+ return null;
316
+ }
317
+ if (bundle[0] !== 0) {
318
+ return null;
319
+ }
320
+ if (bundle.length < 12 + 16 + 1) {
321
+ return null;
322
+ }
323
+ const nonce = bundle.slice(1, 13);
324
+ const authTag = bundle.slice(bundle.length - 16);
325
+ const ciphertext = bundle.slice(13, bundle.length - 16);
326
+ try {
327
+ const decipher = node_crypto.createDecipheriv("aes-256-gcm", dataKey, nonce);
328
+ decipher.setAuthTag(authTag);
329
+ const decrypted = Buffer.concat([
330
+ decipher.update(ciphertext),
331
+ decipher.final()
332
+ ]);
333
+ return JSON.parse(new TextDecoder().decode(decrypted));
334
+ } catch (error) {
335
+ return null;
336
+ }
337
+ }
338
+ function encrypt(key, variant, data) {
339
+ if (variant === "legacy") {
340
+ return encryptLegacy(data, key);
341
+ } else {
342
+ return encryptWithDataKey(data, key);
343
+ }
344
+ }
345
+ function decrypt(key, variant, data) {
346
+ if (variant === "legacy") {
347
+ return decryptLegacy(data, key);
348
+ } else {
349
+ return decryptWithDataKey(data, key);
350
+ }
351
+ }
352
+ function authChallenge(secret) {
353
+ const keypair = tweetnacl.sign.keyPair.fromSeed(secret);
354
+ const challenge = getRandomBytes(32);
355
+ const signature = tweetnacl.sign.detached(challenge, keypair.secretKey);
356
+ return {
357
+ challenge,
358
+ publicKey: keypair.publicKey,
359
+ signature
360
+ };
361
+ }
362
+
363
+ const defaultSettings = {
364
+ onboardingCompleted: false
365
+ };
366
+ async function readSettings() {
367
+ if (!fs.existsSync(configuration.settingsFile)) {
368
+ return { ...defaultSettings };
369
+ }
370
+ try {
371
+ const content = await promises.readFile(configuration.settingsFile, "utf8");
372
+ return JSON.parse(content);
373
+ } catch {
374
+ return { ...defaultSettings };
375
+ }
376
+ }
377
+ async function updateSettings(updater) {
378
+ const LOCK_RETRY_INTERVAL_MS = 100;
379
+ const MAX_LOCK_ATTEMPTS = 50;
380
+ const STALE_LOCK_TIMEOUT_MS = 1e4;
381
+ const lockFile = configuration.settingsFile + ".lock";
382
+ const tmpFile = configuration.settingsFile + ".tmp";
383
+ let fileHandle;
384
+ let attempts = 0;
385
+ while (attempts < MAX_LOCK_ATTEMPTS) {
386
+ try {
387
+ fileHandle = await promises.open(lockFile, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
388
+ break;
389
+ } catch (err) {
390
+ if (err.code === "EEXIST") {
391
+ attempts++;
392
+ await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
393
+ try {
394
+ const stats = await promises.stat(lockFile);
395
+ if (Date.now() - stats.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
396
+ await promises.unlink(lockFile).catch(() => {
397
+ });
398
+ }
399
+ } catch {
400
+ }
401
+ } else {
402
+ throw err;
403
+ }
404
+ }
405
+ }
406
+ if (!fileHandle) {
407
+ throw new Error(`Failed to acquire settings lock after ${MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS / 1e3} seconds`);
408
+ }
409
+ try {
410
+ const current = await readSettings() || { ...defaultSettings };
411
+ const updated = await updater(current);
412
+ if (!fs.existsSync(configuration.happyHomeDir)) {
413
+ await promises.mkdir(configuration.happyHomeDir, { recursive: true });
414
+ }
415
+ await promises.writeFile(tmpFile, JSON.stringify(updated, null, 2));
416
+ await promises.rename(tmpFile, configuration.settingsFile);
417
+ return updated;
418
+ } finally {
419
+ await fileHandle.close();
420
+ await promises.unlink(lockFile).catch(() => {
421
+ });
422
+ }
423
+ }
424
+ const credentialsSchema = z__namespace.object({
425
+ token: z__namespace.string(),
426
+ secret: z__namespace.string().base64().nullish(),
427
+ // Legacy
428
+ encryption: z__namespace.object({
429
+ publicKey: z__namespace.string().base64(),
430
+ // Actually a seed, not a public key (legacy naming)
431
+ machineKey: z__namespace.string().base64()
432
+ }).nullish()
433
+ });
434
+ async function readCredentials() {
435
+ if (!fs.existsSync(configuration.privateKeyFile)) {
436
+ return null;
437
+ }
438
+ try {
439
+ const keyBase64 = await promises.readFile(configuration.privateKeyFile, "utf8");
440
+ const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
441
+ if (credentials.secret) {
442
+ return {
443
+ token: credentials.token,
444
+ encryption: {
445
+ type: "legacy",
446
+ secret: new Uint8Array(Buffer.from(credentials.secret, "base64"))
447
+ }
448
+ };
449
+ } else if (credentials.encryption) {
450
+ return {
451
+ token: credentials.token,
452
+ encryption: {
453
+ type: "dataKey",
454
+ // Map file's "publicKey" field to properly named "dataKeySeed" in memory
455
+ dataKeySeed: new Uint8Array(Buffer.from(credentials.encryption.publicKey, "base64")),
456
+ machineKey: new Uint8Array(Buffer.from(credentials.encryption.machineKey, "base64"))
457
+ }
458
+ };
459
+ }
460
+ } catch {
461
+ return null;
462
+ }
463
+ return null;
464
+ }
465
+ async function writeCredentialsLegacy(credentials) {
466
+ if (!fs.existsSync(configuration.happyHomeDir)) {
467
+ await promises.mkdir(configuration.happyHomeDir, { recursive: true });
468
+ }
469
+ await promises.writeFile(configuration.privateKeyFile, JSON.stringify({
470
+ secret: encodeBase64(credentials.secret),
471
+ token: credentials.token
472
+ }, null, 2));
473
+ }
474
+ async function writeCredentialsDataKey(credentials) {
475
+ if (!fs.existsSync(configuration.happyHomeDir)) {
476
+ await promises.mkdir(configuration.happyHomeDir, { recursive: true });
477
+ }
478
+ await promises.writeFile(configuration.privateKeyFile, JSON.stringify({
479
+ encryption: { publicKey: encodeBase64(credentials.dataKeySeed), machineKey: encodeBase64(credentials.machineKey) },
480
+ token: credentials.token
481
+ }, null, 2));
482
+ }
483
+ async function clearCredentials() {
484
+ if (fs.existsSync(configuration.privateKeyFile)) {
485
+ await promises.unlink(configuration.privateKeyFile);
486
+ }
487
+ }
488
+ async function clearMachineId() {
489
+ await updateSettings((settings) => ({
490
+ ...settings,
491
+ machineId: void 0
492
+ }));
493
+ }
494
+ async function readDaemonState() {
495
+ try {
496
+ if (!fs.existsSync(configuration.daemonStateFile)) {
497
+ return null;
498
+ }
499
+ const content = await promises.readFile(configuration.daemonStateFile, "utf-8");
500
+ return JSON.parse(content);
501
+ } catch (error) {
502
+ console.error(`[PERSISTENCE] Daemon state file corrupted: ${configuration.daemonStateFile}`, error);
503
+ return null;
504
+ }
505
+ }
506
+ function writeDaemonState(state) {
507
+ fs.writeFileSync(configuration.daemonStateFile, JSON.stringify(state, null, 2), "utf-8");
508
+ }
509
+ async function clearDaemonState() {
510
+ if (fs.existsSync(configuration.daemonStateFile)) {
511
+ await promises.unlink(configuration.daemonStateFile);
512
+ }
513
+ if (fs.existsSync(configuration.daemonLockFile)) {
514
+ try {
515
+ await promises.unlink(configuration.daemonLockFile);
516
+ } catch {
517
+ }
518
+ }
519
+ }
520
+ async function acquireDaemonLock(maxAttempts = 5, delayIncrementMs = 200) {
521
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
522
+ try {
523
+ const fileHandle = await promises.open(
524
+ configuration.daemonLockFile,
525
+ fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY
526
+ );
527
+ await fileHandle.writeFile(String(process.pid));
528
+ return fileHandle;
529
+ } catch (error) {
530
+ if (error.code === "EEXIST") {
531
+ try {
532
+ const lockPid = fs.readFileSync(configuration.daemonLockFile, "utf-8").trim();
533
+ if (lockPid && !isNaN(Number(lockPid))) {
534
+ try {
535
+ process.kill(Number(lockPid), 0);
536
+ } catch {
537
+ fs.unlinkSync(configuration.daemonLockFile);
538
+ continue;
539
+ }
540
+ }
541
+ } catch {
542
+ }
543
+ }
544
+ if (attempt === maxAttempts) {
545
+ return null;
546
+ }
547
+ const delayMs = attempt * delayIncrementMs;
548
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
549
+ }
550
+ }
551
+ return null;
552
+ }
553
+ async function releaseDaemonLock(lockHandle) {
554
+ try {
555
+ await lockHandle.close();
556
+ } catch {
557
+ }
558
+ try {
559
+ if (fs.existsSync(configuration.daemonLockFile)) {
560
+ fs.unlinkSync(configuration.daemonLockFile);
561
+ }
562
+ } catch {
563
+ }
564
+ }
565
+
566
+ function createTimestampForFilename(date = /* @__PURE__ */ new Date()) {
567
+ return date.toLocaleString("sv-SE", {
568
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
569
+ year: "numeric",
570
+ month: "2-digit",
571
+ day: "2-digit",
572
+ hour: "2-digit",
573
+ minute: "2-digit",
574
+ second: "2-digit"
575
+ }).replace(/[: ]/g, "-").replace(/,/g, "") + "-pid-" + process.pid;
576
+ }
577
+ function createTimestampForLogEntry(date = /* @__PURE__ */ new Date()) {
578
+ return date.toLocaleTimeString("en-US", {
579
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
580
+ hour12: false,
581
+ hour: "2-digit",
582
+ minute: "2-digit",
583
+ second: "2-digit",
584
+ fractionalSecondDigits: 3
585
+ });
586
+ }
587
+ function getSessionLogPath() {
588
+ const timestamp = createTimestampForFilename();
589
+ const filename = configuration.isDaemonProcess ? `${timestamp}-daemon.log` : `${timestamp}.log`;
590
+ return node_path.join(configuration.logsDir, filename);
591
+ }
592
+ class Logger {
593
+ constructor(logFilePath = getSessionLogPath()) {
594
+ this.logFilePath = logFilePath;
595
+ if (process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING && process.env.HAPPY_SERVER_URL) {
596
+ this.dangerouslyUnencryptedServerLoggingUrl = process.env.HAPPY_SERVER_URL;
597
+ console.log(chalk.yellow("[REMOTE LOGGING] Sending logs to server for AI debugging"));
598
+ }
599
+ }
600
+ dangerouslyUnencryptedServerLoggingUrl;
601
+ // Use local timezone for simplicity of locating the logs,
602
+ // in practice you will not need absolute timestamps
603
+ localTimezoneTimestamp() {
604
+ return createTimestampForLogEntry();
605
+ }
606
+ debug(message, ...args) {
607
+ this.logToFile(`[${this.localTimezoneTimestamp()}]`, message, ...args);
608
+ }
609
+ debugLargeJson(message, object, maxStringLength = 100, maxArrayLength = 10) {
610
+ if (!process.env.DEBUG) {
611
+ this.debug(`In production, skipping message inspection`);
612
+ }
613
+ const truncateStrings = (obj) => {
614
+ if (typeof obj === "string") {
615
+ return obj.length > maxStringLength ? obj.substring(0, maxStringLength) + "... [truncated for logs]" : obj;
616
+ }
617
+ if (Array.isArray(obj)) {
618
+ const truncatedArray = obj.map((item) => truncateStrings(item)).slice(0, maxArrayLength);
619
+ if (obj.length > maxArrayLength) {
620
+ truncatedArray.push(`... [truncated array for logs up to ${maxArrayLength} items]`);
621
+ }
622
+ return truncatedArray;
623
+ }
624
+ if (obj && typeof obj === "object") {
625
+ const result = {};
626
+ for (const [key, value] of Object.entries(obj)) {
627
+ if (key === "usage") {
628
+ continue;
629
+ }
630
+ result[key] = truncateStrings(value);
631
+ }
632
+ return result;
633
+ }
634
+ return obj;
635
+ };
636
+ const truncatedObject = truncateStrings(object);
637
+ const json = JSON.stringify(truncatedObject, null, 2);
638
+ this.logToFile(`[${this.localTimezoneTimestamp()}]`, message, "\n", json);
639
+ }
640
+ info(message, ...args) {
641
+ this.logToConsole("info", "", message, ...args);
642
+ this.debug(message, args);
643
+ }
644
+ infoDeveloper(message, ...args) {
645
+ this.debug(message, ...args);
646
+ if (process.env.DEBUG) {
647
+ this.logToConsole("info", "[DEV]", message, ...args);
648
+ }
649
+ }
650
+ warn(message, ...args) {
651
+ this.logToConsole("warn", "", message, ...args);
652
+ this.debug(`[WARN] ${message}`, ...args);
653
+ }
654
+ getLogPath() {
655
+ return this.logFilePath;
656
+ }
657
+ logToConsole(level, prefix, message, ...args) {
658
+ switch (level) {
659
+ case "debug": {
660
+ console.log(chalk.gray(prefix), message, ...args);
661
+ break;
662
+ }
663
+ case "error": {
664
+ console.error(chalk.red(prefix), message, ...args);
665
+ break;
666
+ }
667
+ case "info": {
668
+ console.log(chalk.blue(prefix), message, ...args);
669
+ break;
670
+ }
671
+ case "warn": {
672
+ console.log(chalk.yellow(prefix), message, ...args);
673
+ break;
674
+ }
675
+ default: {
676
+ this.debug("Unknown log level:", level);
677
+ console.log(chalk.blue(prefix), message, ...args);
678
+ break;
679
+ }
680
+ }
681
+ }
682
+ async sendToRemoteServer(level, message, ...args) {
683
+ if (!this.dangerouslyUnencryptedServerLoggingUrl) return;
684
+ try {
685
+ await fetch(this.dangerouslyUnencryptedServerLoggingUrl + "/logs-combined-from-cli-and-mobile-for-simple-ai-debugging", {
686
+ method: "POST",
687
+ headers: { "Content-Type": "application/json" },
688
+ body: JSON.stringify({
689
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
690
+ level,
691
+ message: `${message} ${args.map(
692
+ (a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)
693
+ ).join(" ")}`,
694
+ source: "cli",
695
+ platform: process.platform
696
+ })
697
+ });
698
+ } catch (error) {
699
+ }
700
+ }
701
+ logToFile(prefix, message, ...args) {
702
+ const logLine = `${prefix} ${message} ${args.map(
703
+ (arg) => typeof arg === "string" ? arg : JSON.stringify(arg)
704
+ ).join(" ")}
705
+ `;
706
+ if (this.dangerouslyUnencryptedServerLoggingUrl) {
707
+ let level = "info";
708
+ if (prefix.includes(this.localTimezoneTimestamp())) {
709
+ level = "debug";
710
+ }
711
+ this.sendToRemoteServer(level, message, ...args).catch(() => {
712
+ });
713
+ }
714
+ try {
715
+ fs$1.appendFileSync(this.logFilePath, logLine);
716
+ } catch (appendError) {
717
+ if (process.env.DEBUG) {
718
+ console.error("[DEV MODE ONLY THROWING] Failed to append to log file:", appendError);
719
+ throw appendError;
720
+ }
721
+ }
722
+ }
723
+ }
724
+ let logger = new Logger();
725
+ async function listDaemonLogFiles(limit = 50) {
726
+ try {
727
+ const logsDir = configuration.logsDir;
728
+ if (!fs.existsSync(logsDir)) {
729
+ return [];
730
+ }
731
+ const logs = fs.readdirSync(logsDir).filter((file) => file.endsWith("-daemon.log")).map((file) => {
732
+ const fullPath = node_path.join(logsDir, file);
733
+ const stats = fs.statSync(fullPath);
734
+ return { file, path: fullPath, modified: stats.mtime };
735
+ }).sort((a, b) => b.modified.getTime() - a.modified.getTime());
736
+ try {
737
+ const state = await readDaemonState();
738
+ if (!state) {
739
+ return logs;
740
+ }
741
+ if (state.daemonLogPath && fs.existsSync(state.daemonLogPath)) {
742
+ const stats = fs.statSync(state.daemonLogPath);
743
+ const persisted = {
744
+ file: node_path.basename(state.daemonLogPath),
745
+ path: state.daemonLogPath,
746
+ modified: stats.mtime
747
+ };
748
+ const idx = logs.findIndex((l) => l.path === persisted.path);
749
+ if (idx >= 0) {
750
+ const [found] = logs.splice(idx, 1);
751
+ logs.unshift(found);
752
+ } else {
753
+ logs.unshift(persisted);
754
+ }
755
+ }
756
+ } catch {
757
+ }
758
+ return logs.slice(0, Math.max(0, limit));
759
+ } catch {
760
+ return [];
761
+ }
762
+ }
763
+ async function getLatestDaemonLog() {
764
+ const [latest] = await listDaemonLogFiles(1);
765
+ return latest || null;
766
+ }
767
+
768
+ const SessionMessageContentSchema = z.z.object({
769
+ c: z.z.string(),
770
+ // Base64 encoded encrypted content
771
+ t: z.z.literal("encrypted")
772
+ });
773
+ const UpdateBodySchema = z.z.object({
774
+ message: z.z.object({
775
+ id: z.z.string(),
776
+ seq: z.z.number(),
777
+ content: SessionMessageContentSchema
778
+ }),
779
+ sid: z.z.string(),
780
+ // Session ID
781
+ t: z.z.literal("new-message")
782
+ });
783
+ const UpdateSessionBodySchema = z.z.object({
784
+ t: z.z.literal("update-session"),
785
+ sid: z.z.string(),
786
+ metadata: z.z.object({
787
+ version: z.z.number(),
788
+ value: z.z.string()
789
+ }).nullish(),
790
+ agentState: z.z.object({
791
+ version: z.z.number(),
792
+ value: z.z.string()
793
+ }).nullish()
794
+ });
795
+ const UpdateMachineBodySchema = z.z.object({
796
+ t: z.z.literal("update-machine"),
797
+ machineId: z.z.string(),
798
+ metadata: z.z.object({
799
+ version: z.z.number(),
800
+ value: z.z.string()
801
+ }).nullish(),
802
+ daemonState: z.z.object({
803
+ version: z.z.number(),
804
+ value: z.z.string()
805
+ }).nullish()
806
+ });
807
+ z.z.object({
808
+ id: z.z.string(),
809
+ seq: z.z.number(),
810
+ body: z.z.union([
811
+ UpdateBodySchema,
812
+ UpdateSessionBodySchema,
813
+ UpdateMachineBodySchema
814
+ ]),
815
+ createdAt: z.z.number()
816
+ });
817
+ z.z.object({
818
+ host: z.z.string(),
819
+ platform: z.z.string(),
820
+ happyCliVersion: z.z.string(),
821
+ homeDir: z.z.string(),
822
+ happyHomeDir: z.z.string(),
823
+ happyLibDir: z.z.string()
824
+ });
825
+ z.z.object({
826
+ status: z.z.union([
827
+ z.z.enum(["running", "shutting-down"]),
828
+ z.z.string()
829
+ // Forward compatibility
830
+ ]),
831
+ pid: z.z.number().optional(),
832
+ httpPort: z.z.number().optional(),
833
+ startedAt: z.z.number().optional(),
834
+ shutdownRequestedAt: z.z.number().optional(),
835
+ shutdownSource: z.z.union([
836
+ z.z.enum(["mobile-app", "cli", "os-signal", "unknown"]),
837
+ z.z.string()
838
+ // Forward compatibility
839
+ ]).optional()
840
+ });
841
+ z.z.object({
842
+ content: SessionMessageContentSchema,
843
+ createdAt: z.z.number(),
844
+ id: z.z.string(),
845
+ seq: z.z.number(),
846
+ updatedAt: z.z.number()
847
+ });
848
+ const MessageMetaSchema = z.z.object({
849
+ sentFrom: z.z.string().optional(),
850
+ // Source identifier
851
+ permissionMode: z.z.string().optional(),
852
+ // Permission mode for this message
853
+ model: z.z.string().nullable().optional(),
854
+ // Model name for this message (null = reset)
855
+ fallbackModel: z.z.string().nullable().optional(),
856
+ // Fallback model for this message (null = reset)
857
+ customSystemPrompt: z.z.string().nullable().optional(),
858
+ // Custom system prompt for this message (null = reset)
859
+ appendSystemPrompt: z.z.string().nullable().optional(),
860
+ // Append to system prompt for this message (null = reset)
861
+ allowedTools: z.z.array(z.z.string()).nullable().optional(),
862
+ // Allowed tools for this message (null = reset)
863
+ disallowedTools: z.z.array(z.z.string()).nullable().optional()
864
+ // Disallowed tools for this message (null = reset)
865
+ });
866
+ z.z.object({
867
+ session: z.z.object({
868
+ id: z.z.string(),
869
+ tag: z.z.string(),
870
+ seq: z.z.number(),
871
+ createdAt: z.z.number(),
872
+ updatedAt: z.z.number(),
873
+ metadata: z.z.string(),
874
+ metadataVersion: z.z.number(),
875
+ agentState: z.z.string().nullable(),
876
+ agentStateVersion: z.z.number()
877
+ })
878
+ });
879
+ const UserMessageSchema = z.z.object({
880
+ role: z.z.literal("user"),
881
+ content: z.z.object({
882
+ type: z.z.literal("text"),
883
+ text: z.z.string()
884
+ }),
885
+ localKey: z.z.string().optional(),
886
+ // Mobile messages include this
887
+ meta: MessageMetaSchema.optional()
888
+ });
889
+ const AgentMessageSchema = z.z.object({
890
+ role: z.z.literal("agent"),
891
+ content: z.z.object({
892
+ type: z.z.literal("output"),
893
+ data: z.z.any()
894
+ }),
895
+ meta: MessageMetaSchema.optional()
896
+ });
897
+ z.z.union([UserMessageSchema, AgentMessageSchema]);
898
+
899
+ async function delay(ms) {
900
+ return new Promise((resolve) => setTimeout(resolve, ms));
901
+ }
902
+ function exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount) {
903
+ let maxDelayRet = minDelay + (maxDelay - minDelay) / maxFailureCount * Math.min(currentFailureCount, maxFailureCount);
904
+ return Math.round(Math.random() * maxDelayRet);
905
+ }
906
+ function createBackoff(opts) {
907
+ return async (callback) => {
908
+ let currentFailureCount = 0;
909
+ const minDelay = 250;
910
+ const maxDelay = 1e3;
911
+ const maxFailureCount = 50;
912
+ while (true) {
913
+ try {
914
+ return await callback();
915
+ } catch (e) {
916
+ if (currentFailureCount < maxFailureCount) {
917
+ currentFailureCount++;
918
+ }
919
+ let waitForRequest = exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount);
920
+ await delay(waitForRequest);
921
+ }
922
+ }
923
+ };
924
+ }
925
+ let backoff = createBackoff();
926
+ function formatTimeAgo(timestamp) {
927
+ const now = Date.now();
928
+ const time = timestamp instanceof Date ? timestamp.getTime() : timestamp;
929
+ const diffMs = now - time;
930
+ if (diffMs < 0) {
931
+ return "in the future";
932
+ }
933
+ const seconds = Math.floor(diffMs / 1e3);
934
+ const minutes = Math.floor(seconds / 60);
935
+ const hours = Math.floor(minutes / 60);
936
+ const days = Math.floor(hours / 24);
937
+ if (seconds < 60) {
938
+ return `${seconds} ${seconds === 1 ? "second" : "seconds"} ago`;
939
+ }
940
+ const parts = [];
941
+ if (days > 0) {
942
+ parts.push(`${days} ${days === 1 ? "day" : "days"}`);
943
+ }
944
+ const remainingHours = hours % 24;
945
+ if (remainingHours > 0) {
946
+ parts.push(`${remainingHours} ${remainingHours === 1 ? "hour" : "hours"}`);
947
+ }
948
+ const remainingMinutes = minutes % 60;
949
+ if (remainingMinutes > 0 && days === 0) {
950
+ parts.push(`${remainingMinutes} ${remainingMinutes === 1 ? "minute" : "minutes"}`);
951
+ }
952
+ return parts.slice(0, 2).join(", ") + " ago";
953
+ }
954
+
955
+ class AsyncLock {
956
+ permits = 1;
957
+ promiseResolverQueue = [];
958
+ async inLock(func) {
959
+ try {
960
+ await this.lock();
961
+ return await func();
962
+ } finally {
963
+ this.unlock();
964
+ }
965
+ }
966
+ async lock() {
967
+ if (this.permits > 0) {
968
+ this.permits = this.permits - 1;
969
+ return;
970
+ }
971
+ await new Promise((resolve) => this.promiseResolverQueue.push(resolve));
972
+ }
973
+ unlock() {
974
+ this.permits += 1;
975
+ if (this.permits > 1 && this.promiseResolverQueue.length > 0) {
976
+ throw new Error("this.permits should never be > 0 when there is someone waiting.");
977
+ } else if (this.permits === 1 && this.promiseResolverQueue.length > 0) {
978
+ this.permits -= 1;
979
+ const nextResolver = this.promiseResolverQueue.shift();
980
+ if (nextResolver) {
981
+ setTimeout(() => {
982
+ nextResolver(true);
983
+ }, 0);
984
+ }
985
+ }
986
+ }
987
+ }
988
+
989
+ class RpcHandlerManager {
990
+ handlers = /* @__PURE__ */ new Map();
991
+ scopePrefix;
992
+ encryptionKey;
993
+ encryptionVariant;
994
+ logger;
995
+ socket = null;
996
+ constructor(config) {
997
+ this.scopePrefix = config.scopePrefix;
998
+ this.encryptionKey = config.encryptionKey;
999
+ this.encryptionVariant = config.encryptionVariant;
1000
+ this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
1001
+ }
1002
+ /**
1003
+ * Register an RPC handler for a specific method
1004
+ * @param method - The method name (without prefix)
1005
+ * @param handler - The handler function
1006
+ */
1007
+ registerHandler(method, handler) {
1008
+ const prefixedMethod = this.getPrefixedMethod(method);
1009
+ this.handlers.set(prefixedMethod, handler);
1010
+ if (this.socket) {
1011
+ this.socket.emit("rpc-register", { method: prefixedMethod });
1012
+ }
1013
+ }
1014
+ /**
1015
+ * Handle an incoming RPC request
1016
+ * @param request - The RPC request data
1017
+ * @param callback - The response callback
1018
+ */
1019
+ async handleRequest(request) {
1020
+ try {
1021
+ const handler = this.handlers.get(request.method);
1022
+ if (!handler) {
1023
+ this.logger("[RPC] [ERROR] Method not found", { method: request.method });
1024
+ const errorResponse = { error: "Method not found" };
1025
+ const encryptedError = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
1026
+ return encryptedError;
1027
+ }
1028
+ const decryptedParams = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(request.params));
1029
+ const result = await handler(decryptedParams);
1030
+ const encryptedResponse = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, result));
1031
+ return encryptedResponse;
1032
+ } catch (error) {
1033
+ this.logger("[RPC] [ERROR] Error handling request", { error });
1034
+ const errorResponse = {
1035
+ error: error instanceof Error ? error.message : "Unknown error"
1036
+ };
1037
+ return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
1038
+ }
1039
+ }
1040
+ onSocketConnect(socket) {
1041
+ this.socket = socket;
1042
+ for (const [prefixedMethod] of this.handlers) {
1043
+ socket.emit("rpc-register", { method: prefixedMethod });
1044
+ }
1045
+ }
1046
+ onSocketDisconnect() {
1047
+ this.socket = null;
1048
+ }
1049
+ /**
1050
+ * Get the number of registered handlers
1051
+ */
1052
+ getHandlerCount() {
1053
+ return this.handlers.size;
1054
+ }
1055
+ /**
1056
+ * Check if a handler is registered
1057
+ * @param method - The method name (without prefix)
1058
+ */
1059
+ hasHandler(method) {
1060
+ const prefixedMethod = this.getPrefixedMethod(method);
1061
+ return this.handlers.has(prefixedMethod);
1062
+ }
1063
+ /**
1064
+ * Clear all handlers
1065
+ */
1066
+ clearHandlers() {
1067
+ this.handlers.clear();
1068
+ this.logger("Cleared all RPC handlers");
1069
+ }
1070
+ /**
1071
+ * Get the prefixed method name
1072
+ * @param method - The method name
1073
+ */
1074
+ getPrefixedMethod(method) {
1075
+ return `${this.scopePrefix}:${method}`;
1076
+ }
1077
+ }
1078
+
1079
+ 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-BsjUgWOx.cjs', document.baseURI).href))));
1080
+ function projectPath() {
1081
+ const path$1 = path.resolve(__dirname$1, "..");
1082
+ return path$1;
1083
+ }
1084
+
1085
+ function run$1(args, options) {
1086
+ const RUNNER_PATH = path.resolve(path.join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
1087
+ return new Promise((resolve2, reject) => {
1088
+ const child = child_process.spawn("node", [RUNNER_PATH, JSON.stringify(args)], {
1089
+ stdio: ["pipe", "pipe", "pipe"],
1090
+ cwd: options?.cwd
1091
+ });
1092
+ let stdout = "";
1093
+ let stderr = "";
1094
+ child.stdout.on("data", (data) => {
1095
+ stdout += data.toString();
1096
+ });
1097
+ child.stderr.on("data", (data) => {
1098
+ stderr += data.toString();
1099
+ });
1100
+ child.on("close", (code) => {
1101
+ resolve2({
1102
+ exitCode: code || 0,
1103
+ stdout,
1104
+ stderr
1105
+ });
1106
+ });
1107
+ child.on("error", (err) => {
1108
+ reject(err);
1109
+ });
1110
+ });
1111
+ }
1112
+
1113
+ function getBinaryPath() {
1114
+ const platformName = os$1.platform();
1115
+ const binaryName = platformName === "win32" ? "difft.exe" : "difft";
1116
+ return path.resolve(path.join(projectPath(), "tools", "unpacked", binaryName));
1117
+ }
1118
+ function run(args, options) {
1119
+ const binaryPath = getBinaryPath();
1120
+ return new Promise((resolve2, reject) => {
1121
+ const child = child_process.spawn(binaryPath, args, {
1122
+ stdio: ["pipe", "pipe", "pipe"],
1123
+ cwd: options?.cwd,
1124
+ env: {
1125
+ ...process.env,
1126
+ // Force color output when needed
1127
+ FORCE_COLOR: "1"
1128
+ }
1129
+ });
1130
+ let stdout = "";
1131
+ let stderr = "";
1132
+ child.stdout.on("data", (data) => {
1133
+ stdout += data.toString();
1134
+ });
1135
+ child.stderr.on("data", (data) => {
1136
+ stderr += data.toString();
1137
+ });
1138
+ child.on("close", (code) => {
1139
+ resolve2({
1140
+ exitCode: code || 0,
1141
+ stdout,
1142
+ stderr
1143
+ });
1144
+ });
1145
+ child.on("error", (err) => {
1146
+ reject(err);
1147
+ });
1148
+ });
1149
+ }
1150
+
1151
+ const execAsync = util.promisify(child_process.exec);
1152
+ function registerCommonHandlers(rpcHandlerManager) {
1153
+ rpcHandlerManager.registerHandler("bash", async (data) => {
1154
+ logger.debug("Shell command request:", data.command);
1155
+ try {
1156
+ const options = {
1157
+ cwd: data.cwd,
1158
+ timeout: data.timeout || 3e4
1159
+ // Default 30 seconds timeout
1160
+ };
1161
+ const { stdout, stderr } = await execAsync(data.command, options);
1162
+ return {
1163
+ success: true,
1164
+ stdout: stdout ? stdout.toString() : "",
1165
+ stderr: stderr ? stderr.toString() : "",
1166
+ exitCode: 0
1167
+ };
1168
+ } catch (error) {
1169
+ const execError = error;
1170
+ if (execError.code === "ETIMEDOUT" || execError.killed) {
1171
+ return {
1172
+ success: false,
1173
+ stdout: execError.stdout || "",
1174
+ stderr: execError.stderr || "",
1175
+ exitCode: typeof execError.code === "number" ? execError.code : -1,
1176
+ error: "Command timed out"
1177
+ };
1178
+ }
1179
+ return {
1180
+ success: false,
1181
+ stdout: execError.stdout ? execError.stdout.toString() : "",
1182
+ stderr: execError.stderr ? execError.stderr.toString() : execError.message || "Command failed",
1183
+ exitCode: typeof execError.code === "number" ? execError.code : 1,
1184
+ error: execError.message || "Command failed"
1185
+ };
1186
+ }
1187
+ });
1188
+ rpcHandlerManager.registerHandler("readFile", async (data) => {
1189
+ logger.debug("Read file request:", data.path);
1190
+ try {
1191
+ const buffer = await fs$2.readFile(data.path);
1192
+ const content = buffer.toString("base64");
1193
+ return { success: true, content };
1194
+ } catch (error) {
1195
+ logger.debug("Failed to read file:", error);
1196
+ return { success: false, error: error instanceof Error ? error.message : "Failed to read file" };
1197
+ }
1198
+ });
1199
+ rpcHandlerManager.registerHandler("writeFile", async (data) => {
1200
+ logger.debug("Write file request:", data.path);
1201
+ try {
1202
+ if (data.expectedHash !== null && data.expectedHash !== void 0) {
1203
+ try {
1204
+ const existingBuffer = await fs$2.readFile(data.path);
1205
+ const existingHash = crypto.createHash("sha256").update(existingBuffer).digest("hex");
1206
+ if (existingHash !== data.expectedHash) {
1207
+ return {
1208
+ success: false,
1209
+ error: `File hash mismatch. Expected: ${data.expectedHash}, Actual: ${existingHash}`
1210
+ };
1211
+ }
1212
+ } catch (error) {
1213
+ const nodeError = error;
1214
+ if (nodeError.code !== "ENOENT") {
1215
+ throw error;
1216
+ }
1217
+ return {
1218
+ success: false,
1219
+ error: "File does not exist but hash was provided"
1220
+ };
1221
+ }
1222
+ } else {
1223
+ try {
1224
+ await fs$2.stat(data.path);
1225
+ return {
1226
+ success: false,
1227
+ error: "File already exists but was expected to be new"
1228
+ };
1229
+ } catch (error) {
1230
+ const nodeError = error;
1231
+ if (nodeError.code !== "ENOENT") {
1232
+ throw error;
1233
+ }
1234
+ }
1235
+ }
1236
+ const buffer = Buffer.from(data.content, "base64");
1237
+ await fs$2.writeFile(data.path, buffer);
1238
+ const hash = crypto.createHash("sha256").update(buffer).digest("hex");
1239
+ return { success: true, hash };
1240
+ } catch (error) {
1241
+ logger.debug("Failed to write file:", error);
1242
+ return { success: false, error: error instanceof Error ? error.message : "Failed to write file" };
1243
+ }
1244
+ });
1245
+ rpcHandlerManager.registerHandler("listDirectory", async (data) => {
1246
+ logger.debug("List directory request:", data.path);
1247
+ try {
1248
+ const entries = await fs$2.readdir(data.path, { withFileTypes: true });
1249
+ const directoryEntries = await Promise.all(
1250
+ entries.map(async (entry) => {
1251
+ const fullPath = path.join(data.path, entry.name);
1252
+ let type = "other";
1253
+ let size;
1254
+ let modified;
1255
+ if (entry.isDirectory()) {
1256
+ type = "directory";
1257
+ } else if (entry.isFile()) {
1258
+ type = "file";
1259
+ }
1260
+ try {
1261
+ const stats = await fs$2.stat(fullPath);
1262
+ size = stats.size;
1263
+ modified = stats.mtime.getTime();
1264
+ } catch (error) {
1265
+ logger.debug(`Failed to stat ${fullPath}:`, error);
1266
+ }
1267
+ return {
1268
+ name: entry.name,
1269
+ type,
1270
+ size,
1271
+ modified
1272
+ };
1273
+ })
1274
+ );
1275
+ directoryEntries.sort((a, b) => {
1276
+ if (a.type === "directory" && b.type !== "directory") return -1;
1277
+ if (a.type !== "directory" && b.type === "directory") return 1;
1278
+ return a.name.localeCompare(b.name);
1279
+ });
1280
+ return { success: true, entries: directoryEntries };
1281
+ } catch (error) {
1282
+ logger.debug("Failed to list directory:", error);
1283
+ return { success: false, error: error instanceof Error ? error.message : "Failed to list directory" };
1284
+ }
1285
+ });
1286
+ rpcHandlerManager.registerHandler("getDirectoryTree", async (data) => {
1287
+ logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
1288
+ async function buildTree(path$1, name, currentDepth) {
1289
+ try {
1290
+ const stats = await fs$2.stat(path$1);
1291
+ const node = {
1292
+ name,
1293
+ path: path$1,
1294
+ type: stats.isDirectory() ? "directory" : "file",
1295
+ size: stats.size,
1296
+ modified: stats.mtime.getTime()
1297
+ };
1298
+ if (stats.isDirectory() && currentDepth < data.maxDepth) {
1299
+ const entries = await fs$2.readdir(path$1, { withFileTypes: true });
1300
+ const children = [];
1301
+ await Promise.all(
1302
+ entries.map(async (entry) => {
1303
+ if (entry.isSymbolicLink()) {
1304
+ logger.debug(`Skipping symlink: ${path.join(path$1, entry.name)}`);
1305
+ return;
1306
+ }
1307
+ const childPath = path.join(path$1, entry.name);
1308
+ const childNode = await buildTree(childPath, entry.name, currentDepth + 1);
1309
+ if (childNode) {
1310
+ children.push(childNode);
1311
+ }
1312
+ })
1313
+ );
1314
+ children.sort((a, b) => {
1315
+ if (a.type === "directory" && b.type !== "directory") return -1;
1316
+ if (a.type !== "directory" && b.type === "directory") return 1;
1317
+ return a.name.localeCompare(b.name);
1318
+ });
1319
+ node.children = children;
1320
+ }
1321
+ return node;
1322
+ } catch (error) {
1323
+ logger.debug(`Failed to process ${path$1}:`, error instanceof Error ? error.message : String(error));
1324
+ return null;
1325
+ }
1326
+ }
1327
+ try {
1328
+ if (data.maxDepth < 0) {
1329
+ return { success: false, error: "maxDepth must be non-negative" };
1330
+ }
1331
+ const baseName = data.path === "/" ? "/" : data.path.split("/").pop() || data.path;
1332
+ const tree = await buildTree(data.path, baseName, 0);
1333
+ if (!tree) {
1334
+ return { success: false, error: "Failed to access the specified path" };
1335
+ }
1336
+ return { success: true, tree };
1337
+ } catch (error) {
1338
+ logger.debug("Failed to get directory tree:", error);
1339
+ return { success: false, error: error instanceof Error ? error.message : "Failed to get directory tree" };
1340
+ }
1341
+ });
1342
+ rpcHandlerManager.registerHandler("ripgrep", async (data) => {
1343
+ logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
1344
+ try {
1345
+ const result = await run$1(data.args, { cwd: data.cwd });
1346
+ return {
1347
+ success: true,
1348
+ exitCode: result.exitCode,
1349
+ stdout: result.stdout.toString(),
1350
+ stderr: result.stderr.toString()
1351
+ };
1352
+ } catch (error) {
1353
+ logger.debug("Failed to run ripgrep:", error);
1354
+ return {
1355
+ success: false,
1356
+ error: error instanceof Error ? error.message : "Failed to run ripgrep"
1357
+ };
1358
+ }
1359
+ });
1360
+ rpcHandlerManager.registerHandler("difftastic", async (data) => {
1361
+ logger.debug("Difftastic request with args:", data.args, "cwd:", data.cwd);
1362
+ try {
1363
+ const result = await run(data.args, { cwd: data.cwd });
1364
+ return {
1365
+ success: true,
1366
+ exitCode: result.exitCode,
1367
+ stdout: result.stdout.toString(),
1368
+ stderr: result.stderr.toString()
1369
+ };
1370
+ } catch (error) {
1371
+ logger.debug("Failed to run difftastic:", error);
1372
+ return {
1373
+ success: false,
1374
+ error: error instanceof Error ? error.message : "Failed to run difftastic"
1375
+ };
1376
+ }
1377
+ });
1378
+ }
1379
+
1380
+ class ApiSessionClient extends node_events.EventEmitter {
1381
+ token;
1382
+ sessionId;
1383
+ metadata;
1384
+ metadataVersion;
1385
+ agentState;
1386
+ agentStateVersion;
1387
+ socket;
1388
+ pendingMessages = [];
1389
+ pendingMessageCallback = null;
1390
+ rpcHandlerManager;
1391
+ agentStateLock = new AsyncLock();
1392
+ metadataLock = new AsyncLock();
1393
+ encryptionKey;
1394
+ encryptionVariant;
1395
+ constructor(token, session) {
1396
+ super();
1397
+ this.token = token;
1398
+ this.sessionId = session.id;
1399
+ this.metadata = session.metadata;
1400
+ this.metadataVersion = session.metadataVersion;
1401
+ this.agentState = session.agentState;
1402
+ this.agentStateVersion = session.agentStateVersion;
1403
+ this.encryptionKey = session.encryptionKey;
1404
+ this.encryptionVariant = session.encryptionVariant;
1405
+ this.rpcHandlerManager = new RpcHandlerManager({
1406
+ scopePrefix: this.sessionId,
1407
+ encryptionKey: this.encryptionKey,
1408
+ encryptionVariant: this.encryptionVariant,
1409
+ logger: (msg, data) => logger.debug(msg, data)
1410
+ });
1411
+ registerCommonHandlers(this.rpcHandlerManager);
1412
+ this.socket = socket_ioClient.io(configuration.serverUrl, {
1413
+ auth: {
1414
+ token: this.token,
1415
+ clientType: "session-scoped",
1416
+ sessionId: this.sessionId
1417
+ },
1418
+ path: "/v1/updates",
1419
+ reconnection: true,
1420
+ reconnectionAttempts: Infinity,
1421
+ reconnectionDelay: 1e3,
1422
+ reconnectionDelayMax: 5e3,
1423
+ transports: ["websocket"],
1424
+ withCredentials: true,
1425
+ autoConnect: false
1426
+ });
1427
+ this.socket.on("connect", () => {
1428
+ logger.debug("Socket connected successfully");
1429
+ this.rpcHandlerManager.onSocketConnect(this.socket);
1430
+ });
1431
+ this.socket.on("rpc-request", async (data, callback) => {
1432
+ callback(await this.rpcHandlerManager.handleRequest(data));
1433
+ });
1434
+ this.socket.on("disconnect", (reason) => {
1435
+ logger.debug("[API] Socket disconnected:", reason);
1436
+ this.rpcHandlerManager.onSocketDisconnect();
1437
+ });
1438
+ this.socket.on("connect_error", (error) => {
1439
+ logger.debug("[API] Socket connection error:", error);
1440
+ this.rpcHandlerManager.onSocketDisconnect();
1441
+ });
1442
+ this.socket.on("update", (data) => {
1443
+ try {
1444
+ logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", data);
1445
+ if (!data.body) {
1446
+ logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
1447
+ return;
1448
+ }
1449
+ if (data.body.t === "new-message" && data.body.message.content.t === "encrypted") {
1450
+ const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.message.content.c));
1451
+ logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
1452
+ const userResult = UserMessageSchema.safeParse(body);
1453
+ if (userResult.success) {
1454
+ if (this.pendingMessageCallback) {
1455
+ this.pendingMessageCallback(userResult.data);
1456
+ } else {
1457
+ this.pendingMessages.push(userResult.data);
1458
+ }
1459
+ } else {
1460
+ this.emit("message", body);
1461
+ }
1462
+ } else if (data.body.t === "update-session") {
1463
+ if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
1464
+ this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.metadata.value));
1465
+ this.metadataVersion = data.body.metadata.version;
1466
+ }
1467
+ if (data.body.agentState && data.body.agentState.version > this.agentStateVersion) {
1468
+ this.agentState = data.body.agentState.value ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.agentState.value)) : null;
1469
+ this.agentStateVersion = data.body.agentState.version;
1470
+ }
1471
+ } else if (data.body.t === "update-machine") {
1472
+ logger.debug(`[SOCKET] WARNING: Session client received unexpected machine update - ignoring`);
1473
+ } else {
1474
+ this.emit("message", data.body);
1475
+ }
1476
+ } catch (error) {
1477
+ logger.debug("[SOCKET] [UPDATE] [ERROR] Error handling update", { error });
1478
+ }
1479
+ });
1480
+ this.socket.on("error", (error) => {
1481
+ logger.debug("[API] Socket error:", error);
1482
+ });
1483
+ this.socket.connect();
1484
+ }
1485
+ onUserMessage(callback) {
1486
+ this.pendingMessageCallback = callback;
1487
+ while (this.pendingMessages.length > 0) {
1488
+ callback(this.pendingMessages.shift());
1489
+ }
1490
+ }
1491
+ /**
1492
+ * Send message to session
1493
+ * @param body - Message body (can be MessageContent or raw content for agent messages)
1494
+ */
1495
+ sendClaudeSessionMessage(body) {
1496
+ let content;
1497
+ if (body.type === "user" && typeof body.message.content === "string" && body.isSidechain !== true && body.isMeta !== true) {
1498
+ content = {
1499
+ role: "user",
1500
+ content: {
1501
+ type: "text",
1502
+ text: body.message.content
1503
+ },
1504
+ meta: {
1505
+ sentFrom: "cli"
1506
+ }
1507
+ };
1508
+ } else {
1509
+ content = {
1510
+ role: "agent",
1511
+ content: {
1512
+ type: "output",
1513
+ data: body
1514
+ // This wraps the entire Claude message
1515
+ },
1516
+ meta: {
1517
+ sentFrom: "cli"
1518
+ }
1519
+ };
1520
+ }
1521
+ logger.debugLargeJson("[SOCKET] Sending message through socket:", content);
1522
+ const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
1523
+ this.socket.emit("message", {
1524
+ sid: this.sessionId,
1525
+ message: encrypted
1526
+ });
1527
+ if (body.type === "assistant" && body.message.usage) {
1528
+ try {
1529
+ this.sendUsageData(body.message.usage);
1530
+ } catch (error) {
1531
+ logger.debug("[SOCKET] Failed to send usage data:", error);
1532
+ }
1533
+ }
1534
+ if (body.type === "summary" && "summary" in body && "leafUuid" in body) {
1535
+ this.updateMetadata((metadata) => ({
1536
+ ...metadata,
1537
+ summary: {
1538
+ text: body.summary,
1539
+ updatedAt: Date.now()
1540
+ }
1541
+ }));
1542
+ }
1543
+ }
1544
+ sendCodexMessage(body) {
1545
+ let content = {
1546
+ role: "agent",
1547
+ content: {
1548
+ type: "codex",
1549
+ data: body
1550
+ // This wraps the entire Claude message
1551
+ },
1552
+ meta: {
1553
+ sentFrom: "cli"
1554
+ }
1555
+ };
1556
+ const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
1557
+ this.socket.emit("message", {
1558
+ sid: this.sessionId,
1559
+ message: encrypted
1560
+ });
1561
+ }
1562
+ sendSessionEvent(event, id) {
1563
+ let content = {
1564
+ role: "agent",
1565
+ content: {
1566
+ id: id ?? node_crypto.randomUUID(),
1567
+ type: "event",
1568
+ data: event
1569
+ }
1570
+ };
1571
+ const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
1572
+ this.socket.emit("message", {
1573
+ sid: this.sessionId,
1574
+ message: encrypted
1575
+ });
1576
+ }
1577
+ /**
1578
+ * Send a ping message to keep the connection alive
1579
+ */
1580
+ keepAlive(thinking, mode) {
1581
+ if (process.env.DEBUG) {
1582
+ logger.debug(`[API] Sending keep alive message: ${thinking}`);
1583
+ }
1584
+ this.socket.volatile.emit("session-alive", {
1585
+ sid: this.sessionId,
1586
+ time: Date.now(),
1587
+ thinking,
1588
+ mode
1589
+ });
1590
+ }
1591
+ /**
1592
+ * Send session death message
1593
+ */
1594
+ sendSessionDeath() {
1595
+ this.socket.emit("session-end", { sid: this.sessionId, time: Date.now() });
1596
+ }
1597
+ /**
1598
+ * Send usage data to the server
1599
+ */
1600
+ sendUsageData(usage) {
1601
+ const totalTokens = usage.input_tokens + usage.output_tokens + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
1602
+ const usageReport = {
1603
+ key: "claude-session",
1604
+ sessionId: this.sessionId,
1605
+ tokens: {
1606
+ total: totalTokens,
1607
+ input: usage.input_tokens,
1608
+ output: usage.output_tokens,
1609
+ cache_creation: usage.cache_creation_input_tokens || 0,
1610
+ cache_read: usage.cache_read_input_tokens || 0
1611
+ },
1612
+ cost: {
1613
+ // TODO: Calculate actual costs based on pricing
1614
+ // For now, using placeholder values
1615
+ total: 0,
1616
+ input: 0,
1617
+ output: 0
1618
+ }
1619
+ };
1620
+ logger.debugLargeJson("[SOCKET] Sending usage data:", usageReport);
1621
+ this.socket.emit("usage-report", usageReport);
1622
+ }
1623
+ /**
1624
+ * Update session metadata
1625
+ * @param handler - Handler function that returns the updated metadata
1626
+ */
1627
+ updateMetadata(handler) {
1628
+ this.metadataLock.inLock(async () => {
1629
+ await backoff(async () => {
1630
+ let updated = handler(this.metadata);
1631
+ const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) });
1632
+ if (answer.result === "success") {
1633
+ this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
1634
+ this.metadataVersion = answer.version;
1635
+ } else if (answer.result === "version-mismatch") {
1636
+ if (answer.version > this.metadataVersion) {
1637
+ this.metadataVersion = answer.version;
1638
+ this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
1639
+ }
1640
+ throw new Error("Metadata version mismatch");
1641
+ } else if (answer.result === "error") ;
1642
+ });
1643
+ });
1644
+ }
1645
+ /**
1646
+ * Update session agent state
1647
+ * @param handler - Handler function that returns the updated agent state
1648
+ */
1649
+ updateAgentState(handler) {
1650
+ logger.debugLargeJson("Updating agent state", this.agentState);
1651
+ this.agentStateLock.inLock(async () => {
1652
+ await backoff(async () => {
1653
+ let updated = handler(this.agentState || {});
1654
+ const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
1655
+ if (answer.result === "success") {
1656
+ this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
1657
+ this.agentStateVersion = answer.version;
1658
+ logger.debug("Agent state updated", this.agentState);
1659
+ } else if (answer.result === "version-mismatch") {
1660
+ if (answer.version > this.agentStateVersion) {
1661
+ this.agentStateVersion = answer.version;
1662
+ this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
1663
+ }
1664
+ throw new Error("Agent state version mismatch");
1665
+ } else if (answer.result === "error") ;
1666
+ });
1667
+ });
1668
+ }
1669
+ /**
1670
+ * Wait for socket buffer to flush
1671
+ */
1672
+ async flush() {
1673
+ if (!this.socket.connected) {
1674
+ return;
1675
+ }
1676
+ return new Promise((resolve) => {
1677
+ this.socket.emit("ping", () => {
1678
+ resolve();
1679
+ });
1680
+ setTimeout(() => {
1681
+ resolve();
1682
+ }, 1e4);
1683
+ });
1684
+ }
1685
+ async close() {
1686
+ logger.debug("[API] socket.close() called");
1687
+ this.socket.close();
1688
+ }
1689
+ }
1690
+
1691
+ class ApiMachineClient {
1692
+ constructor(token, machine) {
1693
+ this.token = token;
1694
+ this.machine = machine;
1695
+ this.rpcHandlerManager = new RpcHandlerManager({
1696
+ scopePrefix: this.machine.id,
1697
+ encryptionKey: this.machine.encryptionKey,
1698
+ encryptionVariant: this.machine.encryptionVariant,
1699
+ logger: (msg, data) => logger.debug(msg, data)
1700
+ });
1701
+ registerCommonHandlers(this.rpcHandlerManager);
1702
+ }
1703
+ socket;
1704
+ keepAliveInterval = null;
1705
+ rpcHandlerManager;
1706
+ setRPCHandlers({
1707
+ spawnSession,
1708
+ stopSession,
1709
+ requestShutdown
1710
+ }) {
1711
+ this.rpcHandlerManager.registerHandler("spawn-happy-session", async (params) => {
1712
+ const { directory, sessionId, machineId, approvedNewDirectoryCreation, agent, token } = params || {};
1713
+ logger.debug(`[API MACHINE] Spawning session with params: ${JSON.stringify(params)}`);
1714
+ if (!directory) {
1715
+ throw new Error("Directory is required");
1716
+ }
1717
+ const result = await spawnSession({ directory, sessionId, machineId, approvedNewDirectoryCreation, agent, token });
1718
+ switch (result.type) {
1719
+ case "success":
1720
+ logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
1721
+ return { type: "success", sessionId: result.sessionId };
1722
+ case "requestToApproveDirectoryCreation":
1723
+ logger.debug(`[API MACHINE] Requesting directory creation approval for: ${result.directory}`);
1724
+ return { type: "requestToApproveDirectoryCreation", directory: result.directory };
1725
+ case "error":
1726
+ throw new Error(result.errorMessage);
1727
+ }
1728
+ });
1729
+ this.rpcHandlerManager.registerHandler("stop-session", (params) => {
1730
+ const { sessionId } = params || {};
1731
+ if (!sessionId) {
1732
+ throw new Error("Session ID is required");
1733
+ }
1734
+ const success = stopSession(sessionId);
1735
+ if (!success) {
1736
+ throw new Error("Session not found or failed to stop");
1737
+ }
1738
+ logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
1739
+ return { message: "Session stopped" };
1740
+ });
1741
+ this.rpcHandlerManager.registerHandler("stop-daemon", () => {
1742
+ logger.debug("[API MACHINE] Received stop-daemon RPC request");
1743
+ setTimeout(() => {
1744
+ logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
1745
+ requestShutdown();
1746
+ }, 100);
1747
+ return { message: "Daemon stop request acknowledged, starting shutdown sequence..." };
1748
+ });
1749
+ this.rpcHandlerManager.registerHandler("ping", () => {
1750
+ logger.debug("[API MACHINE] Received ping RPC request");
1751
+ return {
1752
+ status: "alive",
1753
+ timestamp: Date.now(),
1754
+ uptime: process.uptime(),
1755
+ machineId: this.machine.id
1756
+ };
1757
+ });
1758
+ }
1759
+ /**
1760
+ * Update machine metadata
1761
+ * Currently unused, changes from the mobile client are more likely
1762
+ * for example to set a custom name.
1763
+ */
1764
+ async updateMachineMetadata(handler) {
1765
+ await backoff(async () => {
1766
+ const updated = handler(this.machine.metadata);
1767
+ const answer = await this.socket.emitWithAck("machine-update-metadata", {
1768
+ machineId: this.machine.id,
1769
+ metadata: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
1770
+ expectedVersion: this.machine.metadataVersion
1771
+ });
1772
+ if (answer.result === "success") {
1773
+ this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
1774
+ this.machine.metadataVersion = answer.version;
1775
+ logger.debug("[API MACHINE] Metadata updated successfully");
1776
+ } else if (answer.result === "version-mismatch") {
1777
+ if (answer.version > this.machine.metadataVersion) {
1778
+ this.machine.metadataVersion = answer.version;
1779
+ this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
1780
+ }
1781
+ throw new Error("Metadata version mismatch");
1782
+ }
1783
+ });
1784
+ }
1785
+ /**
1786
+ * Update daemon state (runtime info) - similar to session updateAgentState
1787
+ * Simplified without lock - relies on backoff for retry
1788
+ */
1789
+ async updateDaemonState(handler) {
1790
+ await backoff(async () => {
1791
+ const updated = handler(this.machine.daemonState);
1792
+ const answer = await this.socket.emitWithAck("machine-update-state", {
1793
+ machineId: this.machine.id,
1794
+ daemonState: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
1795
+ expectedVersion: this.machine.daemonStateVersion
1796
+ });
1797
+ if (answer.result === "success") {
1798
+ this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
1799
+ this.machine.daemonStateVersion = answer.version;
1800
+ logger.debug("[API MACHINE] Daemon state updated successfully");
1801
+ } else if (answer.result === "version-mismatch") {
1802
+ if (answer.version > this.machine.daemonStateVersion) {
1803
+ this.machine.daemonStateVersion = answer.version;
1804
+ this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
1805
+ }
1806
+ throw new Error("Daemon state version mismatch");
1807
+ }
1808
+ });
1809
+ }
1810
+ connect() {
1811
+ const serverUrl = configuration.serverUrl.replace(/^http/, "ws");
1812
+ logger.debug(`[API MACHINE] Connecting to ${serverUrl}`);
1813
+ this.socket = socket_ioClient.io(serverUrl, {
1814
+ transports: ["websocket"],
1815
+ auth: {
1816
+ token: this.token,
1817
+ clientType: "machine-scoped",
1818
+ machineId: this.machine.id
1819
+ },
1820
+ path: "/v1/updates",
1821
+ reconnection: true,
1822
+ reconnectionDelay: 1e3,
1823
+ reconnectionDelayMax: 5e3
1824
+ });
1825
+ this.socket.on("connect", () => {
1826
+ logger.debug("[API MACHINE] Connected to server");
1827
+ this.updateDaemonState((state) => ({
1828
+ ...state,
1829
+ status: "running",
1830
+ pid: process.pid,
1831
+ httpPort: this.machine.daemonState?.httpPort,
1832
+ startedAt: Date.now()
1833
+ }));
1834
+ this.rpcHandlerManager.onSocketConnect(this.socket);
1835
+ this.startKeepAlive();
1836
+ });
1837
+ this.socket.on("disconnect", () => {
1838
+ logger.debug("[API MACHINE] Disconnected from server");
1839
+ this.rpcHandlerManager.onSocketDisconnect();
1840
+ this.stopKeepAlive();
1841
+ });
1842
+ this.socket.on("rpc-request", async (data, callback) => {
1843
+ logger.debugLargeJson(`[API MACHINE] Received RPC request:`, data);
1844
+ callback(await this.rpcHandlerManager.handleRequest(data));
1845
+ });
1846
+ this.socket.on("update", (data) => {
1847
+ if (data.body.t === "update-machine" && data.body.machineId === this.machine.id) {
1848
+ const update = data.body;
1849
+ if (update.metadata) {
1850
+ logger.debug("[API MACHINE] Received external metadata update");
1851
+ this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.metadata.value));
1852
+ this.machine.metadataVersion = update.metadata.version;
1853
+ }
1854
+ if (update.daemonState) {
1855
+ logger.debug("[API MACHINE] Received external daemon state update");
1856
+ this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.daemonState.value));
1857
+ this.machine.daemonStateVersion = update.daemonState.version;
1858
+ }
1859
+ } else {
1860
+ logger.debug(`[API MACHINE] Received unknown update type: ${data.body.t}`);
1861
+ }
1862
+ });
1863
+ this.socket.on("connect_error", (error) => {
1864
+ logger.debug(`[API MACHINE] Connection error: ${error.message}`);
1865
+ });
1866
+ this.socket.io.on("error", (error) => {
1867
+ logger.debug("[API MACHINE] Socket error:", error);
1868
+ });
1869
+ }
1870
+ startKeepAlive() {
1871
+ this.stopKeepAlive();
1872
+ this.keepAliveInterval = setInterval(() => {
1873
+ const payload = {
1874
+ machineId: this.machine.id,
1875
+ time: Date.now()
1876
+ };
1877
+ if (process.env.DEBUG) {
1878
+ logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
1879
+ }
1880
+ this.socket.emit("machine-alive", payload);
1881
+ }, 2e4);
1882
+ logger.debug("[API MACHINE] Keep-alive started (20s interval)");
1883
+ }
1884
+ stopKeepAlive() {
1885
+ if (this.keepAliveInterval) {
1886
+ clearInterval(this.keepAliveInterval);
1887
+ this.keepAliveInterval = null;
1888
+ logger.debug("[API MACHINE] Keep-alive stopped");
1889
+ }
1890
+ }
1891
+ shutdown() {
1892
+ logger.debug("[API MACHINE] Shutting down");
1893
+ this.stopKeepAlive();
1894
+ if (this.socket) {
1895
+ this.socket.close();
1896
+ logger.debug("[API MACHINE] Socket closed");
1897
+ }
1898
+ }
1899
+ }
1900
+
1901
+ class PushNotificationClient {
1902
+ token;
1903
+ baseUrl;
1904
+ expo;
1905
+ constructor(token, baseUrl = "https://api.cluster-fluster.com") {
1906
+ this.token = token;
1907
+ this.baseUrl = baseUrl;
1908
+ this.expo = new expoServerSdk.Expo();
1909
+ }
1910
+ /**
1911
+ * Fetch all push tokens for the authenticated user
1912
+ */
1913
+ async fetchPushTokens() {
1914
+ try {
1915
+ const response = await axios.get(
1916
+ `${this.baseUrl}/v1/push-tokens`,
1917
+ {
1918
+ headers: {
1919
+ "Authorization": `Bearer ${this.token}`,
1920
+ "Content-Type": "application/json"
1921
+ }
1922
+ }
1923
+ );
1924
+ logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
1925
+ response.data.tokens.forEach((token, index) => {
1926
+ logger.debug(`[PUSH] Token ${index + 1}: id=${token.id}, token=${token.token}, created=${new Date(token.createdAt).toISOString()}, updated=${new Date(token.updatedAt).toISOString()}`);
1927
+ });
1928
+ return response.data.tokens;
1929
+ } catch (error) {
1930
+ logger.debug("[PUSH] [ERROR] Failed to fetch push tokens:", error);
1931
+ throw new Error(`Failed to fetch push tokens: ${error instanceof Error ? error.message : "Unknown error"}`);
1932
+ }
1933
+ }
1934
+ /**
1935
+ * Send push notification via Expo Push API with retry
1936
+ * @param messages - Array of push messages to send
1937
+ */
1938
+ async sendPushNotifications(messages) {
1939
+ logger.debug(`Sending ${messages.length} push notifications`);
1940
+ const validMessages = messages.filter((message) => {
1941
+ if (Array.isArray(message.to)) {
1942
+ return message.to.every((token) => expoServerSdk.Expo.isExpoPushToken(token));
1943
+ }
1944
+ return expoServerSdk.Expo.isExpoPushToken(message.to);
1945
+ });
1946
+ if (validMessages.length === 0) {
1947
+ logger.debug("No valid Expo push tokens found");
1948
+ return;
1949
+ }
1950
+ const chunks = this.expo.chunkPushNotifications(validMessages);
1951
+ for (const chunk of chunks) {
1952
+ const startTime = Date.now();
1953
+ const timeout = 3e5;
1954
+ let attempt = 0;
1955
+ while (true) {
1956
+ try {
1957
+ const ticketChunk = await this.expo.sendPushNotificationsAsync(chunk);
1958
+ const errors = ticketChunk.filter((ticket) => ticket.status === "error");
1959
+ if (errors.length > 0) {
1960
+ const errorDetails = errors.map((e) => ({ message: e.message, details: e.details }));
1961
+ logger.debug("[PUSH] Some notifications failed:", errorDetails);
1962
+ }
1963
+ if (errors.length === ticketChunk.length) {
1964
+ throw new Error("All push notifications in chunk failed");
1965
+ }
1966
+ break;
1967
+ } catch (error) {
1968
+ const elapsed = Date.now() - startTime;
1969
+ if (elapsed >= timeout) {
1970
+ logger.debug("[PUSH] Timeout reached after 5 minutes, giving up on chunk");
1971
+ break;
1972
+ }
1973
+ attempt++;
1974
+ const delay = Math.min(1e3 * Math.pow(2, attempt), 3e4);
1975
+ const remainingTime = timeout - elapsed;
1976
+ const waitTime = Math.min(delay, remainingTime);
1977
+ if (waitTime > 0) {
1978
+ logger.debug(`[PUSH] Retrying in ${waitTime}ms (attempt ${attempt})`);
1979
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
1980
+ }
1981
+ }
1982
+ }
1983
+ }
1984
+ logger.debug(`Push notifications sent successfully`);
1985
+ }
1986
+ /**
1987
+ * Send a push notification to all registered devices for the user
1988
+ * @param title - Notification title
1989
+ * @param body - Notification body
1990
+ * @param data - Additional data to send with the notification
1991
+ */
1992
+ sendToAllDevices(title, body, data) {
1993
+ logger.debug(`[PUSH] sendToAllDevices called with title: "${title}", body: "${body}"`);
1994
+ (async () => {
1995
+ try {
1996
+ logger.debug("[PUSH] Fetching push tokens...");
1997
+ const tokens = await this.fetchPushTokens();
1998
+ logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
1999
+ tokens.forEach((token, index) => {
2000
+ logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}, token=${token.token}`);
2001
+ });
2002
+ if (tokens.length === 0) {
2003
+ logger.debug("No push tokens found for user");
2004
+ return;
2005
+ }
2006
+ const messages = tokens.map((token, index) => {
2007
+ logger.debug(`[PUSH] Creating message ${index + 1} for token: ${token.token}`);
2008
+ return {
2009
+ to: token.token,
2010
+ title,
2011
+ body,
2012
+ data,
2013
+ sound: "default",
2014
+ priority: "high"
2015
+ };
2016
+ });
2017
+ logger.debug(`[PUSH] Sending ${messages.length} push notifications...`);
2018
+ await this.sendPushNotifications(messages);
2019
+ logger.debug("[PUSH] Push notifications sent successfully");
2020
+ } catch (error) {
2021
+ logger.debug("[PUSH] Error sending to all devices:", error);
2022
+ }
2023
+ })();
2024
+ }
2025
+ }
2026
+
2027
+ class ApiClient {
2028
+ static async create(credential) {
2029
+ return new ApiClient(credential);
2030
+ }
2031
+ credential;
2032
+ pushClient;
2033
+ constructor(credential) {
2034
+ this.credential = credential;
2035
+ this.pushClient = new PushNotificationClient(credential.token, configuration.serverUrl);
2036
+ }
2037
+ /**
2038
+ * Create a new session or load existing one with the given tag
2039
+ */
2040
+ async getOrCreateSession(opts) {
2041
+ let dataEncryptionKey = null;
2042
+ let encryptionKey;
2043
+ let encryptionVariant;
2044
+ if (this.credential.encryption.type === "dataKey") {
2045
+ encryptionKey = this.credential.encryption.machineKey;
2046
+ encryptionVariant = "dataKey";
2047
+ const derivedPublicKey = derivePublicKeyFromSeed(this.credential.encryption.dataKeySeed);
2048
+ let encryptedDataKey = libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, derivedPublicKey);
2049
+ dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
2050
+ dataEncryptionKey.set([0], 0);
2051
+ dataEncryptionKey.set(encryptedDataKey, 1);
2052
+ } else {
2053
+ encryptionKey = this.credential.encryption.secret;
2054
+ encryptionVariant = "legacy";
2055
+ }
2056
+ try {
2057
+ const response = await axios.post(
2058
+ `${configuration.serverUrl}/v1/sessions`,
2059
+ {
2060
+ tag: opts.tag,
2061
+ metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
2062
+ agentState: opts.state ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.state)) : null,
2063
+ dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : null
2064
+ },
2065
+ {
2066
+ headers: {
2067
+ "Authorization": `Bearer ${this.credential.token}`,
2068
+ "Content-Type": "application/json"
2069
+ },
2070
+ timeout: 6e4
2071
+ // 1 minute timeout for very bad network connections
2072
+ }
2073
+ );
2074
+ logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
2075
+ let raw = response.data.session;
2076
+ let session = {
2077
+ id: raw.id,
2078
+ seq: raw.seq,
2079
+ metadata: decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)),
2080
+ metadataVersion: raw.metadataVersion,
2081
+ agentState: raw.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.agentState)) : null,
2082
+ agentStateVersion: raw.agentStateVersion,
2083
+ encryptionKey,
2084
+ encryptionVariant
2085
+ };
2086
+ return session;
2087
+ } catch (error) {
2088
+ logger.debug("[API] [ERROR] Failed to get or create session:", error);
2089
+ throw new Error(`Failed to get or create session: ${error instanceof Error ? error.message : "Unknown error"}`);
2090
+ }
2091
+ }
2092
+ /**
2093
+ * Register or update machine with the server
2094
+ * Returns the current machine state from the server with decrypted metadata and daemonState
2095
+ */
2096
+ async getOrCreateMachine(opts) {
2097
+ let dataEncryptionKey = null;
2098
+ let encryptionKey;
2099
+ let encryptionVariant;
2100
+ if (this.credential.encryption.type === "dataKey") {
2101
+ encryptionVariant = "dataKey";
2102
+ encryptionKey = this.credential.encryption.machineKey;
2103
+ const derivedPublicKey = derivePublicKeyFromSeed(this.credential.encryption.dataKeySeed);
2104
+ let encryptedDataKey = libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, derivedPublicKey);
2105
+ dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
2106
+ dataEncryptionKey.set([0], 0);
2107
+ dataEncryptionKey.set(encryptedDataKey, 1);
2108
+ } else {
2109
+ encryptionKey = this.credential.encryption.secret;
2110
+ encryptionVariant = "legacy";
2111
+ }
2112
+ const response = await axios.post(
2113
+ `${configuration.serverUrl}/v1/machines`,
2114
+ {
2115
+ id: opts.machineId,
2116
+ metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
2117
+ daemonState: opts.daemonState ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.daemonState)) : void 0,
2118
+ dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : void 0
2119
+ },
2120
+ {
2121
+ headers: {
2122
+ "Authorization": `Bearer ${this.credential.token}`,
2123
+ "Content-Type": "application/json"
2124
+ },
2125
+ timeout: 6e4
2126
+ // 1 minute timeout for very bad network connections
2127
+ }
2128
+ );
2129
+ if (response.status !== 200) {
2130
+ console.error(chalk.red(`[API] Failed to create machine: ${response.statusText}`));
2131
+ console.log(chalk.yellow(`[API] Failed to create machine: ${response.statusText}, most likely you have re-authenticated, but you still have a machine associated with the old account. Now we are trying to re-associate the machine with the new account. That is not allowed. Please run 'happy doctor clean' to clean up your happy state, and try your original command again. Please create an issue on github if this is causing you problems. We apologize for the inconvenience.`));
2132
+ process.exit(1);
2133
+ }
2134
+ const raw = response.data.machine;
2135
+ logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
2136
+ const machine = {
2137
+ id: raw.id,
2138
+ encryptionKey,
2139
+ encryptionVariant,
2140
+ metadata: raw.metadata ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)) : null,
2141
+ metadataVersion: raw.metadataVersion || 0,
2142
+ daemonState: raw.daemonState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.daemonState)) : null,
2143
+ daemonStateVersion: raw.daemonStateVersion || 0
2144
+ };
2145
+ return machine;
2146
+ }
2147
+ sessionSyncClient(session) {
2148
+ return new ApiSessionClient(this.credential.token, session);
2149
+ }
2150
+ machineSyncClient(machine) {
2151
+ return new ApiMachineClient(this.credential.token, machine);
2152
+ }
2153
+ push() {
2154
+ return this.pushClient;
2155
+ }
2156
+ /**
2157
+ * Register a vendor API token with the server
2158
+ * The token is sent as a JSON string - server handles encryption
2159
+ */
2160
+ async registerVendorToken(vendor, apiKey) {
2161
+ try {
2162
+ const response = await axios.post(
2163
+ `${configuration.serverUrl}/v1/connect/${vendor}/register`,
2164
+ {
2165
+ token: JSON.stringify(apiKey)
2166
+ },
2167
+ {
2168
+ headers: {
2169
+ "Authorization": `Bearer ${this.credential.token}`,
2170
+ "Content-Type": "application/json"
2171
+ },
2172
+ timeout: 5e3
2173
+ }
2174
+ );
2175
+ if (response.status !== 200 && response.status !== 201) {
2176
+ throw new Error(`Server returned status ${response.status}`);
2177
+ }
2178
+ logger.debug(`[API] Vendor token for ${vendor} registered successfully`);
2179
+ } catch (error) {
2180
+ logger.debug(`[API] [ERROR] Failed to register vendor token:`, error);
2181
+ throw new Error(`Failed to register vendor token: ${error instanceof Error ? error.message : "Unknown error"}`);
2182
+ }
2183
+ }
2184
+ }
2185
+
2186
+ const UsageSchema = z.z.object({
2187
+ input_tokens: z.z.number().int().nonnegative(),
2188
+ cache_creation_input_tokens: z.z.number().int().nonnegative().optional(),
2189
+ cache_read_input_tokens: z.z.number().int().nonnegative().optional(),
2190
+ output_tokens: z.z.number().int().nonnegative(),
2191
+ service_tier: z.z.string().optional()
2192
+ }).passthrough();
2193
+ const RawJSONLinesSchema = z.z.discriminatedUnion("type", [
2194
+ // User message - validates uuid and message.content
2195
+ z.z.object({
2196
+ type: z.z.literal("user"),
2197
+ isSidechain: z.z.boolean().optional(),
2198
+ isMeta: z.z.boolean().optional(),
2199
+ uuid: z.z.string(),
2200
+ // Used in getMessageKey()
2201
+ message: z.z.object({
2202
+ content: z.z.union([z.z.string(), z.z.any()])
2203
+ // Used in sessionScanner.ts
2204
+ }).passthrough()
2205
+ }).passthrough(),
2206
+ // Assistant message - validates message object with usage and content
2207
+ z.z.object({
2208
+ uuid: z.z.string(),
2209
+ type: z.z.literal("assistant"),
2210
+ message: z.z.object({
2211
+ // Entire message used in getMessageKey()
2212
+ usage: UsageSchema.optional(),
2213
+ // Used in apiSession.ts
2214
+ content: z.z.any()
2215
+ // Used in tests
2216
+ }).passthrough()
2217
+ }).passthrough(),
2218
+ // Summary message - validates summary and leafUuid
2219
+ z.z.object({
2220
+ type: z.z.literal("summary"),
2221
+ summary: z.z.string(),
2222
+ // Used in apiSession.ts
2223
+ leafUuid: z.z.string()
2224
+ // Used in getMessageKey()
2225
+ }).passthrough(),
2226
+ // System message - validates uuid
2227
+ z.z.object({
2228
+ type: z.z.literal("system"),
2229
+ uuid: z.z.string()
2230
+ // Used in getMessageKey()
2231
+ }).passthrough()
2232
+ ]);
2233
+
2234
+ exports.ApiClient = ApiClient;
2235
+ exports.ApiSessionClient = ApiSessionClient;
2236
+ exports.AsyncLock = AsyncLock;
2237
+ exports.RawJSONLinesSchema = RawJSONLinesSchema;
2238
+ exports.acquireDaemonLock = acquireDaemonLock;
2239
+ exports.authChallenge = authChallenge;
2240
+ exports.backoff = backoff;
2241
+ exports.clearCredentials = clearCredentials;
2242
+ exports.clearDaemonState = clearDaemonState;
2243
+ exports.clearMachineId = clearMachineId;
2244
+ exports.configuration = configuration;
2245
+ exports.decodeBase64 = decodeBase64;
2246
+ exports.decrypt = decrypt;
2247
+ exports.delay = delay;
2248
+ exports.encodeBase64 = encodeBase64;
2249
+ exports.encodeBase64Url = encodeBase64Url;
2250
+ exports.encrypt = encrypt;
2251
+ exports.formatTimeAgo = formatTimeAgo;
2252
+ exports.getLatestDaemonLog = getLatestDaemonLog;
2253
+ exports.libsodiumDecryptFromPublicKey = libsodiumDecryptFromPublicKey;
2254
+ exports.logger = logger;
2255
+ exports.packageJson = packageJson;
2256
+ exports.projectPath = projectPath;
2257
+ exports.readCredentials = readCredentials;
2258
+ exports.readDaemonState = readDaemonState;
2259
+ exports.readSettings = readSettings;
2260
+ exports.releaseDaemonLock = releaseDaemonLock;
2261
+ exports.updateSettings = updateSettings;
2262
+ exports.writeCredentialsDataKey = writeCredentialsDataKey;
2263
+ exports.writeCredentialsLegacy = writeCredentialsLegacy;
2264
+ exports.writeDaemonState = writeDaemonState;