happy-coder 0.11.2 → 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.
@@ -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: { tools: {} } }
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: { tools: {} } }
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-Bg43e3vc.cjs');
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 resolvedSessionId;
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
- for (let file of await readSessionLog(projectDir, session)) {
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-DmJ8WyYo.cjs', document.baseURI).href)));
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
- return node_path.join(__dirname$1, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
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
- if (!fs.existsSync(pathToClaudeCodeExecutable)) {
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
- logDebug(`Spawning Claude Code process: ${executable} ${[...executableArgs, pathToClaudeCodeExecutable, ...args].join(" ")}`);
1256
- const child = node_child_process.spawn(executable, [...executableArgs, pathToClaudeCodeExecutable, ...args], {
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
- ...process.env
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-DQPZNHzH.cjs'); });
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") {