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.
- package/dist/index.cjs +354 -917
- package/dist/index.mjs +280 -843
- package/dist/install-B2r_gX72.cjs +109 -0
- package/dist/install-HKe7dyS4.mjs +107 -0
- package/dist/lib.cjs +32 -0
- package/dist/lib.d.cts +727 -0
- package/dist/lib.d.mts +727 -0
- package/dist/lib.mjs +14 -0
- package/dist/run-FBXkmmN7.mjs +32 -0
- package/dist/run-q2To6b-c.cjs +34 -0
- package/dist/types-fXgEaaqP.mjs +861 -0
- package/dist/types-mykDX2xe.cjs +872 -0
- package/dist/uninstall-C42CoSCI.cjs +53 -0
- package/dist/uninstall-CLkTtlMv.mjs +51 -0
- package/package.json +28 -13
- package/dist/auth/auth.d.ts +0 -38
- package/dist/auth/auth.js +0 -76
- package/dist/auth/auth.test.d.ts +0 -7
- package/dist/auth/auth.test.js +0 -96
- package/dist/auth/crypto.d.ts +0 -25
- package/dist/auth/crypto.js +0 -36
- package/dist/claude/claude.d.ts +0 -54
- package/dist/claude/claude.js +0 -170
- package/dist/claude/claude.test.d.ts +0 -7
- package/dist/claude/claude.test.js +0 -130
- package/dist/claude/types.d.ts +0 -37
- package/dist/claude/types.js +0 -7
- package/dist/commands/start.d.ts +0 -38
- package/dist/commands/start.js +0 -161
- package/dist/commands/start.test.d.ts +0 -7
- package/dist/commands/start.test.js +0 -307
- package/dist/handlers/message-handler.d.ts +0 -65
- package/dist/handlers/message-handler.js +0 -187
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/session/service.d.ts +0 -27
- package/dist/session/service.js +0 -93
- package/dist/session/service.test.d.ts +0 -7
- package/dist/session/service.test.js +0 -71
- package/dist/session/types.d.ts +0 -44
- package/dist/session/types.js +0 -4
- package/dist/socket/client.d.ts +0 -50
- package/dist/socket/client.js +0 -136
- package/dist/socket/client.test.d.ts +0 -7
- package/dist/socket/client.test.js +0 -74
- package/dist/socket/types.d.ts +0 -80
- package/dist/socket/types.js +0 -12
- package/dist/utils/config.d.ts +0 -22
- package/dist/utils/config.js +0 -23
- package/dist/utils/logger.d.ts +0 -26
- package/dist/utils/logger.js +0 -60
- package/dist/utils/paths.d.ts +0 -18
- package/dist/utils/paths.js +0 -24
- package/dist/utils/qrcode.d.ts +0 -19
- package/dist/utils/qrcode.js +0 -37
- package/dist/utils/qrcode.test.d.ts +0 -7
- 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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
615
|
-
|
|
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
|
-
|
|
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.
|
|
748
|
+
if (process.stdin.isTTY) {
|
|
749
|
+
process.stdin.setRawMode(false);
|
|
750
|
+
}
|
|
1532
751
|
};
|
|
1533
752
|
process.stdin.resume();
|
|
1534
|
-
process.stdin.
|
|
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.
|
|
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
|
-
|
|
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 === "
|
|
1909
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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;
|