happy-coder 0.1.10 → 0.1.12
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/README.md +2 -0
- package/bin/happy +1 -0
- package/bin/happy.cmd +1 -0
- package/dist/index-B2GqfEZV.cjs +1564 -0
- package/dist/index-QItBXhux.mjs +1540 -0
- package/dist/index.cjs +585 -279
- package/dist/index.mjs +575 -269
- package/dist/install-B0DnBGS_.mjs +29 -0
- package/dist/install-B2r_gX72.cjs +109 -0
- package/dist/install-C809w0Cj.cjs +31 -0
- package/dist/install-DEPy62QN.mjs +97 -0
- package/dist/install-GZIzyuIE.cjs +99 -0
- package/dist/install-HKe7dyS4.mjs +107 -0
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +22 -3
- package/dist/lib.d.mts +22 -3
- package/dist/lib.mjs +1 -1
- package/dist/run-BmEaINbl.cjs +250 -0
- package/dist/run-DMbKhYfb.mjs +247 -0
- package/dist/run-FBXkmmN7.mjs +32 -0
- package/dist/run-q2To6b-c.cjs +34 -0
- package/dist/types-BRICSarm.mjs +870 -0
- package/dist/types-BTQRfIr3.cjs +892 -0
- package/dist/types-CEvzGLMI.cjs +882 -0
- package/dist/{types-DnQGY77F.mjs → types-D39L8JSd.mjs} +55 -23
- package/dist/types-DYBiuNUQ.cjs +883 -0
- package/dist/types-Df5dlWLV.mjs +871 -0
- package/dist/types-fXgEaaqP.mjs +861 -0
- package/dist/{types-B2JzqUiU.cjs → types-hotUTaWz.cjs} +53 -21
- package/dist/types-mykDX2xe.cjs +872 -0
- package/dist/types-tLWMaptR.mjs +879 -0
- package/dist/uninstall-BGgl5V8F.mjs +29 -0
- package/dist/uninstall-BWHglipH.mjs +40 -0
- package/dist/uninstall-C42CoSCI.cjs +53 -0
- package/dist/uninstall-CLkTtlMv.mjs +51 -0
- package/dist/uninstall-CdHMb6wi.cjs +31 -0
- package/dist/uninstall-FXyyAuGU.cjs +42 -0
- package/package.json +9 -3
- package/ripgrep/COPYING +3 -0
- package/ripgrep/arm64-darwin/rg +0 -0
- package/ripgrep/arm64-darwin/ripgrep.node +0 -0
- package/ripgrep/arm64-linux/rg +0 -0
- package/ripgrep/arm64-linux/ripgrep.node +0 -0
- package/ripgrep/x64-darwin/rg +0 -0
- package/ripgrep/x64-darwin/ripgrep.node +0 -0
- package/ripgrep/x64-linux/rg +0 -0
- package/ripgrep/x64-linux/ripgrep.node +0 -0
- package/ripgrep/x64-win32/rg.exe +0 -0
- package/ripgrep/x64-win32/ripgrep.node +0 -0
- package/scripts/ripgrep_launcher.cjs +57 -0
package/dist/index.cjs
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
|
-
var types = require('./types-
|
|
4
|
+
var types = require('./types-hotUTaWz.cjs');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
6
|
var claudeCode = require('@anthropic-ai/claude-code');
|
|
7
7
|
var node_fs = require('node:fs');
|
|
8
|
-
var os = require('node:os');
|
|
9
8
|
var node_path = require('node:path');
|
|
9
|
+
var os = require('node:os');
|
|
10
|
+
var promises = require('fs/promises');
|
|
10
11
|
var node_child_process = require('node:child_process');
|
|
11
12
|
var node_readline = require('node:readline');
|
|
12
13
|
var node_url = require('node:url');
|
|
13
|
-
var promises = require('node:fs/promises');
|
|
14
|
+
var promises$1 = require('node:fs/promises');
|
|
14
15
|
var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
15
16
|
var node_http = require('node:http');
|
|
16
17
|
var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
17
18
|
var z = require('zod');
|
|
18
|
-
var node_https = require('node:https');
|
|
19
|
-
var net = require('node:net');
|
|
20
19
|
var child_process = require('child_process');
|
|
21
20
|
var util = require('util');
|
|
22
|
-
var promises$1 = require('fs/promises');
|
|
23
21
|
var crypto = require('crypto');
|
|
24
22
|
var path = require('path');
|
|
23
|
+
var url = require('url');
|
|
24
|
+
var httpProxy = require('http-proxy');
|
|
25
25
|
var tweetnacl = require('tweetnacl');
|
|
26
26
|
var axios = require('axios');
|
|
27
27
|
var qrcode = require('qrcode-terminal');
|
|
@@ -171,9 +171,13 @@ function printDivider() {
|
|
|
171
171
|
console.log(chalk.gray("\u2550".repeat(60)));
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
function getProjectPath(workingDirectory) {
|
|
175
|
+
const projectId = node_path.resolve(workingDirectory).replace(/[\\\/\.:]/g, "-");
|
|
176
|
+
return node_path.join(os.homedir(), ".claude", "projects", projectId);
|
|
177
|
+
}
|
|
178
|
+
|
|
174
179
|
function claudeCheckSession(sessionId, path) {
|
|
175
|
-
const
|
|
176
|
-
const projectDir = node_path.join(os.homedir(), ".claude", "projects", projectName);
|
|
180
|
+
const projectDir = getProjectPath(path);
|
|
177
181
|
const sessionFile = node_path.join(projectDir, `${sessionId}.jsonl`);
|
|
178
182
|
const sessionExists = node_fs.existsSync(sessionFile);
|
|
179
183
|
if (!sessionExists) {
|
|
@@ -191,11 +195,29 @@ function claudeCheckSession(sessionId, path) {
|
|
|
191
195
|
return hasGoodMessage;
|
|
192
196
|
}
|
|
193
197
|
|
|
198
|
+
async function awaitFileExist(file, timeout = 1e4) {
|
|
199
|
+
const startTime = Date.now();
|
|
200
|
+
while (Date.now() - startTime < timeout) {
|
|
201
|
+
try {
|
|
202
|
+
await promises.access(file);
|
|
203
|
+
return true;
|
|
204
|
+
} catch (e) {
|
|
205
|
+
await types.delay(1e3);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
194
211
|
async function claudeRemote(opts) {
|
|
195
212
|
let startFrom = opts.sessionId;
|
|
196
213
|
if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
|
|
197
214
|
startFrom = null;
|
|
198
215
|
}
|
|
216
|
+
if (opts.claudeEnvVars) {
|
|
217
|
+
Object.entries(opts.claudeEnvVars).forEach(([key, value]) => {
|
|
218
|
+
process.env[key] = value;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
199
221
|
const abortController = new AbortController();
|
|
200
222
|
const sdkOptions = {
|
|
201
223
|
cwd: opts.path,
|
|
@@ -205,6 +227,9 @@ async function claudeRemote(opts) {
|
|
|
205
227
|
executable: "node",
|
|
206
228
|
abortController
|
|
207
229
|
};
|
|
230
|
+
if (opts.claudeArgs && opts.claudeArgs.length > 0) {
|
|
231
|
+
sdkOptions.executableArgs = [...sdkOptions.executableArgs || [], ...opts.claudeArgs];
|
|
232
|
+
}
|
|
208
233
|
let aborted = false;
|
|
209
234
|
let response;
|
|
210
235
|
opts.abort.addEventListener("abort", () => {
|
|
@@ -239,18 +264,14 @@ async function claudeRemote(opts) {
|
|
|
239
264
|
try {
|
|
240
265
|
types.logger.debug(`[claudeRemote] Starting to iterate over response`);
|
|
241
266
|
for await (const message of response) {
|
|
242
|
-
types.logger.
|
|
267
|
+
types.logger.debugLargeJson(`[claudeRemote] Message ${message.type}`, message);
|
|
243
268
|
formatClaudeMessage(message, opts.onAssistantResult);
|
|
244
269
|
if (message.type === "system" && message.subtype === "init") {
|
|
245
|
-
|
|
246
|
-
const projectDir =
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
opts.onSessionFound(message.session_id);
|
|
251
|
-
watcher.close();
|
|
252
|
-
}
|
|
253
|
-
});
|
|
270
|
+
types.logger.debug(`[claudeRemote] Waiting for session file to be written to disk: ${message.session_id}`);
|
|
271
|
+
const projectDir = getProjectPath(opts.path);
|
|
272
|
+
const found = await awaitFileExist(node_path.join(projectDir, `${message.session_id}.jsonl`));
|
|
273
|
+
types.logger.debug(`[claudeRemote] Session file found: ${message.session_id} ${found}`);
|
|
274
|
+
opts.onSessionFound(message.session_id);
|
|
254
275
|
}
|
|
255
276
|
}
|
|
256
277
|
types.logger.debug(`[claudeRemote] Finished iterating over response`);
|
|
@@ -272,10 +293,9 @@ async function claudeRemote(opts) {
|
|
|
272
293
|
types.logger.debug(`[claudeRemote] Function completed`);
|
|
273
294
|
}
|
|
274
295
|
|
|
275
|
-
const __dirname$
|
|
296
|
+
const __dirname$2 = 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))));
|
|
276
297
|
async function claudeLocal(opts) {
|
|
277
|
-
const
|
|
278
|
-
const projectDir = node_path.join(os.homedir(), ".claude", "projects", projectName);
|
|
298
|
+
const projectDir = getProjectPath(opts.path);
|
|
279
299
|
node_fs.mkdirSync(projectDir, { recursive: true });
|
|
280
300
|
const watcher = node_fs.watch(projectDir);
|
|
281
301
|
let resolvedSessionId = null;
|
|
@@ -309,11 +329,19 @@ async function claudeLocal(opts) {
|
|
|
309
329
|
if (startFrom) {
|
|
310
330
|
args.push("--resume", startFrom);
|
|
311
331
|
}
|
|
312
|
-
|
|
332
|
+
if (opts.claudeArgs) {
|
|
333
|
+
args.push(...opts.claudeArgs);
|
|
334
|
+
}
|
|
335
|
+
const claudeCliPath = process.env.HAPPY_CLAUDE_CLI_PATH || node_path.resolve(node_path.join(__dirname$2, "..", "scripts", "claudeInteractiveLaunch.cjs"));
|
|
336
|
+
const env = {
|
|
337
|
+
...process.env,
|
|
338
|
+
...opts.claudeEnvVars
|
|
339
|
+
};
|
|
313
340
|
const child = node_child_process.spawn("node", [claudeCliPath, ...args], {
|
|
314
341
|
stdio: ["inherit", "inherit", "inherit", "pipe"],
|
|
315
342
|
signal: opts.abort,
|
|
316
|
-
cwd: opts.path
|
|
343
|
+
cwd: opts.path,
|
|
344
|
+
env
|
|
317
345
|
});
|
|
318
346
|
if (child.stdio[3]) {
|
|
319
347
|
const rl = node_readline.createInterface({
|
|
@@ -538,16 +566,44 @@ class InvalidateSync {
|
|
|
538
566
|
};
|
|
539
567
|
}
|
|
540
568
|
|
|
569
|
+
function startFileWatcher(file, onFileChange) {
|
|
570
|
+
const abortController = new AbortController();
|
|
571
|
+
void (async () => {
|
|
572
|
+
while (true) {
|
|
573
|
+
try {
|
|
574
|
+
types.logger.debug(`[FILE_WATCHER] Starting watcher for ${file}`);
|
|
575
|
+
const watcher = promises.watch(file, { persistent: true, signal: abortController.signal });
|
|
576
|
+
for await (const event of watcher) {
|
|
577
|
+
if (abortController.signal.aborted) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
types.logger.debug(`[FILE_WATCHER] File changed: ${file}`);
|
|
581
|
+
onFileChange(file);
|
|
582
|
+
}
|
|
583
|
+
} catch (e) {
|
|
584
|
+
if (abortController.signal.aborted) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
types.logger.debug(`[FILE_WATCHER] Watch error: ${e.message}, restarting watcher in a second`);
|
|
588
|
+
await types.delay(1e3);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
})();
|
|
592
|
+
return () => {
|
|
593
|
+
abortController.abort();
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
541
597
|
function createSessionScanner(opts) {
|
|
542
|
-
const
|
|
543
|
-
const projectDir = node_path.join(os.homedir(), ".claude", "projects", projectName);
|
|
598
|
+
const projectDir = getProjectPath(opts.workingDirectory);
|
|
544
599
|
let finishedSessions = /* @__PURE__ */ new Set();
|
|
545
600
|
let pendingSessions = /* @__PURE__ */ new Set();
|
|
546
601
|
let currentSessionId = null;
|
|
547
|
-
let
|
|
548
|
-
let
|
|
549
|
-
let
|
|
602
|
+
let watchers = /* @__PURE__ */ new Map();
|
|
603
|
+
let processedMessageKeys = /* @__PURE__ */ new Set();
|
|
604
|
+
let unmatchedServerMessageContents = /* @__PURE__ */ new Set();
|
|
550
605
|
const sync = new InvalidateSync(async () => {
|
|
606
|
+
types.logger.debug(`[SESSION_SCANNER] Syncing...`);
|
|
551
607
|
let sessions = [];
|
|
552
608
|
for (let p of pendingSessions) {
|
|
553
609
|
sessions.push(p);
|
|
@@ -559,13 +615,17 @@ function createSessionScanner(opts) {
|
|
|
559
615
|
const expectedSessionFile = node_path.join(projectDir, `${sessionId}.jsonl`);
|
|
560
616
|
let file;
|
|
561
617
|
try {
|
|
562
|
-
file = await promises.readFile(expectedSessionFile, "utf-8");
|
|
618
|
+
file = await promises$1.readFile(expectedSessionFile, "utf-8");
|
|
563
619
|
} catch (error) {
|
|
620
|
+
types.logger.debug(`[SESSION_SCANNER] Session file not found: ${expectedSessionFile}`);
|
|
564
621
|
return;
|
|
565
622
|
}
|
|
566
623
|
let lines = file.split("\n");
|
|
567
624
|
for (let l of lines) {
|
|
568
625
|
try {
|
|
626
|
+
if (l.trim() === "") {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
569
629
|
let message = JSON.parse(l);
|
|
570
630
|
let parsed = types.RawJSONLinesSchema.safeParse(message);
|
|
571
631
|
if (!parsed.success) {
|
|
@@ -573,21 +633,22 @@ function createSessionScanner(opts) {
|
|
|
573
633
|
continue;
|
|
574
634
|
}
|
|
575
635
|
let key = getMessageKey(parsed.data);
|
|
576
|
-
if (
|
|
636
|
+
if (processedMessageKeys.has(key)) {
|
|
577
637
|
continue;
|
|
578
638
|
}
|
|
579
|
-
|
|
639
|
+
processedMessageKeys.add(key);
|
|
580
640
|
types.logger.debugLargeJson(`[SESSION_SCANNER] Processing message`, parsed.data);
|
|
581
641
|
types.logger.debug(`[SESSION_SCANNER] Message key (new): ${key}`);
|
|
582
642
|
if (parsed.data.type === "user" && typeof parsed.data.message.content === "string" && parsed.data.isSidechain !== true && parsed.data.isMeta !== true) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
643
|
+
if (unmatchedServerMessageContents.has(parsed.data.message.content)) {
|
|
644
|
+
types.logger.debug(`[SESSION_SCANNER] Matched server message echo: ${parsed.data.uuid}`);
|
|
645
|
+
unmatchedServerMessageContents.delete(parsed.data.message.content);
|
|
586
646
|
continue;
|
|
587
647
|
}
|
|
588
648
|
}
|
|
589
649
|
opts.onMessage(message);
|
|
590
650
|
} catch (e) {
|
|
651
|
+
types.logger.debug(`[SESSION_SCANNER] Error processing message: ${e}`);
|
|
591
652
|
continue;
|
|
592
653
|
}
|
|
593
654
|
}
|
|
@@ -601,40 +662,37 @@ function createSessionScanner(opts) {
|
|
|
601
662
|
finishedSessions.add(p);
|
|
602
663
|
}
|
|
603
664
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
try {
|
|
610
|
-
for await (const change of promises.watch(sessionFile, { persistent: true, signal: currentSessionWatcherAbortController.signal })) {
|
|
611
|
-
await processSessionFile(currentSessionId);
|
|
612
|
-
}
|
|
613
|
-
} catch (error) {
|
|
614
|
-
if (error.name !== "AbortError") {
|
|
615
|
-
types.logger.debug(`[SESSION_SCANNER] Watch error: ${error.message}`);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
665
|
+
for (let p of sessions) {
|
|
666
|
+
if (!watchers.has(p)) {
|
|
667
|
+
watchers.set(p, startFileWatcher(node_path.join(projectDir, `${p}.jsonl`), () => {
|
|
668
|
+
sync.invalidate();
|
|
669
|
+
}));
|
|
618
670
|
}
|
|
619
|
-
}
|
|
671
|
+
}
|
|
620
672
|
});
|
|
673
|
+
sync.invalidate();
|
|
621
674
|
const intervalId = setInterval(() => {
|
|
622
675
|
sync.invalidate();
|
|
623
676
|
}, 3e3);
|
|
624
677
|
return {
|
|
625
|
-
refresh: () => sync.invalidate(),
|
|
626
678
|
cleanup: () => {
|
|
627
679
|
clearInterval(intervalId);
|
|
628
|
-
|
|
680
|
+
for (let w of watchers.values()) {
|
|
681
|
+
w();
|
|
682
|
+
}
|
|
683
|
+
watchers.clear();
|
|
629
684
|
},
|
|
630
685
|
onNewSession: (sessionId) => {
|
|
631
686
|
if (currentSessionId === sessionId) {
|
|
687
|
+
types.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is the same as the current session, skipping`);
|
|
632
688
|
return;
|
|
633
689
|
}
|
|
634
690
|
if (finishedSessions.has(sessionId)) {
|
|
691
|
+
types.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is already finished, skipping`);
|
|
635
692
|
return;
|
|
636
693
|
}
|
|
637
694
|
if (pendingSessions.has(sessionId)) {
|
|
695
|
+
types.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is already pending, skipping`);
|
|
638
696
|
return;
|
|
639
697
|
}
|
|
640
698
|
if (currentSessionId) {
|
|
@@ -645,13 +703,18 @@ function createSessionScanner(opts) {
|
|
|
645
703
|
sync.invalidate();
|
|
646
704
|
},
|
|
647
705
|
onRemoteUserMessageForDeduplication: (messageContent) => {
|
|
648
|
-
|
|
706
|
+
types.logger.debug(`[SESSION_SCANNER] Adding unmatched server message content: ${messageContent.substring(0, 50)}...`);
|
|
707
|
+
unmatchedServerMessageContents.add(messageContent);
|
|
649
708
|
}
|
|
650
709
|
};
|
|
651
710
|
}
|
|
652
711
|
function getMessageKey(message) {
|
|
653
712
|
if (message.type === "user") {
|
|
654
|
-
|
|
713
|
+
if (Array.isArray(message.message.content) && message.message.content.length > 0 && typeof message.message.content[0] === "object" && "text" in message.message.content[0]) {
|
|
714
|
+
return `user-message-content:${stableStringify(message.message.content[0].text)}`;
|
|
715
|
+
} else {
|
|
716
|
+
return `user-message-content:${stableStringify(message.message.content)}`;
|
|
717
|
+
}
|
|
655
718
|
} else if (message.type === "assistant") {
|
|
656
719
|
const { usage, ...messageWithoutUsage } = message.message;
|
|
657
720
|
return stableStringify(messageWithoutUsage);
|
|
@@ -663,6 +726,9 @@ function getMessageKey(message) {
|
|
|
663
726
|
return `unknown:<error, this should be unreachable>`;
|
|
664
727
|
}
|
|
665
728
|
function stableStringify(obj) {
|
|
729
|
+
if (!obj) {
|
|
730
|
+
return "null";
|
|
731
|
+
}
|
|
666
732
|
return JSON.stringify(sortKeys(obj), null, 2);
|
|
667
733
|
}
|
|
668
734
|
function sortKeys(value) {
|
|
@@ -679,7 +745,7 @@ function sortKeys(value) {
|
|
|
679
745
|
}
|
|
680
746
|
|
|
681
747
|
async function loop(opts) {
|
|
682
|
-
let mode = opts.startingMode ?? "
|
|
748
|
+
let mode = opts.startingMode ?? "local";
|
|
683
749
|
let currentMessageQueue = new MessageQueue();
|
|
684
750
|
let sessionId = null;
|
|
685
751
|
let onMessage = null;
|
|
@@ -703,38 +769,74 @@ async function loop(opts) {
|
|
|
703
769
|
};
|
|
704
770
|
while (true) {
|
|
705
771
|
if (currentMessageQueue.size() > 0) {
|
|
706
|
-
mode
|
|
772
|
+
if (mode !== "remote") {
|
|
773
|
+
mode = "remote";
|
|
774
|
+
if (opts.onModeChange) {
|
|
775
|
+
opts.onModeChange(mode);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
707
778
|
continue;
|
|
708
779
|
}
|
|
709
|
-
if (mode === "
|
|
780
|
+
if (mode === "local") {
|
|
710
781
|
let abortedOutside = false;
|
|
711
782
|
const interactiveAbortController = new AbortController();
|
|
712
783
|
opts.session.setHandler("switch", () => {
|
|
713
784
|
if (!interactiveAbortController.signal.aborted) {
|
|
714
785
|
abortedOutside = true;
|
|
715
|
-
mode
|
|
786
|
+
if (mode !== "remote") {
|
|
787
|
+
mode = "remote";
|
|
788
|
+
if (opts.onModeChange) {
|
|
789
|
+
opts.onModeChange(mode);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
716
792
|
interactiveAbortController.abort();
|
|
717
793
|
}
|
|
718
794
|
});
|
|
795
|
+
opts.session.setHandler("abort", () => {
|
|
796
|
+
if (onMessage) {
|
|
797
|
+
onMessage();
|
|
798
|
+
}
|
|
799
|
+
});
|
|
719
800
|
onMessage = () => {
|
|
720
801
|
if (!interactiveAbortController.signal.aborted) {
|
|
721
802
|
abortedOutside = true;
|
|
722
|
-
mode
|
|
803
|
+
if (mode !== "remote") {
|
|
804
|
+
mode = "remote";
|
|
805
|
+
if (opts.onModeChange) {
|
|
806
|
+
opts.onModeChange(mode);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
opts.session.sendSessionEvent({ type: "message", message: "Inference aborted" });
|
|
723
810
|
interactiveAbortController.abort();
|
|
724
811
|
}
|
|
725
812
|
onMessage = null;
|
|
726
813
|
};
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
814
|
+
try {
|
|
815
|
+
if (opts.onProcessStart) {
|
|
816
|
+
opts.onProcessStart("local");
|
|
817
|
+
}
|
|
818
|
+
await claudeLocal({
|
|
819
|
+
path: opts.path,
|
|
820
|
+
sessionId,
|
|
821
|
+
onSessionFound,
|
|
822
|
+
abort: interactiveAbortController.signal,
|
|
823
|
+
claudeEnvVars: opts.claudeEnvVars,
|
|
824
|
+
claudeArgs: opts.claudeArgs
|
|
825
|
+
});
|
|
826
|
+
} catch (e) {
|
|
827
|
+
if (!interactiveAbortController.signal.aborted) {
|
|
828
|
+
opts.session.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
|
|
829
|
+
}
|
|
830
|
+
} finally {
|
|
831
|
+
if (opts.onProcessStop) {
|
|
832
|
+
opts.onProcessStop("local");
|
|
833
|
+
}
|
|
834
|
+
}
|
|
733
835
|
onMessage = null;
|
|
734
836
|
if (!abortedOutside) {
|
|
735
837
|
return;
|
|
736
838
|
}
|
|
737
|
-
if (mode !== "
|
|
839
|
+
if (mode !== "local") {
|
|
738
840
|
console.log("Switching to remote mode...");
|
|
739
841
|
}
|
|
740
842
|
}
|
|
@@ -748,7 +850,13 @@ async function loop(opts) {
|
|
|
748
850
|
});
|
|
749
851
|
const abortHandler = () => {
|
|
750
852
|
if (!remoteAbortController.signal.aborted) {
|
|
751
|
-
mode
|
|
853
|
+
if (mode !== "local") {
|
|
854
|
+
mode = "local";
|
|
855
|
+
if (opts.onModeChange) {
|
|
856
|
+
opts.onModeChange(mode);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
opts.session.sendSessionEvent({ type: "message", message: "Inference aborted" });
|
|
752
860
|
remoteAbortController.abort();
|
|
753
861
|
}
|
|
754
862
|
if (process.stdin.isTTY) {
|
|
@@ -763,6 +871,9 @@ async function loop(opts) {
|
|
|
763
871
|
process.stdin.on("data", abortHandler);
|
|
764
872
|
try {
|
|
765
873
|
types.logger.debug(`Starting claudeRemote with messages: ${currentMessageQueue.size()}`);
|
|
874
|
+
if (opts.onProcessStart) {
|
|
875
|
+
opts.onProcessStart("remote");
|
|
876
|
+
}
|
|
766
877
|
await claudeRemote({
|
|
767
878
|
abort: remoteAbortController.signal,
|
|
768
879
|
sessionId,
|
|
@@ -772,9 +883,18 @@ async function loop(opts) {
|
|
|
772
883
|
onSessionFound,
|
|
773
884
|
messages: currentMessageQueue,
|
|
774
885
|
onAssistantResult: opts.onAssistantResult,
|
|
775
|
-
interruptController: opts.interruptController
|
|
886
|
+
interruptController: opts.interruptController,
|
|
887
|
+
claudeEnvVars: opts.claudeEnvVars,
|
|
888
|
+
claudeArgs: opts.claudeArgs
|
|
776
889
|
});
|
|
890
|
+
} catch (e) {
|
|
891
|
+
if (!remoteAbortController.signal.aborted) {
|
|
892
|
+
opts.session.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
|
|
893
|
+
}
|
|
777
894
|
} finally {
|
|
895
|
+
if (opts.onProcessStop) {
|
|
896
|
+
opts.onProcessStop("remote");
|
|
897
|
+
}
|
|
778
898
|
process.stdin.off("data", abortHandler);
|
|
779
899
|
if (process.stdin.isTTY) {
|
|
780
900
|
process.stdin.setRawMode(false);
|
|
@@ -889,159 +1009,41 @@ class InterruptController {
|
|
|
889
1009
|
}
|
|
890
1010
|
}
|
|
891
1011
|
|
|
892
|
-
var version = "0.1.
|
|
1012
|
+
var version = "0.1.12";
|
|
893
1013
|
var packageJson = {
|
|
894
1014
|
version: version};
|
|
895
1015
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const timeout = requestTimeouts.get(requestId);
|
|
904
|
-
if (timeout) {
|
|
905
|
-
clearTimeout(timeout);
|
|
906
|
-
requestTimeouts.delete(requestId);
|
|
907
|
-
types.logger.debug(`[AnthropicProxy #${requestId}] Cleaned up (${reason}), active requests: ${requestTimeouts.size}`);
|
|
908
|
-
claudeDidSomeWork();
|
|
909
|
-
}
|
|
910
|
-
};
|
|
911
|
-
const claudeDidSomeWork = () => {
|
|
912
|
-
if (idleTimer) clearTimeout(idleTimer);
|
|
913
|
-
if (requestTimeouts.size === 0) {
|
|
914
|
-
idleTimer = setTimeout(() => {
|
|
915
|
-
types.logger.debug(`[AnthropicProxy] Idle for ${maxTimeBeforeIdle}ms, active requests: ${requestTimeouts.size}`);
|
|
916
|
-
onClaudeActivity("idle");
|
|
917
|
-
}, maxTimeBeforeIdle);
|
|
918
|
-
}
|
|
919
|
-
};
|
|
920
|
-
const server = node_http.createServer((req, res) => {
|
|
921
|
-
const requestId = ++requestCounter;
|
|
922
|
-
const isAnthropicRequest = req.headers.host === "api.anthropic.com" || req.url?.includes("anthropic.com");
|
|
923
|
-
if (isAnthropicRequest) {
|
|
924
|
-
const timeout = setTimeout(() => {
|
|
925
|
-
types.logger.debug(`[AnthropicProxy #${requestId}] Request timeout after ${requestTimeout}ms`);
|
|
926
|
-
cleanupRequest(requestId, "timeout");
|
|
927
|
-
}, requestTimeout);
|
|
928
|
-
requestTimeouts.set(requestId, timeout);
|
|
929
|
-
onClaudeActivity("working");
|
|
930
|
-
types.logger.debug(`[AnthropicProxy #${requestId}] Anthropic request: ${req.method} ${req.url}, active requests: ${requestTimeouts.size}`);
|
|
931
|
-
}
|
|
932
|
-
const chunks = [];
|
|
933
|
-
req.on("data", (chunk) => {
|
|
934
|
-
chunks.push(chunk);
|
|
935
|
-
if (isAnthropicRequest) {
|
|
936
|
-
claudeDidSomeWork();
|
|
937
|
-
}
|
|
1016
|
+
const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
1017
|
+
const RUNNER_PATH = path.join(__dirname$1, "..", "..", "scripts", "ripgrep_launcher.cjs");
|
|
1018
|
+
function run(args, options) {
|
|
1019
|
+
return new Promise((resolve, reject) => {
|
|
1020
|
+
const child = child_process.spawn("node", [RUNNER_PATH, JSON.stringify(args)], {
|
|
1021
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1022
|
+
cwd: options?.cwd
|
|
938
1023
|
});
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
targetUrl = new node_url.URL(req.url || "/", "https://api.anthropic.com");
|
|
944
|
-
} else {
|
|
945
|
-
const protocol = req.headers["x-forwarded-proto"] || "https";
|
|
946
|
-
const host = req.headers.host || "localhost";
|
|
947
|
-
targetUrl = new node_url.URL(req.url || "/", `${protocol}://${host}`);
|
|
948
|
-
}
|
|
949
|
-
const options = {
|
|
950
|
-
hostname: targetUrl.hostname,
|
|
951
|
-
port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
|
|
952
|
-
path: targetUrl.pathname + targetUrl.search,
|
|
953
|
-
method: req.method,
|
|
954
|
-
headers: {
|
|
955
|
-
...req.headers,
|
|
956
|
-
host: targetUrl.hostname
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
|
-
const requestMethod = targetUrl.protocol === "https:" ? node_https.request : node_http.request;
|
|
960
|
-
const proxyReq = requestMethod(options, (proxyRes) => {
|
|
961
|
-
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
962
|
-
proxyRes.pipe(res);
|
|
963
|
-
proxyRes.on("end", () => {
|
|
964
|
-
if (isAnthropicRequest) {
|
|
965
|
-
cleanupRequest(requestId, "completed");
|
|
966
|
-
}
|
|
967
|
-
});
|
|
968
|
-
});
|
|
969
|
-
proxyReq.on("error", (error) => {
|
|
970
|
-
if (isAnthropicRequest) {
|
|
971
|
-
cleanupRequest(requestId, `error: ${error.message}`);
|
|
972
|
-
} else {
|
|
973
|
-
types.logger.debug(`[AnthropicProxy #${requestId}] Error:`, error.message);
|
|
974
|
-
}
|
|
975
|
-
res.writeHead(502);
|
|
976
|
-
res.end("Bad Gateway");
|
|
977
|
-
});
|
|
978
|
-
if (body.length > 0) {
|
|
979
|
-
proxyReq.write(body);
|
|
980
|
-
}
|
|
981
|
-
proxyReq.end();
|
|
1024
|
+
let stdout = "";
|
|
1025
|
+
let stderr = "";
|
|
1026
|
+
child.stdout.on("data", (data) => {
|
|
1027
|
+
stdout += data.toString();
|
|
982
1028
|
});
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
const requestId = ++requestCounter;
|
|
986
|
-
const [hostname, port] = req.url?.split(":") || ["", "443"];
|
|
987
|
-
const isAnthropicRequest = hostname === "api.anthropic.com";
|
|
988
|
-
if (isAnthropicRequest) {
|
|
989
|
-
const timeout = setTimeout(() => {
|
|
990
|
-
types.logger.debug(`[AnthropicProxy #${requestId}] CONNECT timeout after ${requestTimeout}ms`);
|
|
991
|
-
cleanupRequest(requestId, "timeout");
|
|
992
|
-
}, requestTimeout);
|
|
993
|
-
requestTimeouts.set(requestId, timeout);
|
|
994
|
-
onClaudeActivity("working");
|
|
995
|
-
types.logger.debug(`[AnthropicProxy #${requestId}] CONNECT to api.anthropic.com, active requests: ${requestTimeouts.size}`);
|
|
996
|
-
}
|
|
997
|
-
const serverSocket = net.connect(parseInt(port) || 443, hostname, () => {
|
|
998
|
-
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
999
|
-
serverSocket.write(head);
|
|
1000
|
-
serverSocket.pipe(clientSocket);
|
|
1001
|
-
clientSocket.pipe(serverSocket);
|
|
1029
|
+
child.stderr.on("data", (data) => {
|
|
1030
|
+
stderr += data.toString();
|
|
1002
1031
|
});
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
types.logger.debug(`[AnthropicProxy #${requestId}] CONNECT error:`, err.message);
|
|
1010
|
-
clientSocket.end();
|
|
1011
|
-
cleanup();
|
|
1032
|
+
child.on("close", (code) => {
|
|
1033
|
+
resolve({
|
|
1034
|
+
exitCode: code || 0,
|
|
1035
|
+
stdout,
|
|
1036
|
+
stderr
|
|
1037
|
+
});
|
|
1012
1038
|
});
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
serverSocket.on("end", cleanup);
|
|
1016
|
-
});
|
|
1017
|
-
const url = await new Promise((resolve) => {
|
|
1018
|
-
server.listen(0, "127.0.0.1", () => {
|
|
1019
|
-
const addr = server.address();
|
|
1020
|
-
if (addr && typeof addr === "object") {
|
|
1021
|
-
resolve(`http://127.0.0.1:${addr.port}`);
|
|
1022
|
-
}
|
|
1039
|
+
child.on("error", (err) => {
|
|
1040
|
+
reject(err);
|
|
1023
1041
|
});
|
|
1024
1042
|
});
|
|
1025
|
-
types.logger.debug(`[AnthropicProxy] Started at ${url}`);
|
|
1026
|
-
return {
|
|
1027
|
-
url,
|
|
1028
|
-
cleanup: () => {
|
|
1029
|
-
if (idleTimer) clearTimeout(idleTimer);
|
|
1030
|
-
for (const [requestId, timeout] of requestTimeouts) {
|
|
1031
|
-
clearTimeout(timeout);
|
|
1032
|
-
types.logger.debug(`[AnthropicProxy] Cleaning up timeout for request #${requestId}`);
|
|
1033
|
-
}
|
|
1034
|
-
requestTimeouts.clear();
|
|
1035
|
-
if (requestTimeouts.size > 0) {
|
|
1036
|
-
types.logger.debug(`[AnthropicProxy] Warning: ${requestTimeouts.size} active requests still pending at cleanup:`, Array.from(requestTimeouts.keys()));
|
|
1037
|
-
}
|
|
1038
|
-
server.close();
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
1043
|
}
|
|
1042
1044
|
|
|
1043
1045
|
const execAsync = util.promisify(child_process.exec);
|
|
1044
|
-
function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
1046
|
+
function registerHandlers(session, interruptController, permissionCallbacks, onSwitchRemoteRequested) {
|
|
1045
1047
|
session.setHandler("abort", async () => {
|
|
1046
1048
|
types.logger.info("Abort request - interrupting Claude");
|
|
1047
1049
|
await interruptController.interrupt();
|
|
@@ -1063,11 +1065,22 @@ function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
|
1063
1065
|
return;
|
|
1064
1066
|
}
|
|
1065
1067
|
session.updateAgentState((currentState) => {
|
|
1068
|
+
const request = currentState.requests?.[id];
|
|
1069
|
+
if (!request) return currentState;
|
|
1066
1070
|
let r = { ...currentState.requests };
|
|
1067
1071
|
delete r[id];
|
|
1068
1072
|
return {
|
|
1069
1073
|
...currentState,
|
|
1070
|
-
requests: r
|
|
1074
|
+
requests: r,
|
|
1075
|
+
completedRequests: {
|
|
1076
|
+
...currentState.completedRequests,
|
|
1077
|
+
[id]: {
|
|
1078
|
+
...request,
|
|
1079
|
+
completedAt: Date.now(),
|
|
1080
|
+
status: message.approved ? "approved" : "denied",
|
|
1081
|
+
reason: message.reason
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1071
1084
|
};
|
|
1072
1085
|
});
|
|
1073
1086
|
});
|
|
@@ -1110,7 +1123,7 @@ function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
|
1110
1123
|
session.setHandler("readFile", async (data) => {
|
|
1111
1124
|
types.logger.info("Read file request:", data.path);
|
|
1112
1125
|
try {
|
|
1113
|
-
const buffer = await promises
|
|
1126
|
+
const buffer = await promises.readFile(data.path);
|
|
1114
1127
|
const content = buffer.toString("base64");
|
|
1115
1128
|
return { success: true, content };
|
|
1116
1129
|
} catch (error) {
|
|
@@ -1123,7 +1136,7 @@ function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
|
1123
1136
|
try {
|
|
1124
1137
|
if (data.expectedHash !== null && data.expectedHash !== void 0) {
|
|
1125
1138
|
try {
|
|
1126
|
-
const existingBuffer = await promises
|
|
1139
|
+
const existingBuffer = await promises.readFile(data.path);
|
|
1127
1140
|
const existingHash = crypto.createHash("sha256").update(existingBuffer).digest("hex");
|
|
1128
1141
|
if (existingHash !== data.expectedHash) {
|
|
1129
1142
|
return {
|
|
@@ -1143,7 +1156,7 @@ function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
|
1143
1156
|
}
|
|
1144
1157
|
} else {
|
|
1145
1158
|
try {
|
|
1146
|
-
await promises
|
|
1159
|
+
await promises.stat(data.path);
|
|
1147
1160
|
return {
|
|
1148
1161
|
success: false,
|
|
1149
1162
|
error: "File already exists but was expected to be new"
|
|
@@ -1156,7 +1169,7 @@ function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
|
1156
1169
|
}
|
|
1157
1170
|
}
|
|
1158
1171
|
const buffer = Buffer.from(data.content, "base64");
|
|
1159
|
-
await promises
|
|
1172
|
+
await promises.writeFile(data.path, buffer);
|
|
1160
1173
|
const hash = crypto.createHash("sha256").update(buffer).digest("hex");
|
|
1161
1174
|
return { success: true, hash };
|
|
1162
1175
|
} catch (error) {
|
|
@@ -1167,7 +1180,7 @@ function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
|
1167
1180
|
session.setHandler("listDirectory", async (data) => {
|
|
1168
1181
|
types.logger.info("List directory request:", data.path);
|
|
1169
1182
|
try {
|
|
1170
|
-
const entries = await promises
|
|
1183
|
+
const entries = await promises.readdir(data.path, { withFileTypes: true });
|
|
1171
1184
|
const directoryEntries = await Promise.all(
|
|
1172
1185
|
entries.map(async (entry) => {
|
|
1173
1186
|
const fullPath = path.join(data.path, entry.name);
|
|
@@ -1180,7 +1193,7 @@ function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
|
1180
1193
|
type = "file";
|
|
1181
1194
|
}
|
|
1182
1195
|
try {
|
|
1183
|
-
const stats = await promises
|
|
1196
|
+
const stats = await promises.stat(fullPath);
|
|
1184
1197
|
size = stats.size;
|
|
1185
1198
|
modified = stats.mtime.getTime();
|
|
1186
1199
|
} catch (error) {
|
|
@@ -1209,7 +1222,7 @@ function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
|
1209
1222
|
types.logger.info("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
|
|
1210
1223
|
async function buildTree(path$1, name, currentDepth) {
|
|
1211
1224
|
try {
|
|
1212
|
-
const stats = await promises
|
|
1225
|
+
const stats = await promises.stat(path$1);
|
|
1213
1226
|
const node = {
|
|
1214
1227
|
name,
|
|
1215
1228
|
path: path$1,
|
|
@@ -1218,7 +1231,7 @@ function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
|
1218
1231
|
modified: stats.mtime.getTime()
|
|
1219
1232
|
};
|
|
1220
1233
|
if (stats.isDirectory() && currentDepth < data.maxDepth) {
|
|
1221
|
-
const entries = await promises
|
|
1234
|
+
const entries = await promises.readdir(path$1, { withFileTypes: true });
|
|
1222
1235
|
const children = [];
|
|
1223
1236
|
await Promise.all(
|
|
1224
1237
|
entries.map(async (entry) => {
|
|
@@ -1261,6 +1274,158 @@ function registerHandlers(session, interruptController, permissionCallbacks) {
|
|
|
1261
1274
|
return { success: false, error: error instanceof Error ? error.message : "Failed to get directory tree" };
|
|
1262
1275
|
}
|
|
1263
1276
|
});
|
|
1277
|
+
session.setHandler("ripgrep", async (data) => {
|
|
1278
|
+
types.logger.info("Ripgrep request with args:", data.args, "cwd:", data.cwd);
|
|
1279
|
+
try {
|
|
1280
|
+
const result = await run(data.args, { cwd: data.cwd });
|
|
1281
|
+
return {
|
|
1282
|
+
success: true,
|
|
1283
|
+
exitCode: result.exitCode,
|
|
1284
|
+
stdout: result.stdout,
|
|
1285
|
+
stderr: result.stderr
|
|
1286
|
+
};
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
types.logger.debug("Failed to run ripgrep:", error);
|
|
1289
|
+
return {
|
|
1290
|
+
success: false,
|
|
1291
|
+
error: error instanceof Error ? error.message : "Failed to run ripgrep"
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
async function startHTTPDirectProxy(options) {
|
|
1298
|
+
const proxy = httpProxy.createProxyServer({
|
|
1299
|
+
target: options.target,
|
|
1300
|
+
changeOrigin: true,
|
|
1301
|
+
secure: false
|
|
1302
|
+
});
|
|
1303
|
+
proxy.on("error", (err, req, res) => {
|
|
1304
|
+
types.logger.debug(`[HTTPProxy] Proxy error: ${err.message} for ${req.method} ${req.url}`);
|
|
1305
|
+
if (res instanceof node_http.ServerResponse && !res.headersSent) {
|
|
1306
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
1307
|
+
res.end("Proxy error");
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
proxy.on("proxyReq", (proxyReq, req, res) => {
|
|
1311
|
+
if (options.onRequest) {
|
|
1312
|
+
options.onRequest(req, proxyReq);
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
proxy.on("proxyRes", (proxyRes, req, res) => {
|
|
1316
|
+
if (options.onResponse) {
|
|
1317
|
+
options.onResponse(req, proxyRes);
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
const server = node_http.createServer((req, res) => {
|
|
1321
|
+
proxy.web(req, res);
|
|
1322
|
+
});
|
|
1323
|
+
const url = await new Promise((resolve, reject) => {
|
|
1324
|
+
server.listen(0, "127.0.0.1", () => {
|
|
1325
|
+
const addr = server.address();
|
|
1326
|
+
if (addr && typeof addr === "object") {
|
|
1327
|
+
const proxyUrl = `http://127.0.0.1:${addr.port}`;
|
|
1328
|
+
types.logger.debug(`[HTTPProxy] Started on ${proxyUrl} --> ${options.target}`);
|
|
1329
|
+
resolve(proxyUrl);
|
|
1330
|
+
} else {
|
|
1331
|
+
reject(new Error("Failed to get server address"));
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
});
|
|
1335
|
+
return url;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
async function startClaudeActivityTracker(onThinking) {
|
|
1339
|
+
types.logger.debug(`[ClaudeActivityTracker] Starting activity tracker`);
|
|
1340
|
+
let requestCounter = 0;
|
|
1341
|
+
const activeRequests = /* @__PURE__ */ new Map();
|
|
1342
|
+
let stopThinkingTimeout = null;
|
|
1343
|
+
let isThinking = false;
|
|
1344
|
+
const REQUEST_TIMEOUT = 5 * 60 * 1e3;
|
|
1345
|
+
const checkAndStopThinking = () => {
|
|
1346
|
+
if (activeRequests.size === 0 && isThinking && !stopThinkingTimeout) {
|
|
1347
|
+
stopThinkingTimeout = setTimeout(() => {
|
|
1348
|
+
if (isThinking && activeRequests.size === 0) {
|
|
1349
|
+
isThinking = false;
|
|
1350
|
+
onThinking(false);
|
|
1351
|
+
}
|
|
1352
|
+
stopThinkingTimeout = null;
|
|
1353
|
+
}, 500);
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
const proxyUrl = await startHTTPDirectProxy({
|
|
1357
|
+
target: process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com",
|
|
1358
|
+
onRequest: (req, proxyReq) => {
|
|
1359
|
+
if (req.method === "POST" && req.url?.startsWith("/v1/messages")) {
|
|
1360
|
+
const requestId = ++requestCounter;
|
|
1361
|
+
req._requestId = requestId;
|
|
1362
|
+
if (stopThinkingTimeout) {
|
|
1363
|
+
clearTimeout(stopThinkingTimeout);
|
|
1364
|
+
stopThinkingTimeout = null;
|
|
1365
|
+
}
|
|
1366
|
+
const timeout = setTimeout(() => {
|
|
1367
|
+
activeRequests.delete(requestId);
|
|
1368
|
+
checkAndStopThinking();
|
|
1369
|
+
}, REQUEST_TIMEOUT);
|
|
1370
|
+
activeRequests.set(requestId, timeout);
|
|
1371
|
+
if (!isThinking) {
|
|
1372
|
+
isThinking = true;
|
|
1373
|
+
onThinking(true);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
},
|
|
1377
|
+
onResponse: (req, proxyRes) => {
|
|
1378
|
+
proxyRes.headers["connection"] = "close";
|
|
1379
|
+
if (req.method === "POST" && req.url?.startsWith("/v1/messages")) {
|
|
1380
|
+
const requestId = req._requestId;
|
|
1381
|
+
const timeout = activeRequests.get(requestId);
|
|
1382
|
+
if (timeout) {
|
|
1383
|
+
clearTimeout(timeout);
|
|
1384
|
+
}
|
|
1385
|
+
let cleaned = false;
|
|
1386
|
+
const cleanupRequest = () => {
|
|
1387
|
+
if (!cleaned) {
|
|
1388
|
+
cleaned = true;
|
|
1389
|
+
activeRequests.delete(requestId);
|
|
1390
|
+
checkAndStopThinking();
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
proxyRes.on("end", () => {
|
|
1394
|
+
cleanupRequest();
|
|
1395
|
+
});
|
|
1396
|
+
proxyRes.on("error", (err) => {
|
|
1397
|
+
cleanupRequest();
|
|
1398
|
+
});
|
|
1399
|
+
proxyRes.on("aborted", () => {
|
|
1400
|
+
cleanupRequest();
|
|
1401
|
+
});
|
|
1402
|
+
proxyRes.on("close", () => {
|
|
1403
|
+
cleanupRequest();
|
|
1404
|
+
});
|
|
1405
|
+
req.on("close", () => {
|
|
1406
|
+
cleanupRequest();
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
const reset = () => {
|
|
1412
|
+
for (const [requestId, timeout] of activeRequests) {
|
|
1413
|
+
clearTimeout(timeout);
|
|
1414
|
+
}
|
|
1415
|
+
activeRequests.clear();
|
|
1416
|
+
if (stopThinkingTimeout) {
|
|
1417
|
+
clearTimeout(stopThinkingTimeout);
|
|
1418
|
+
stopThinkingTimeout = null;
|
|
1419
|
+
}
|
|
1420
|
+
if (isThinking) {
|
|
1421
|
+
isThinking = false;
|
|
1422
|
+
onThinking(false);
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
return {
|
|
1426
|
+
proxyUrl,
|
|
1427
|
+
reset
|
|
1428
|
+
};
|
|
1264
1429
|
}
|
|
1265
1430
|
|
|
1266
1431
|
async function start(credentials, options = {}) {
|
|
@@ -1274,22 +1439,15 @@ async function start(credentials, options = {}) {
|
|
|
1274
1439
|
const session = api.session(response);
|
|
1275
1440
|
const pushClient = api.push();
|
|
1276
1441
|
let thinking = false;
|
|
1442
|
+
let mode = "local";
|
|
1277
1443
|
let pingInterval = setInterval(() => {
|
|
1278
|
-
session.keepAlive(thinking);
|
|
1444
|
+
session.keepAlive(thinking, mode);
|
|
1279
1445
|
}, 2e3);
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
types.logger.debug(`[PING] Thinking state changed: ${thinking}`);
|
|
1286
|
-
session.keepAlive(thinking);
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
);
|
|
1290
|
-
process.env.HTTP_PROXY = antropicActivityProxy.url;
|
|
1291
|
-
process.env.HTTPS_PROXY = antropicActivityProxy.url;
|
|
1292
|
-
types.logger.debug(`[AnthropicProxy] Set HTTP_PROXY and HTTPS_PROXY to ${antropicActivityProxy.url}`);
|
|
1446
|
+
const activityTracker = await startClaudeActivityTracker((newThinking) => {
|
|
1447
|
+
thinking = newThinking;
|
|
1448
|
+
session.keepAlive(thinking, mode);
|
|
1449
|
+
});
|
|
1450
|
+
process.env.ANTHROPIC_BASE_URL = activityTracker.proxyUrl;
|
|
1293
1451
|
const logPath = await types.logger.logFilePathPromise;
|
|
1294
1452
|
types.logger.infoDeveloper(`Session: ${response.id}`);
|
|
1295
1453
|
types.logger.infoDeveloper(`Logs: ${logPath}`);
|
|
@@ -1301,22 +1459,33 @@ async function start(credentials, options = {}) {
|
|
|
1301
1459
|
requests.set(id, resolve);
|
|
1302
1460
|
});
|
|
1303
1461
|
let timeout = setTimeout(async () => {
|
|
1304
|
-
types.logger.
|
|
1462
|
+
types.logger.debug("Permission timeout - attempting to interrupt Claude");
|
|
1305
1463
|
const interrupted = await interruptController.interrupt();
|
|
1306
1464
|
if (interrupted) {
|
|
1307
|
-
types.logger.
|
|
1465
|
+
types.logger.debug("Claude interrupted successfully");
|
|
1308
1466
|
}
|
|
1309
1467
|
requests.delete(id);
|
|
1310
1468
|
session.updateAgentState((currentState) => {
|
|
1469
|
+
const request2 = currentState.requests?.[id];
|
|
1470
|
+
if (!request2) return currentState;
|
|
1311
1471
|
let r = { ...currentState.requests };
|
|
1312
1472
|
delete r[id];
|
|
1313
1473
|
return {
|
|
1314
1474
|
...currentState,
|
|
1315
|
-
requests: r
|
|
1475
|
+
requests: r,
|
|
1476
|
+
completedRequests: {
|
|
1477
|
+
...currentState.completedRequests,
|
|
1478
|
+
[id]: {
|
|
1479
|
+
...request2,
|
|
1480
|
+
completedAt: Date.now(),
|
|
1481
|
+
status: "canceled",
|
|
1482
|
+
reason: "Timeout"
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1316
1485
|
};
|
|
1317
1486
|
});
|
|
1318
1487
|
}, 1e3 * 60 * 4.5);
|
|
1319
|
-
types.logger.
|
|
1488
|
+
types.logger.debug("Permission request" + id + " " + JSON.stringify(request));
|
|
1320
1489
|
try {
|
|
1321
1490
|
await pushClient.sendToAllDevices(
|
|
1322
1491
|
"Permission Request",
|
|
@@ -1328,7 +1497,7 @@ async function start(credentials, options = {}) {
|
|
|
1328
1497
|
type: "permission_request"
|
|
1329
1498
|
}
|
|
1330
1499
|
);
|
|
1331
|
-
types.logger.
|
|
1500
|
+
types.logger.debug("Push notification sent for permission request");
|
|
1332
1501
|
} catch (error) {
|
|
1333
1502
|
types.logger.debug("Failed to send push notification:", error);
|
|
1334
1503
|
}
|
|
@@ -1338,7 +1507,8 @@ async function start(credentials, options = {}) {
|
|
|
1338
1507
|
...currentState.requests,
|
|
1339
1508
|
[id]: {
|
|
1340
1509
|
tool: request.name,
|
|
1341
|
-
arguments: request.arguments
|
|
1510
|
+
arguments: request.arguments,
|
|
1511
|
+
createdAt: Date.now()
|
|
1342
1512
|
}
|
|
1343
1513
|
}
|
|
1344
1514
|
}));
|
|
@@ -1370,6 +1540,63 @@ async function start(credentials, options = {}) {
|
|
|
1370
1540
|
model: options.model,
|
|
1371
1541
|
permissionMode: options.permissionMode,
|
|
1372
1542
|
startingMode: options.startingMode,
|
|
1543
|
+
onModeChange: (newMode) => {
|
|
1544
|
+
mode = newMode;
|
|
1545
|
+
session.sendSessionEvent({ type: "switch", mode: newMode });
|
|
1546
|
+
session.keepAlive(thinking, mode);
|
|
1547
|
+
if (newMode === "local") {
|
|
1548
|
+
types.logger.debug("Switching to local mode - clearing pending permission requests");
|
|
1549
|
+
for (const [id, resolve] of requests) {
|
|
1550
|
+
types.logger.debug(`Rejecting pending permission request: ${id}`);
|
|
1551
|
+
resolve({ approved: false, reason: "Session switched to local mode" });
|
|
1552
|
+
}
|
|
1553
|
+
requests.clear();
|
|
1554
|
+
session.updateAgentState((currentState) => {
|
|
1555
|
+
const pendingRequests = currentState.requests || {};
|
|
1556
|
+
const completedRequests = { ...currentState.completedRequests };
|
|
1557
|
+
for (const [id, request] of Object.entries(pendingRequests)) {
|
|
1558
|
+
completedRequests[id] = {
|
|
1559
|
+
...request,
|
|
1560
|
+
completedAt: Date.now(),
|
|
1561
|
+
status: "canceled",
|
|
1562
|
+
reason: "Session switched to local mode"
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
return {
|
|
1566
|
+
...currentState,
|
|
1567
|
+
controlledByUser: true,
|
|
1568
|
+
requests: {},
|
|
1569
|
+
// Clear all pending requests
|
|
1570
|
+
completedRequests
|
|
1571
|
+
};
|
|
1572
|
+
});
|
|
1573
|
+
} else {
|
|
1574
|
+
session.updateAgentState((currentState) => ({
|
|
1575
|
+
...currentState,
|
|
1576
|
+
controlledByUser: false
|
|
1577
|
+
}));
|
|
1578
|
+
}
|
|
1579
|
+
},
|
|
1580
|
+
onProcessStart: (processMode) => {
|
|
1581
|
+
types.logger.debug(`[Process Lifecycle] Starting ${processMode} mode`);
|
|
1582
|
+
activityTracker.reset();
|
|
1583
|
+
types.logger.debug("Starting process - clearing any stale permission requests");
|
|
1584
|
+
for (const [id, resolve] of requests) {
|
|
1585
|
+
types.logger.debug(`Rejecting stale permission request: ${id}`);
|
|
1586
|
+
resolve({ approved: false, reason: "Process restarted" });
|
|
1587
|
+
}
|
|
1588
|
+
requests.clear();
|
|
1589
|
+
},
|
|
1590
|
+
onProcessStop: (processMode) => {
|
|
1591
|
+
types.logger.debug(`[Process Lifecycle] Stopped ${processMode} mode`);
|
|
1592
|
+
activityTracker.reset();
|
|
1593
|
+
types.logger.debug("Stopping process - clearing any stale permission requests");
|
|
1594
|
+
for (const [id, resolve] of requests) {
|
|
1595
|
+
types.logger.debug(`Rejecting stale permission request: ${id}`);
|
|
1596
|
+
resolve({ approved: false, reason: "Process restarted" });
|
|
1597
|
+
}
|
|
1598
|
+
requests.clear();
|
|
1599
|
+
},
|
|
1373
1600
|
mcpServers: {
|
|
1374
1601
|
"permission": {
|
|
1375
1602
|
type: "http",
|
|
@@ -1379,13 +1606,11 @@ async function start(credentials, options = {}) {
|
|
|
1379
1606
|
permissionPromptToolName: "mcp__permission__" + permissionServer.toolName,
|
|
1380
1607
|
session,
|
|
1381
1608
|
onAssistantResult,
|
|
1382
|
-
interruptController
|
|
1609
|
+
interruptController,
|
|
1610
|
+
claudeEnvVars: options.claudeEnvVars,
|
|
1611
|
+
claudeArgs: options.claudeArgs
|
|
1383
1612
|
});
|
|
1384
1613
|
clearInterval(pingInterval);
|
|
1385
|
-
if (antropicActivityProxy) {
|
|
1386
|
-
types.logger.debug("[AnthropicProxy] Shutting down thinking activity monitoring proxy");
|
|
1387
|
-
antropicActivityProxy.cleanup();
|
|
1388
|
-
}
|
|
1389
1614
|
process.exit(0);
|
|
1390
1615
|
}
|
|
1391
1616
|
|
|
@@ -1397,7 +1622,7 @@ async function readSettings() {
|
|
|
1397
1622
|
return { ...defaultSettings };
|
|
1398
1623
|
}
|
|
1399
1624
|
try {
|
|
1400
|
-
const content = await promises.readFile(types.configuration.settingsFile, "utf8");
|
|
1625
|
+
const content = await promises$1.readFile(types.configuration.settingsFile, "utf8");
|
|
1401
1626
|
return JSON.parse(content);
|
|
1402
1627
|
} catch {
|
|
1403
1628
|
return { ...defaultSettings };
|
|
@@ -1405,9 +1630,9 @@ async function readSettings() {
|
|
|
1405
1630
|
}
|
|
1406
1631
|
async function writeSettings(settings) {
|
|
1407
1632
|
if (!node_fs.existsSync(types.configuration.happyDir)) {
|
|
1408
|
-
await promises.mkdir(types.configuration.happyDir, { recursive: true });
|
|
1633
|
+
await promises$1.mkdir(types.configuration.happyDir, { recursive: true });
|
|
1409
1634
|
}
|
|
1410
|
-
await promises.writeFile(types.configuration.settingsFile, JSON.stringify(settings, null, 2));
|
|
1635
|
+
await promises$1.writeFile(types.configuration.settingsFile, JSON.stringify(settings, null, 2));
|
|
1411
1636
|
}
|
|
1412
1637
|
const credentialsSchema = z__namespace.object({
|
|
1413
1638
|
secret: z__namespace.string().base64(),
|
|
@@ -1418,7 +1643,7 @@ async function readCredentials() {
|
|
|
1418
1643
|
return null;
|
|
1419
1644
|
}
|
|
1420
1645
|
try {
|
|
1421
|
-
const keyBase64 = await promises.readFile(types.configuration.privateKeyFile, "utf8");
|
|
1646
|
+
const keyBase64 = await promises$1.readFile(types.configuration.privateKeyFile, "utf8");
|
|
1422
1647
|
const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
|
|
1423
1648
|
return {
|
|
1424
1649
|
secret: new Uint8Array(Buffer.from(credentials.secret, "base64")),
|
|
@@ -1430,9 +1655,9 @@ async function readCredentials() {
|
|
|
1430
1655
|
}
|
|
1431
1656
|
async function writeCredentials(credentials) {
|
|
1432
1657
|
if (!node_fs.existsSync(types.configuration.happyDir)) {
|
|
1433
|
-
await promises.mkdir(types.configuration.happyDir, { recursive: true });
|
|
1658
|
+
await promises$1.mkdir(types.configuration.happyDir, { recursive: true });
|
|
1434
1659
|
}
|
|
1435
|
-
await promises.writeFile(types.configuration.privateKeyFile, JSON.stringify({
|
|
1660
|
+
await promises$1.writeFile(types.configuration.privateKeyFile, JSON.stringify({
|
|
1436
1661
|
secret: types.encodeBase64(credentials.secret),
|
|
1437
1662
|
token: credentials.token
|
|
1438
1663
|
}, null, 2));
|
|
@@ -1635,17 +1860,25 @@ class ApiDaemonSession extends node_events.EventEmitter {
|
|
|
1635
1860
|
}
|
|
1636
1861
|
}
|
|
1637
1862
|
|
|
1638
|
-
const DAEMON_PID_FILE = path.join(os$1.homedir(), ".happy", "daemon-pid");
|
|
1639
1863
|
async function startDaemon() {
|
|
1640
|
-
|
|
1864
|
+
console.log("[DAEMON] Starting daemon process...");
|
|
1865
|
+
if (await isDaemonRunning()) {
|
|
1641
1866
|
console.log("Happy daemon is already running");
|
|
1642
1867
|
process.exit(0);
|
|
1643
1868
|
}
|
|
1644
|
-
|
|
1869
|
+
console.log("[DAEMON] Writing PID file with PID:", process.pid);
|
|
1645
1870
|
writePidFile();
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
process.on("
|
|
1871
|
+
console.log("[DAEMON] PID file written successfully");
|
|
1872
|
+
types.logger.info("Happy CLI daemon started successfully");
|
|
1873
|
+
process.on("SIGINT", () => {
|
|
1874
|
+
stopDaemon().catch(console.error);
|
|
1875
|
+
});
|
|
1876
|
+
process.on("SIGTERM", () => {
|
|
1877
|
+
stopDaemon().catch(console.error);
|
|
1878
|
+
});
|
|
1879
|
+
process.on("exit", () => {
|
|
1880
|
+
stopDaemon().catch(console.error);
|
|
1881
|
+
});
|
|
1649
1882
|
try {
|
|
1650
1883
|
const settings = await readSettings() || { onboardingCompleted: false };
|
|
1651
1884
|
if (!settings.machineId) {
|
|
@@ -1696,22 +1929,37 @@ async function startDaemon() {
|
|
|
1696
1929
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1697
1930
|
}
|
|
1698
1931
|
}
|
|
1699
|
-
function isDaemonRunning() {
|
|
1932
|
+
async function isDaemonRunning() {
|
|
1700
1933
|
try {
|
|
1701
|
-
if
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1934
|
+
console.log("[isDaemonRunning] Checking if daemon is running...");
|
|
1935
|
+
if (fs.existsSync(types.configuration.daemonPidFile)) {
|
|
1936
|
+
console.log("[isDaemonRunning] PID file exists");
|
|
1937
|
+
const pid = parseInt(fs.readFileSync(types.configuration.daemonPidFile, "utf-8"));
|
|
1938
|
+
console.log("[isDaemonRunning] PID from file:", pid);
|
|
1939
|
+
try {
|
|
1940
|
+
process.kill(pid, 0);
|
|
1941
|
+
console.log("[isDaemonRunning] Process exists, checking if it's a happy daemon...");
|
|
1942
|
+
const isHappyDaemon = await isProcessHappyDaemon(pid);
|
|
1943
|
+
console.log("[isDaemonRunning] isHappyDaemon:", isHappyDaemon);
|
|
1944
|
+
if (isHappyDaemon) {
|
|
1945
|
+
return true;
|
|
1946
|
+
} else {
|
|
1947
|
+
console.log("[isDaemonRunning] PID is not a happy daemon, cleaning up");
|
|
1948
|
+
types.logger.debug(`[DAEMON] PID ${pid} is not a happy daemon, cleaning up`);
|
|
1949
|
+
fs.unlinkSync(types.configuration.daemonPidFile);
|
|
1950
|
+
}
|
|
1951
|
+
} catch (error) {
|
|
1952
|
+
console.log("[isDaemonRunning] Process not running, cleaning up stale PID file");
|
|
1953
|
+
types.logger.debug("[DAEMON] Process not running, cleaning up stale PID file");
|
|
1954
|
+
fs.unlinkSync(types.configuration.daemonPidFile);
|
|
1955
|
+
}
|
|
1956
|
+
} else {
|
|
1957
|
+
console.log("[isDaemonRunning] No PID file found");
|
|
1713
1958
|
}
|
|
1714
|
-
|
|
1959
|
+
return false;
|
|
1960
|
+
} catch (error) {
|
|
1961
|
+
console.log("[isDaemonRunning] Error:", error);
|
|
1962
|
+
types.logger.debug("[DAEMON] Error checking daemon status", error);
|
|
1715
1963
|
return false;
|
|
1716
1964
|
}
|
|
1717
1965
|
}
|
|
@@ -1720,19 +1968,54 @@ function writePidFile() {
|
|
|
1720
1968
|
if (!fs.existsSync(happyDir)) {
|
|
1721
1969
|
fs.mkdirSync(happyDir, { recursive: true });
|
|
1722
1970
|
}
|
|
1723
|
-
|
|
1971
|
+
try {
|
|
1972
|
+
fs.writeFileSync(types.configuration.daemonPidFile, process.pid.toString(), { flag: "wx" });
|
|
1973
|
+
} catch (error) {
|
|
1974
|
+
if (error.code === "EEXIST") {
|
|
1975
|
+
types.logger.debug("[DAEMON] PID file already exists, another daemon may be starting");
|
|
1976
|
+
throw new Error("Daemon PID file already exists");
|
|
1977
|
+
}
|
|
1978
|
+
throw error;
|
|
1979
|
+
}
|
|
1724
1980
|
}
|
|
1725
|
-
function stopDaemon() {
|
|
1981
|
+
async function stopDaemon() {
|
|
1726
1982
|
try {
|
|
1727
|
-
if (fs.existsSync(
|
|
1728
|
-
types.
|
|
1729
|
-
|
|
1730
|
-
|
|
1983
|
+
if (fs.existsSync(types.configuration.daemonPidFile)) {
|
|
1984
|
+
const pid = parseInt(fs.readFileSync(types.configuration.daemonPidFile, "utf-8"));
|
|
1985
|
+
types.logger.debug(`[DAEMON] Stopping daemon with PID ${pid}`);
|
|
1986
|
+
try {
|
|
1987
|
+
process.kill(pid, "SIGTERM");
|
|
1988
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1989
|
+
try {
|
|
1990
|
+
process.kill(pid, 0);
|
|
1991
|
+
process.kill(pid, "SIGKILL");
|
|
1992
|
+
} catch {
|
|
1993
|
+
}
|
|
1994
|
+
} catch (error) {
|
|
1995
|
+
types.logger.debug("[DAEMON] Process already dead or inaccessible", error);
|
|
1996
|
+
}
|
|
1997
|
+
fs.unlinkSync(types.configuration.daemonPidFile);
|
|
1731
1998
|
}
|
|
1732
1999
|
} catch (error) {
|
|
1733
|
-
types.logger.debug("[DAEMON] Error
|
|
2000
|
+
types.logger.debug("[DAEMON] Error stopping daemon", error);
|
|
1734
2001
|
}
|
|
1735
2002
|
}
|
|
2003
|
+
async function isProcessHappyDaemon(pid) {
|
|
2004
|
+
return new Promise((resolve) => {
|
|
2005
|
+
const ps = child_process.spawn("ps", ["-p", pid.toString(), "-o", "command="]);
|
|
2006
|
+
let output = "";
|
|
2007
|
+
ps.stdout.on("data", (data) => {
|
|
2008
|
+
output += data.toString();
|
|
2009
|
+
});
|
|
2010
|
+
ps.on("close", () => {
|
|
2011
|
+
const isHappyDaemon = output.includes("daemon start") && (output.includes("happy") || output.includes("src/index"));
|
|
2012
|
+
resolve(isHappyDaemon);
|
|
2013
|
+
});
|
|
2014
|
+
ps.on("error", () => {
|
|
2015
|
+
resolve(false);
|
|
2016
|
+
});
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
1736
2019
|
|
|
1737
2020
|
function trimIdent(text) {
|
|
1738
2021
|
const lines = text.split("\n");
|
|
@@ -1931,10 +2214,19 @@ Currently only supported on macOS.
|
|
|
1931
2214
|
options.model = args[++i];
|
|
1932
2215
|
} else if (arg === "-p" || arg === "--permission-mode") {
|
|
1933
2216
|
options.permissionMode = z.z.enum(["auto", "default", "plan"]).parse(args[++i]);
|
|
1934
|
-
} else if (arg === "--local") {
|
|
1935
|
-
i
|
|
1936
|
-
} else if (arg === "--
|
|
1937
|
-
|
|
2217
|
+
} else if (arg === "--local") ; else if (arg === "--happy-starting-mode") {
|
|
2218
|
+
options.startingMode = z.z.enum(["local", "remote"]).parse(args[++i]);
|
|
2219
|
+
} else if (arg === "--claude-env") {
|
|
2220
|
+
const envVar = args[++i];
|
|
2221
|
+
const [key, value] = envVar.split("=", 2);
|
|
2222
|
+
if (!key || value === void 0) {
|
|
2223
|
+
console.error(chalk.red(`Invalid environment variable format: ${envVar}. Use KEY=VALUE`));
|
|
2224
|
+
process.exit(1);
|
|
2225
|
+
}
|
|
2226
|
+
options.claudeEnvVars = { ...options.claudeEnvVars, [key]: value };
|
|
2227
|
+
} else if (arg === "--claude-arg") {
|
|
2228
|
+
const claudeArg = args[++i];
|
|
2229
|
+
options.claudeArgs = [...options.claudeArgs || [], claudeArg];
|
|
1938
2230
|
} else {
|
|
1939
2231
|
console.error(chalk.red(`Unknown argument: ${arg}`));
|
|
1940
2232
|
process.exit(1);
|
|
@@ -1947,6 +2239,7 @@ ${chalk.bold("happy")} - Claude Code session sharing
|
|
|
1947
2239
|
${chalk.bold("Usage:")}
|
|
1948
2240
|
happy [options]
|
|
1949
2241
|
happy logout Logs out of your account and removes data directory
|
|
2242
|
+
happy daemon Manage the background daemon (macOS only)
|
|
1950
2243
|
|
|
1951
2244
|
${chalk.bold("Options:")}
|
|
1952
2245
|
-h, --help Show this help message
|
|
@@ -1954,6 +2247,14 @@ ${chalk.bold("Options:")}
|
|
|
1954
2247
|
-m, --model <model> Claude model to use (default: sonnet)
|
|
1955
2248
|
-p, --permission-mode Permission mode: auto, default, or plan
|
|
1956
2249
|
--auth, --login Force re-authentication
|
|
2250
|
+
--claude-env KEY=VALUE Set environment variable for Claude Code
|
|
2251
|
+
--claude-arg ARG Pass additional argument to Claude CLI
|
|
2252
|
+
|
|
2253
|
+
[Daemon Management]
|
|
2254
|
+
--happy-daemon-start Start the daemon in background
|
|
2255
|
+
--happy-daemon-stop Stop the daemon
|
|
2256
|
+
--happy-daemon-install Install daemon to run on startup
|
|
2257
|
+
--happy-daemon-uninstall Uninstall daemon from startup
|
|
1957
2258
|
|
|
1958
2259
|
[Advanced]
|
|
1959
2260
|
--local < global | local >
|
|
@@ -1967,6 +2268,10 @@ ${chalk.bold("Examples:")}
|
|
|
1967
2268
|
happy -m opus Use Claude Opus model
|
|
1968
2269
|
happy -p plan Use plan permission mode
|
|
1969
2270
|
happy --auth Force re-authentication before starting session
|
|
2271
|
+
happy --claude-env KEY=VALUE
|
|
2272
|
+
Set environment variable for Claude Code
|
|
2273
|
+
happy --claude-arg --option
|
|
2274
|
+
Pass argument to Claude CLI
|
|
1970
2275
|
happy logout Logs out of your account and removes data directory
|
|
1971
2276
|
`);
|
|
1972
2277
|
process.exit(0);
|
|
@@ -1983,6 +2288,7 @@ ${chalk.bold("Examples:")}
|
|
|
1983
2288
|
}
|
|
1984
2289
|
credentials = res;
|
|
1985
2290
|
}
|
|
2291
|
+
await readSettings() || { };
|
|
1986
2292
|
try {
|
|
1987
2293
|
await start(credentials, options);
|
|
1988
2294
|
} catch (error) {
|