happy-coder 0.1.6 → 0.1.9

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 (57) hide show
  1. package/dist/index.cjs +354 -917
  2. package/dist/index.mjs +280 -843
  3. package/dist/install-B2r_gX72.cjs +109 -0
  4. package/dist/install-HKe7dyS4.mjs +107 -0
  5. package/dist/lib.cjs +32 -0
  6. package/dist/lib.d.cts +727 -0
  7. package/dist/lib.d.mts +727 -0
  8. package/dist/lib.mjs +14 -0
  9. package/dist/run-FBXkmmN7.mjs +32 -0
  10. package/dist/run-q2To6b-c.cjs +34 -0
  11. package/dist/types-fXgEaaqP.mjs +861 -0
  12. package/dist/types-mykDX2xe.cjs +872 -0
  13. package/dist/uninstall-C42CoSCI.cjs +53 -0
  14. package/dist/uninstall-CLkTtlMv.mjs +51 -0
  15. package/package.json +28 -13
  16. package/dist/auth/auth.d.ts +0 -38
  17. package/dist/auth/auth.js +0 -76
  18. package/dist/auth/auth.test.d.ts +0 -7
  19. package/dist/auth/auth.test.js +0 -96
  20. package/dist/auth/crypto.d.ts +0 -25
  21. package/dist/auth/crypto.js +0 -36
  22. package/dist/claude/claude.d.ts +0 -54
  23. package/dist/claude/claude.js +0 -170
  24. package/dist/claude/claude.test.d.ts +0 -7
  25. package/dist/claude/claude.test.js +0 -130
  26. package/dist/claude/types.d.ts +0 -37
  27. package/dist/claude/types.js +0 -7
  28. package/dist/commands/start.d.ts +0 -38
  29. package/dist/commands/start.js +0 -161
  30. package/dist/commands/start.test.d.ts +0 -7
  31. package/dist/commands/start.test.js +0 -307
  32. package/dist/handlers/message-handler.d.ts +0 -65
  33. package/dist/handlers/message-handler.js +0 -187
  34. package/dist/index.d.ts +0 -1
  35. package/dist/index.js +0 -1
  36. package/dist/session/service.d.ts +0 -27
  37. package/dist/session/service.js +0 -93
  38. package/dist/session/service.test.d.ts +0 -7
  39. package/dist/session/service.test.js +0 -71
  40. package/dist/session/types.d.ts +0 -44
  41. package/dist/session/types.js +0 -4
  42. package/dist/socket/client.d.ts +0 -50
  43. package/dist/socket/client.js +0 -136
  44. package/dist/socket/client.test.d.ts +0 -7
  45. package/dist/socket/client.test.js +0 -74
  46. package/dist/socket/types.d.ts +0 -80
  47. package/dist/socket/types.js +0 -12
  48. package/dist/utils/config.d.ts +0 -22
  49. package/dist/utils/config.js +0 -23
  50. package/dist/utils/logger.d.ts +0 -26
  51. package/dist/utils/logger.js +0 -60
  52. package/dist/utils/paths.d.ts +0 -18
  53. package/dist/utils/paths.js +0 -24
  54. package/dist/utils/qrcode.d.ts +0 -19
  55. package/dist/utils/qrcode.js +0 -37
  56. package/dist/utils/qrcode.test.d.ts +0 -7
  57. package/dist/utils/qrcode.test.js +0 -14
package/dist/index.cjs CHANGED
@@ -1,740 +1,52 @@
1
1
  'use strict';
2
2
 
3
3
  var chalk = require('chalk');
4
- var axios = require('axios');
5
- var fs = require('fs');
6
- var os = require('node:os');
7
- var node_path = require('node:path');
8
- var promises = require('node:fs/promises');
9
- var node_fs = require('node:fs');
10
- var node_events = require('node:events');
11
- var socket_ioClient = require('socket.io-client');
12
- var z = require('zod');
4
+ var types = require('./types-mykDX2xe.cjs');
13
5
  var node_crypto = require('node:crypto');
14
- var tweetnacl = require('tweetnacl');
15
- var expoServerSdk = require('expo-server-sdk');
16
6
  var claudeCode = require('@anthropic-ai/claude-code');
7
+ var node_fs = require('node:fs');
8
+ var os = require('node:os');
9
+ var node_path = require('node:path');
17
10
  var node_child_process = require('node:child_process');
18
11
  var node_readline = require('node:readline');
19
12
  var node_url = require('node:url');
13
+ var promises = require('node:fs/promises');
20
14
  var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
21
15
  var node_http = require('node:http');
22
16
  var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
17
+ var z = require('zod');
18
+ var node_https = require('node:https');
19
+ var net = require('node:net');
20
+ var tweetnacl = require('tweetnacl');
21
+ var axios = require('axios');
23
22
  var qrcode = require('qrcode-terminal');
23
+ require('fs');
24
+ require('node:events');
25
+ require('socket.io-client');
26
+ require('expo-server-sdk');
24
27
 
25
28
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
26
29
  function _interopNamespaceDefault(e) {
27
- var n = Object.create(null);
28
- if (e) {
29
- Object.keys(e).forEach(function (k) {
30
- if (k !== 'default') {
31
- var d = Object.getOwnPropertyDescriptor(e, k);
32
- Object.defineProperty(n, k, d.get ? d : {
33
- enumerable: true,
34
- get: function () { return e[k]; }
30
+ var n = Object.create(null);
31
+ if (e) {
32
+ Object.keys(e).forEach(function (k) {
33
+ if (k !== 'default') {
34
+ var d = Object.getOwnPropertyDescriptor(e, k);
35
+ Object.defineProperty(n, k, d.get ? d : {
36
+ enumerable: true,
37
+ get: function () { return e[k]; }
38
+ });
39
+ }
35
40
  });
36
- }
37
- });
38
- }
39
- n.default = e;
40
- return Object.freeze(n);
41
- }
42
-
43
- var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
44
-
45
- class Configuration {
46
- serverUrl;
47
- // Directories and paths (from persistence)
48
- happyDir;
49
- logsDir;
50
- settingsFile;
51
- privateKeyFile;
52
- constructor(location) {
53
- this.serverUrl = process.env.HANDY_SERVER_URL || "https://handy-api.korshakov.org";
54
- if (location === "local") {
55
- this.happyDir = node_path.join(process.cwd(), ".happy");
56
- } else {
57
- this.happyDir = node_path.join(os.homedir(), ".happy");
58
- }
59
- this.logsDir = node_path.join(this.happyDir, "logs");
60
- this.settingsFile = node_path.join(this.happyDir, "settings.json");
61
- this.privateKeyFile = node_path.join(this.happyDir, "access.key");
62
- }
63
- }
64
- let configuration = void 0;
65
- function initializeConfiguration(location) {
66
- configuration = new Configuration(location);
67
- }
68
-
69
- async function getSessionLogPath() {
70
- if (!node_fs.existsSync(configuration.logsDir)) {
71
- await promises.mkdir(configuration.logsDir, { recursive: true });
72
- }
73
- const now = /* @__PURE__ */ new Date();
74
- const timestamp = now.toLocaleString("sv-SE", {
75
- timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
76
- year: "numeric",
77
- month: "2-digit",
78
- day: "2-digit",
79
- hour: "2-digit",
80
- minute: "2-digit",
81
- second: "2-digit"
82
- }).replace(/[: ]/g, "-").replace(/,/g, "");
83
- return node_path.join(configuration.logsDir, `${timestamp}.log`);
84
- }
85
- class Logger {
86
- constructor(logFilePathPromise = getSessionLogPath()) {
87
- this.logFilePathPromise = logFilePathPromise;
88
- }
89
- // Use local timezone for simplicity of locating the logs,
90
- // in practice you will not need absolute timestamps
91
- localTimezoneTimestamp() {
92
- return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
93
- timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
94
- hour12: false,
95
- hour: "2-digit",
96
- minute: "2-digit",
97
- second: "2-digit",
98
- fractionalSecondDigits: 3
99
- });
100
- }
101
- debug(message, ...args) {
102
- this.logToFile(`[${this.localTimezoneTimestamp()}]`, message, ...args);
103
- }
104
- debugLargeJson(message, object, maxStringLength = 100, maxArrayLength = 10) {
105
- if (!process.env.DEBUG) {
106
- this.debug(`In production, skipping message inspection`);
107
- }
108
- const truncateStrings = (obj) => {
109
- if (typeof obj === "string") {
110
- return obj.length > maxStringLength ? obj.substring(0, maxStringLength) + "... [truncated for logs]" : obj;
111
- }
112
- if (Array.isArray(obj)) {
113
- const truncatedArray = obj.map((item) => truncateStrings(item)).slice(0, maxArrayLength);
114
- if (obj.length > maxArrayLength) {
115
- truncatedArray.push(`... [truncated array for logs up to ${maxArrayLength} items]`);
116
- }
117
- return truncatedArray;
118
- }
119
- if (obj && typeof obj === "object") {
120
- const result = {};
121
- for (const [key, value] of Object.entries(obj)) {
122
- if (key === "usage") {
123
- continue;
124
- }
125
- result[key] = truncateStrings(value);
126
- }
127
- return result;
128
- }
129
- return obj;
130
- };
131
- const truncatedObject = truncateStrings(object);
132
- const json = JSON.stringify(truncatedObject, null, 2);
133
- this.logToFile(`[${this.localTimezoneTimestamp()}]`, message, "\n", json);
134
- }
135
- info(message, ...args) {
136
- this.logToConsole("info", "", message, ...args);
137
- this.debug(message, args);
138
- }
139
- logToConsole(level, prefix, message, ...args) {
140
- switch (level) {
141
- case "debug": {
142
- console.log(chalk.gray(prefix), message, ...args);
143
- break;
144
- }
145
- case "error": {
146
- console.error(chalk.red(prefix), message, ...args);
147
- break;
148
- }
149
- case "info": {
150
- console.log(chalk.blue(prefix), message, ...args);
151
- break;
152
- }
153
- case "warn": {
154
- console.log(chalk.yellow(prefix), message, ...args);
155
- break;
156
- }
157
- default: {
158
- this.debug("Unknown log level:", level);
159
- console.log(chalk.blue(prefix), message, ...args);
160
- break;
161
- }
162
- }
163
- }
164
- logToFile(prefix, message, ...args) {
165
- const logLine = `${prefix} ${message} ${args.map(
166
- (arg) => typeof arg === "string" ? arg : JSON.stringify(arg)
167
- ).join(" ")}
168
- `;
169
- this.logFilePathPromise.then((logFilePath) => {
170
- try {
171
- fs.appendFileSync(logFilePath, logLine);
172
- } catch (appendError) {
173
- if (process.env.DEBUG) {
174
- console.error("Failed to append to log file:", appendError);
175
- throw appendError;
176
- }
177
- }
178
- }).catch((error) => {
179
- if (process.env.DEBUG) {
180
- console.log("This message only visible in DEBUG mode, not in production");
181
- console.error("Failed to resolve log file path:", error);
182
- console.log(prefix, message, ...args);
183
- }
184
- });
185
- }
186
- }
187
- let logger;
188
- function initLoggerWithGlobalConfiguration() {
189
- logger = new Logger();
190
- }
191
-
192
- const SessionMessageContentSchema = z.z.object({
193
- c: z.z.string(),
194
- // Base64 encoded encrypted content
195
- t: z.z.literal("encrypted")
196
- });
197
- const UpdateBodySchema = z.z.object({
198
- message: z.z.object({
199
- id: z.z.string(),
200
- seq: z.z.number(),
201
- content: SessionMessageContentSchema
202
- }),
203
- sid: z.z.string(),
204
- // Session ID
205
- t: z.z.literal("new-message")
206
- });
207
- const UpdateSessionBodySchema = z.z.object({
208
- t: z.z.literal("update-session"),
209
- sid: z.z.string(),
210
- metadata: z.z.object({
211
- version: z.z.number(),
212
- metadata: z.z.string()
213
- }).nullish(),
214
- agentState: z.z.object({
215
- version: z.z.number(),
216
- agentState: z.z.string()
217
- }).nullish()
218
- });
219
- z.z.object({
220
- id: z.z.string(),
221
- seq: z.z.number(),
222
- body: z.z.union([UpdateBodySchema, UpdateSessionBodySchema]),
223
- createdAt: z.z.number()
224
- });
225
- z.z.object({
226
- createdAt: z.z.number(),
227
- id: z.z.string(),
228
- seq: z.z.number(),
229
- updatedAt: z.z.number(),
230
- metadata: z.z.any(),
231
- metadataVersion: z.z.number(),
232
- agentState: z.z.any().nullable(),
233
- agentStateVersion: z.z.number()
234
- });
235
- z.z.object({
236
- content: SessionMessageContentSchema,
237
- createdAt: z.z.number(),
238
- id: z.z.string(),
239
- seq: z.z.number(),
240
- updatedAt: z.z.number()
241
- });
242
- z.z.object({
243
- session: z.z.object({
244
- id: z.z.string(),
245
- tag: z.z.string(),
246
- seq: z.z.number(),
247
- createdAt: z.z.number(),
248
- updatedAt: z.z.number(),
249
- metadata: z.z.string(),
250
- metadataVersion: z.z.number(),
251
- agentState: z.z.string().nullable(),
252
- agentStateVersion: z.z.number()
253
- })
254
- });
255
- const UserMessageSchema$1 = z.z.object({
256
- role: z.z.literal("user"),
257
- content: z.z.object({
258
- type: z.z.literal("text"),
259
- text: z.z.string()
260
- }),
261
- localKey: z.z.string().optional(),
262
- // Mobile messages include this
263
- sentFrom: z.z.enum(["mobile", "cli"]).optional()
264
- // Source identifier
265
- });
266
- const AgentMessageSchema = z.z.object({
267
- role: z.z.literal("agent"),
268
- content: z.z.object({
269
- type: z.z.literal("output"),
270
- data: z.z.any()
271
- })
272
- });
273
- z.z.union([UserMessageSchema$1, AgentMessageSchema]);
274
-
275
- function encodeBase64(buffer) {
276
- return Buffer.from(buffer).toString("base64");
277
- }
278
- function encodeBase64Url(buffer) {
279
- return Buffer.from(buffer).toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
280
- }
281
- function decodeBase64(base64) {
282
- return new Uint8Array(Buffer.from(base64, "base64"));
283
- }
284
- function getRandomBytes(size) {
285
- return new Uint8Array(node_crypto.randomBytes(size));
286
- }
287
- function encrypt(data, secret) {
288
- const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
289
- const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
290
- const result = new Uint8Array(nonce.length + encrypted.length);
291
- result.set(nonce);
292
- result.set(encrypted, nonce.length);
293
- return result;
294
- }
295
- function decrypt(data, secret) {
296
- const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
297
- const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
298
- const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
299
- if (!decrypted) {
300
- return null;
301
- }
302
- return JSON.parse(new TextDecoder().decode(decrypted));
303
- }
304
-
305
- async function delay(ms) {
306
- return new Promise((resolve) => setTimeout(resolve, ms));
307
- }
308
- function exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount) {
309
- let maxDelayRet = minDelay + (maxDelay - minDelay) / maxFailureCount * Math.max(currentFailureCount, maxFailureCount);
310
- return Math.round(Math.random() * maxDelayRet);
311
- }
312
- function createBackoff(opts) {
313
- return async (callback) => {
314
- let currentFailureCount = 0;
315
- const minDelay = 250;
316
- const maxDelay = 1e3;
317
- const maxFailureCount = 50;
318
- while (true) {
319
- try {
320
- return await callback();
321
- } catch (e) {
322
- if (currentFailureCount < maxFailureCount) {
323
- currentFailureCount++;
324
- }
325
- let waitForRequest = exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount);
326
- await delay(waitForRequest);
327
- }
328
- }
329
- };
330
- }
331
- let backoff = createBackoff();
332
-
333
- class ApiSessionClient extends node_events.EventEmitter {
334
- token;
335
- secret;
336
- sessionId;
337
- metadata;
338
- metadataVersion;
339
- agentState;
340
- agentStateVersion;
341
- socket;
342
- pendingMessages = [];
343
- pendingMessageCallback = null;
344
- rpcHandlers = /* @__PURE__ */ new Map();
345
- constructor(token, secret, session) {
346
- super();
347
- this.token = token;
348
- this.secret = secret;
349
- this.sessionId = session.id;
350
- this.metadata = session.metadata;
351
- this.metadataVersion = session.metadataVersion;
352
- this.agentState = session.agentState;
353
- this.agentStateVersion = session.agentStateVersion;
354
- this.socket = socket_ioClient.io(configuration.serverUrl, {
355
- auth: {
356
- token: this.token,
357
- clientType: "session-scoped",
358
- sessionId: this.sessionId
359
- },
360
- path: "/v1/updates",
361
- reconnection: true,
362
- reconnectionAttempts: Infinity,
363
- reconnectionDelay: 1e3,
364
- reconnectionDelayMax: 5e3,
365
- transports: ["websocket"],
366
- withCredentials: true,
367
- autoConnect: false
368
- });
369
- this.socket.on("connect", () => {
370
- logger.debug("Socket connected successfully");
371
- this.reregisterHandlers();
372
- });
373
- this.socket.on("rpc-request", async (data, callback) => {
374
- try {
375
- const method = data.method;
376
- const handler = this.rpcHandlers.get(method);
377
- if (!handler) {
378
- logger.debug("[SOCKET] [RPC] [ERROR] method not found", { method });
379
- const errorResponse = { error: "Method not found" };
380
- const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
381
- callback(encryptedError);
382
- return;
383
- }
384
- const decryptedParams = decrypt(decodeBase64(data.params), this.secret);
385
- const result = await handler(decryptedParams);
386
- const encryptedResponse = encodeBase64(encrypt(result, this.secret));
387
- callback(encryptedResponse);
388
- } catch (error) {
389
- logger.debug("[SOCKET] [RPC] [ERROR] Error handling RPC request", { error });
390
- const errorResponse = { error: error instanceof Error ? error.message : "Unknown error" };
391
- const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
392
- callback(encryptedError);
393
- }
394
- });
395
- this.socket.on("disconnect", (reason) => {
396
- logger.debug("[API] Socket disconnected:", reason);
397
- });
398
- this.socket.on("connect_error", (error) => {
399
- logger.debug("[API] Socket connection error:", error);
400
- });
401
- this.socket.on("update", (data) => {
402
- if (data.body.t === "new-message" && data.body.message.content.t === "encrypted") {
403
- const body = decrypt(decodeBase64(data.body.message.content.c), this.secret);
404
- logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
405
- const userResult = UserMessageSchema$1.safeParse(body);
406
- if (userResult.success) {
407
- if (this.pendingMessageCallback) {
408
- this.pendingMessageCallback(userResult.data);
409
- } else {
410
- this.pendingMessages.push(userResult.data);
411
- }
412
- } else {
413
- this.emit("message", body);
414
- }
415
- } else if (data.body.t === "update-session") {
416
- if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
417
- this.metadata = decrypt(decodeBase64(data.body.metadata.metadata), this.secret);
418
- this.metadataVersion = data.body.metadata.version;
419
- }
420
- if (data.body.agentState && data.body.agentState.version > this.agentStateVersion) {
421
- this.agentState = data.body.agentState.agentState ? decrypt(decodeBase64(data.body.agentState.agentState), this.secret) : null;
422
- this.agentStateVersion = data.body.agentState.version;
423
- }
424
- }
425
- });
426
- this.socket.on("error", (error) => {
427
- logger.debug("[API] Socket error:", error);
428
- });
429
- this.socket.connect();
430
- }
431
- onUserMessage(callback) {
432
- this.pendingMessageCallback = callback;
433
- while (this.pendingMessages.length > 0) {
434
- callback(this.pendingMessages.shift());
435
- }
436
- }
437
- /**
438
- * Send message to session
439
- * @param body - Message body (can be MessageContent or raw content for agent messages)
440
- */
441
- sendClaudeSessionMessage(body) {
442
- let content;
443
- if (body.type === "user" && typeof body.message.content === "string") {
444
- content = {
445
- role: "user",
446
- content: {
447
- type: "text",
448
- text: body.message.content
449
- }
450
- };
451
- } else {
452
- content = {
453
- role: "agent",
454
- content: {
455
- type: "output",
456
- data: body
457
- // This wraps the entire Claude message
458
- }
459
- };
460
- }
461
- logger.debugLargeJson("[SOCKET] Sending message through socket:", content);
462
- const encrypted = encodeBase64(encrypt(content, this.secret));
463
- this.socket.emit("message", {
464
- sid: this.sessionId,
465
- message: encrypted
466
- });
467
- }
468
- /**
469
- * Send a ping message to keep the connection alive
470
- */
471
- keepAlive(thinking) {
472
- this.socket.volatile.emit("session-alive", { sid: this.sessionId, time: Date.now(), thinking });
473
- }
474
- /**
475
- * Send session death message
476
- */
477
- sendSessionDeath() {
478
- this.socket.emit("session-end", { sid: this.sessionId, time: Date.now() });
479
- }
480
- /**
481
- * Update session metadata
482
- * @param handler - Handler function that returns the updated metadata
483
- */
484
- updateMetadata(handler) {
485
- backoff(async () => {
486
- let updated = handler(this.metadata);
487
- const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(updated, this.secret)) });
488
- if (answer.result === "success") {
489
- this.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
490
- this.metadataVersion = answer.version;
491
- } else if (answer.result === "version-mismatch") {
492
- if (answer.version > this.metadataVersion) {
493
- this.metadataVersion = answer.version;
494
- this.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
495
- }
496
- throw new Error("Metadata version mismatch");
497
- } else if (answer.result === "error") ;
498
- });
499
- }
500
- /**
501
- * Update session agent state
502
- * @param handler - Handler function that returns the updated agent state
503
- */
504
- updateAgentState(handler) {
505
- console.log("Updating agent state", this.agentState);
506
- backoff(async () => {
507
- let updated = handler(this.agentState || {});
508
- const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(updated, this.secret)) : null });
509
- if (answer.result === "success") {
510
- this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState), this.secret) : null;
511
- this.agentStateVersion = answer.version;
512
- console.log("Agent state updated", this.agentState);
513
- } else if (answer.result === "version-mismatch") {
514
- if (answer.version > this.agentStateVersion) {
515
- this.agentStateVersion = answer.version;
516
- this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState), this.secret) : null;
517
- }
518
- throw new Error("Agent state version mismatch");
519
- } else if (answer.result === "error") {
520
- console.error("Agent state update error", answer);
521
- }
522
- });
523
- }
524
- /**
525
- * Set a custom RPC handler for a specific method with encrypted arguments and responses
526
- * @param method - The method name to handle
527
- * @param handler - The handler function to call when the method is invoked
528
- */
529
- setHandler(method, handler) {
530
- const prefixedMethod = `${this.sessionId}:${method}`;
531
- this.rpcHandlers.set(prefixedMethod, handler);
532
- this.socket.emit("rpc-register", { method: prefixedMethod });
533
- logger.debug("Registered RPC handler", { method, prefixedMethod });
534
- }
535
- /**
536
- * Re-register all RPC handlers after reconnection
537
- */
538
- reregisterHandlers() {
539
- logger.debug("Re-registering RPC handlers after reconnection", {
540
- totalMethods: this.rpcHandlers.size
541
- });
542
- for (const [prefixedMethod] of this.rpcHandlers) {
543
- this.socket.emit("rpc-register", { method: prefixedMethod });
544
- logger.debug("Re-registered method", { prefixedMethod });
545
- }
546
- }
547
- /**
548
- * Wait for socket buffer to flush
549
- */
550
- async flush() {
551
- if (!this.socket.connected) {
552
- return;
553
- }
554
- return new Promise((resolve) => {
555
- this.socket.emit("ping", () => {
556
- resolve();
557
- });
558
- setTimeout(() => {
559
- resolve();
560
- }, 1e4);
561
- });
562
- }
563
- async close() {
564
- this.socket.close();
565
- }
566
- }
567
-
568
- class PushNotificationClient {
569
- token;
570
- baseUrl;
571
- expo;
572
- constructor(token, baseUrl = "https://handy-api.korshakov.org") {
573
- this.token = token;
574
- this.baseUrl = baseUrl;
575
- this.expo = new expoServerSdk.Expo();
576
- }
577
- /**
578
- * Fetch all push tokens for the authenticated user
579
- */
580
- async fetchPushTokens() {
581
- try {
582
- const response = await axios.get(
583
- `${this.baseUrl}/v1/push-tokens`,
584
- {
585
- headers: {
586
- "Authorization": `Bearer ${this.token}`,
587
- "Content-Type": "application/json"
588
- }
589
- }
590
- );
591
- logger.info(`Fetched ${response.data.tokens.length} push tokens`);
592
- return response.data.tokens;
593
- } catch (error) {
594
- logger.debug("[PUSH] [ERROR] Failed to fetch push tokens:", error);
595
- throw new Error(`Failed to fetch push tokens: ${error instanceof Error ? error.message : "Unknown error"}`);
596
- }
597
- }
598
- /**
599
- * Send push notification via Expo Push API with retry
600
- * @param messages - Array of push messages to send
601
- */
602
- async sendPushNotifications(messages) {
603
- logger.info(`Sending ${messages.length} push notifications`);
604
- const validMessages = messages.filter((message) => {
605
- if (Array.isArray(message.to)) {
606
- return message.to.every((token) => expoServerSdk.Expo.isExpoPushToken(token));
607
- }
608
- return expoServerSdk.Expo.isExpoPushToken(message.to);
609
- });
610
- if (validMessages.length === 0) {
611
- logger.info("No valid Expo push tokens found");
612
- return;
613
41
  }
614
- const chunks = this.expo.chunkPushNotifications(validMessages);
615
- for (const chunk of chunks) {
616
- const startTime = Date.now();
617
- const timeout = 3e5;
618
- let attempt = 0;
619
- while (true) {
620
- try {
621
- const ticketChunk = await this.expo.sendPushNotificationsAsync(chunk);
622
- const errors = ticketChunk.filter((ticket) => ticket.status === "error");
623
- if (errors.length > 0) {
624
- logger.debug("[PUSH] Some notifications failed:", errors);
625
- }
626
- if (errors.length === ticketChunk.length) {
627
- throw new Error("All push notifications in chunk failed");
628
- }
629
- break;
630
- } catch (error) {
631
- const elapsed = Date.now() - startTime;
632
- if (elapsed >= timeout) {
633
- logger.debug("[PUSH] Timeout reached after 5 minutes, giving up on chunk");
634
- break;
635
- }
636
- attempt++;
637
- const delay = Math.min(1e3 * Math.pow(2, attempt), 3e4);
638
- const remainingTime = timeout - elapsed;
639
- const waitTime = Math.min(delay, remainingTime);
640
- if (waitTime > 0) {
641
- logger.debug(`[PUSH] Retrying in ${waitTime}ms (attempt ${attempt})`);
642
- await new Promise((resolve) => setTimeout(resolve, waitTime));
643
- }
644
- }
645
- }
646
- }
647
- logger.info(`Push notifications sent successfully`);
648
- }
649
- /**
650
- * Send a push notification to all registered devices for the user
651
- * @param title - Notification title
652
- * @param body - Notification body
653
- * @param data - Additional data to send with the notification
654
- */
655
- async sendToAllDevices(title, body, data) {
656
- const tokens = await this.fetchPushTokens();
657
- if (tokens.length === 0) {
658
- logger.info("No push tokens found for user");
659
- return;
660
- }
661
- const messages = tokens.map((token) => ({
662
- to: token.token,
663
- title,
664
- body,
665
- data,
666
- sound: "default",
667
- priority: "high"
668
- }));
669
- await this.sendPushNotifications(messages);
670
- }
42
+ n.default = e;
43
+ return Object.freeze(n);
671
44
  }
672
45
 
673
- class ApiClient {
674
- token;
675
- secret;
676
- pushClient;
677
- constructor(token, secret) {
678
- this.token = token;
679
- this.secret = secret;
680
- this.pushClient = new PushNotificationClient(token);
681
- }
682
- /**
683
- * Create a new session or load existing one with the given tag
684
- */
685
- async getOrCreateSession(opts) {
686
- try {
687
- const response = await axios.post(
688
- `${configuration.serverUrl}/v1/sessions`,
689
- {
690
- tag: opts.tag,
691
- metadata: encodeBase64(encrypt(opts.metadata, this.secret)),
692
- agentState: opts.state ? encodeBase64(encrypt(opts.state, this.secret)) : null
693
- },
694
- {
695
- headers: {
696
- "Authorization": `Bearer ${this.token}`,
697
- "Content-Type": "application/json"
698
- }
699
- }
700
- );
701
- logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
702
- let raw = response.data.session;
703
- let session = {
704
- id: raw.id,
705
- createdAt: raw.createdAt,
706
- updatedAt: raw.updatedAt,
707
- seq: raw.seq,
708
- metadata: decrypt(decodeBase64(raw.metadata), this.secret),
709
- metadataVersion: raw.metadataVersion,
710
- agentState: raw.agentState ? decrypt(decodeBase64(raw.agentState), this.secret) : null,
711
- agentStateVersion: raw.agentStateVersion
712
- };
713
- return session;
714
- } catch (error) {
715
- logger.debug("[API] [ERROR] Failed to get or create session:", error);
716
- throw new Error(`Failed to get or create session: ${error instanceof Error ? error.message : "Unknown error"}`);
717
- }
718
- }
719
- /**
720
- * Start realtime session client
721
- * @param id - Session ID
722
- * @returns Session client
723
- */
724
- session(session) {
725
- return new ApiSessionClient(this.token, this.secret, session);
726
- }
727
- /**
728
- * Get push notification client
729
- * @returns Push notification client
730
- */
731
- push() {
732
- return this.pushClient;
733
- }
734
- }
46
+ var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
735
47
 
736
48
  function formatClaudeMessage(message, onAssistantResult) {
737
- logger.debugLargeJson("[CLAUDE] Message from non interactive & remote mode:", message);
49
+ types.logger.debugLargeJson("[CLAUDE] Message from non interactive & remote mode:", message);
738
50
  switch (message.type) {
739
51
  case "system": {
740
52
  const sysMsg = message;
@@ -827,7 +139,7 @@ function formatClaudeMessage(message, onAssistantResult) {
827
139
  console.log(chalk.green("\u{1F449} Press any key to continue your session in `claude`"));
828
140
  if (onAssistantResult) {
829
141
  Promise.resolve(onAssistantResult(resultMsg)).catch((err) => {
830
- logger.debug("Error in onAssistantResult callback:", err);
142
+ types.logger.debug("Error in onAssistantResult callback:", err);
831
143
  });
832
144
  }
833
145
  }
@@ -837,7 +149,7 @@ function formatClaudeMessage(message, onAssistantResult) {
837
149
  } else if (resultMsg.subtype === "error_during_execution") {
838
150
  console.log(chalk.red.bold("\n\u274C Error during execution"));
839
151
  console.log(chalk.gray(`Completed ${resultMsg.num_turns} turns before error`));
840
- logger.debugLargeJson("[RESULT] Error during execution", resultMsg);
152
+ types.logger.debugLargeJson("[RESULT] Error during execution", resultMsg);
841
153
  }
842
154
  break;
843
155
  }
@@ -859,7 +171,7 @@ function claudeCheckSession(sessionId, path) {
859
171
  const sessionFile = node_path.join(projectDir, `${sessionId}.jsonl`);
860
172
  const sessionExists = node_fs.existsSync(sessionFile);
861
173
  if (!sessionExists) {
862
- logger.debug(`[claudeCheckSession] Path ${sessionFile} does not exist`);
174
+ types.logger.debug(`[claudeCheckSession] Path ${sessionFile} does not exist`);
863
175
  return false;
864
176
  }
865
177
  const sessionData = node_fs.readFileSync(sessionFile, "utf-8").split("\n");
@@ -905,7 +217,7 @@ async function claudeRemote(opts) {
905
217
  }
906
218
  }
907
219
  });
908
- logger.debug(`[claudeRemote] Starting query with messages`);
220
+ types.logger.debug(`[claudeRemote] Starting query with messages`);
909
221
  response = claudeCode.query({
910
222
  prompt: opts.messages,
911
223
  abortController,
@@ -913,15 +225,15 @@ async function claudeRemote(opts) {
913
225
  });
914
226
  if (opts.interruptController) {
915
227
  opts.interruptController.register(async () => {
916
- logger.debug("[claudeRemote] Interrupting Claude via SDK");
228
+ types.logger.debug("[claudeRemote] Interrupting Claude via SDK");
917
229
  await response.interrupt();
918
230
  });
919
231
  }
920
232
  printDivider();
921
233
  try {
922
- logger.debug(`[claudeRemote] Starting to iterate over response`);
234
+ types.logger.debug(`[claudeRemote] Starting to iterate over response`);
923
235
  for await (const message of response) {
924
- logger.debug(`[claudeRemote] Received message from SDK: ${message.type}`);
236
+ types.logger.debug(`[claudeRemote] Received message from SDK: ${message.type}`);
925
237
  formatClaudeMessage(message, opts.onAssistantResult);
926
238
  if (message.type === "system" && message.subtype === "init") {
927
239
  const projectName = node_path.resolve(opts.path).replace(/\//g, "-");
@@ -935,13 +247,13 @@ async function claudeRemote(opts) {
935
247
  });
936
248
  }
937
249
  }
938
- logger.debug(`[claudeRemote] Finished iterating over response`);
250
+ types.logger.debug(`[claudeRemote] Finished iterating over response`);
939
251
  } catch (e) {
940
252
  if (abortController.signal.aborted) {
941
- logger.debug(`[claudeRemote] Aborted`);
253
+ types.logger.debug(`[claudeRemote] Aborted`);
942
254
  }
943
255
  if (e instanceof claudeCode.AbortError) {
944
- logger.debug(`[claudeRemote] Aborted`);
256
+ types.logger.debug(`[claudeRemote] Aborted`);
945
257
  } else {
946
258
  throw e;
947
259
  }
@@ -951,7 +263,7 @@ async function claudeRemote(opts) {
951
263
  }
952
264
  }
953
265
  printDivider();
954
- logger.debug(`[claudeRemote] Function completed`);
266
+ types.logger.debug(`[claudeRemote] Function completed`);
955
267
  }
956
268
 
957
269
  const __dirname$1 = node_path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
@@ -965,7 +277,7 @@ async function claudeLocal(opts) {
965
277
  const detectedIdsFileSystem = /* @__PURE__ */ new Set();
966
278
  watcher.on("change", (event, filename) => {
967
279
  if (typeof filename === "string" && filename.toLowerCase().endsWith(".jsonl")) {
968
- logger.debug("change", event, filename);
280
+ types.logger.debug("change", event, filename);
969
281
  const sessionId = filename.replace(".jsonl", "");
970
282
  if (detectedIdsFileSystem.has(sessionId)) {
971
283
  return;
@@ -1056,10 +368,10 @@ class MessageQueue {
1056
368
  if (this.closed) {
1057
369
  throw new Error("Cannot push to closed queue");
1058
370
  }
1059
- logger.debug(`[MessageQueue] push() called. Waiters: ${this.waiters.length}, Queue size before: ${this.queue.length}`);
371
+ types.logger.debug(`[MessageQueue] push() called. Waiters: ${this.waiters.length}, Queue size before: ${this.queue.length}`);
1060
372
  const waiter = this.waiters.shift();
1061
373
  if (waiter) {
1062
- logger.debug(`[MessageQueue] Found waiter! Delivering message directly: "${message}"`);
374
+ types.logger.debug(`[MessageQueue] Found waiter! Delivering message directly: "${message}"`);
1063
375
  waiter({
1064
376
  type: "user",
1065
377
  message: {
@@ -1070,7 +382,7 @@ class MessageQueue {
1070
382
  session_id: ""
1071
383
  });
1072
384
  } else {
1073
- logger.debug(`[MessageQueue] No waiter found. Adding to queue: "${message}"`);
385
+ types.logger.debug(`[MessageQueue] No waiter found. Adding to queue: "${message}"`);
1074
386
  this.queue.push({
1075
387
  type: "user",
1076
388
  message: {
@@ -1081,13 +393,13 @@ class MessageQueue {
1081
393
  session_id: ""
1082
394
  });
1083
395
  }
1084
- logger.debug(`[MessageQueue] push() completed. Waiters: ${this.waiters.length}, Queue size after: ${this.queue.length}`);
396
+ types.logger.debug(`[MessageQueue] push() completed. Waiters: ${this.waiters.length}, Queue size after: ${this.queue.length}`);
1085
397
  }
1086
398
  /**
1087
399
  * Close the queue - no more messages can be pushed
1088
400
  */
1089
401
  close() {
1090
- logger.debug(`[MessageQueue] close() called. Waiters: ${this.waiters.length}`);
402
+ types.logger.debug(`[MessageQueue] close() called. Waiters: ${this.waiters.length}`);
1091
403
  this.closed = true;
1092
404
  this.closeResolve?.();
1093
405
  }
@@ -1107,25 +419,25 @@ class MessageQueue {
1107
419
  * Async iterator implementation
1108
420
  */
1109
421
  async *[Symbol.asyncIterator]() {
1110
- logger.debug(`[MessageQueue] Iterator started`);
422
+ types.logger.debug(`[MessageQueue] Iterator started`);
1111
423
  while (true) {
1112
424
  const message = this.queue.shift();
1113
425
  if (message !== void 0) {
1114
- logger.debug(`[MessageQueue] Iterator yielding queued message`);
426
+ types.logger.debug(`[MessageQueue] Iterator yielding queued message`);
1115
427
  yield message;
1116
428
  continue;
1117
429
  }
1118
430
  if (this.closed) {
1119
- logger.debug(`[MessageQueue] Iterator ending - queue closed`);
431
+ types.logger.debug(`[MessageQueue] Iterator ending - queue closed`);
1120
432
  return;
1121
433
  }
1122
- logger.debug(`[MessageQueue] Iterator waiting for next message...`);
434
+ types.logger.debug(`[MessageQueue] Iterator waiting for next message...`);
1123
435
  const nextMessage = await this.waitForNext();
1124
436
  if (nextMessage === void 0) {
1125
- logger.debug(`[MessageQueue] Iterator ending - no more messages`);
437
+ types.logger.debug(`[MessageQueue] Iterator ending - no more messages`);
1126
438
  return;
1127
439
  }
1128
- logger.debug(`[MessageQueue] Iterator yielding waited message`);
440
+ types.logger.debug(`[MessageQueue] Iterator yielding waited message`);
1129
441
  yield nextMessage;
1130
442
  }
1131
443
  }
@@ -1135,18 +447,18 @@ class MessageQueue {
1135
447
  waitForNext() {
1136
448
  return new Promise((resolve) => {
1137
449
  if (this.closed) {
1138
- logger.debug(`[MessageQueue] waitForNext() called but queue is closed`);
450
+ types.logger.debug(`[MessageQueue] waitForNext() called but queue is closed`);
1139
451
  resolve(void 0);
1140
452
  return;
1141
453
  }
1142
454
  const waiter = (value) => resolve(value);
1143
455
  this.waiters.push(waiter);
1144
- logger.debug(`[MessageQueue] waitForNext() adding waiter. Total waiters: ${this.waiters.length}`);
456
+ types.logger.debug(`[MessageQueue] waitForNext() adding waiter. Total waiters: ${this.waiters.length}`);
1145
457
  this.closePromise?.then(() => {
1146
458
  const index = this.waiters.indexOf(waiter);
1147
459
  if (index !== -1) {
1148
460
  this.waiters.splice(index, 1);
1149
- logger.debug(`[MessageQueue] waitForNext() waiter removed due to close. Remaining waiters: ${this.waiters.length}`);
461
+ types.logger.debug(`[MessageQueue] waitForNext() waiter removed due to close. Remaining waiters: ${this.waiters.length}`);
1150
462
  resolve(void 0);
1151
463
  }
1152
464
  });
@@ -1200,7 +512,7 @@ class InvalidateSync {
1200
512
  this._pendings = [];
1201
513
  };
1202
514
  _doSync = async () => {
1203
- await backoff(async () => {
515
+ await types.backoff(async () => {
1204
516
  if (this._stopped) {
1205
517
  return;
1206
518
  }
@@ -1220,105 +532,6 @@ class InvalidateSync {
1220
532
  };
1221
533
  }
1222
534
 
1223
- const UsageSchema = z.z.object({
1224
- input_tokens: z.z.number().int().nonnegative(),
1225
- cache_creation_input_tokens: z.z.number().int().nonnegative().optional(),
1226
- cache_read_input_tokens: z.z.number().int().nonnegative().optional(),
1227
- output_tokens: z.z.number().int().nonnegative(),
1228
- service_tier: z.z.string().optional()
1229
- });
1230
- const TextContentSchema = z.z.object({
1231
- type: z.z.literal("text"),
1232
- text: z.z.string()
1233
- });
1234
- const ThinkingContentSchema = z.z.object({
1235
- type: z.z.literal("thinking"),
1236
- thinking: z.z.string(),
1237
- signature: z.z.string()
1238
- });
1239
- const ToolUseContentSchema = z.z.object({
1240
- type: z.z.literal("tool_use"),
1241
- id: z.z.string(),
1242
- name: z.z.string(),
1243
- input: z.z.unknown()
1244
- // Tool-specific input parameters
1245
- });
1246
- const ToolResultContentSchema = z.z.object({
1247
- tool_use_id: z.z.string(),
1248
- type: z.z.literal("tool_result"),
1249
- content: z.z.union([
1250
- z.z.string(),
1251
- // For simple string responses
1252
- z.z.array(TextContentSchema)
1253
- // For structured content blocks (typically text)
1254
- ]),
1255
- is_error: z.z.boolean().optional()
1256
- });
1257
- const ContentSchema = z.z.union([
1258
- TextContentSchema,
1259
- ThinkingContentSchema,
1260
- ToolUseContentSchema,
1261
- ToolResultContentSchema
1262
- ]);
1263
- const UserMessageSchema = z.z.object({
1264
- role: z.z.literal("user"),
1265
- content: z.z.union([
1266
- z.z.string(),
1267
- // Simple string content
1268
- z.z.array(z.z.union([ToolResultContentSchema, TextContentSchema]))
1269
- ])
1270
- });
1271
- const AssistantMessageSchema = z.z.object({
1272
- id: z.z.string(),
1273
- type: z.z.literal("message"),
1274
- role: z.z.literal("assistant"),
1275
- model: z.z.string(),
1276
- content: z.z.array(ContentSchema),
1277
- stop_reason: z.z.string().nullable(),
1278
- stop_sequence: z.z.string().nullable(),
1279
- usage: UsageSchema
1280
- });
1281
- const BaseEntrySchema = z.z.object({
1282
- cwd: z.z.string(),
1283
- sessionId: z.z.string(),
1284
- version: z.z.string(),
1285
- uuid: z.z.string(),
1286
- timestamp: z.z.string().datetime(),
1287
- parent_tool_use_id: z.z.string().nullable().optional()
1288
- });
1289
- const SummaryEntrySchema = z.z.object({
1290
- type: z.z.literal("summary"),
1291
- summary: z.z.string(),
1292
- leafUuid: z.z.string()
1293
- });
1294
- const UserEntrySchema = BaseEntrySchema.extend({
1295
- type: z.z.literal("user"),
1296
- message: UserMessageSchema,
1297
- isMeta: z.z.boolean().optional(),
1298
- toolUseResult: z.z.unknown().optional()
1299
- // Present when user responds to tool use
1300
- });
1301
- const AssistantEntrySchema = BaseEntrySchema.extend({
1302
- type: z.z.literal("assistant"),
1303
- message: AssistantMessageSchema,
1304
- requestId: z.z.string().optional()
1305
- });
1306
- const SystemEntrySchema = BaseEntrySchema.extend({
1307
- type: z.z.literal("system"),
1308
- content: z.z.string(),
1309
- isMeta: z.z.boolean().optional(),
1310
- level: z.z.string().optional(),
1311
- parentUuid: z.z.string().optional(),
1312
- isSidechain: z.z.boolean().optional(),
1313
- userType: z.z.string().optional()
1314
- });
1315
- const RawJSONLinesSchema = z.z.discriminatedUnion("type", [
1316
- UserEntrySchema,
1317
- AssistantEntrySchema,
1318
- SummaryEntrySchema,
1319
- SystemEntrySchema
1320
- ]);
1321
-
1322
535
  function createSessionScanner(opts) {
1323
536
  const projectName = node_path.resolve(opts.workingDirectory).replace(/\//g, "-");
1324
537
  const projectDir = node_path.join(os.homedir(), ".claude", "projects", projectName);
@@ -1327,6 +540,7 @@ function createSessionScanner(opts) {
1327
540
  let currentSessionId = null;
1328
541
  let currentSessionWatcherAbortController = null;
1329
542
  let processedMessages = /* @__PURE__ */ new Set();
543
+ let seenRemoteUserMessageCounters = /* @__PURE__ */ new Map();
1330
544
  const sync = new InvalidateSync(async () => {
1331
545
  let sessions = [];
1332
546
  for (let p of pendingSessions) {
@@ -1347,9 +561,9 @@ function createSessionScanner(opts) {
1347
561
  for (let l of lines) {
1348
562
  try {
1349
563
  let message = JSON.parse(l);
1350
- let parsed = RawJSONLinesSchema.safeParse(message);
564
+ let parsed = types.RawJSONLinesSchema.safeParse(message);
1351
565
  if (!parsed.success) {
1352
- logger.debugLargeJson(`[SESSION_SCANNER] Failed to parse message`, message);
566
+ types.logger.debugLargeJson(`[SESSION_SCANNER] Failed to parse message`, message);
1353
567
  continue;
1354
568
  }
1355
569
  let key = getMessageKey(parsed.data);
@@ -1357,8 +571,15 @@ function createSessionScanner(opts) {
1357
571
  continue;
1358
572
  }
1359
573
  processedMessages.add(key);
1360
- logger.debugLargeJson(`[SESSION_SCANNER] Processing message`, parsed.data);
1361
- logger.debug(`[SESSION_SCANNER] Message key (new): ${key}`);
574
+ types.logger.debugLargeJson(`[SESSION_SCANNER] Processing message`, parsed.data);
575
+ types.logger.debug(`[SESSION_SCANNER] Message key (new): ${key}`);
576
+ if (parsed.data.type === "user" && typeof parsed.data.message.content === "string") {
577
+ const currentCounter = seenRemoteUserMessageCounters.get(parsed.data.message.content);
578
+ if (currentCounter && currentCounter > 0) {
579
+ seenRemoteUserMessageCounters.set(parsed.data.message.content, currentCounter - 1);
580
+ continue;
581
+ }
582
+ }
1362
583
  opts.onMessage(parsed.data);
1363
584
  } catch (e) {
1364
585
  continue;
@@ -1385,7 +606,7 @@ function createSessionScanner(opts) {
1385
606
  }
1386
607
  } catch (error) {
1387
608
  if (error.name !== "AbortError") {
1388
- logger.debug(`[SESSION_SCANNER] Watch error: ${error.message}`);
609
+ types.logger.debug(`[SESSION_SCANNER] Watch error: ${error.message}`);
1389
610
  }
1390
611
  }
1391
612
  }
@@ -1413,8 +634,12 @@ function createSessionScanner(opts) {
1413
634
  if (currentSessionId) {
1414
635
  pendingSessions.add(currentSessionId);
1415
636
  }
637
+ types.logger.debug(`[SESSION_SCANNER] New session: ${sessionId}`);
1416
638
  currentSessionId = sessionId;
1417
639
  sync.invalidate();
640
+ },
641
+ onRemoteUserMessageForDeduplication: (messageContent) => {
642
+ seenRemoteUserMessageCounters.set(messageContent, (seenRemoteUserMessageCounters.get(messageContent) || 0) + 1);
1418
643
  }
1419
644
  };
1420
645
  }
@@ -1448,32 +673,24 @@ function sortKeys(value) {
1448
673
  }
1449
674
 
1450
675
  async function loop(opts) {
1451
- let mode = "interactive";
676
+ let mode = opts.startingMode ?? "interactive";
1452
677
  let currentMessageQueue = new MessageQueue();
1453
678
  let sessionId = null;
1454
679
  let onMessage = null;
1455
- let seenRemoteUserMessageCounters = /* @__PURE__ */ new Map();
1456
- opts.session.onUserMessage((message) => {
1457
- logger.debugLargeJson("User message pushed to queue:", message);
1458
- currentMessageQueue.push(message.content.text);
1459
- seenRemoteUserMessageCounters.set(message.content.text, (seenRemoteUserMessageCounters.get(message.content.text) || 0) + 1);
1460
- if (onMessage) {
1461
- onMessage();
1462
- }
1463
- });
1464
680
  const sessionScanner = createSessionScanner({
1465
681
  workingDirectory: opts.path,
1466
682
  onMessage: (message) => {
1467
- if (message.type === "user" && typeof message.message.content === "string") {
1468
- const currentCounter = seenRemoteUserMessageCounters.get(message.message.content);
1469
- if (currentCounter && currentCounter > 0) {
1470
- seenRemoteUserMessageCounters.set(message.message.content, currentCounter - 1);
1471
- return;
1472
- }
1473
- }
1474
683
  opts.session.sendClaudeSessionMessage(message);
1475
684
  }
1476
685
  });
686
+ opts.session.onUserMessage((message) => {
687
+ sessionScanner.onRemoteUserMessageForDeduplication(message.content.text);
688
+ currentMessageQueue.push(message.content.text);
689
+ types.logger.debugLargeJson("User message pushed to queue:", message);
690
+ if (onMessage) {
691
+ onMessage();
692
+ }
693
+ });
1477
694
  let onSessionFound = (newSessionId) => {
1478
695
  sessionId = newSessionId;
1479
696
  sessionScanner.onNewSession(newSessionId);
@@ -1516,7 +733,7 @@ async function loop(opts) {
1516
733
  }
1517
734
  }
1518
735
  if (mode === "remote") {
1519
- logger.debug("Starting " + sessionId);
736
+ types.logger.debug("Starting " + sessionId);
1520
737
  const remoteAbortController = new AbortController();
1521
738
  opts.session.setHandler("abort", () => {
1522
739
  if (!remoteAbortController.signal.aborted) {
@@ -1528,14 +745,18 @@ async function loop(opts) {
1528
745
  mode = "interactive";
1529
746
  remoteAbortController.abort();
1530
747
  }
1531
- process.stdin.setRawMode(false);
748
+ if (process.stdin.isTTY) {
749
+ process.stdin.setRawMode(false);
750
+ }
1532
751
  };
1533
752
  process.stdin.resume();
1534
- process.stdin.setRawMode(true);
753
+ if (process.stdin.isTTY) {
754
+ process.stdin.setRawMode(true);
755
+ }
1535
756
  process.stdin.setEncoding("utf8");
1536
757
  process.stdin.on("data", abortHandler);
1537
758
  try {
1538
- logger.debug(`Starting claudeRemote with messages: ${currentMessageQueue.size()}`);
759
+ types.logger.debug(`Starting claudeRemote with messages: ${currentMessageQueue.size()}`);
1539
760
  await claudeRemote({
1540
761
  abort: remoteAbortController.signal,
1541
762
  sessionId,
@@ -1549,7 +770,9 @@ async function loop(opts) {
1549
770
  });
1550
771
  } finally {
1551
772
  process.stdin.off("data", abortHandler);
1552
- process.stdin.setRawMode(false);
773
+ if (process.stdin.isTTY) {
774
+ process.stdin.setRawMode(false);
775
+ }
1553
776
  currentMessageQueue.close();
1554
777
  currentMessageQueue = new MessageQueue();
1555
778
  }
@@ -1600,7 +823,7 @@ async function startPermissionServerV2(handler) {
1600
823
  try {
1601
824
  await transport.handleRequest(req, res);
1602
825
  } catch (error) {
1603
- logger.debug("Error handling request:", error);
826
+ types.logger.debug("Error handling request:", error);
1604
827
  if (!res.headersSent) {
1605
828
  res.writeHead(500).end();
1606
829
  }
@@ -1646,7 +869,7 @@ class InterruptController {
1646
869
  await this.interruptFn();
1647
870
  return true;
1648
871
  } catch (error) {
1649
- logger.debug("Failed to interrupt Claude:", error);
872
+ types.logger.debug("Failed to interrupt Claude:", error);
1650
873
  return false;
1651
874
  } finally {
1652
875
  this.isInterrupting = false;
@@ -1660,16 +883,187 @@ class InterruptController {
1660
883
  }
1661
884
  }
1662
885
 
886
+ var version = "0.1.9";
887
+ var packageJson = {
888
+ version: version};
889
+
890
+ async function startAnthropicActivityProxy(onClaudeActivity) {
891
+ const requestTimeouts = /* @__PURE__ */ new Map();
892
+ let requestCounter = 0;
893
+ let idleTimer = null;
894
+ const maxTimeBeforeIdle = 50;
895
+ const requestTimeout = 5 * 60 * 1e3;
896
+ const cleanupRequest = (requestId, reason) => {
897
+ const timeout = requestTimeouts.get(requestId);
898
+ if (timeout) {
899
+ clearTimeout(timeout);
900
+ requestTimeouts.delete(requestId);
901
+ types.logger.debug(`[AnthropicProxy #${requestId}] Cleaned up (${reason}), active requests: ${requestTimeouts.size}`);
902
+ claudeDidSomeWork();
903
+ }
904
+ };
905
+ const claudeDidSomeWork = () => {
906
+ if (idleTimer) clearTimeout(idleTimer);
907
+ if (requestTimeouts.size === 0) {
908
+ idleTimer = setTimeout(() => {
909
+ types.logger.debug(`[AnthropicProxy] Idle for ${maxTimeBeforeIdle}ms, active requests: ${requestTimeouts.size}`);
910
+ onClaudeActivity("idle");
911
+ }, maxTimeBeforeIdle);
912
+ }
913
+ };
914
+ const server = node_http.createServer((req, res) => {
915
+ const requestId = ++requestCounter;
916
+ const isAnthropicRequest = req.headers.host === "api.anthropic.com" || req.url?.includes("anthropic.com");
917
+ if (isAnthropicRequest) {
918
+ const timeout = setTimeout(() => {
919
+ types.logger.debug(`[AnthropicProxy #${requestId}] Request timeout after ${requestTimeout}ms`);
920
+ cleanupRequest(requestId, "timeout");
921
+ }, requestTimeout);
922
+ requestTimeouts.set(requestId, timeout);
923
+ onClaudeActivity("working");
924
+ types.logger.debug(`[AnthropicProxy #${requestId}] Anthropic request: ${req.method} ${req.url}, active requests: ${requestTimeouts.size}`);
925
+ }
926
+ const chunks = [];
927
+ req.on("data", (chunk) => {
928
+ chunks.push(chunk);
929
+ if (isAnthropicRequest) {
930
+ claudeDidSomeWork();
931
+ }
932
+ });
933
+ req.on("end", () => {
934
+ const body = Buffer.concat(chunks);
935
+ let targetUrl;
936
+ if (isAnthropicRequest) {
937
+ targetUrl = new node_url.URL(req.url || "/", "https://api.anthropic.com");
938
+ } else {
939
+ const protocol = req.headers["x-forwarded-proto"] || "https";
940
+ const host = req.headers.host || "localhost";
941
+ targetUrl = new node_url.URL(req.url || "/", `${protocol}://${host}`);
942
+ }
943
+ const options = {
944
+ hostname: targetUrl.hostname,
945
+ port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
946
+ path: targetUrl.pathname + targetUrl.search,
947
+ method: req.method,
948
+ headers: {
949
+ ...req.headers,
950
+ host: targetUrl.hostname
951
+ }
952
+ };
953
+ const requestMethod = targetUrl.protocol === "https:" ? node_https.request : node_http.request;
954
+ const proxyReq = requestMethod(options, (proxyRes) => {
955
+ res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
956
+ proxyRes.pipe(res);
957
+ proxyRes.on("end", () => {
958
+ if (isAnthropicRequest) {
959
+ cleanupRequest(requestId, "completed");
960
+ }
961
+ });
962
+ });
963
+ proxyReq.on("error", (error) => {
964
+ if (isAnthropicRequest) {
965
+ cleanupRequest(requestId, `error: ${error.message}`);
966
+ } else {
967
+ types.logger.debug(`[AnthropicProxy #${requestId}] Error:`, error.message);
968
+ }
969
+ res.writeHead(502);
970
+ res.end("Bad Gateway");
971
+ });
972
+ if (body.length > 0) {
973
+ proxyReq.write(body);
974
+ }
975
+ proxyReq.end();
976
+ });
977
+ });
978
+ server.on("connect", (req, clientSocket, head) => {
979
+ const requestId = ++requestCounter;
980
+ const [hostname, port] = req.url?.split(":") || ["", "443"];
981
+ const isAnthropicRequest = hostname === "api.anthropic.com";
982
+ if (isAnthropicRequest) {
983
+ const timeout = setTimeout(() => {
984
+ types.logger.debug(`[AnthropicProxy #${requestId}] CONNECT timeout after ${requestTimeout}ms`);
985
+ cleanupRequest(requestId, "timeout");
986
+ }, requestTimeout);
987
+ requestTimeouts.set(requestId, timeout);
988
+ onClaudeActivity("working");
989
+ types.logger.debug(`[AnthropicProxy #${requestId}] CONNECT to api.anthropic.com, active requests: ${requestTimeouts.size}`);
990
+ }
991
+ const serverSocket = net.connect(parseInt(port) || 443, hostname, () => {
992
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
993
+ serverSocket.write(head);
994
+ serverSocket.pipe(clientSocket);
995
+ clientSocket.pipe(serverSocket);
996
+ });
997
+ const cleanup = () => {
998
+ if (isAnthropicRequest) {
999
+ cleanupRequest(requestId, "CONNECT closed");
1000
+ }
1001
+ };
1002
+ serverSocket.on("error", (err) => {
1003
+ types.logger.debug(`[AnthropicProxy #${requestId}] CONNECT error:`, err.message);
1004
+ clientSocket.end();
1005
+ cleanup();
1006
+ });
1007
+ clientSocket.on("error", cleanup);
1008
+ clientSocket.on("end", cleanup);
1009
+ serverSocket.on("end", cleanup);
1010
+ });
1011
+ const url = await new Promise((resolve) => {
1012
+ server.listen(0, "127.0.0.1", () => {
1013
+ const addr = server.address();
1014
+ if (addr && typeof addr === "object") {
1015
+ resolve(`http://127.0.0.1:${addr.port}`);
1016
+ }
1017
+ });
1018
+ });
1019
+ types.logger.debug(`[AnthropicProxy] Started at ${url}`);
1020
+ return {
1021
+ url,
1022
+ cleanup: () => {
1023
+ if (idleTimer) clearTimeout(idleTimer);
1024
+ for (const [requestId, timeout] of requestTimeouts) {
1025
+ clearTimeout(timeout);
1026
+ types.logger.debug(`[AnthropicProxy] Cleaning up timeout for request #${requestId}`);
1027
+ }
1028
+ requestTimeouts.clear();
1029
+ if (requestTimeouts.size > 0) {
1030
+ types.logger.debug(`[AnthropicProxy] Warning: ${requestTimeouts.size} active requests still pending at cleanup:`, Array.from(requestTimeouts.keys()));
1031
+ }
1032
+ server.close();
1033
+ }
1034
+ };
1035
+ }
1036
+
1663
1037
  async function start(credentials, options = {}) {
1664
1038
  const workingDirectory = process.cwd();
1665
1039
  const sessionTag = node_crypto.randomUUID();
1666
- const api = new ApiClient(credentials.token, credentials.secret);
1040
+ const api = new types.ApiClient(credentials.token, credentials.secret);
1667
1041
  let state = {};
1668
- let metadata = { path: workingDirectory, host: os.hostname() };
1042
+ let metadata = { path: workingDirectory, host: os.hostname(), version: packageJson.version };
1669
1043
  const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
1670
- logger.debug(`Session created: ${response.id}`);
1044
+ types.logger.debug(`Session created: ${response.id}`);
1671
1045
  const session = api.session(response);
1672
1046
  const pushClient = api.push();
1047
+ let thinking = false;
1048
+ let pingInterval = setInterval(() => {
1049
+ session.keepAlive(thinking);
1050
+ }, 2e3);
1051
+ const antropicActivityProxy = await startAnthropicActivityProxy(
1052
+ (activity) => {
1053
+ const newThinking = activity === "working";
1054
+ if (newThinking !== thinking) {
1055
+ thinking = newThinking;
1056
+ types.logger.debug(`[PING] Thinking state changed: ${thinking}`);
1057
+ session.keepAlive(thinking);
1058
+ }
1059
+ }
1060
+ );
1061
+ process.env.HTTP_PROXY = antropicActivityProxy.url;
1062
+ process.env.HTTPS_PROXY = antropicActivityProxy.url;
1063
+ types.logger.debug(`[AnthropicProxy] Set HTTP_PROXY and HTTPS_PROXY to ${antropicActivityProxy.url}`);
1064
+ const logPath = await types.logger.logFilePathPromise;
1065
+ types.logger.info(`Session: ${response.id}`);
1066
+ types.logger.infoDeveloper(`Logs: ${logPath}`);
1673
1067
  const interruptController = new InterruptController();
1674
1068
  let requests = /* @__PURE__ */ new Map();
1675
1069
  const permissionServer = await startPermissionServerV2(async (request) => {
@@ -1678,10 +1072,10 @@ async function start(credentials, options = {}) {
1678
1072
  requests.set(id, resolve);
1679
1073
  });
1680
1074
  let timeout = setTimeout(async () => {
1681
- logger.info("Permission timeout - attempting to interrupt Claude");
1075
+ types.logger.info("Permission timeout - attempting to interrupt Claude");
1682
1076
  const interrupted = await interruptController.interrupt();
1683
1077
  if (interrupted) {
1684
- logger.info("Claude interrupted successfully");
1078
+ types.logger.info("Claude interrupted successfully");
1685
1079
  }
1686
1080
  requests.delete(id);
1687
1081
  session.updateAgentState((currentState) => {
@@ -1693,7 +1087,7 @@ async function start(credentials, options = {}) {
1693
1087
  };
1694
1088
  });
1695
1089
  }, 1e3 * 60 * 4.5);
1696
- logger.info("Permission request" + id + " " + JSON.stringify(request));
1090
+ types.logger.info("Permission request" + id + " " + JSON.stringify(request));
1697
1091
  try {
1698
1092
  await pushClient.sendToAllDevices(
1699
1093
  "Permission Request",
@@ -1705,9 +1099,9 @@ async function start(credentials, options = {}) {
1705
1099
  type: "permission_request"
1706
1100
  }
1707
1101
  );
1708
- logger.info("Push notification sent for permission request");
1102
+ types.logger.info("Push notification sent for permission request");
1709
1103
  } catch (error) {
1710
- logger.debug("Failed to send push notification:", error);
1104
+ types.logger.debug("Failed to send push notification:", error);
1711
1105
  }
1712
1106
  session.updateAgentState((currentState) => ({
1713
1107
  ...currentState,
@@ -1723,13 +1117,13 @@ async function start(credentials, options = {}) {
1723
1117
  return promise;
1724
1118
  });
1725
1119
  session.setHandler("permission", (message) => {
1726
- logger.info("Permission response" + JSON.stringify(message));
1120
+ types.logger.info("Permission response" + JSON.stringify(message));
1727
1121
  const id = message.id;
1728
1122
  const resolve = requests.get(id);
1729
1123
  if (resolve) {
1730
1124
  resolve({ approved: message.approved, reason: message.reason });
1731
1125
  } else {
1732
- logger.info("Permission request stale, likely timed out");
1126
+ types.logger.info("Permission request stale, likely timed out");
1733
1127
  return;
1734
1128
  }
1735
1129
  session.updateAgentState((currentState) => {
@@ -1742,13 +1136,9 @@ async function start(credentials, options = {}) {
1742
1136
  });
1743
1137
  });
1744
1138
  session.setHandler("abort", async () => {
1745
- logger.info("Abort request - interrupting Claude");
1139
+ types.logger.info("Abort request - interrupting Claude");
1746
1140
  await interruptController.interrupt();
1747
1141
  });
1748
- let thinking = false;
1749
- const pingInterval = setInterval(() => {
1750
- session.keepAlive(thinking);
1751
- }, 15e3);
1752
1142
  const onAssistantResult = async (result) => {
1753
1143
  try {
1754
1144
  const summary = "result" in result && result.result ? result.result.substring(0, 100) + (result.result.length > 100 ? "..." : "") : "";
@@ -1763,15 +1153,16 @@ async function start(credentials, options = {}) {
1763
1153
  cost_usd: result.total_cost_usd
1764
1154
  }
1765
1155
  );
1766
- logger.debug("Push notification sent: Assistant result");
1156
+ types.logger.debug("Push notification sent: Assistant result");
1767
1157
  } catch (error) {
1768
- logger.debug("Failed to send assistant result push notification:", error);
1158
+ types.logger.debug("Failed to send assistant result push notification:", error);
1769
1159
  }
1770
1160
  };
1771
1161
  await loop({
1772
1162
  path: workingDirectory,
1773
1163
  model: options.model,
1774
1164
  permissionMode: options.permissionMode,
1165
+ startingMode: options.startingMode,
1775
1166
  mcpServers: {
1776
1167
  "permission": {
1777
1168
  type: "http",
@@ -1779,15 +1170,15 @@ async function start(credentials, options = {}) {
1779
1170
  }
1780
1171
  },
1781
1172
  permissionPromptToolName: "mcp__permission__" + permissionServer.toolName,
1782
- onThinking: (t) => {
1783
- thinking = t;
1784
- session.keepAlive(t);
1785
- },
1786
1173
  session,
1787
1174
  onAssistantResult,
1788
1175
  interruptController
1789
1176
  });
1790
1177
  clearInterval(pingInterval);
1178
+ if (antropicActivityProxy) {
1179
+ types.logger.info("[AnthropicProxy] Shutting down activity monitoring proxy");
1180
+ antropicActivityProxy.cleanup();
1181
+ }
1791
1182
  process.exit(0);
1792
1183
  }
1793
1184
 
@@ -1796,11 +1187,11 @@ const credentialsSchema = z__namespace.object({
1796
1187
  token: z__namespace.string()
1797
1188
  });
1798
1189
  async function readCredentials() {
1799
- if (!node_fs.existsSync(configuration.privateKeyFile)) {
1190
+ if (!node_fs.existsSync(types.configuration.privateKeyFile)) {
1800
1191
  return null;
1801
1192
  }
1802
1193
  try {
1803
- const keyBase64 = await promises.readFile(configuration.privateKeyFile, "utf8");
1194
+ const keyBase64 = await promises.readFile(types.configuration.privateKeyFile, "utf8");
1804
1195
  const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
1805
1196
  return {
1806
1197
  secret: new Uint8Array(Buffer.from(credentials.secret, "base64")),
@@ -1811,11 +1202,11 @@ async function readCredentials() {
1811
1202
  }
1812
1203
  }
1813
1204
  async function writeCredentials(credentials) {
1814
- if (!node_fs.existsSync(configuration.happyDir)) {
1815
- await promises.mkdir(configuration.happyDir, { recursive: true });
1205
+ if (!node_fs.existsSync(types.configuration.happyDir)) {
1206
+ await promises.mkdir(types.configuration.happyDir, { recursive: true });
1816
1207
  }
1817
- await promises.writeFile(configuration.privateKeyFile, JSON.stringify({
1818
- secret: encodeBase64(credentials.secret),
1208
+ await promises.writeFile(types.configuration.privateKeyFile, JSON.stringify({
1209
+ secret: types.encodeBase64(credentials.secret),
1819
1210
  token: credentials.token
1820
1211
  }, null, 2));
1821
1212
  }
@@ -1837,24 +1228,29 @@ async function doAuth() {
1837
1228
  const secret = new Uint8Array(node_crypto.randomBytes(32));
1838
1229
  const keypair = tweetnacl.box.keyPair.fromSecretKey(secret);
1839
1230
  try {
1840
- await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
1841
- publicKey: encodeBase64(keypair.publicKey)
1231
+ await axios.post(`${types.configuration.serverUrl}/v1/auth/request`, {
1232
+ publicKey: types.encodeBase64(keypair.publicKey)
1842
1233
  });
1843
1234
  } catch (error) {
1844
1235
  console.log("Failed to create authentication request, please try again later.");
1845
1236
  return null;
1846
1237
  }
1847
1238
  console.log("Please, authenticate using mobile app");
1848
- displayQRCode("happy://terminal?" + encodeBase64Url(keypair.publicKey));
1239
+ const authUrl = "happy://terminal?" + types.encodeBase64Url(keypair.publicKey);
1240
+ displayQRCode(authUrl);
1241
+ if (process.env.DEBUG === "1") {
1242
+ console.log("\n\u{1F4CB} For manual entry, copy this URL:");
1243
+ console.log(authUrl);
1244
+ }
1849
1245
  let credentials = null;
1850
1246
  while (true) {
1851
1247
  try {
1852
- const response = await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
1853
- publicKey: encodeBase64(keypair.publicKey)
1248
+ const response = await axios.post(`${types.configuration.serverUrl}/v1/auth/request`, {
1249
+ publicKey: types.encodeBase64(keypair.publicKey)
1854
1250
  });
1855
1251
  if (response.data.state === "authorized") {
1856
1252
  let token = response.data.token;
1857
- let r = decodeBase64(response.data.response);
1253
+ let r = types.decodeBase64(response.data.response);
1858
1254
  let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
1859
1255
  if (decrypted) {
1860
1256
  credentials = {
@@ -1872,7 +1268,7 @@ async function doAuth() {
1872
1268
  console.log("Failed to create authentication request, please try again later.");
1873
1269
  return null;
1874
1270
  }
1875
- await delay(1e3);
1271
+ await types.delay(1e3);
1876
1272
  }
1877
1273
  return null;
1878
1274
  }
@@ -1890,9 +1286,9 @@ function decryptWithEphemeralKey(encryptedBundle, recipientSecretKey) {
1890
1286
  (async () => {
1891
1287
  const args = process.argv.slice(2);
1892
1288
  let installationLocation = args.includes("--local") || process.env.HANDY_LOCAL ? "local" : "global";
1893
- initializeConfiguration(installationLocation);
1894
- initLoggerWithGlobalConfiguration();
1895
- logger.debug("Starting happy CLI with args: ", process.argv);
1289
+ types.initializeConfiguration(installationLocation);
1290
+ types.initLoggerWithGlobalConfiguration();
1291
+ types.logger.debug("Starting happy CLI with args: ", process.argv);
1896
1292
  const subcommand = args[0];
1897
1293
  if (subcommand === "logout") {
1898
1294
  try {
@@ -1905,25 +1301,63 @@ function decryptWithEphemeralKey(encryptedBundle, recipientSecretKey) {
1905
1301
  process.exit(1);
1906
1302
  }
1907
1303
  return;
1908
- } else if (subcommand === "login" || subcommand === "auth") {
1909
- await doAuth();
1304
+ } else if (subcommand === "daemon") {
1305
+ if (process.env.HAPPY_DAEMON_MODE) {
1306
+ const { run } = await Promise.resolve().then(function () { return require('./run-q2To6b-c.cjs'); });
1307
+ await run();
1308
+ } else {
1309
+ const daemonSubcommand = args[1];
1310
+ if (daemonSubcommand === "install") {
1311
+ const { install } = await Promise.resolve().then(function () { return require('./install-B2r_gX72.cjs'); });
1312
+ try {
1313
+ await install();
1314
+ } catch (error) {
1315
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
1316
+ process.exit(1);
1317
+ }
1318
+ } else if (daemonSubcommand === "uninstall") {
1319
+ const { uninstall } = await Promise.resolve().then(function () { return require('./uninstall-C42CoSCI.cjs'); });
1320
+ try {
1321
+ await uninstall();
1322
+ } catch (error) {
1323
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
1324
+ process.exit(1);
1325
+ }
1326
+ } else {
1327
+ console.log(`
1328
+ ${chalk.bold("happy daemon")} - Daemon management
1329
+
1330
+ ${chalk.bold("Usage:")}
1331
+ sudo happy daemon install Install the daemon (requires sudo)
1332
+ sudo happy daemon uninstall Uninstall the daemon (requires sudo)
1333
+
1334
+ ${chalk.bold("Note:")} The daemon runs in the background and provides persistent services.
1335
+ Currently only supported on macOS.
1336
+ `);
1337
+ }
1338
+ }
1910
1339
  return;
1911
1340
  } else {
1912
1341
  const options = {};
1913
1342
  let showHelp = false;
1914
1343
  let showVersion = false;
1344
+ let forceAuth = false;
1915
1345
  for (let i = 0; i < args.length; i++) {
1916
1346
  const arg = args[i];
1917
1347
  if (arg === "-h" || arg === "--help") {
1918
1348
  showHelp = true;
1919
1349
  } else if (arg === "-v" || arg === "--version") {
1920
1350
  showVersion = true;
1351
+ } else if (arg === "--auth" || arg === "--login") {
1352
+ forceAuth = true;
1921
1353
  } else if (arg === "-m" || arg === "--model") {
1922
1354
  options.model = args[++i];
1923
1355
  } else if (arg === "-p" || arg === "--permission-mode") {
1924
- options.permissionMode = args[++i];
1356
+ options.permissionMode = z.z.enum(["auto", "default", "plan"]).parse(args[++i]);
1925
1357
  } else if (arg === "--local") {
1926
1358
  i++;
1359
+ } else if (arg === "--happy-starting-mode") {
1360
+ options.startingMode = z.z.enum(["interactive", "remote"]).parse(args[++i]);
1927
1361
  } else {
1928
1362
  console.error(chalk.red(`Unknown argument: ${arg}`));
1929
1363
  process.exit(1);
@@ -1936,35 +1370,38 @@ ${chalk.bold("happy")} - Claude Code session sharing
1936
1370
  ${chalk.bold("Usage:")}
1937
1371
  happy [options]
1938
1372
  happy logout Logs out of your account and removes data directory
1939
- happy login Show your secret QR code
1940
- happy auth Same as login
1373
+ happy daemon Manage the background daemon (macOS only)
1941
1374
 
1942
1375
  ${chalk.bold("Options:")}
1943
1376
  -h, --help Show this help message
1944
1377
  -v, --version Show version
1945
1378
  -m, --model <model> Claude model to use (default: sonnet)
1946
1379
  -p, --permission-mode Permission mode: auto, default, or plan
1380
+ --auth, --login Force re-authentication
1947
1381
 
1948
1382
  [Advanced]
1949
1383
  --local < global | local >
1950
1384
  Will use .happy folder in the current directory for storing your private key and debug logs.
1951
1385
  You will require re-login each time you run this in a new directory.
1952
- Use with login to show either global or local QR code.
1386
+
1387
+ --happy-starting-mode <mode> Start in specified mode (interactive or remote)
1388
+ Default: interactive
1953
1389
 
1954
1390
  ${chalk.bold("Examples:")}
1955
1391
  happy Start a session with default settings
1956
1392
  happy -m opus Use Claude Opus model
1957
1393
  happy -p plan Use plan permission mode
1394
+ happy --auth Force re-authentication before starting session
1958
1395
  happy logout Logs out of your account and removes data directory
1959
1396
  `);
1960
1397
  process.exit(0);
1961
1398
  }
1962
1399
  if (showVersion) {
1963
- console.log("0.1.3");
1400
+ console.log(packageJson.version);
1964
1401
  process.exit(0);
1965
1402
  }
1966
1403
  let credentials = await readCredentials();
1967
- if (!credentials) {
1404
+ if (!credentials || forceAuth) {
1968
1405
  let res = await doAuth();
1969
1406
  if (!res) {
1970
1407
  process.exit(1);
@@ -1983,7 +1420,7 @@ ${chalk.bold("Examples:")}
1983
1420
  }
1984
1421
  })();
1985
1422
  async function cleanKey() {
1986
- const happyDir = configuration.happyDir;
1423
+ const happyDir = types.configuration.happyDir;
1987
1424
  if (!node_fs.existsSync(happyDir)) {
1988
1425
  console.log(chalk.yellow("No happy data directory found at:"), happyDir);
1989
1426
  return;