@zonease/happy 0.6.38 → 0.6.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/bin/happy.mjs +10 -0
  2. package/dist/{types-DvTsFaqR.cjs → api-C7FyJfZ1.cjs} +400 -1964
  3. package/dist/{types-BWpRU1VN.mjs → api-D6WL40-U.mjs} +220 -1729
  4. package/dist/auth-C7ZScWEw.cjs +274 -0
  5. package/dist/auth-DCsIoZLT.cjs +183 -0
  6. package/dist/auth-DO9GexAR.mjs +181 -0
  7. package/dist/auth-DxJ3VvrX.mjs +270 -0
  8. package/dist/browser-AoVGRX7y.mjs +20 -0
  9. package/dist/browser-DzgO4oaD.cjs +22 -0
  10. package/dist/claudeLocal-C6BdzhDx.cjs +352 -0
  11. package/dist/claudeLocal-CWNGpJ0-.mjs +344 -0
  12. package/dist/codex/happyMcpStdioBridge.cjs +4 -1
  13. package/dist/codex/happyMcpStdioBridge.mjs +4 -1
  14. package/dist/common-BdNgghUJ.mjs +75 -0
  15. package/dist/common-CZvSRU4p.cjs +83 -0
  16. package/dist/{config-0Xnc8ARM.cjs → config-CclB3QW9.cjs} +21 -34
  17. package/dist/{config-_GW4YBCP.mjs → config-DZk-mJ0K.mjs} +7 -20
  18. package/dist/connect-BXLUKJ5P.mjs +679 -0
  19. package/dist/connect-k9CqDJ-T.cjs +681 -0
  20. package/dist/{constants-B4x1K7by.cjs → constants-E_UWYTo3.cjs} +2 -2
  21. package/dist/{constants-BO9OAFOT.mjs → constants-pmXZGibX.mjs} +1 -1
  22. package/dist/controlClient-B23M-WPp.cjs +177 -0
  23. package/dist/controlClient-DB5tDU7V.mjs +167 -0
  24. package/dist/deviceKeys-BTdo34Uz.mjs +175 -0
  25. package/dist/deviceKeys-C540DcXl.cjs +182 -0
  26. package/dist/diagnose-B3D1a_v0.cjs +60 -0
  27. package/dist/diagnose-CuRKXxt1.mjs +58 -0
  28. package/dist/doctor-BQGLhrcI.cjs +214 -0
  29. package/dist/doctor-C08m5Z9f.cjs +78 -0
  30. package/dist/doctor-CU4JLGQ1.mjs +211 -0
  31. package/dist/doctor-ON7pN5G5.mjs +74 -0
  32. package/dist/downloadImage-DZXI3mXL.mjs +887 -0
  33. package/dist/downloadImage-cQ1Z7hkC.cjs +897 -0
  34. package/dist/enroll-B55BQR56.mjs +113 -0
  35. package/dist/enroll-Bo-CNio1.cjs +115 -0
  36. package/dist/evidence-DQp8QWcv.cjs +129 -0
  37. package/dist/evidence-DTYRAaOZ.mjs +127 -0
  38. package/dist/formatImageMessage-CixDYY6G.cjs +46 -0
  39. package/dist/formatImageMessage-DbKSSjMx.mjs +43 -0
  40. package/dist/github-DKcyF1zp.mjs +57 -0
  41. package/dist/github-h9GhJJ_C.cjs +59 -0
  42. package/dist/index-CDTNgfUf.cjs +578 -0
  43. package/dist/index-CPKdvi7J.mjs +554 -0
  44. package/dist/index.cjs +812 -40
  45. package/dist/index.mjs +813 -40
  46. package/dist/install-B1e6rdy7.mjs +94 -0
  47. package/dist/install-CgUPGIoF.mjs +95 -0
  48. package/dist/install-D7Ir7ciZ.cjs +96 -0
  49. package/dist/install-DSDufaPd.cjs +98 -0
  50. package/dist/lib.cjs +18 -12
  51. package/dist/lib.d.cts +77 -0
  52. package/dist/lib.d.mts +77 -0
  53. package/dist/lib.mjs +13 -7
  54. package/dist/{loadableToolOutput-va3RLplh.mjs → loadableToolOutput-Bfei9wPq.mjs} +4 -2
  55. package/dist/{loadableToolOutput-5vdQB98M.cjs → loadableToolOutput-CbLGtvST.cjs} +63 -61
  56. package/dist/mcpToolSchemas-CjeTfrAs.mjs +62 -0
  57. package/dist/mcpToolSchemas-DK42yoZ_.cjs +64 -0
  58. package/dist/persistence-CBTtC_dm.cjs +996 -0
  59. package/dist/persistence-DKfF81pd.mjs +944 -0
  60. package/dist/projectPath-BTBIMB57.mjs +10 -0
  61. package/dist/projectPath-ChLhdOyt.cjs +13 -0
  62. package/dist/prompt-BTWDZZO0.cjs +25 -0
  63. package/dist/prompt-BsAZsPbo.mjs +22 -0
  64. package/dist/release-BMEzNONE.mjs +168 -0
  65. package/dist/release-EhOJeAZe.cjs +171 -0
  66. package/dist/run-BVZsMNwN.mjs +2164 -0
  67. package/dist/run-CURx7LhK.cjs +2188 -0
  68. package/dist/runClaude-6Gi0sYQy.mjs +3763 -0
  69. package/dist/runClaude-CL3Kt5ex.cjs +3766 -0
  70. package/dist/{runCodex-DiKzBf9q.mjs → runCodex-ClwEgJ7M.mjs} +36 -20
  71. package/dist/{runCodex-skq0j05d.cjs → runCodex-YKyVBkJ3.cjs} +169 -153
  72. package/dist/{runGemini-BeqrVq8R.mjs → runGemini-BPWxVr60.mjs} +34 -17
  73. package/dist/{runGemini-CVp-CEIh.cjs → runGemini-Xz2W1spn.cjs} +212 -195
  74. package/dist/{runOneShot-ltCpHbBB.mjs → runOneShot-CdCWiiLr.mjs} +14 -38
  75. package/dist/{runOneShot-BQNkNW61.cjs → runOneShot-DjZyCvDI.cjs} +31 -55
  76. package/dist/{serve-BNdoEDVx.mjs → serve-B48JttQ3.mjs} +12 -35
  77. package/dist/{serve-BECdtivY.cjs → serve-B8vaWQA0.cjs} +35 -58
  78. package/dist/session-BXboDn2V.mjs +108 -0
  79. package/dist/session-BZO25zxp.cjs +110 -0
  80. package/dist/sessionKeyCache-B6cGadYL.mjs +33 -0
  81. package/dist/sessionKeyCache-CDmexJ9p.cjs +36 -0
  82. package/dist/sessionList-CgAYZpNj.mjs +51 -0
  83. package/dist/sessionList-lQ-nqEhc.cjs +54 -0
  84. package/dist/sessionSummary-CcORiQSq.cjs +393 -0
  85. package/dist/sessionSummary-DCWQoaDP.mjs +387 -0
  86. package/dist/spawnHappyCLI-KiazE3kL.cjs +70 -0
  87. package/dist/spawnHappyCLI-yBIP_Oqi.mjs +66 -0
  88. package/dist/ssh-q5uZhqBf.mjs +359 -0
  89. package/dist/ssh-yMllSHu5.cjs +365 -0
  90. package/dist/systemdScope-BeOtAkhk.mjs +25 -0
  91. package/dist/systemdScope-DW3-UoKB.cjs +29 -0
  92. package/dist/task-_nr7J19j.cjs +167 -0
  93. package/dist/task-wNX1N2i4.mjs +165 -0
  94. package/dist/time-Bj2HRkKH.cjs +33 -0
  95. package/dist/time-DgsBLJA_.mjs +29 -0
  96. package/dist/trimIdent-4gvk9Wk6.cjs +22 -0
  97. package/dist/trimIdent-BAjmv_TG.mjs +20 -0
  98. package/dist/types-BoJh4Qfv.mjs +57 -0
  99. package/dist/types-DWeB7aqh.cjs +59 -0
  100. package/dist/uninstall-Buaz7d-X.cjs +77 -0
  101. package/dist/uninstall-Dxf6_4mQ.mjs +75 -0
  102. package/dist/update-CGlIfa8e.cjs +111 -0
  103. package/dist/update-CnnoX2cV.mjs +109 -0
  104. package/dist/workflowCommon-B0d01Y_a.mjs +56 -0
  105. package/dist/workflowCommon-Bl568-rt.cjs +62 -0
  106. package/dist/workflowReports-BEWuQesA.cjs +113 -0
  107. package/dist/workflowReports-Dd8-4-8f.mjs +108 -0
  108. package/package.json +1 -1
  109. package/dist/index-BbCDDvoB.cjs +0 -11376
  110. package/dist/index-vvZXdswy.mjs +0 -11336
  111. package/dist/mcpToolSchemas-CdPXJQmb.mjs +0 -175
  112. package/dist/mcpToolSchemas-f-kttjFw.cjs +0 -186
@@ -0,0 +1,270 @@
1
+ import { g as encodeBase64, c as configuration, l as logger, r as readCredentials, u as updateSettings, i as isDebug, s as encodeBase64Url, f as decodeBase64, t as writeCredentialsLegacy, w as writeCredentialsDataKey } from './persistence-DKfF81pd.mjs';
2
+ import { randomBytes, randomUUID } from 'node:crypto';
3
+ import tweetnacl from 'tweetnacl';
4
+ import axios from 'axios';
5
+ import qrcode from 'qrcode-terminal';
6
+ import { d as delay } from './time-DgsBLJA_.mjs';
7
+ import { o as openBrowser } from './browser-AoVGRX7y.mjs';
8
+ import React, { useState } from 'react';
9
+ import { useInput, Box, Text, render } from 'ink';
10
+ import 'node:fs/promises';
11
+ import 'node:fs';
12
+ import 'node:os';
13
+ import 'node:path';
14
+ import 'zod';
15
+ import 'chalk';
16
+ import 'fs';
17
+ import 'open';
18
+
19
+ function displayQRCode(url) {
20
+ console.log("=".repeat(80));
21
+ console.log("\u{1F4F1} To authenticate, scan this QR code with your mobile device:");
22
+ console.log("=".repeat(80));
23
+ qrcode.generate(url, { small: true }, (qr) => {
24
+ for (let l of qr.split("\n")) {
25
+ console.log(" ".repeat(10) + l);
26
+ }
27
+ });
28
+ console.log("=".repeat(80));
29
+ }
30
+
31
+ function generateWebAuthUrl(publicKey) {
32
+ const publicKeyBase64 = encodeBase64(publicKey, "base64url");
33
+ return `${configuration.webappUrl}/terminal/connect#key=${publicKeyBase64}`;
34
+ }
35
+
36
+ const AuthSelector = ({ onSelect, onCancel }) => {
37
+ const [selectedIndex, setSelectedIndex] = useState(0);
38
+ const options = [
39
+ {
40
+ method: "mobile",
41
+ label: "Mobile App"
42
+ },
43
+ {
44
+ method: "web",
45
+ label: "Web Browser"
46
+ }
47
+ ];
48
+ useInput((input, key) => {
49
+ if (key.upArrow) {
50
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
51
+ } else if (key.downArrow) {
52
+ setSelectedIndex((prev) => Math.min(options.length - 1, prev + 1));
53
+ } else if (key.return) {
54
+ onSelect(options[selectedIndex].method);
55
+ } else if (key.escape || key.ctrl && input === "c") {
56
+ onCancel();
57
+ } else if (input === "1") {
58
+ setSelectedIndex(0);
59
+ onSelect("mobile");
60
+ } else if (input === "2") {
61
+ setSelectedIndex(1);
62
+ onSelect("web");
63
+ }
64
+ });
65
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, null, "How would you like to authenticate?")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, options.map((option, index) => {
66
+ const isSelected = selectedIndex === index;
67
+ return /* @__PURE__ */ React.createElement(Box, { key: option.method, marginY: 0 }, /* @__PURE__ */ React.createElement(Text, { color: isSelected ? "cyan" : "gray" }, isSelected ? "\u203A " : " ", index + 1, ". ", option.label));
68
+ })), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Use arrows or 1-2 to select, Enter to confirm")));
69
+ };
70
+
71
+ async function doAuth() {
72
+ console.clear();
73
+ const authMethod = await selectAuthenticationMethod();
74
+ if (!authMethod) {
75
+ console.log("\nAuthentication cancelled.\n");
76
+ process.exit(0);
77
+ }
78
+ const secret = new Uint8Array(randomBytes(32));
79
+ const keypair = tweetnacl.box.keyPair.fromSecretKey(secret);
80
+ try {
81
+ if (isDebug()) {
82
+ console.log(`[AUTH DEBUG] Sending auth request to: ${configuration.serverUrl}/v1/auth/request`);
83
+ console.log(`[AUTH DEBUG] Public key: ${encodeBase64(keypair.publicKey).substring(0, 20)}...`);
84
+ }
85
+ await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
86
+ publicKey: encodeBase64(keypair.publicKey),
87
+ supportsV2: true
88
+ });
89
+ if (isDebug()) {
90
+ console.log(`[AUTH DEBUG] Auth request sent successfully`);
91
+ }
92
+ } catch (error) {
93
+ if (isDebug()) {
94
+ console.log(`[AUTH DEBUG] Failed to send auth request:`, error);
95
+ }
96
+ console.log("Failed to create authentication request, please try again later.");
97
+ return null;
98
+ }
99
+ if (authMethod === "mobile") {
100
+ return await doMobileAuth(keypair);
101
+ } else {
102
+ return await doWebAuth(keypair);
103
+ }
104
+ }
105
+ function selectAuthenticationMethod() {
106
+ return new Promise((resolve) => {
107
+ let hasResolved = false;
108
+ const onSelect = (method) => {
109
+ if (!hasResolved) {
110
+ hasResolved = true;
111
+ app.unmount();
112
+ resolve(method);
113
+ }
114
+ };
115
+ const onCancel = () => {
116
+ if (!hasResolved) {
117
+ hasResolved = true;
118
+ app.unmount();
119
+ resolve(null);
120
+ }
121
+ };
122
+ const app = render(React.createElement(AuthSelector, { onSelect, onCancel }), {
123
+ exitOnCtrlC: false,
124
+ patchConsole: false
125
+ });
126
+ });
127
+ }
128
+ async function doMobileAuth(keypair) {
129
+ console.clear();
130
+ console.log("\nMobile Authentication\n");
131
+ console.log("Scan this QR code with your Happy mobile app:\n");
132
+ const authUrl = "happy://terminal?" + encodeBase64Url(keypair.publicKey);
133
+ displayQRCode(authUrl);
134
+ console.log("\nOr manually enter this URL:");
135
+ console.log(authUrl);
136
+ console.log("");
137
+ return await waitForAuthentication(keypair);
138
+ }
139
+ async function doWebAuth(keypair) {
140
+ console.clear();
141
+ console.log("\nWeb Authentication\n");
142
+ const webUrl = generateWebAuthUrl(keypair.publicKey);
143
+ console.log("Opening your browser...");
144
+ const browserOpened = await openBrowser(webUrl);
145
+ if (browserOpened) {
146
+ console.log("\u2713 Browser opened\n");
147
+ console.log("Complete authentication in your browser window.");
148
+ } else {
149
+ console.log("Could not open browser automatically.");
150
+ }
151
+ console.log("\nIf the browser did not open, please copy and paste this URL:");
152
+ console.log(webUrl);
153
+ console.log("");
154
+ return await waitForAuthentication(keypair);
155
+ }
156
+ async function waitForAuthentication(keypair) {
157
+ process.stdout.write("Waiting for authentication");
158
+ let dots = 0;
159
+ let cancelled = false;
160
+ const handleInterrupt = () => {
161
+ cancelled = true;
162
+ console.log("\n\nAuthentication cancelled.");
163
+ process.exit(0);
164
+ };
165
+ process.on("SIGINT", handleInterrupt);
166
+ try {
167
+ while (!cancelled) {
168
+ try {
169
+ const response = await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
170
+ publicKey: encodeBase64(keypair.publicKey),
171
+ supportsV2: true
172
+ });
173
+ if (response.data.state === "authorized") {
174
+ let token = response.data.token;
175
+ let r = decodeBase64(response.data.response);
176
+ let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
177
+ if (decrypted) {
178
+ if (decrypted.length === 32) {
179
+ const credentials = {
180
+ secret: decrypted,
181
+ token
182
+ };
183
+ await writeCredentialsLegacy(credentials);
184
+ console.log("\n\n\u2713 Authentication successful\n");
185
+ return {
186
+ encryption: {
187
+ type: "legacy",
188
+ secret: decrypted
189
+ },
190
+ token
191
+ };
192
+ } else {
193
+ if (decrypted[0] === 0) {
194
+ const credentials = {
195
+ publicKey: decrypted.slice(1, 33),
196
+ machineKey: randomBytes(32),
197
+ token
198
+ };
199
+ await writeCredentialsDataKey(credentials);
200
+ console.log("\n\n\u2713 Authentication successful\n");
201
+ return {
202
+ encryption: {
203
+ type: "dataKey",
204
+ publicKey: credentials.publicKey,
205
+ machineKey: credentials.machineKey
206
+ },
207
+ token
208
+ };
209
+ } else {
210
+ console.log("\n\nFailed to decrypt response. Please try again.");
211
+ return null;
212
+ }
213
+ }
214
+ } else {
215
+ console.log("\n\nFailed to decrypt response. Please try again.");
216
+ return null;
217
+ }
218
+ }
219
+ } catch (error) {
220
+ console.log("\n\nFailed to check authentication status. Please try again.");
221
+ return null;
222
+ }
223
+ process.stdout.write("\rWaiting for authentication" + ".".repeat(dots % 3 + 1) + " ");
224
+ dots++;
225
+ await delay(1e3);
226
+ }
227
+ } finally {
228
+ process.off("SIGINT", handleInterrupt);
229
+ }
230
+ return null;
231
+ }
232
+ function decryptWithEphemeralKey(encryptedBundle, recipientSecretKey) {
233
+ const ephemeralPublicKey = encryptedBundle.slice(0, 32);
234
+ const nonce = encryptedBundle.slice(32, 32 + tweetnacl.box.nonceLength);
235
+ const encrypted = encryptedBundle.slice(32 + tweetnacl.box.nonceLength);
236
+ const decrypted = tweetnacl.box.open(encrypted, nonce, ephemeralPublicKey, recipientSecretKey);
237
+ if (!decrypted) {
238
+ return null;
239
+ }
240
+ return decrypted;
241
+ }
242
+ async function authAndSetupMachineIfNeeded() {
243
+ logger.debug("[AUTH] Starting auth and machine setup...");
244
+ let credentials = await readCredentials();
245
+ let newAuth = false;
246
+ if (!credentials) {
247
+ logger.debug("[AUTH] No credentials found, starting authentication flow...");
248
+ const authResult = await doAuth();
249
+ if (!authResult) {
250
+ throw new Error("Authentication failed or was cancelled");
251
+ }
252
+ credentials = authResult;
253
+ newAuth = true;
254
+ } else {
255
+ logger.debug("[AUTH] Using existing credentials");
256
+ }
257
+ const settings = await updateSettings(async (s) => {
258
+ if (newAuth || !s.machineId) {
259
+ return {
260
+ ...s,
261
+ machineId: randomUUID()
262
+ };
263
+ }
264
+ return s;
265
+ });
266
+ logger.debug(`[AUTH] Machine ID: ${settings.machineId}`);
267
+ return { credentials, machineId: settings.machineId };
268
+ }
269
+
270
+ export { authAndSetupMachineIfNeeded, decryptWithEphemeralKey, doAuth };
@@ -0,0 +1,20 @@
1
+ import open from 'open';
2
+ import { l as logger } from './persistence-DKfF81pd.mjs';
3
+
4
+ async function openBrowser(url) {
5
+ try {
6
+ if (!process.stdout.isTTY || process.env.CI || process.env.HEADLESS) {
7
+ logger.debug("[browser] Headless environment detected, skipping browser open");
8
+ return false;
9
+ }
10
+ logger.debug(`[browser] Attempting to open URL: ${url}`);
11
+ await open(url);
12
+ logger.debug("[browser] Browser opened successfully");
13
+ return true;
14
+ } catch (error) {
15
+ logger.debug("[browser] Failed to open browser:", error);
16
+ return false;
17
+ }
18
+ }
19
+
20
+ export { openBrowser as o };
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ var open = require('open');
4
+ var logger = require('./persistence-CBTtC_dm.cjs');
5
+
6
+ async function openBrowser(url) {
7
+ try {
8
+ if (!process.stdout.isTTY || process.env.CI || process.env.HEADLESS) {
9
+ logger.logger.debug("[browser] Headless environment detected, skipping browser open");
10
+ return false;
11
+ }
12
+ logger.logger.debug(`[browser] Attempting to open URL: ${url}`);
13
+ await open(url);
14
+ logger.logger.debug("[browser] Browser opened successfully");
15
+ return true;
16
+ } catch (error) {
17
+ logger.logger.debug("[browser] Failed to open browser:", error);
18
+ return false;
19
+ }
20
+ }
21
+
22
+ exports.openBrowser = openBrowser;
@@ -0,0 +1,352 @@
1
+ 'use strict';
2
+
3
+ var node_child_process = require('node:child_process');
4
+ var path = require('node:path');
5
+ var node_readline = require('node:readline');
6
+ var fs = require('node:fs');
7
+ var node_crypto = require('node:crypto');
8
+ var logger = require('./persistence-CBTtC_dm.cjs');
9
+ var os = require('node:os');
10
+ var projectPath = require('./projectPath-ChLhdOyt.cjs');
11
+ var prompt = require('./prompt-BTWDZZO0.cjs');
12
+
13
+ function getProjectPath(workingDirectory) {
14
+ const projectId = path.resolve(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
15
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
16
+ return path.join(claudeConfigDir, "projects", projectId);
17
+ }
18
+
19
+ function claudeCheckSession(sessionId, path$1) {
20
+ const projectDir = getProjectPath(path$1);
21
+ const sessionFile = path.join(projectDir, `${sessionId}.jsonl`);
22
+ const sessionExists = fs.existsSync(sessionFile);
23
+ if (!sessionExists) {
24
+ logger.logger.debug(`[claudeCheckSession] Path ${sessionFile} does not exist`);
25
+ return false;
26
+ }
27
+ const sessionData = fs.readFileSync(sessionFile, "utf-8").split("\n");
28
+ const hasGoodMessage = !!sessionData.find((v, index) => {
29
+ if (!v.trim()) return false;
30
+ try {
31
+ const parsed = JSON.parse(v);
32
+ return typeof parsed.uuid === "string" && parsed.uuid.length > 0 || // Claude Code 2.1.x
33
+ typeof parsed.messageId === "string" && parsed.messageId.length > 0 || // Older Claude Code
34
+ typeof parsed.leafUuid === "string" && parsed.leafUuid.length > 0;
35
+ } catch (e) {
36
+ logger.logger.debug(`[claudeCheckSession] Malformed JSON at line ${index + 1}:`, e);
37
+ return false;
38
+ }
39
+ });
40
+ logger.logger.debug(`[claudeCheckSession] Session ${sessionId}: ${hasGoodMessage ? "valid" : "invalid"}`);
41
+ return hasGoodMessage;
42
+ }
43
+
44
+ function claudeFindLastSession(workingDirectory) {
45
+ try {
46
+ const projectDir = getProjectPath(workingDirectory);
47
+ const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
48
+ const files = fs.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => {
49
+ const sessionId = f.replace(".jsonl", "");
50
+ if (!uuidPattern.test(sessionId)) {
51
+ return null;
52
+ }
53
+ if (claudeCheckSession(sessionId, workingDirectory)) {
54
+ return {
55
+ name: f,
56
+ sessionId,
57
+ mtime: fs.statSync(path.join(projectDir, f)).mtime.getTime()
58
+ };
59
+ }
60
+ return null;
61
+ }).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime);
62
+ return files.length > 0 ? files[0].sessionId : null;
63
+ } catch (e) {
64
+ logger.logger.debug("[claudeFindLastSession] Error finding sessions:", e);
65
+ return null;
66
+ }
67
+ }
68
+
69
+ const DEFAULT_COMMIT_ATTRIBUTION = [
70
+ "Generated with [Claude Code](https://claude.ai/code)",
71
+ "",
72
+ "Co-Authored-By: Claude <noreply@anthropic.com>"
73
+ ].join("\n");
74
+ function getClaudeSettingsPath() {
75
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
76
+ return path.join(claudeConfigDir, "settings.json");
77
+ }
78
+ function readClaudeSettings() {
79
+ try {
80
+ const settingsPath = getClaudeSettingsPath();
81
+ if (!fs.existsSync(settingsPath)) {
82
+ logger.logger.debug(`[ClaudeSettings] No Claude settings file found at ${settingsPath}`);
83
+ return null;
84
+ }
85
+ const settingsContent = fs.readFileSync(settingsPath, "utf-8");
86
+ const settings = JSON.parse(settingsContent);
87
+ logger.logger.debug(`[ClaudeSettings] Successfully read Claude settings from ${settingsPath}`);
88
+ logger.logger.debug(`[ClaudeSettings] attribution: ${JSON.stringify(settings.attribution)}, includeCoAuthoredBy: ${settings.includeCoAuthoredBy}`);
89
+ return settings;
90
+ } catch (error) {
91
+ logger.logger.debug(`[ClaudeSettings] Error reading Claude settings: ${error}`);
92
+ return null;
93
+ }
94
+ }
95
+ function getCommitAttribution() {
96
+ const settings = readClaudeSettings();
97
+ if (!settings) return null;
98
+ if (settings.attribution?.commit !== void 0) {
99
+ return settings.attribution.commit || null;
100
+ }
101
+ if (settings.includeCoAuthoredBy === true) {
102
+ return DEFAULT_COMMIT_ATTRIBUTION;
103
+ }
104
+ return null;
105
+ }
106
+
107
+ const systemPrompt = (() => {
108
+ const base = prompt.getBaseSystemPrompt();
109
+ if (base === null) return "";
110
+ const attribution = getCommitAttribution();
111
+ if (!attribution) return base;
112
+ return base + "\n\n# Commit\n\nWhen making commit messages, add this footer:\n\n" + attribution;
113
+ })();
114
+
115
+ class ExitCodeError extends Error {
116
+ exitCode;
117
+ constructor(exitCode) {
118
+ super(`Process exited with code: ${exitCode}`);
119
+ this.name = "ExitCodeError";
120
+ this.exitCode = exitCode;
121
+ }
122
+ }
123
+ const claudeCliPath = path.resolve(path.join(projectPath.projectPath(), "scripts", "claude_local_launcher.cjs"));
124
+ async function claudeLocal(opts) {
125
+ const projectDir = getProjectPath(opts.path);
126
+ fs.mkdirSync(projectDir, { recursive: true });
127
+ const hasContinueFlag = opts.claudeArgs?.includes("--continue");
128
+ const hasResumeFlag = opts.claudeArgs?.includes("--resume");
129
+ const hasUserSessionControl = hasContinueFlag || hasResumeFlag;
130
+ let startFrom = opts.sessionId;
131
+ const extractFlag = (flags, withValue = false) => {
132
+ if (!opts.claudeArgs) return { found: false };
133
+ for (const flag of flags) {
134
+ const index = opts.claudeArgs.indexOf(flag);
135
+ if (index !== -1) {
136
+ if (withValue && index + 1 < opts.claudeArgs.length) {
137
+ const nextArg = opts.claudeArgs[index + 1];
138
+ if (!nextArg.startsWith("-")) {
139
+ const value = nextArg;
140
+ opts.claudeArgs = opts.claudeArgs.filter((_, i) => i !== index && i !== index + 1);
141
+ return { found: true, value };
142
+ }
143
+ }
144
+ if (!withValue) {
145
+ opts.claudeArgs = opts.claudeArgs.filter((_, i) => i !== index);
146
+ return { found: true };
147
+ }
148
+ return { found: false };
149
+ }
150
+ }
151
+ return { found: false };
152
+ };
153
+ const sessionIdFlag = extractFlag(["--session-id"], true);
154
+ if (sessionIdFlag.found && sessionIdFlag.value) {
155
+ startFrom = null;
156
+ logger.logger.debug(`[ClaudeLocal] Using explicit --session-id: ${sessionIdFlag.value}`);
157
+ }
158
+ if (!startFrom && !sessionIdFlag.value) {
159
+ const resumeFlag = extractFlag(["--resume", "-r"], true);
160
+ if (resumeFlag.found) {
161
+ if (resumeFlag.value) {
162
+ startFrom = resumeFlag.value;
163
+ logger.logger.debug(`[ClaudeLocal] Using provided session ID from --resume: ${startFrom}`);
164
+ } else {
165
+ const lastSession = claudeFindLastSession(opts.path);
166
+ if (lastSession) {
167
+ startFrom = lastSession;
168
+ logger.logger.debug(`[ClaudeLocal] --resume: Found last session: ${lastSession}`);
169
+ }
170
+ }
171
+ }
172
+ }
173
+ if (!startFrom && !sessionIdFlag.value) {
174
+ const continueFlag = extractFlag(["--continue", "-c"], false);
175
+ if (continueFlag.found) {
176
+ const lastSession = claudeFindLastSession(opts.path);
177
+ if (lastSession) {
178
+ startFrom = lastSession;
179
+ logger.logger.debug(`[ClaudeLocal] --continue: Found last session: ${lastSession}`);
180
+ }
181
+ }
182
+ }
183
+ const explicitSessionId = sessionIdFlag.value || null;
184
+ let newSessionId = null;
185
+ let effectiveSessionId = startFrom;
186
+ if (!opts.hookSettingsPath) {
187
+ newSessionId = startFrom ? null : explicitSessionId || node_crypto.randomUUID();
188
+ effectiveSessionId = startFrom || newSessionId;
189
+ if (startFrom) {
190
+ logger.logger.debug(`[ClaudeLocal] Resuming session: ${startFrom}`);
191
+ opts.onSessionFound(startFrom);
192
+ } else if (explicitSessionId) {
193
+ logger.logger.debug(`[ClaudeLocal] Using explicit session ID: ${explicitSessionId}`);
194
+ opts.onSessionFound(explicitSessionId);
195
+ } else {
196
+ logger.logger.debug(`[ClaudeLocal] Generated new session ID: ${newSessionId}`);
197
+ opts.onSessionFound(newSessionId);
198
+ }
199
+ } else {
200
+ if (startFrom) {
201
+ logger.logger.debug(`[ClaudeLocal] Will resume existing session: ${startFrom}`);
202
+ } else if (hasUserSessionControl) {
203
+ logger.logger.debug(`[ClaudeLocal] User passed ${hasContinueFlag ? "--continue" : "--resume"} flag, session ID will be determined by hook`);
204
+ } else {
205
+ logger.logger.debug(`[ClaudeLocal] Fresh start, session ID will be provided by hook`);
206
+ }
207
+ }
208
+ let thinking = false;
209
+ let stopThinkingTimeout = null;
210
+ const updateThinking = (newThinking) => {
211
+ if (thinking !== newThinking) {
212
+ thinking = newThinking;
213
+ logger.logger.debug(`[ClaudeLocal] Thinking state changed to: ${thinking}`);
214
+ if (opts.onThinkingChange) {
215
+ opts.onThinkingChange(thinking);
216
+ }
217
+ }
218
+ };
219
+ try {
220
+ process.stdin.pause();
221
+ await new Promise((r, reject) => {
222
+ const args = [];
223
+ if (!opts.hookSettingsPath) {
224
+ const hasResumeFlag2 = opts.claudeArgs?.includes("--resume") || opts.claudeArgs?.includes("-r");
225
+ if (startFrom) {
226
+ args.push("--resume", startFrom);
227
+ } else if (!hasResumeFlag2 && newSessionId) {
228
+ args.push("--session-id", newSessionId);
229
+ }
230
+ } else {
231
+ if (startFrom) {
232
+ args.push("--resume", startFrom);
233
+ }
234
+ }
235
+ args.push("--append-system-prompt", systemPrompt);
236
+ if (opts.mcpServers && Object.keys(opts.mcpServers).length > 0) {
237
+ args.push("--mcp-config", JSON.stringify({ mcpServers: opts.mcpServers }));
238
+ }
239
+ if (opts.allowedTools && opts.allowedTools.length > 0) {
240
+ args.push("--allowedTools", opts.allowedTools.join(","));
241
+ }
242
+ if (opts.claudeArgs) {
243
+ args.push(...opts.claudeArgs);
244
+ }
245
+ if (opts.hookSettingsPath) {
246
+ args.push("--settings", opts.hookSettingsPath);
247
+ logger.logger.debug(`[ClaudeLocal] Using hook settings: ${opts.hookSettingsPath}`);
248
+ }
249
+ if (!claudeCliPath || !fs.existsSync(claudeCliPath)) {
250
+ throw new Error("Claude local launcher not found. Please ensure HAPPY_PROJECT_ROOT is set correctly for development.");
251
+ }
252
+ const env = {
253
+ ...process.env,
254
+ ...opts.claudeEnvVars
255
+ };
256
+ logger.logger.debug(`[ClaudeLocal] Spawning launcher: ${claudeCliPath}`);
257
+ logger.logger.debug(`[ClaudeLocal] Args: ${JSON.stringify(args)}`);
258
+ const child = node_child_process.spawn("node", [claudeCliPath, ...args], {
259
+ stdio: ["inherit", "inherit", "inherit", "pipe"],
260
+ signal: opts.abort,
261
+ cwd: opts.path,
262
+ env
263
+ });
264
+ if (child.stdio[3]) {
265
+ const rl = node_readline.createInterface({
266
+ input: child.stdio[3],
267
+ crlfDelay: Infinity
268
+ });
269
+ const activeFetches = /* @__PURE__ */ new Map();
270
+ rl.on("line", (line) => {
271
+ try {
272
+ const message = JSON.parse(line);
273
+ switch (message.type) {
274
+ case "fetch-start":
275
+ activeFetches.set(message.id, {
276
+ hostname: message.hostname,
277
+ path: message.path,
278
+ startTime: message.timestamp
279
+ });
280
+ if (stopThinkingTimeout) {
281
+ clearTimeout(stopThinkingTimeout);
282
+ stopThinkingTimeout = null;
283
+ }
284
+ updateThinking(true);
285
+ break;
286
+ case "fetch-end":
287
+ activeFetches.delete(message.id);
288
+ if (activeFetches.size === 0 && thinking && !stopThinkingTimeout) {
289
+ stopThinkingTimeout = setTimeout(() => {
290
+ if (activeFetches.size === 0) {
291
+ updateThinking(false);
292
+ }
293
+ stopThinkingTimeout = null;
294
+ }, 500);
295
+ }
296
+ break;
297
+ default:
298
+ logger.logger.debug(`[ClaudeLocal] Unknown message type: ${message.type}`);
299
+ }
300
+ } catch (e) {
301
+ logger.logger.debug(`[ClaudeLocal] Non-JSON line from fd3: ${line}`);
302
+ }
303
+ });
304
+ rl.on("error", (err) => {
305
+ console.error("Error reading from fd 3:", err);
306
+ });
307
+ child.on("exit", () => {
308
+ if (stopThinkingTimeout) {
309
+ clearTimeout(stopThinkingTimeout);
310
+ }
311
+ updateThinking(false);
312
+ });
313
+ }
314
+ child.on("error", (error) => {
315
+ });
316
+ child.on("exit", (code, signal) => {
317
+ if (signal === "SIGTERM" && opts.abort.aborted) {
318
+ r();
319
+ } else if (signal) {
320
+ reject(new Error(`Process terminated with signal: ${signal}`));
321
+ } else if (code !== 0 && code !== null) {
322
+ reject(new ExitCodeError(code));
323
+ } else {
324
+ r();
325
+ }
326
+ });
327
+ });
328
+ } finally {
329
+ process.stdin.resume();
330
+ if (stopThinkingTimeout) {
331
+ clearTimeout(stopThinkingTimeout);
332
+ stopThinkingTimeout = null;
333
+ }
334
+ updateThinking(false);
335
+ }
336
+ return effectiveSessionId;
337
+ }
338
+
339
+ var claudeLocal$1 = /*#__PURE__*/Object.freeze({
340
+ __proto__: null,
341
+ ExitCodeError: ExitCodeError,
342
+ claudeCliPath: claudeCliPath,
343
+ claudeLocal: claudeLocal
344
+ });
345
+
346
+ exports.ExitCodeError = ExitCodeError;
347
+ exports.claudeCheckSession = claudeCheckSession;
348
+ exports.claudeCliPath = claudeCliPath;
349
+ exports.claudeLocal = claudeLocal;
350
+ exports.claudeLocal$1 = claudeLocal$1;
351
+ exports.getProjectPath = getProjectPath;
352
+ exports.systemPrompt = systemPrompt;