happy-coder 0.11.2-0 → 0.12.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codex/happyMcpStdioBridge.cjs +2 -3
- package/dist/codex/happyMcpStdioBridge.mjs +2 -3
- package/dist/{index-CFWfkM2k.cjs → index-BCQGaH21.cjs} +141 -45
- package/dist/{index-B6KoidwE.mjs → index-BWDbDDaX.mjs} +143 -47
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +6 -15
- package/dist/lib.d.mts +6 -15
- package/dist/lib.mjs +1 -1
- package/dist/{runCodex-Dxi7L-oi.cjs → runCodex-BwpHgdkt.cjs} +3 -3
- package/dist/{runCodex-BJlyTS8v.mjs → runCodex-IZ3YFeAC.mjs} +3 -3
- package/dist/{types-D6ZewYb6.cjs → types-C7skJO9Y.cjs} +74 -29
- package/dist/{types-CVOFMcT8.mjs → types-DdwJ6K-A.mjs} +73 -28
- package/package.json +15 -15
- package/scripts/claude_local_launcher.cjs +5 -30
- package/scripts/claude_remote_launcher.cjs +4 -1
- package/scripts/claude_version_utils.cjs +378 -0
|
@@ -31,7 +31,7 @@ async function main() {
|
|
|
31
31
|
if (httpClient) return httpClient;
|
|
32
32
|
const client = new index_js.Client(
|
|
33
33
|
{ name: "happy-stdio-bridge", version: "1.0.0" },
|
|
34
|
-
{ capabilities: {
|
|
34
|
+
{ capabilities: {} }
|
|
35
35
|
);
|
|
36
36
|
const transport = new streamableHttp_js.StreamableHTTPClientTransport(new URL(baseUrl));
|
|
37
37
|
await client.connect(transport);
|
|
@@ -40,8 +40,7 @@ async function main() {
|
|
|
40
40
|
}
|
|
41
41
|
const server = new mcp_js.McpServer({
|
|
42
42
|
name: "Happy MCP Bridge",
|
|
43
|
-
version: "1.0.0"
|
|
44
|
-
description: "STDIO bridge forwarding to Happy HTTP MCP"
|
|
43
|
+
version: "1.0.0"
|
|
45
44
|
});
|
|
46
45
|
server.registerTool(
|
|
47
46
|
"change_title",
|
|
@@ -29,7 +29,7 @@ async function main() {
|
|
|
29
29
|
if (httpClient) return httpClient;
|
|
30
30
|
const client = new Client(
|
|
31
31
|
{ name: "happy-stdio-bridge", version: "1.0.0" },
|
|
32
|
-
{ capabilities: {
|
|
32
|
+
{ capabilities: {} }
|
|
33
33
|
);
|
|
34
34
|
const transport = new StreamableHTTPClientTransport(new URL(baseUrl));
|
|
35
35
|
await client.connect(transport);
|
|
@@ -38,8 +38,7 @@ async function main() {
|
|
|
38
38
|
}
|
|
39
39
|
const server = new McpServer({
|
|
40
40
|
name: "Happy MCP Bridge",
|
|
41
|
-
version: "1.0.0"
|
|
42
|
-
description: "STDIO bridge forwarding to Happy HTTP MCP"
|
|
41
|
+
version: "1.0.0"
|
|
43
42
|
});
|
|
44
43
|
server.registerTool(
|
|
45
44
|
"change_title",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
4
|
var os = require('node:os');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
|
-
var types = require('./types-
|
|
6
|
+
var types = require('./types-C7skJO9Y.cjs');
|
|
7
7
|
var node_child_process = require('node:child_process');
|
|
8
8
|
var node_path = require('node:path');
|
|
9
9
|
var node_readline = require('node:readline');
|
|
@@ -242,31 +242,19 @@ const claudeCliPath = node_path.resolve(node_path.join(types.projectPath(), "scr
|
|
|
242
242
|
async function claudeLocal(opts) {
|
|
243
243
|
const projectDir = getProjectPath(opts.path);
|
|
244
244
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
245
|
-
const watcher = fs.watch(projectDir);
|
|
246
|
-
let resolvedSessionId = null;
|
|
247
|
-
const detectedIdsRandomUUID = /* @__PURE__ */ new Set();
|
|
248
|
-
const detectedIdsFileSystem = /* @__PURE__ */ new Set();
|
|
249
|
-
watcher.on("change", (event, filename) => {
|
|
250
|
-
if (typeof filename === "string" && filename.toLowerCase().endsWith(".jsonl")) {
|
|
251
|
-
types.logger.debug("change", event, filename);
|
|
252
|
-
const sessionId = filename.replace(".jsonl", "");
|
|
253
|
-
if (detectedIdsFileSystem.has(sessionId)) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
detectedIdsFileSystem.add(sessionId);
|
|
257
|
-
if (resolvedSessionId) {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
if (detectedIdsRandomUUID.has(sessionId)) {
|
|
261
|
-
resolvedSessionId = sessionId;
|
|
262
|
-
opts.onSessionFound(sessionId);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
245
|
let startFrom = opts.sessionId;
|
|
267
246
|
if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
|
|
268
247
|
startFrom = null;
|
|
269
248
|
}
|
|
249
|
+
const newSessionId = startFrom ? null : node_crypto.randomUUID();
|
|
250
|
+
const effectiveSessionId = startFrom || newSessionId;
|
|
251
|
+
if (newSessionId) {
|
|
252
|
+
types.logger.debug(`[ClaudeLocal] Generated new session ID: ${newSessionId}`);
|
|
253
|
+
opts.onSessionFound(newSessionId);
|
|
254
|
+
} else {
|
|
255
|
+
types.logger.debug(`[ClaudeLocal] Resuming session: ${startFrom}`);
|
|
256
|
+
opts.onSessionFound(startFrom);
|
|
257
|
+
}
|
|
270
258
|
let thinking = false;
|
|
271
259
|
let stopThinkingTimeout = null;
|
|
272
260
|
const updateThinking = (newThinking) => {
|
|
@@ -284,6 +272,8 @@ async function claudeLocal(opts) {
|
|
|
284
272
|
const args = [];
|
|
285
273
|
if (startFrom) {
|
|
286
274
|
args.push("--resume", startFrom);
|
|
275
|
+
} else {
|
|
276
|
+
args.push("--session-id", newSessionId);
|
|
287
277
|
}
|
|
288
278
|
args.push("--append-system-prompt", systemPrompt);
|
|
289
279
|
if (opts.mcpServers && Object.keys(opts.mcpServers).length > 0) {
|
|
@@ -302,6 +292,8 @@ async function claudeLocal(opts) {
|
|
|
302
292
|
...process.env,
|
|
303
293
|
...opts.claudeEnvVars
|
|
304
294
|
};
|
|
295
|
+
types.logger.debug(`[ClaudeLocal] Spawning launcher: ${claudeCliPath}`);
|
|
296
|
+
types.logger.debug(`[ClaudeLocal] Args: ${JSON.stringify(args)}`);
|
|
305
297
|
const child = node_child_process.spawn("node", [claudeCliPath, ...args], {
|
|
306
298
|
stdio: ["inherit", "inherit", "inherit", "pipe"],
|
|
307
299
|
signal: opts.abort,
|
|
@@ -318,13 +310,6 @@ async function claudeLocal(opts) {
|
|
|
318
310
|
try {
|
|
319
311
|
const message = JSON.parse(line);
|
|
320
312
|
switch (message.type) {
|
|
321
|
-
case "uuid":
|
|
322
|
-
detectedIdsRandomUUID.add(message.value);
|
|
323
|
-
if (!resolvedSessionId && detectedIdsFileSystem.has(message.value)) {
|
|
324
|
-
resolvedSessionId = message.value;
|
|
325
|
-
opts.onSessionFound(message.value);
|
|
326
|
-
}
|
|
327
|
-
break;
|
|
328
313
|
case "fetch-start":
|
|
329
314
|
activeFetches.set(message.id, {
|
|
330
315
|
hostname: message.hostname,
|
|
@@ -378,7 +363,6 @@ async function claudeLocal(opts) {
|
|
|
378
363
|
});
|
|
379
364
|
});
|
|
380
365
|
} finally {
|
|
381
|
-
watcher.close();
|
|
382
366
|
process.stdin.resume();
|
|
383
367
|
if (stopThinkingTimeout) {
|
|
384
368
|
clearTimeout(stopThinkingTimeout);
|
|
@@ -386,7 +370,7 @@ async function claudeLocal(opts) {
|
|
|
386
370
|
}
|
|
387
371
|
updateThinking(false);
|
|
388
372
|
}
|
|
389
|
-
return
|
|
373
|
+
return effectiveSessionId;
|
|
390
374
|
}
|
|
391
375
|
|
|
392
376
|
class Future {
|
|
@@ -504,6 +488,11 @@ function startFileWatcher(file, onFileChange) {
|
|
|
504
488
|
};
|
|
505
489
|
}
|
|
506
490
|
|
|
491
|
+
const INTERNAL_CLAUDE_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
492
|
+
"file-history-snapshot",
|
|
493
|
+
"change",
|
|
494
|
+
"queue-operation"
|
|
495
|
+
]);
|
|
507
496
|
async function createSessionScanner(opts) {
|
|
508
497
|
const projectDir = getProjectPath(opts.workingDirectory);
|
|
509
498
|
let finishedSessions = /* @__PURE__ */ new Set();
|
|
@@ -513,26 +502,42 @@ async function createSessionScanner(opts) {
|
|
|
513
502
|
let processedMessageKeys = /* @__PURE__ */ new Set();
|
|
514
503
|
if (opts.sessionId) {
|
|
515
504
|
let messages = await readSessionLog(projectDir, opts.sessionId);
|
|
505
|
+
types.logger.debug(`[SESSION_SCANNER] Marking ${messages.length} existing messages as processed from session ${opts.sessionId}`);
|
|
516
506
|
for (let m of messages) {
|
|
517
507
|
processedMessageKeys.add(messageKey(m));
|
|
518
508
|
}
|
|
509
|
+
currentSessionId = opts.sessionId;
|
|
519
510
|
}
|
|
520
511
|
const sync = new InvalidateSync(async () => {
|
|
521
512
|
let sessions = [];
|
|
522
513
|
for (let p of pendingSessions) {
|
|
523
514
|
sessions.push(p);
|
|
524
515
|
}
|
|
525
|
-
if (currentSessionId) {
|
|
516
|
+
if (currentSessionId && !pendingSessions.has(currentSessionId)) {
|
|
526
517
|
sessions.push(currentSessionId);
|
|
527
518
|
}
|
|
519
|
+
for (let [sessionId] of watchers) {
|
|
520
|
+
if (!sessions.includes(sessionId)) {
|
|
521
|
+
sessions.push(sessionId);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
528
524
|
for (let session of sessions) {
|
|
529
|
-
|
|
525
|
+
const sessionMessages = await readSessionLog(projectDir, session);
|
|
526
|
+
let skipped = 0;
|
|
527
|
+
let sent = 0;
|
|
528
|
+
for (let file of sessionMessages) {
|
|
530
529
|
let key = messageKey(file);
|
|
531
530
|
if (processedMessageKeys.has(key)) {
|
|
531
|
+
skipped++;
|
|
532
532
|
continue;
|
|
533
533
|
}
|
|
534
534
|
processedMessageKeys.add(key);
|
|
535
|
+
types.logger.debug(`[SESSION_SCANNER] Sending new message: type=${file.type}, uuid=${file.type === "summary" ? file.leafUuid : file.uuid}`);
|
|
535
536
|
opts.onMessage(file);
|
|
537
|
+
sent++;
|
|
538
|
+
}
|
|
539
|
+
if (sessionMessages.length > 0) {
|
|
540
|
+
types.logger.debug(`[SESSION_SCANNER] Session ${session}: found=${sessionMessages.length}, skipped=${skipped}, sent=${sent}`);
|
|
536
541
|
}
|
|
537
542
|
}
|
|
538
543
|
for (let p of sessions) {
|
|
@@ -543,6 +548,7 @@ async function createSessionScanner(opts) {
|
|
|
543
548
|
}
|
|
544
549
|
for (let p of sessions) {
|
|
545
550
|
if (!watchers.has(p)) {
|
|
551
|
+
types.logger.debug(`[SESSION_SCANNER] Starting watcher for session: ${p}`);
|
|
546
552
|
watchers.set(p, startFileWatcher(node_path.join(projectDir, `${p}.jsonl`), () => {
|
|
547
553
|
sync.invalidate();
|
|
548
554
|
}));
|
|
@@ -616,9 +622,11 @@ async function readSessionLog(projectDir, sessionId) {
|
|
|
616
622
|
continue;
|
|
617
623
|
}
|
|
618
624
|
let message = JSON.parse(l);
|
|
625
|
+
if (message.type && INTERNAL_CLAUDE_EVENT_TYPES.has(message.type)) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
619
628
|
let parsed = types.RawJSONLinesSchema.safeParse(message);
|
|
620
629
|
if (!parsed.success) {
|
|
621
|
-
types.logger.debugLargeJson(`[SESSION_SCANNER] Failed to parse message`, message);
|
|
622
630
|
continue;
|
|
623
631
|
}
|
|
624
632
|
messages.push(parsed.data);
|
|
@@ -981,10 +989,94 @@ class AbortError extends Error {
|
|
|
981
989
|
}
|
|
982
990
|
}
|
|
983
991
|
|
|
984
|
-
const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-
|
|
992
|
+
const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BCQGaH21.cjs', document.baseURI).href)));
|
|
985
993
|
const __dirname$1 = node_path.join(__filename$1, "..");
|
|
994
|
+
function getGlobalClaudeVersion() {
|
|
995
|
+
try {
|
|
996
|
+
const cleanEnv = getCleanEnv();
|
|
997
|
+
const output = node_child_process.execSync("claude --version", {
|
|
998
|
+
encoding: "utf8",
|
|
999
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1000
|
+
cwd: os.homedir(),
|
|
1001
|
+
env: cleanEnv
|
|
1002
|
+
}).trim();
|
|
1003
|
+
const match = output.match(/(\d+\.\d+\.\d+)/);
|
|
1004
|
+
types.logger.debug(`[Claude SDK] Global claude --version output: ${output}`);
|
|
1005
|
+
return match ? match[1] : null;
|
|
1006
|
+
} catch {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
function getCleanEnv() {
|
|
1011
|
+
const env = { ...process.env };
|
|
1012
|
+
const cwd = process.cwd();
|
|
1013
|
+
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
1014
|
+
const pathKey = process.platform === "win32" ? "Path" : "PATH";
|
|
1015
|
+
const actualPathKey = Object.keys(env).find((k) => k.toLowerCase() === "path") || pathKey;
|
|
1016
|
+
if (env[actualPathKey]) {
|
|
1017
|
+
const cleanPath = env[actualPathKey].split(pathSep).filter((p) => {
|
|
1018
|
+
const normalizedP = p.replace(/\\/g, "/").toLowerCase();
|
|
1019
|
+
const normalizedCwd = cwd.replace(/\\/g, "/").toLowerCase();
|
|
1020
|
+
return !normalizedP.startsWith(normalizedCwd);
|
|
1021
|
+
}).join(pathSep);
|
|
1022
|
+
env[actualPathKey] = cleanPath;
|
|
1023
|
+
types.logger.debug(`[Claude SDK] Cleaned PATH, removed local paths from: ${cwd}`);
|
|
1024
|
+
}
|
|
1025
|
+
return env;
|
|
1026
|
+
}
|
|
1027
|
+
function findGlobalClaudePath() {
|
|
1028
|
+
const homeDir = os.homedir();
|
|
1029
|
+
const cleanEnv = getCleanEnv();
|
|
1030
|
+
try {
|
|
1031
|
+
node_child_process.execSync("claude --version", {
|
|
1032
|
+
encoding: "utf8",
|
|
1033
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1034
|
+
cwd: homeDir,
|
|
1035
|
+
env: cleanEnv
|
|
1036
|
+
});
|
|
1037
|
+
types.logger.debug("[Claude SDK] Global claude command available (checked with clean PATH)");
|
|
1038
|
+
return "claude";
|
|
1039
|
+
} catch {
|
|
1040
|
+
}
|
|
1041
|
+
if (process.platform !== "win32") {
|
|
1042
|
+
try {
|
|
1043
|
+
const result = node_child_process.execSync("which claude", {
|
|
1044
|
+
encoding: "utf8",
|
|
1045
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1046
|
+
cwd: homeDir,
|
|
1047
|
+
env: cleanEnv
|
|
1048
|
+
}).trim();
|
|
1049
|
+
if (result && fs.existsSync(result)) {
|
|
1050
|
+
types.logger.debug(`[Claude SDK] Found global claude path via which: ${result}`);
|
|
1051
|
+
return result;
|
|
1052
|
+
}
|
|
1053
|
+
} catch {
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
986
1058
|
function getDefaultClaudeCodePath() {
|
|
987
|
-
|
|
1059
|
+
const nodeModulesPath = node_path.join(__dirname$1, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
|
|
1060
|
+
if (process.env.HAPPY_CLAUDE_PATH) {
|
|
1061
|
+
types.logger.debug(`[Claude SDK] Using HAPPY_CLAUDE_PATH: ${process.env.HAPPY_CLAUDE_PATH}`);
|
|
1062
|
+
return process.env.HAPPY_CLAUDE_PATH;
|
|
1063
|
+
}
|
|
1064
|
+
if (process.env.HAPPY_USE_BUNDLED_CLAUDE === "1") {
|
|
1065
|
+
types.logger.debug(`[Claude SDK] Forced bundled version: ${nodeModulesPath}`);
|
|
1066
|
+
return nodeModulesPath;
|
|
1067
|
+
}
|
|
1068
|
+
const globalPath = findGlobalClaudePath();
|
|
1069
|
+
if (!globalPath) {
|
|
1070
|
+
types.logger.debug(`[Claude SDK] No global claude found, using bundled: ${nodeModulesPath}`);
|
|
1071
|
+
return nodeModulesPath;
|
|
1072
|
+
}
|
|
1073
|
+
const globalVersion = getGlobalClaudeVersion();
|
|
1074
|
+
types.logger.debug(`[Claude SDK] Global version: ${globalVersion || "unknown"}`);
|
|
1075
|
+
if (!globalVersion) {
|
|
1076
|
+
types.logger.debug(`[Claude SDK] Cannot compare versions, using global: ${globalPath}`);
|
|
1077
|
+
return globalPath;
|
|
1078
|
+
}
|
|
1079
|
+
return globalPath;
|
|
988
1080
|
}
|
|
989
1081
|
function logDebug(message) {
|
|
990
1082
|
if (process.env.DEBUG) {
|
|
@@ -1249,17 +1341,22 @@ function query(config) {
|
|
|
1249
1341
|
} else {
|
|
1250
1342
|
args.push("--input-format", "stream-json");
|
|
1251
1343
|
}
|
|
1252
|
-
|
|
1344
|
+
const isJsFile = pathToClaudeCodeExecutable.endsWith(".js") || pathToClaudeCodeExecutable.endsWith(".cjs");
|
|
1345
|
+
const isCommandOnly = pathToClaudeCodeExecutable === "claude";
|
|
1346
|
+
if (!isCommandOnly && !fs.existsSync(pathToClaudeCodeExecutable)) {
|
|
1253
1347
|
throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`);
|
|
1254
1348
|
}
|
|
1255
|
-
|
|
1256
|
-
const
|
|
1349
|
+
const spawnCommand = isJsFile ? executable : pathToClaudeCodeExecutable;
|
|
1350
|
+
const spawnArgs = isJsFile ? [...executableArgs, pathToClaudeCodeExecutable, ...args] : args;
|
|
1351
|
+
const spawnEnv = isCommandOnly ? getCleanEnv() : process.env;
|
|
1352
|
+
logDebug(`Spawning Claude Code process: ${spawnCommand} ${spawnArgs.join(" ")} (using ${isCommandOnly ? "clean" : "normal"} env)`);
|
|
1353
|
+
const child = node_child_process.spawn(spawnCommand, spawnArgs, {
|
|
1257
1354
|
cwd,
|
|
1258
1355
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1259
1356
|
signal: config.options?.abort,
|
|
1260
|
-
env:
|
|
1261
|
-
|
|
1262
|
-
|
|
1357
|
+
env: spawnEnv,
|
|
1358
|
+
// Use shell on Windows for global binaries and command-only mode
|
|
1359
|
+
shell: !isJsFile && process.platform === "win32"
|
|
1263
1360
|
});
|
|
1264
1361
|
let childStdin = null;
|
|
1265
1362
|
if (typeof prompt === "string") {
|
|
@@ -4605,8 +4702,7 @@ async function startHappyServer(client) {
|
|
|
4605
4702
|
};
|
|
4606
4703
|
const mcp = new mcp_js.McpServer({
|
|
4607
4704
|
name: "Happy MCP",
|
|
4608
|
-
version: "1.0.0"
|
|
4609
|
-
description: "Happy CLI MCP server with chat session management tools"
|
|
4705
|
+
version: "1.0.0"
|
|
4610
4706
|
});
|
|
4611
4707
|
mcp.registerTool("change_title", {
|
|
4612
4708
|
description: "Change the title of the current chat session",
|
|
@@ -5816,7 +5912,7 @@ async function handleConnectVendor(vendor, displayName) {
|
|
|
5816
5912
|
return;
|
|
5817
5913
|
} else if (subcommand === "codex") {
|
|
5818
5914
|
try {
|
|
5819
|
-
const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-
|
|
5915
|
+
const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-BwpHgdkt.cjs'); });
|
|
5820
5916
|
let startedBy = void 0;
|
|
5821
5917
|
for (let i = 1; i < args.length; i++) {
|
|
5822
5918
|
if (args[i] === "--started-by") {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import os$1, { homedir } from 'node:os';
|
|
3
3
|
import { randomUUID, randomBytes } from 'node:crypto';
|
|
4
|
-
import { l as logger, p as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, f as AsyncLock, c as configuration, g as readDaemonState, h as clearDaemonState, b as packageJson, r as readSettings, i as readCredentials, j as encodeBase64, u as updateSettings, k as encodeBase64Url, m as decodeBase64, w as writeCredentialsLegacy, n as writeCredentialsDataKey, o as acquireDaemonLock, q as writeDaemonState, A as ApiClient, s as releaseDaemonLock, t as clearCredentials, v as clearMachineId, x as getLatestDaemonLog } from './types-
|
|
4
|
+
import { l as logger, p as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, f as AsyncLock, c as configuration, g as readDaemonState, h as clearDaemonState, b as packageJson, r as readSettings, i as readCredentials, j as encodeBase64, u as updateSettings, k as encodeBase64Url, m as decodeBase64, w as writeCredentialsLegacy, n as writeCredentialsDataKey, o as acquireDaemonLock, q as writeDaemonState, A as ApiClient, s as releaseDaemonLock, t as clearCredentials, v as clearMachineId, x as getLatestDaemonLog } from './types-DdwJ6K-A.mjs';
|
|
5
5
|
import { spawn, execSync, execFileSync } from 'node:child_process';
|
|
6
6
|
import { resolve, join } from 'node:path';
|
|
7
7
|
import { createInterface } from 'node:readline';
|
|
8
|
-
import { existsSync, readFileSync, mkdirSync,
|
|
8
|
+
import { existsSync, readFileSync, mkdirSync, readdirSync, statSync, rmSync } from 'node:fs';
|
|
9
9
|
import { readFile } from 'node:fs/promises';
|
|
10
|
-
import fs, { watch
|
|
10
|
+
import fs, { watch, access } from 'fs/promises';
|
|
11
11
|
import { useStdout, useInput, Box, Text, render } from 'ink';
|
|
12
12
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
@@ -220,31 +220,19 @@ const claudeCliPath = resolve(join(projectPath(), "scripts", "claude_local_launc
|
|
|
220
220
|
async function claudeLocal(opts) {
|
|
221
221
|
const projectDir = getProjectPath(opts.path);
|
|
222
222
|
mkdirSync(projectDir, { recursive: true });
|
|
223
|
-
const watcher = watch(projectDir);
|
|
224
|
-
let resolvedSessionId = null;
|
|
225
|
-
const detectedIdsRandomUUID = /* @__PURE__ */ new Set();
|
|
226
|
-
const detectedIdsFileSystem = /* @__PURE__ */ new Set();
|
|
227
|
-
watcher.on("change", (event, filename) => {
|
|
228
|
-
if (typeof filename === "string" && filename.toLowerCase().endsWith(".jsonl")) {
|
|
229
|
-
logger.debug("change", event, filename);
|
|
230
|
-
const sessionId = filename.replace(".jsonl", "");
|
|
231
|
-
if (detectedIdsFileSystem.has(sessionId)) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
detectedIdsFileSystem.add(sessionId);
|
|
235
|
-
if (resolvedSessionId) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if (detectedIdsRandomUUID.has(sessionId)) {
|
|
239
|
-
resolvedSessionId = sessionId;
|
|
240
|
-
opts.onSessionFound(sessionId);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
223
|
let startFrom = opts.sessionId;
|
|
245
224
|
if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
|
|
246
225
|
startFrom = null;
|
|
247
226
|
}
|
|
227
|
+
const newSessionId = startFrom ? null : randomUUID();
|
|
228
|
+
const effectiveSessionId = startFrom || newSessionId;
|
|
229
|
+
if (newSessionId) {
|
|
230
|
+
logger.debug(`[ClaudeLocal] Generated new session ID: ${newSessionId}`);
|
|
231
|
+
opts.onSessionFound(newSessionId);
|
|
232
|
+
} else {
|
|
233
|
+
logger.debug(`[ClaudeLocal] Resuming session: ${startFrom}`);
|
|
234
|
+
opts.onSessionFound(startFrom);
|
|
235
|
+
}
|
|
248
236
|
let thinking = false;
|
|
249
237
|
let stopThinkingTimeout = null;
|
|
250
238
|
const updateThinking = (newThinking) => {
|
|
@@ -262,6 +250,8 @@ async function claudeLocal(opts) {
|
|
|
262
250
|
const args = [];
|
|
263
251
|
if (startFrom) {
|
|
264
252
|
args.push("--resume", startFrom);
|
|
253
|
+
} else {
|
|
254
|
+
args.push("--session-id", newSessionId);
|
|
265
255
|
}
|
|
266
256
|
args.push("--append-system-prompt", systemPrompt);
|
|
267
257
|
if (opts.mcpServers && Object.keys(opts.mcpServers).length > 0) {
|
|
@@ -280,6 +270,8 @@ async function claudeLocal(opts) {
|
|
|
280
270
|
...process.env,
|
|
281
271
|
...opts.claudeEnvVars
|
|
282
272
|
};
|
|
273
|
+
logger.debug(`[ClaudeLocal] Spawning launcher: ${claudeCliPath}`);
|
|
274
|
+
logger.debug(`[ClaudeLocal] Args: ${JSON.stringify(args)}`);
|
|
283
275
|
const child = spawn("node", [claudeCliPath, ...args], {
|
|
284
276
|
stdio: ["inherit", "inherit", "inherit", "pipe"],
|
|
285
277
|
signal: opts.abort,
|
|
@@ -296,13 +288,6 @@ async function claudeLocal(opts) {
|
|
|
296
288
|
try {
|
|
297
289
|
const message = JSON.parse(line);
|
|
298
290
|
switch (message.type) {
|
|
299
|
-
case "uuid":
|
|
300
|
-
detectedIdsRandomUUID.add(message.value);
|
|
301
|
-
if (!resolvedSessionId && detectedIdsFileSystem.has(message.value)) {
|
|
302
|
-
resolvedSessionId = message.value;
|
|
303
|
-
opts.onSessionFound(message.value);
|
|
304
|
-
}
|
|
305
|
-
break;
|
|
306
291
|
case "fetch-start":
|
|
307
292
|
activeFetches.set(message.id, {
|
|
308
293
|
hostname: message.hostname,
|
|
@@ -356,7 +341,6 @@ async function claudeLocal(opts) {
|
|
|
356
341
|
});
|
|
357
342
|
});
|
|
358
343
|
} finally {
|
|
359
|
-
watcher.close();
|
|
360
344
|
process.stdin.resume();
|
|
361
345
|
if (stopThinkingTimeout) {
|
|
362
346
|
clearTimeout(stopThinkingTimeout);
|
|
@@ -364,7 +348,7 @@ async function claudeLocal(opts) {
|
|
|
364
348
|
}
|
|
365
349
|
updateThinking(false);
|
|
366
350
|
}
|
|
367
|
-
return
|
|
351
|
+
return effectiveSessionId;
|
|
368
352
|
}
|
|
369
353
|
|
|
370
354
|
class Future {
|
|
@@ -460,7 +444,7 @@ function startFileWatcher(file, onFileChange) {
|
|
|
460
444
|
while (true) {
|
|
461
445
|
try {
|
|
462
446
|
logger.debug(`[FILE_WATCHER] Starting watcher for ${file}`);
|
|
463
|
-
const watcher = watch
|
|
447
|
+
const watcher = watch(file, { persistent: true, signal: abortController.signal });
|
|
464
448
|
for await (const event of watcher) {
|
|
465
449
|
if (abortController.signal.aborted) {
|
|
466
450
|
return;
|
|
@@ -482,6 +466,11 @@ function startFileWatcher(file, onFileChange) {
|
|
|
482
466
|
};
|
|
483
467
|
}
|
|
484
468
|
|
|
469
|
+
const INTERNAL_CLAUDE_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
470
|
+
"file-history-snapshot",
|
|
471
|
+
"change",
|
|
472
|
+
"queue-operation"
|
|
473
|
+
]);
|
|
485
474
|
async function createSessionScanner(opts) {
|
|
486
475
|
const projectDir = getProjectPath(opts.workingDirectory);
|
|
487
476
|
let finishedSessions = /* @__PURE__ */ new Set();
|
|
@@ -491,26 +480,42 @@ async function createSessionScanner(opts) {
|
|
|
491
480
|
let processedMessageKeys = /* @__PURE__ */ new Set();
|
|
492
481
|
if (opts.sessionId) {
|
|
493
482
|
let messages = await readSessionLog(projectDir, opts.sessionId);
|
|
483
|
+
logger.debug(`[SESSION_SCANNER] Marking ${messages.length} existing messages as processed from session ${opts.sessionId}`);
|
|
494
484
|
for (let m of messages) {
|
|
495
485
|
processedMessageKeys.add(messageKey(m));
|
|
496
486
|
}
|
|
487
|
+
currentSessionId = opts.sessionId;
|
|
497
488
|
}
|
|
498
489
|
const sync = new InvalidateSync(async () => {
|
|
499
490
|
let sessions = [];
|
|
500
491
|
for (let p of pendingSessions) {
|
|
501
492
|
sessions.push(p);
|
|
502
493
|
}
|
|
503
|
-
if (currentSessionId) {
|
|
494
|
+
if (currentSessionId && !pendingSessions.has(currentSessionId)) {
|
|
504
495
|
sessions.push(currentSessionId);
|
|
505
496
|
}
|
|
497
|
+
for (let [sessionId] of watchers) {
|
|
498
|
+
if (!sessions.includes(sessionId)) {
|
|
499
|
+
sessions.push(sessionId);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
506
502
|
for (let session of sessions) {
|
|
507
|
-
|
|
503
|
+
const sessionMessages = await readSessionLog(projectDir, session);
|
|
504
|
+
let skipped = 0;
|
|
505
|
+
let sent = 0;
|
|
506
|
+
for (let file of sessionMessages) {
|
|
508
507
|
let key = messageKey(file);
|
|
509
508
|
if (processedMessageKeys.has(key)) {
|
|
509
|
+
skipped++;
|
|
510
510
|
continue;
|
|
511
511
|
}
|
|
512
512
|
processedMessageKeys.add(key);
|
|
513
|
+
logger.debug(`[SESSION_SCANNER] Sending new message: type=${file.type}, uuid=${file.type === "summary" ? file.leafUuid : file.uuid}`);
|
|
513
514
|
opts.onMessage(file);
|
|
515
|
+
sent++;
|
|
516
|
+
}
|
|
517
|
+
if (sessionMessages.length > 0) {
|
|
518
|
+
logger.debug(`[SESSION_SCANNER] Session ${session}: found=${sessionMessages.length}, skipped=${skipped}, sent=${sent}`);
|
|
514
519
|
}
|
|
515
520
|
}
|
|
516
521
|
for (let p of sessions) {
|
|
@@ -521,6 +526,7 @@ async function createSessionScanner(opts) {
|
|
|
521
526
|
}
|
|
522
527
|
for (let p of sessions) {
|
|
523
528
|
if (!watchers.has(p)) {
|
|
529
|
+
logger.debug(`[SESSION_SCANNER] Starting watcher for session: ${p}`);
|
|
524
530
|
watchers.set(p, startFileWatcher(join(projectDir, `${p}.jsonl`), () => {
|
|
525
531
|
sync.invalidate();
|
|
526
532
|
}));
|
|
@@ -594,9 +600,11 @@ async function readSessionLog(projectDir, sessionId) {
|
|
|
594
600
|
continue;
|
|
595
601
|
}
|
|
596
602
|
let message = JSON.parse(l);
|
|
603
|
+
if (message.type && INTERNAL_CLAUDE_EVENT_TYPES.has(message.type)) {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
597
606
|
let parsed = RawJSONLinesSchema.safeParse(message);
|
|
598
607
|
if (!parsed.success) {
|
|
599
|
-
logger.debugLargeJson(`[SESSION_SCANNER] Failed to parse message`, message);
|
|
600
608
|
continue;
|
|
601
609
|
}
|
|
602
610
|
messages.push(parsed.data);
|
|
@@ -961,8 +969,92 @@ class AbortError extends Error {
|
|
|
961
969
|
|
|
962
970
|
const __filename = fileURLToPath(import.meta.url);
|
|
963
971
|
const __dirname = join(__filename, "..");
|
|
972
|
+
function getGlobalClaudeVersion() {
|
|
973
|
+
try {
|
|
974
|
+
const cleanEnv = getCleanEnv();
|
|
975
|
+
const output = execSync("claude --version", {
|
|
976
|
+
encoding: "utf8",
|
|
977
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
978
|
+
cwd: homedir(),
|
|
979
|
+
env: cleanEnv
|
|
980
|
+
}).trim();
|
|
981
|
+
const match = output.match(/(\d+\.\d+\.\d+)/);
|
|
982
|
+
logger.debug(`[Claude SDK] Global claude --version output: ${output}`);
|
|
983
|
+
return match ? match[1] : null;
|
|
984
|
+
} catch {
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
function getCleanEnv() {
|
|
989
|
+
const env = { ...process.env };
|
|
990
|
+
const cwd = process.cwd();
|
|
991
|
+
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
992
|
+
const pathKey = process.platform === "win32" ? "Path" : "PATH";
|
|
993
|
+
const actualPathKey = Object.keys(env).find((k) => k.toLowerCase() === "path") || pathKey;
|
|
994
|
+
if (env[actualPathKey]) {
|
|
995
|
+
const cleanPath = env[actualPathKey].split(pathSep).filter((p) => {
|
|
996
|
+
const normalizedP = p.replace(/\\/g, "/").toLowerCase();
|
|
997
|
+
const normalizedCwd = cwd.replace(/\\/g, "/").toLowerCase();
|
|
998
|
+
return !normalizedP.startsWith(normalizedCwd);
|
|
999
|
+
}).join(pathSep);
|
|
1000
|
+
env[actualPathKey] = cleanPath;
|
|
1001
|
+
logger.debug(`[Claude SDK] Cleaned PATH, removed local paths from: ${cwd}`);
|
|
1002
|
+
}
|
|
1003
|
+
return env;
|
|
1004
|
+
}
|
|
1005
|
+
function findGlobalClaudePath() {
|
|
1006
|
+
const homeDir = homedir();
|
|
1007
|
+
const cleanEnv = getCleanEnv();
|
|
1008
|
+
try {
|
|
1009
|
+
execSync("claude --version", {
|
|
1010
|
+
encoding: "utf8",
|
|
1011
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1012
|
+
cwd: homeDir,
|
|
1013
|
+
env: cleanEnv
|
|
1014
|
+
});
|
|
1015
|
+
logger.debug("[Claude SDK] Global claude command available (checked with clean PATH)");
|
|
1016
|
+
return "claude";
|
|
1017
|
+
} catch {
|
|
1018
|
+
}
|
|
1019
|
+
if (process.platform !== "win32") {
|
|
1020
|
+
try {
|
|
1021
|
+
const result = execSync("which claude", {
|
|
1022
|
+
encoding: "utf8",
|
|
1023
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1024
|
+
cwd: homeDir,
|
|
1025
|
+
env: cleanEnv
|
|
1026
|
+
}).trim();
|
|
1027
|
+
if (result && existsSync(result)) {
|
|
1028
|
+
logger.debug(`[Claude SDK] Found global claude path via which: ${result}`);
|
|
1029
|
+
return result;
|
|
1030
|
+
}
|
|
1031
|
+
} catch {
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
964
1036
|
function getDefaultClaudeCodePath() {
|
|
965
|
-
|
|
1037
|
+
const nodeModulesPath = join(__dirname, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
|
|
1038
|
+
if (process.env.HAPPY_CLAUDE_PATH) {
|
|
1039
|
+
logger.debug(`[Claude SDK] Using HAPPY_CLAUDE_PATH: ${process.env.HAPPY_CLAUDE_PATH}`);
|
|
1040
|
+
return process.env.HAPPY_CLAUDE_PATH;
|
|
1041
|
+
}
|
|
1042
|
+
if (process.env.HAPPY_USE_BUNDLED_CLAUDE === "1") {
|
|
1043
|
+
logger.debug(`[Claude SDK] Forced bundled version: ${nodeModulesPath}`);
|
|
1044
|
+
return nodeModulesPath;
|
|
1045
|
+
}
|
|
1046
|
+
const globalPath = findGlobalClaudePath();
|
|
1047
|
+
if (!globalPath) {
|
|
1048
|
+
logger.debug(`[Claude SDK] No global claude found, using bundled: ${nodeModulesPath}`);
|
|
1049
|
+
return nodeModulesPath;
|
|
1050
|
+
}
|
|
1051
|
+
const globalVersion = getGlobalClaudeVersion();
|
|
1052
|
+
logger.debug(`[Claude SDK] Global version: ${globalVersion || "unknown"}`);
|
|
1053
|
+
if (!globalVersion) {
|
|
1054
|
+
logger.debug(`[Claude SDK] Cannot compare versions, using global: ${globalPath}`);
|
|
1055
|
+
return globalPath;
|
|
1056
|
+
}
|
|
1057
|
+
return globalPath;
|
|
966
1058
|
}
|
|
967
1059
|
function logDebug(message) {
|
|
968
1060
|
if (process.env.DEBUG) {
|
|
@@ -1227,17 +1319,22 @@ function query(config) {
|
|
|
1227
1319
|
} else {
|
|
1228
1320
|
args.push("--input-format", "stream-json");
|
|
1229
1321
|
}
|
|
1230
|
-
|
|
1322
|
+
const isJsFile = pathToClaudeCodeExecutable.endsWith(".js") || pathToClaudeCodeExecutable.endsWith(".cjs");
|
|
1323
|
+
const isCommandOnly = pathToClaudeCodeExecutable === "claude";
|
|
1324
|
+
if (!isCommandOnly && !existsSync(pathToClaudeCodeExecutable)) {
|
|
1231
1325
|
throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`);
|
|
1232
1326
|
}
|
|
1233
|
-
|
|
1234
|
-
const
|
|
1327
|
+
const spawnCommand = isJsFile ? executable : pathToClaudeCodeExecutable;
|
|
1328
|
+
const spawnArgs = isJsFile ? [...executableArgs, pathToClaudeCodeExecutable, ...args] : args;
|
|
1329
|
+
const spawnEnv = isCommandOnly ? getCleanEnv() : process.env;
|
|
1330
|
+
logDebug(`Spawning Claude Code process: ${spawnCommand} ${spawnArgs.join(" ")} (using ${isCommandOnly ? "clean" : "normal"} env)`);
|
|
1331
|
+
const child = spawn(spawnCommand, spawnArgs, {
|
|
1235
1332
|
cwd,
|
|
1236
1333
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1237
1334
|
signal: config.options?.abort,
|
|
1238
|
-
env:
|
|
1239
|
-
|
|
1240
|
-
|
|
1335
|
+
env: spawnEnv,
|
|
1336
|
+
// Use shell on Windows for global binaries and command-only mode
|
|
1337
|
+
shell: !isJsFile && process.platform === "win32"
|
|
1241
1338
|
});
|
|
1242
1339
|
let childStdin = null;
|
|
1243
1340
|
if (typeof prompt === "string") {
|
|
@@ -4583,8 +4680,7 @@ async function startHappyServer(client) {
|
|
|
4583
4680
|
};
|
|
4584
4681
|
const mcp = new McpServer({
|
|
4585
4682
|
name: "Happy MCP",
|
|
4586
|
-
version: "1.0.0"
|
|
4587
|
-
description: "Happy CLI MCP server with chat session management tools"
|
|
4683
|
+
version: "1.0.0"
|
|
4588
4684
|
});
|
|
4589
4685
|
mcp.registerTool("change_title", {
|
|
4590
4686
|
description: "Change the title of the current chat session",
|
|
@@ -5794,7 +5890,7 @@ async function handleConnectVendor(vendor, displayName) {
|
|
|
5794
5890
|
return;
|
|
5795
5891
|
} else if (subcommand === "codex") {
|
|
5796
5892
|
try {
|
|
5797
|
-
const { runCodex } = await import('./runCodex-
|
|
5893
|
+
const { runCodex } = await import('./runCodex-IZ3YFeAC.mjs');
|
|
5798
5894
|
let startedBy = void 0;
|
|
5799
5895
|
for (let i = 1; i < args.length; i++) {
|
|
5800
5896
|
if (args[i] === "--started-by") {
|