happy-coder 0.9.0-6 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,27 +1,156 @@
1
1
  import axios from 'axios';
2
2
  import chalk from 'chalk';
3
3
  import { appendFileSync } from 'fs';
4
+ import { existsSync, mkdirSync, constants, readFileSync, unlinkSync, writeFileSync, readdirSync, statSync } from 'node:fs';
4
5
  import { homedir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { mkdir } from 'node:fs/promises';
7
- import { existsSync } from 'node:fs';
8
- import { EventEmitter } from 'node:events';
9
- import { io } from 'socket.io-client';
10
- import { z } from 'zod';
6
+ import { join, basename } from 'node:path';
7
+ import { readFile, open, stat, unlink, mkdir, writeFile, rename } from 'node:fs/promises';
8
+ import * as z from 'zod';
9
+ import { z as z$1 } from 'zod';
11
10
  import { randomBytes, randomUUID } from 'node:crypto';
12
11
  import tweetnacl from 'tweetnacl';
12
+ import { EventEmitter } from 'node:events';
13
+ import { io } from 'socket.io-client';
13
14
  import { Expo } from 'expo-server-sdk';
14
15
 
16
+ var name = "happy-coder";
17
+ var version = "0.9.0";
18
+ var description = "Claude Code session sharing CLI";
19
+ var author = "Kirill Dubovitskiy";
20
+ var license = "MIT";
21
+ var type = "module";
22
+ var homepage = "https://github.com/slopus/happy-cli";
23
+ var bugs = "https://github.com/slopus/happy-cli/issues";
24
+ var repository = "slopus/happy-cli";
25
+ var bin = {
26
+ happy: "./bin/happy.mjs"
27
+ };
28
+ var main = "./dist/index.cjs";
29
+ var module = "./dist/index.mjs";
30
+ var types = "./dist/index.d.cts";
31
+ var exports = {
32
+ ".": {
33
+ require: {
34
+ types: "./dist/index.d.cts",
35
+ "default": "./dist/index.cjs"
36
+ },
37
+ "import": {
38
+ types: "./dist/index.d.mts",
39
+ "default": "./dist/index.mjs"
40
+ }
41
+ },
42
+ "./lib": {
43
+ require: {
44
+ types: "./dist/lib.d.cts",
45
+ "default": "./dist/lib.cjs"
46
+ },
47
+ "import": {
48
+ types: "./dist/lib.d.mts",
49
+ "default": "./dist/lib.mjs"
50
+ }
51
+ }
52
+ };
53
+ var files = [
54
+ "dist",
55
+ "bin",
56
+ "scripts",
57
+ "ripgrep",
58
+ "package.json"
59
+ ];
60
+ var scripts = {
61
+ "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",
62
+ typecheck: "tsc --noEmit",
63
+ build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
64
+ test: "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
65
+ start: "yarn build && ./bin/happy.mjs",
66
+ dev: "yarn build && tsx --env-file .env.dev src/index.ts",
67
+ "dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
68
+ "dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
69
+ prepublishOnly: "yarn build && yarn test",
70
+ release: "release-it"
71
+ };
72
+ var dependencies = {
73
+ "@anthropic-ai/claude-code": "^1.0.89",
74
+ "@anthropic-ai/sdk": "^0.56.0",
75
+ "@modelcontextprotocol/sdk": "^1.15.1",
76
+ "@stablelib/base64": "^2.0.1",
77
+ "@types/http-proxy": "^1.17.16",
78
+ "@types/qrcode-terminal": "^0.12.2",
79
+ "@types/react": "^19.1.9",
80
+ axios: "^1.10.0",
81
+ chalk: "^5.4.1",
82
+ "expo-server-sdk": "^3.15.0",
83
+ fastify: "^5.5.0",
84
+ "fastify-type-provider-zod": "4.0.2",
85
+ "http-proxy": "^1.18.1",
86
+ "http-proxy-middleware": "^3.0.5",
87
+ ink: "^6.1.0",
88
+ open: "^10.2.0",
89
+ "qrcode-terminal": "^0.12.0",
90
+ react: "^19.1.1",
91
+ "socket.io-client": "^4.8.1",
92
+ tweetnacl: "^1.0.3",
93
+ zod: "^3.23.8"
94
+ };
95
+ var devDependencies = {
96
+ "@eslint/compat": "^1",
97
+ "@types/node": ">=20",
98
+ "cross-env": "^10.0.0",
99
+ dotenv: "^16.6.1",
100
+ eslint: "^9",
101
+ "eslint-config-prettier": "^10",
102
+ pkgroll: "^2.14.2",
103
+ "release-it": "^19.0.4",
104
+ shx: "^0.3.3",
105
+ "ts-node": "^10",
106
+ tsx: "^4.20.3",
107
+ typescript: "^5",
108
+ vitest: "^3.2.4"
109
+ };
110
+ var resolutions = {
111
+ "whatwg-url": "14.2.0",
112
+ "parse-path": "7.0.3",
113
+ "@types/parse-path": "7.0.3"
114
+ };
115
+ var publishConfig = {
116
+ registry: "https://registry.npmjs.org"
117
+ };
118
+ var packageManager = "yarn@1.22.22";
119
+ var packageJson = {
120
+ name: name,
121
+ version: version,
122
+ description: description,
123
+ author: author,
124
+ license: license,
125
+ type: type,
126
+ homepage: homepage,
127
+ bugs: bugs,
128
+ repository: repository,
129
+ bin: bin,
130
+ main: main,
131
+ module: module,
132
+ types: types,
133
+ exports: exports,
134
+ files: files,
135
+ scripts: scripts,
136
+ dependencies: dependencies,
137
+ devDependencies: devDependencies,
138
+ resolutions: resolutions,
139
+ publishConfig: publishConfig,
140
+ packageManager: packageManager
141
+ };
142
+
15
143
  class Configuration {
16
144
  serverUrl;
17
145
  isDaemonProcess;
18
146
  // Directories and paths (from persistence)
19
147
  happyHomeDir;
20
148
  logsDir;
21
- daemonLogsDir;
22
149
  settingsFile;
23
150
  privateKeyFile;
24
151
  daemonStateFile;
152
+ daemonLockFile;
153
+ currentCliVersion;
25
154
  isExperimentalEnabled;
26
155
  constructor() {
27
156
  this.serverUrl = process.env.HAPPY_SERVER_URL || "https://handy-api.korshakov.org";
@@ -34,15 +163,231 @@ class Configuration {
34
163
  this.happyHomeDir = join(homedir(), ".happy");
35
164
  }
36
165
  this.logsDir = join(this.happyHomeDir, "logs");
37
- this.daemonLogsDir = join(this.happyHomeDir, "logs-daemon");
38
166
  this.settingsFile = join(this.happyHomeDir, "settings.json");
39
167
  this.privateKeyFile = join(this.happyHomeDir, "access.key");
40
168
  this.daemonStateFile = join(this.happyHomeDir, "daemon.state.json");
169
+ this.daemonLockFile = join(this.happyHomeDir, "daemon.state.json.lock");
41
170
  this.isExperimentalEnabled = ["true", "1", "yes"].includes(process.env.HAPPY_EXPERIMENTAL?.toLowerCase() || "");
171
+ this.currentCliVersion = packageJson.version;
172
+ if (!existsSync(this.happyHomeDir)) {
173
+ mkdirSync(this.happyHomeDir, { recursive: true });
174
+ }
175
+ if (!existsSync(this.logsDir)) {
176
+ mkdirSync(this.logsDir, { recursive: true });
177
+ }
42
178
  }
43
179
  }
44
180
  const configuration = new Configuration();
45
181
 
182
+ function encodeBase64(buffer, variant = "base64") {
183
+ if (variant === "base64url") {
184
+ return encodeBase64Url(buffer);
185
+ }
186
+ return Buffer.from(buffer).toString("base64");
187
+ }
188
+ function encodeBase64Url(buffer) {
189
+ return Buffer.from(buffer).toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
190
+ }
191
+ function decodeBase64(base64, variant = "base64") {
192
+ if (variant === "base64url") {
193
+ const base64Standard = base64.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - base64.length % 4) % 4);
194
+ return new Uint8Array(Buffer.from(base64Standard, "base64"));
195
+ }
196
+ return new Uint8Array(Buffer.from(base64, "base64"));
197
+ }
198
+ function getRandomBytes(size) {
199
+ return new Uint8Array(randomBytes(size));
200
+ }
201
+ function encrypt(data, secret) {
202
+ const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
203
+ const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
204
+ const result = new Uint8Array(nonce.length + encrypted.length);
205
+ result.set(nonce);
206
+ result.set(encrypted, nonce.length);
207
+ return result;
208
+ }
209
+ function decrypt(data, secret) {
210
+ const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
211
+ const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
212
+ const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
213
+ if (!decrypted) {
214
+ return null;
215
+ }
216
+ return JSON.parse(new TextDecoder().decode(decrypted));
217
+ }
218
+
219
+ const defaultSettings = {
220
+ onboardingCompleted: false
221
+ };
222
+ async function readSettings() {
223
+ if (!existsSync(configuration.settingsFile)) {
224
+ return { ...defaultSettings };
225
+ }
226
+ try {
227
+ const content = await readFile(configuration.settingsFile, "utf8");
228
+ return JSON.parse(content);
229
+ } catch {
230
+ return { ...defaultSettings };
231
+ }
232
+ }
233
+ async function updateSettings(updater) {
234
+ const LOCK_RETRY_INTERVAL_MS = 100;
235
+ const MAX_LOCK_ATTEMPTS = 50;
236
+ const STALE_LOCK_TIMEOUT_MS = 1e4;
237
+ const lockFile = configuration.settingsFile + ".lock";
238
+ const tmpFile = configuration.settingsFile + ".tmp";
239
+ let fileHandle;
240
+ let attempts = 0;
241
+ while (attempts < MAX_LOCK_ATTEMPTS) {
242
+ try {
243
+ fileHandle = await open(lockFile, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY);
244
+ break;
245
+ } catch (err) {
246
+ if (err.code === "EEXIST") {
247
+ attempts++;
248
+ await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
249
+ try {
250
+ const stats = await stat(lockFile);
251
+ if (Date.now() - stats.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
252
+ await unlink(lockFile).catch(() => {
253
+ });
254
+ }
255
+ } catch {
256
+ }
257
+ } else {
258
+ throw err;
259
+ }
260
+ }
261
+ }
262
+ if (!fileHandle) {
263
+ throw new Error(`Failed to acquire settings lock after ${MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS / 1e3} seconds`);
264
+ }
265
+ try {
266
+ const current = await readSettings() || { ...defaultSettings };
267
+ const updated = await updater(current);
268
+ if (!existsSync(configuration.happyHomeDir)) {
269
+ await mkdir(configuration.happyHomeDir, { recursive: true });
270
+ }
271
+ await writeFile(tmpFile, JSON.stringify(updated, null, 2));
272
+ await rename(tmpFile, configuration.settingsFile);
273
+ return updated;
274
+ } finally {
275
+ await fileHandle.close();
276
+ await unlink(lockFile).catch(() => {
277
+ });
278
+ }
279
+ }
280
+ const credentialsSchema = z.object({
281
+ secret: z.string().base64(),
282
+ token: z.string()
283
+ });
284
+ async function readCredentials() {
285
+ if (!existsSync(configuration.privateKeyFile)) {
286
+ return null;
287
+ }
288
+ try {
289
+ const keyBase64 = await readFile(configuration.privateKeyFile, "utf8");
290
+ const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
291
+ return {
292
+ secret: new Uint8Array(Buffer.from(credentials.secret, "base64")),
293
+ token: credentials.token
294
+ };
295
+ } catch {
296
+ return null;
297
+ }
298
+ }
299
+ async function writeCredentials(credentials) {
300
+ if (!existsSync(configuration.happyHomeDir)) {
301
+ await mkdir(configuration.happyHomeDir, { recursive: true });
302
+ }
303
+ await writeFile(configuration.privateKeyFile, JSON.stringify({
304
+ secret: encodeBase64(credentials.secret),
305
+ token: credentials.token
306
+ }, null, 2));
307
+ }
308
+ async function clearCredentials() {
309
+ if (existsSync(configuration.privateKeyFile)) {
310
+ await unlink(configuration.privateKeyFile);
311
+ }
312
+ }
313
+ async function clearMachineId() {
314
+ await updateSettings((settings) => ({
315
+ ...settings,
316
+ machineId: void 0
317
+ }));
318
+ }
319
+ async function readDaemonState() {
320
+ try {
321
+ if (!existsSync(configuration.daemonStateFile)) {
322
+ return null;
323
+ }
324
+ const content = await readFile(configuration.daemonStateFile, "utf-8");
325
+ return JSON.parse(content);
326
+ } catch (error) {
327
+ console.error(`[PERSISTENCE] Daemon state file corrupted: ${configuration.daemonStateFile}`, error);
328
+ return null;
329
+ }
330
+ }
331
+ function writeDaemonState(state) {
332
+ writeFileSync(configuration.daemonStateFile, JSON.stringify(state, null, 2), "utf-8");
333
+ }
334
+ async function clearDaemonState() {
335
+ if (existsSync(configuration.daemonStateFile)) {
336
+ await unlink(configuration.daemonStateFile);
337
+ }
338
+ if (existsSync(configuration.daemonLockFile)) {
339
+ try {
340
+ await unlink(configuration.daemonLockFile);
341
+ } catch {
342
+ }
343
+ }
344
+ }
345
+ async function acquireDaemonLock(maxAttempts = 5, delayIncrementMs = 200) {
346
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
347
+ try {
348
+ const fileHandle = await open(
349
+ configuration.daemonLockFile,
350
+ constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY
351
+ );
352
+ await fileHandle.writeFile(String(process.pid));
353
+ return fileHandle;
354
+ } catch (error) {
355
+ if (error.code === "EEXIST") {
356
+ try {
357
+ const lockPid = readFileSync(configuration.daemonLockFile, "utf-8").trim();
358
+ if (lockPid && !isNaN(Number(lockPid))) {
359
+ try {
360
+ process.kill(Number(lockPid), 0);
361
+ } catch {
362
+ unlinkSync(configuration.daemonLockFile);
363
+ continue;
364
+ }
365
+ }
366
+ } catch {
367
+ }
368
+ }
369
+ if (attempt === maxAttempts) {
370
+ return null;
371
+ }
372
+ const delayMs = attempt * delayIncrementMs;
373
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
374
+ }
375
+ }
376
+ return null;
377
+ }
378
+ async function releaseDaemonLock(lockHandle) {
379
+ try {
380
+ await lockHandle.close();
381
+ } catch {
382
+ }
383
+ try {
384
+ if (existsSync(configuration.daemonLockFile)) {
385
+ unlinkSync(configuration.daemonLockFile);
386
+ }
387
+ } catch {
388
+ }
389
+ }
390
+
46
391
  function createTimestampForFilename(date = /* @__PURE__ */ new Date()) {
47
392
  return date.toLocaleString("sv-SE", {
48
393
  timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
@@ -52,7 +397,7 @@ function createTimestampForFilename(date = /* @__PURE__ */ new Date()) {
52
397
  hour: "2-digit",
53
398
  minute: "2-digit",
54
399
  second: "2-digit"
55
- }).replace(/[: ]/g, "-").replace(/,/g, "");
400
+ }).replace(/[: ]/g, "-").replace(/,/g, "") + "-pid-" + process.pid;
56
401
  }
57
402
  function createTimestampForLogEntry(date = /* @__PURE__ */ new Date()) {
58
403
  return date.toLocaleTimeString("en-US", {
@@ -64,17 +409,14 @@ function createTimestampForLogEntry(date = /* @__PURE__ */ new Date()) {
64
409
  fractionalSecondDigits: 3
65
410
  });
66
411
  }
67
- async function getSessionLogPath() {
68
- if (!existsSync(configuration.logsDir)) {
69
- await mkdir(configuration.logsDir, { recursive: true });
70
- }
412
+ function getSessionLogPath() {
71
413
  const timestamp = createTimestampForFilename();
72
414
  const filename = configuration.isDaemonProcess ? `${timestamp}-daemon.log` : `${timestamp}.log`;
73
415
  return join(configuration.logsDir, filename);
74
416
  }
75
417
  class Logger {
76
- constructor(logFilePathPromise = getSessionLogPath()) {
77
- this.logFilePathPromise = logFilePathPromise;
418
+ constructor(logFilePath = getSessionLogPath()) {
419
+ this.logFilePath = logFilePath;
78
420
  if (process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING && process.env.HAPPY_SERVER_URL) {
79
421
  this.dangerouslyUnencryptedServerLoggingUrl = process.env.HAPPY_SERVER_URL;
80
422
  console.log(chalk.yellow("[REMOTE LOGGING] Sending logs to server for AI debugging"));
@@ -187,218 +529,214 @@ class Logger {
187
529
  this.sendToRemoteServer(level, message, ...args).catch(() => {
188
530
  });
189
531
  }
190
- this.logFilePathPromise.then((logFilePath) => {
191
- try {
192
- appendFileSync(logFilePath, logLine);
193
- } catch (appendError) {
194
- if (process.env.DEBUG) {
195
- console.error("Failed to append to log file:", appendError);
196
- throw appendError;
197
- }
198
- }
199
- }).catch((error) => {
532
+ try {
533
+ appendFileSync(this.logFilePath, logLine);
534
+ } catch (appendError) {
200
535
  if (process.env.DEBUG) {
201
- console.log("This message only visible in DEBUG mode, not in production");
202
- console.error("Failed to resolve log file path:", error);
203
- console.log(prefix, message, ...args);
536
+ console.error("Failed to append to log file:", appendError);
537
+ throw appendError;
204
538
  }
205
- });
539
+ }
206
540
  }
207
541
  }
208
542
  let logger = new Logger();
543
+ async function listDaemonLogFiles(limit = 50) {
544
+ try {
545
+ const logsDir = configuration.logsDir;
546
+ if (!existsSync(logsDir)) {
547
+ return [];
548
+ }
549
+ const logs = readdirSync(logsDir).filter((file) => file.endsWith("-daemon.log")).map((file) => {
550
+ const fullPath = join(logsDir, file);
551
+ const stats = statSync(fullPath);
552
+ return { file, path: fullPath, modified: stats.mtime };
553
+ }).sort((a, b) => b.modified.getTime() - a.modified.getTime());
554
+ try {
555
+ const state = await readDaemonState();
556
+ if (!state) {
557
+ return logs;
558
+ }
559
+ if (state.daemonLogPath && existsSync(state.daemonLogPath)) {
560
+ const stats = statSync(state.daemonLogPath);
561
+ const persisted = {
562
+ file: basename(state.daemonLogPath),
563
+ path: state.daemonLogPath,
564
+ modified: stats.mtime
565
+ };
566
+ const idx = logs.findIndex((l) => l.path === persisted.path);
567
+ if (idx >= 0) {
568
+ const [found] = logs.splice(idx, 1);
569
+ logs.unshift(found);
570
+ } else {
571
+ logs.unshift(persisted);
572
+ }
573
+ }
574
+ } catch {
575
+ }
576
+ return logs.slice(0, Math.max(0, limit));
577
+ } catch {
578
+ return [];
579
+ }
580
+ }
581
+ async function getLatestDaemonLog() {
582
+ const [latest] = await listDaemonLogFiles(1);
583
+ return latest || null;
584
+ }
209
585
 
210
- const SessionMessageContentSchema = z.object({
211
- c: z.string(),
586
+ const SessionMessageContentSchema = z$1.object({
587
+ c: z$1.string(),
212
588
  // Base64 encoded encrypted content
213
- t: z.literal("encrypted")
589
+ t: z$1.literal("encrypted")
214
590
  });
215
- const UpdateBodySchema = z.object({
216
- message: z.object({
217
- id: z.string(),
218
- seq: z.number(),
591
+ const UpdateBodySchema = z$1.object({
592
+ message: z$1.object({
593
+ id: z$1.string(),
594
+ seq: z$1.number(),
219
595
  content: SessionMessageContentSchema
220
596
  }),
221
- sid: z.string(),
597
+ sid: z$1.string(),
222
598
  // Session ID
223
- t: z.literal("new-message")
599
+ t: z$1.literal("new-message")
224
600
  });
225
- const UpdateSessionBodySchema = z.object({
226
- t: z.literal("update-session"),
227
- sid: z.string(),
228
- metadata: z.object({
229
- version: z.number(),
230
- value: z.string()
601
+ const UpdateSessionBodySchema = z$1.object({
602
+ t: z$1.literal("update-session"),
603
+ sid: z$1.string(),
604
+ metadata: z$1.object({
605
+ version: z$1.number(),
606
+ value: z$1.string()
231
607
  }).nullish(),
232
- agentState: z.object({
233
- version: z.number(),
234
- value: z.string()
608
+ agentState: z$1.object({
609
+ version: z$1.number(),
610
+ value: z$1.string()
235
611
  }).nullish()
236
612
  });
237
- const UpdateMachineBodySchema = z.object({
238
- t: z.literal("update-machine"),
239
- machineId: z.string(),
240
- metadata: z.object({
241
- version: z.number(),
242
- value: z.string()
613
+ const UpdateMachineBodySchema = z$1.object({
614
+ t: z$1.literal("update-machine"),
615
+ machineId: z$1.string(),
616
+ metadata: z$1.object({
617
+ version: z$1.number(),
618
+ value: z$1.string()
243
619
  }).nullish(),
244
- daemonState: z.object({
245
- version: z.number(),
246
- value: z.string()
620
+ daemonState: z$1.object({
621
+ version: z$1.number(),
622
+ value: z$1.string()
247
623
  }).nullish()
248
624
  });
249
- z.object({
250
- id: z.string(),
251
- seq: z.number(),
252
- body: z.union([
625
+ z$1.object({
626
+ id: z$1.string(),
627
+ seq: z$1.number(),
628
+ body: z$1.union([
253
629
  UpdateBodySchema,
254
630
  UpdateSessionBodySchema,
255
631
  UpdateMachineBodySchema
256
632
  ]),
257
- createdAt: z.number()
633
+ createdAt: z$1.number()
258
634
  });
259
- z.object({
260
- createdAt: z.number(),
261
- id: z.string(),
262
- seq: z.number(),
263
- updatedAt: z.number(),
264
- metadata: z.any(),
265
- metadataVersion: z.number(),
266
- agentState: z.any().nullable(),
267
- agentStateVersion: z.number()
635
+ z$1.object({
636
+ createdAt: z$1.number(),
637
+ id: z$1.string(),
638
+ seq: z$1.number(),
639
+ updatedAt: z$1.number(),
640
+ metadata: z$1.any(),
641
+ metadataVersion: z$1.number(),
642
+ agentState: z$1.any().nullable(),
643
+ agentStateVersion: z$1.number()
268
644
  });
269
- z.object({
270
- host: z.string(),
271
- platform: z.string(),
272
- happyCliVersion: z.string(),
273
- homeDir: z.string(),
274
- happyHomeDir: z.string()
645
+ z$1.object({
646
+ host: z$1.string(),
647
+ platform: z$1.string(),
648
+ happyCliVersion: z$1.string(),
649
+ homeDir: z$1.string(),
650
+ happyHomeDir: z$1.string()
275
651
  });
276
- z.object({
277
- status: z.union([
278
- z.enum(["running", "shutting-down"]),
279
- z.string()
652
+ z$1.object({
653
+ status: z$1.union([
654
+ z$1.enum(["running", "shutting-down"]),
655
+ z$1.string()
280
656
  // Forward compatibility
281
657
  ]),
282
- pid: z.number().optional(),
283
- httpPort: z.number().optional(),
284
- startedAt: z.number().optional(),
285
- shutdownRequestedAt: z.number().optional(),
286
- shutdownSource: z.union([
287
- z.enum(["mobile-app", "cli", "os-signal", "unknown"]),
288
- z.string()
658
+ pid: z$1.number().optional(),
659
+ httpPort: z$1.number().optional(),
660
+ startedAt: z$1.number().optional(),
661
+ shutdownRequestedAt: z$1.number().optional(),
662
+ shutdownSource: z$1.union([
663
+ z$1.enum(["mobile-app", "cli", "os-signal", "unknown"]),
664
+ z$1.string()
289
665
  // Forward compatibility
290
666
  ]).optional()
291
667
  });
292
- z.object({
293
- id: z.string(),
294
- metadata: z.any(),
668
+ z$1.object({
669
+ id: z$1.string(),
670
+ metadata: z$1.any(),
295
671
  // Decrypted MachineMetadata
296
- metadataVersion: z.number(),
297
- daemonState: z.any().nullable(),
672
+ metadataVersion: z$1.number(),
673
+ daemonState: z$1.any().nullable(),
298
674
  // Decrypted DaemonState
299
- daemonStateVersion: z.number(),
675
+ daemonStateVersion: z$1.number(),
300
676
  // We don't really care about these on the CLI for now
301
677
  // ApiMachineClient will not sync these
302
- active: z.boolean(),
303
- activeAt: z.number(),
304
- createdAt: z.number(),
305
- updatedAt: z.number()
678
+ active: z$1.boolean(),
679
+ activeAt: z$1.number(),
680
+ createdAt: z$1.number(),
681
+ updatedAt: z$1.number()
306
682
  });
307
- z.object({
683
+ z$1.object({
308
684
  content: SessionMessageContentSchema,
309
- createdAt: z.number(),
310
- id: z.string(),
311
- seq: z.number(),
312
- updatedAt: z.number()
685
+ createdAt: z$1.number(),
686
+ id: z$1.string(),
687
+ seq: z$1.number(),
688
+ updatedAt: z$1.number()
313
689
  });
314
- const MessageMetaSchema = z.object({
315
- sentFrom: z.string().optional(),
690
+ const MessageMetaSchema = z$1.object({
691
+ sentFrom: z$1.string().optional(),
316
692
  // Source identifier
317
- permissionMode: z.string().optional(),
693
+ permissionMode: z$1.string().optional(),
318
694
  // Permission mode for this message
319
- model: z.string().nullable().optional(),
695
+ model: z$1.string().nullable().optional(),
320
696
  // Model name for this message (null = reset)
321
- fallbackModel: z.string().nullable().optional(),
697
+ fallbackModel: z$1.string().nullable().optional(),
322
698
  // Fallback model for this message (null = reset)
323
- customSystemPrompt: z.string().nullable().optional(),
699
+ customSystemPrompt: z$1.string().nullable().optional(),
324
700
  // Custom system prompt for this message (null = reset)
325
- appendSystemPrompt: z.string().nullable().optional(),
701
+ appendSystemPrompt: z$1.string().nullable().optional(),
326
702
  // Append to system prompt for this message (null = reset)
327
- allowedTools: z.array(z.string()).nullable().optional(),
703
+ allowedTools: z$1.array(z$1.string()).nullable().optional(),
328
704
  // Allowed tools for this message (null = reset)
329
- disallowedTools: z.array(z.string()).nullable().optional()
705
+ disallowedTools: z$1.array(z$1.string()).nullable().optional()
330
706
  // Disallowed tools for this message (null = reset)
331
707
  });
332
- z.object({
333
- session: z.object({
334
- id: z.string(),
335
- tag: z.string(),
336
- seq: z.number(),
337
- createdAt: z.number(),
338
- updatedAt: z.number(),
339
- metadata: z.string(),
340
- metadataVersion: z.number(),
341
- agentState: z.string().nullable(),
342
- agentStateVersion: z.number()
708
+ z$1.object({
709
+ session: z$1.object({
710
+ id: z$1.string(),
711
+ tag: z$1.string(),
712
+ seq: z$1.number(),
713
+ createdAt: z$1.number(),
714
+ updatedAt: z$1.number(),
715
+ metadata: z$1.string(),
716
+ metadataVersion: z$1.number(),
717
+ agentState: z$1.string().nullable(),
718
+ agentStateVersion: z$1.number()
343
719
  })
344
720
  });
345
- const UserMessageSchema = z.object({
346
- role: z.literal("user"),
347
- content: z.object({
348
- type: z.literal("text"),
349
- text: z.string()
721
+ const UserMessageSchema = z$1.object({
722
+ role: z$1.literal("user"),
723
+ content: z$1.object({
724
+ type: z$1.literal("text"),
725
+ text: z$1.string()
350
726
  }),
351
- localKey: z.string().optional(),
727
+ localKey: z$1.string().optional(),
352
728
  // Mobile messages include this
353
729
  meta: MessageMetaSchema.optional()
354
730
  });
355
- const AgentMessageSchema = z.object({
356
- role: z.literal("agent"),
357
- content: z.object({
358
- type: z.literal("output"),
359
- data: z.any()
731
+ const AgentMessageSchema = z$1.object({
732
+ role: z$1.literal("agent"),
733
+ content: z$1.object({
734
+ type: z$1.literal("output"),
735
+ data: z$1.any()
360
736
  }),
361
737
  meta: MessageMetaSchema.optional()
362
738
  });
363
- z.union([UserMessageSchema, AgentMessageSchema]);
364
-
365
- function encodeBase64(buffer, variant = "base64") {
366
- if (variant === "base64url") {
367
- return encodeBase64Url(buffer);
368
- }
369
- return Buffer.from(buffer).toString("base64");
370
- }
371
- function encodeBase64Url(buffer) {
372
- return Buffer.from(buffer).toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
373
- }
374
- function decodeBase64(base64, variant = "base64") {
375
- if (variant === "base64url") {
376
- const base64Standard = base64.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - base64.length % 4) % 4);
377
- return new Uint8Array(Buffer.from(base64Standard, "base64"));
378
- }
379
- return new Uint8Array(Buffer.from(base64, "base64"));
380
- }
381
- function getRandomBytes(size) {
382
- return new Uint8Array(randomBytes(size));
383
- }
384
- function encrypt(data, secret) {
385
- const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
386
- const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
387
- const result = new Uint8Array(nonce.length + encrypted.length);
388
- result.set(nonce);
389
- result.set(encrypted, nonce.length);
390
- return result;
391
- }
392
- function decrypt(data, secret) {
393
- const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
394
- const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
395
- const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
396
- if (!decrypted) {
397
- logger.debug("[ERROR] Decryption failed");
398
- return null;
399
- }
400
- return JSON.parse(new TextDecoder().decode(decrypted));
401
- }
739
+ z$1.union([UserMessageSchema, AgentMessageSchema]);
402
740
 
403
741
  async function delay(ms) {
404
742
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -653,6 +991,7 @@ class ApiSessionClient extends EventEmitter {
653
991
  * Send a ping message to keep the connection alive
654
992
  */
655
993
  keepAlive(thinking, mode) {
994
+ logger.debug(`[API] Sending keep alive message: ${thinking}`);
656
995
  this.socket.volatile.emit("session-alive", {
657
996
  sid: this.sessionId,
658
997
  time: Date.now(),
@@ -905,11 +1244,17 @@ class ApiMachineClient {
905
1244
  if (!session) {
906
1245
  throw new Error("Failed to spawn session");
907
1246
  }
908
- logger.debug(`[API MACHINE] Spawned session ${session.happySessionId || "pending"} with PID ${session.pid}`);
1247
+ if (session.error) {
1248
+ throw new Error(session.error);
1249
+ }
1250
+ logger.debug(`[API MACHINE] Spawned session ${session.happySessionId || "WARNING - not session Id recieved in webhook"} with PID ${session.pid}`);
909
1251
  if (!session.happySessionId) {
910
1252
  throw new Error(`Session spawned (PID ${session.pid}) but no sessionId received from webhook. The session process may still be initializing.`);
911
1253
  }
912
- const response = { sessionId: session.happySessionId };
1254
+ const response = {
1255
+ sessionId: session.happySessionId,
1256
+ message: session.message
1257
+ };
913
1258
  logger.debug(`[API MACHINE] Sending RPC response:`, response);
914
1259
  callback(encodeBase64(encrypt(response, this.secret)));
915
1260
  return;
@@ -995,9 +1340,7 @@ class ApiMachineClient {
995
1340
  machineId: this.machine.id,
996
1341
  time: Date.now()
997
1342
  };
998
- if (process.env.VERBOSE) {
999
- logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
1000
- }
1343
+ logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
1001
1344
  this.socket.emit("machine-alive", payload);
1002
1345
  }, 2e4);
1003
1346
  logger.debug("[API MACHINE] Keep-alive started (20s interval)");
@@ -1243,6 +1586,11 @@ class ApiClient {
1243
1586
  timeout: 5e3
1244
1587
  }
1245
1588
  );
1589
+ if (response.status !== 200) {
1590
+ console.error(chalk.red(`[API] Failed to create machine: ${response.statusText}`));
1591
+ 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.`));
1592
+ process.exit(1);
1593
+ }
1246
1594
  const raw = response.data.machine;
1247
1595
  logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
1248
1596
  const machine = {
@@ -1269,52 +1617,52 @@ class ApiClient {
1269
1617
  }
1270
1618
  }
1271
1619
 
1272
- const UsageSchema = z.object({
1273
- input_tokens: z.number().int().nonnegative(),
1274
- cache_creation_input_tokens: z.number().int().nonnegative().optional(),
1275
- cache_read_input_tokens: z.number().int().nonnegative().optional(),
1276
- output_tokens: z.number().int().nonnegative(),
1277
- service_tier: z.string().optional()
1620
+ const UsageSchema = z$1.object({
1621
+ input_tokens: z$1.number().int().nonnegative(),
1622
+ cache_creation_input_tokens: z$1.number().int().nonnegative().optional(),
1623
+ cache_read_input_tokens: z$1.number().int().nonnegative().optional(),
1624
+ output_tokens: z$1.number().int().nonnegative(),
1625
+ service_tier: z$1.string().optional()
1278
1626
  }).passthrough();
1279
- const RawJSONLinesSchema = z.discriminatedUnion("type", [
1627
+ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
1280
1628
  // User message - validates uuid and message.content
1281
- z.object({
1282
- type: z.literal("user"),
1283
- isSidechain: z.boolean().optional(),
1284
- isMeta: z.boolean().optional(),
1285
- uuid: z.string(),
1629
+ z$1.object({
1630
+ type: z$1.literal("user"),
1631
+ isSidechain: z$1.boolean().optional(),
1632
+ isMeta: z$1.boolean().optional(),
1633
+ uuid: z$1.string(),
1286
1634
  // Used in getMessageKey()
1287
- message: z.object({
1288
- content: z.union([z.string(), z.any()])
1635
+ message: z$1.object({
1636
+ content: z$1.union([z$1.string(), z$1.any()])
1289
1637
  // Used in sessionScanner.ts
1290
1638
  }).passthrough()
1291
1639
  }).passthrough(),
1292
1640
  // Assistant message - validates message object with usage and content
1293
- z.object({
1294
- uuid: z.string(),
1295
- type: z.literal("assistant"),
1296
- message: z.object({
1641
+ z$1.object({
1642
+ uuid: z$1.string(),
1643
+ type: z$1.literal("assistant"),
1644
+ message: z$1.object({
1297
1645
  // Entire message used in getMessageKey()
1298
1646
  usage: UsageSchema.optional(),
1299
1647
  // Used in apiSession.ts
1300
- content: z.any()
1648
+ content: z$1.any()
1301
1649
  // Used in tests
1302
1650
  }).passthrough()
1303
1651
  }).passthrough(),
1304
1652
  // Summary message - validates summary and leafUuid
1305
- z.object({
1306
- type: z.literal("summary"),
1307
- summary: z.string(),
1653
+ z$1.object({
1654
+ type: z$1.literal("summary"),
1655
+ summary: z$1.string(),
1308
1656
  // Used in apiSession.ts
1309
- leafUuid: z.string()
1657
+ leafUuid: z$1.string()
1310
1658
  // Used in getMessageKey()
1311
1659
  }).passthrough(),
1312
1660
  // System message - validates uuid
1313
- z.object({
1314
- type: z.literal("system"),
1315
- uuid: z.string()
1661
+ z$1.object({
1662
+ type: z$1.literal("system"),
1663
+ uuid: z$1.string()
1316
1664
  // Used in getMessageKey()
1317
1665
  }).passthrough()
1318
1666
  ]);
1319
1667
 
1320
- export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, backoff as b, configuration as c, delay as d, AsyncLock as e, encodeBase64 as f, encodeBase64Url as g, decodeBase64 as h, logger as l };
1668
+ export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, backoff as b, configuration as c, delay as d, AsyncLock as e, clearDaemonState as f, readSettings as g, readCredentials as h, encodeBase64 as i, encodeBase64Url as j, decodeBase64 as k, logger as l, acquireDaemonLock as m, writeDaemonState as n, releaseDaemonLock as o, packageJson as p, clearCredentials as q, readDaemonState as r, clearMachineId as s, getLatestDaemonLog as t, updateSettings as u, writeCredentials as w };