very-happy-cli 0.2.159 → 0.2.161
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/{AcpBackend-VsfkZiVF.mjs → AcpBackend-6U6pQgx1.mjs} +4 -2
- package/dist/{AcpBackend-DUYFlyfN.cjs → AcpBackend-Ba98QDif.cjs} +78 -76
- package/dist/{AcpSessionManager-Bdv8LWXh.cjs → AcpSessionManager-BAY1Pggx.cjs} +9 -9
- package/dist/{AcpSessionManager-rYtVb0kN.mjs → AcpSessionManager-CoMWFVw3.mjs} +1 -1
- package/dist/BasePermissionHandler-2R0uH2CI.cjs +157 -0
- package/dist/BasePermissionHandler-BvvhBTja.mjs +155 -0
- package/dist/BaseReasoningProcessor-DyhO1uoU.mjs +198 -0
- package/dist/BaseReasoningProcessor-fsVNmCEF.cjs +200 -0
- package/dist/RemoteModeDisplay-D-HewyXg.mjs +150 -0
- package/dist/RemoteModeDisplay-NC1gPHtj.cjs +153 -0
- package/dist/agentAttachments-Dbw8iMCZ.cjs +20 -0
- package/dist/agentAttachments-M_B5qEF2.mjs +18 -0
- package/dist/agentGuidance-B5NATuFa.cjs +28 -0
- package/dist/agentGuidance-CQuNlNx8.mjs +20 -0
- package/dist/{agentHome-CvsJyeQB.mjs → agentHome-Ck1i1TQB.mjs} +8 -22
- package/dist/{agentHome-CHh4lmjM.cjs → agentHome-DvNnGphb.cjs} +14 -28
- package/dist/api-B2vLMS-z.cjs +3346 -0
- package/dist/api-SffjRQ1b.mjs +3339 -0
- package/dist/{types-Dh-s4Lqs.mjs → apiMachine-BpW9t8MO.mjs} +9116 -16205
- package/dist/{types-BbvK1Tr_.cjs → apiMachine-gFA54X7y.cjs} +9104 -16247
- package/dist/assistantTools-BfA0R3_m.cjs +380 -0
- package/dist/assistantTools-CgiTbnWb.mjs +376 -0
- package/dist/attachmentContent-CSGxr71v.mjs +82 -0
- package/dist/attachmentContent-DMMZzevR.cjs +88 -0
- package/dist/attachmentLimits-CzVrqgLR.cjs +354 -0
- package/dist/attachmentLimits-yba1uHFO.mjs +348 -0
- package/dist/auth-Bt4ceBBy.cjs +227 -0
- package/dist/auth-CYHjQ8oQ.mjs +225 -0
- package/dist/auth-DdyVZSo6.mjs +218 -0
- package/dist/auth-x4CEoysH.cjs +220 -0
- package/dist/authRelay-BdYkC1Po.cjs +156 -0
- package/dist/authRelay-DSAvg1uv.mjs +143 -0
- package/dist/{auto-DKmEls8Z.cjs → auto-BDXDUnjB.cjs} +70 -79
- package/dist/{auto-nb8GhRRy.mjs → auto-CXLlRi3a.mjs} +27 -36
- package/dist/backgroundTaskActivity-8PdPxrvy.mjs +149 -0
- package/dist/backgroundTaskActivity-CBvUdj39.cjs +157 -0
- package/dist/bootstrap-BNQq05jR.mjs +112 -0
- package/dist/bootstrap-CkgIq8N3.cjs +116 -0
- package/dist/browser-9pyhQr1R.mjs +20 -0
- package/dist/browser-BDOlKWYe.cjs +22 -0
- package/dist/claudeCliPath-BqkaH2oR.mjs +8 -0
- package/dist/claudeCliPath-U4HHmV4r.cjs +10 -0
- package/dist/claudeLocal-BDuGFnjJ.cjs +343 -0
- package/dist/claudeLocal-CQa8qnwq.mjs +339 -0
- package/dist/claudeLocalLauncher-Bd5I2-j6.cjs +154 -0
- package/dist/claudeLocalLauncher-CDqQI9ak.mjs +152 -0
- package/dist/cliUpdate-DsKXPFYp.cjs +120 -0
- package/dist/cliUpdate-Y34VGe9Y.mjs +112 -0
- package/dist/client-Brn-UwVk.mjs +100 -0
- package/dist/client-CXb1VJui.mjs +112 -0
- package/dist/client-C_axwRry.cjs +104 -0
- package/dist/client-DOBWJL3u.cjs +119 -0
- package/dist/codex/happyMcpStdioBridge.cjs +33 -14
- package/dist/codex/happyMcpStdioBridge.mjs +26 -7
- package/dist/codexAppServerClient-ZzhUTWPo.cjs +1090 -0
- package/dist/codexAppServerClient-oeQKD6Qf.mjs +1088 -0
- package/dist/codexCommand-C7H0NpBe.cjs +2088 -0
- package/dist/codexCommand-CJ82bl3l.mjs +2086 -0
- package/dist/codexThreadFork-BCaEMBiM.mjs +338 -0
- package/dist/codexThreadFork-BZqF-9bX.cjs +350 -0
- package/dist/{config-ClLtM6F3.mjs → config-BglnktIo.mjs} +7 -40
- package/dist/{config-DLx-vW3I.cjs → config-CYpL1Ahd.cjs} +26 -59
- package/dist/configuration-DoWFJUPm.mjs +170 -0
- package/dist/configuration-v9f8LM9T.cjs +181 -0
- package/dist/connect-hgcNjyAB.mjs +682 -0
- package/dist/connect-yEvjdRvx.cjs +684 -0
- package/dist/constants-BC4St0xd.mjs +11 -0
- package/dist/constants-VJ0N6WuG.cjs +17 -0
- package/dist/contentLogMetadata-BlWI0egu.mjs +51 -0
- package/dist/contentLogMetadata-ZSFk_NjQ.cjs +54 -0
- package/dist/context-B45vMtSZ.cjs +69 -0
- package/dist/context-C9GHPLEm.mjs +65 -0
- package/dist/controlClient-DjoKTtgK.mjs +260 -0
- package/dist/controlClient-fEHE85bH.cjs +279 -0
- package/dist/createSessionMetadata-BRLB9CNO.cjs +58 -0
- package/dist/createSessionMetadata-mIeehclp.mjs +54 -0
- package/dist/defaultPermissionMode-BnxJPVHT.mjs +4 -0
- package/dist/defaultPermissionMode-CxmTXUQR.cjs +7 -0
- package/dist/detectCLI-CSGMlV51.mjs +58 -0
- package/dist/detectCLI-DR9ZaN6B.cjs +61 -0
- package/dist/deterministicJson-DKDP_mM-.cjs +507 -0
- package/dist/deterministicJson-p835AdAU.mjs +502 -0
- package/dist/doctor-BPkYWSuq.mjs +75 -0
- package/dist/doctor-BeUuQXFs.mjs +391 -0
- package/dist/doctor-hVNnSZJn.cjs +395 -0
- package/dist/doctor-u46qYYKX.cjs +79 -0
- package/dist/doctorReadiness-BCZGcN0a.cjs +151 -0
- package/dist/doctorReadiness-C208SMN9.mjs +142 -0
- package/dist/editPaths-BVfS_FFL.mjs +68 -0
- package/dist/editPaths-Bqp4OFPw.cjs +73 -0
- package/dist/ensureDaemonRunning-CO7d1gOC.mjs +61 -0
- package/dist/ensureDaemonRunning-Dg9aZfjs.cjs +64 -0
- package/dist/handleResumeCommand-BiTPK0qd.mjs +240 -0
- package/dist/handleResumeCommand-DnVmy7a3.cjs +245 -0
- package/dist/ids-B6ZC0HGt.cjs +13 -0
- package/dist/ids-DU6sLx4C.mjs +10 -0
- package/dist/index-B6UGRQ7h.mjs +102 -0
- package/dist/{index-2mCaeKyf.mjs → index-CAFwetLk.mjs} +68 -38
- package/dist/index-Cqx-Raq6.mjs +68 -0
- package/dist/index-D3kI0hDR.cjs +107 -0
- package/dist/index-DA7yINuk.cjs +994 -0
- package/dist/{index-BEOMECtE.cjs → index-LIn6nCcq.cjs} +121 -91
- package/dist/index-Pf2cV9cn.cjs +182 -0
- package/dist/index-SPGPenK5.mjs +158 -0
- package/dist/index-gNTWtSKN.cjs +71 -0
- package/dist/index-qdS1friN.mjs +949 -0
- package/dist/index.cjs +5 -46
- package/dist/index.mjs +5 -46
- package/dist/{install-BPhJppvd.mjs → install-BO_W7VhL.mjs} +37 -37
- package/dist/install-Bu85vlHD.cjs +88 -0
- package/dist/install-ClUUl3yp.mjs +86 -0
- package/dist/{install-BOHrUaFM.cjs → install-Cs5qAPrB.cjs} +38 -38
- package/dist/{installPiTools-DuZ6jZvX.cjs → installPiTools-BKnXjrVu.cjs} +2 -3
- package/dist/{installPiTools-Cj7yU9Fc.mjs → installPiTools-B_CdM5_2.mjs} +1 -2
- package/dist/{installTerminalHooks-Cou3WUf3.cjs → installTerminalHooks-BQZbvxsZ.cjs} +5 -40
- package/dist/{installTerminalHooks-WeznjkuC.mjs → installTerminalHooks-By7gBMEF.mjs} +3 -38
- package/dist/lib.cjs +28 -19
- package/dist/lib.d.cts +12 -2
- package/dist/lib.d.mts +12 -2
- package/dist/lib.mjs +23 -14
- package/dist/lidState-C2dI0FAN.mjs +297 -0
- package/dist/lidState-KHHu1015.cjs +305 -0
- package/dist/limits-C-a7gr04.cjs +43 -0
- package/dist/limits-DIo7rEBR.mjs +36 -0
- package/dist/localHappyAgentAuth-BXiHPXdC.cjs +86 -0
- package/dist/localHappyAgentAuth-DCsetiyG.mjs +82 -0
- package/dist/logger-Bc8-u1w0.cjs +275 -0
- package/dist/logger-fXoNzNuB.mjs +271 -0
- package/dist/loginShellEnv-BkuYtExZ.cjs +182 -0
- package/dist/loginShellEnv-Dw8M7tkI.mjs +167 -0
- package/dist/machineMetadata-BHjehxJY.mjs +38 -0
- package/dist/machineMetadata-BibDMFVx.cjs +41 -0
- package/dist/main-BktM4A1K.mjs +939 -0
- package/dist/main-CLaX-t-b.cjs +941 -0
- package/dist/manager-BQ4flvQZ.mjs +101 -0
- package/dist/manager-Cly8xVIR.cjs +105 -0
- package/dist/{mcp-Dy3f_K0h.mjs → mcp-CeUd9KDr.mjs} +27 -33
- package/dist/{mcp-CPYbtsYG.cjs → mcp-GBfE70nk.cjs} +42 -48
- package/dist/messageBuffer-BWlGgR3A.mjs +71 -0
- package/dist/messageBuffer-fJMH6uY8.cjs +73 -0
- package/dist/mirrorProtocol-BNLtoWjO.mjs +170 -0
- package/dist/mirrorProtocol-BUtmg1ws.cjs +186 -0
- package/dist/oneShotPrompts-CL0dip9C.cjs +16 -0
- package/dist/oneShotPrompts-DT4p6RNd.mjs +12 -0
- package/dist/package-Cl5BEP5x.cjs +173 -0
- package/dist/package-uOg9SCKi.mjs +171 -0
- package/dist/path-B7zW-WwE.cjs +18 -0
- package/dist/path-D2wdaNSk.mjs +15 -0
- package/dist/peerTools-CIeiy9wn.cjs +283 -0
- package/dist/peerTools-Dz3_7Bkk.mjs +272 -0
- package/dist/permissionMode-CH4FoZfU.mjs +80 -0
- package/dist/permissionMode-CrV-XuMs.cjs +87 -0
- package/dist/persistence-CBNMzSa8.cjs +414 -0
- package/dist/persistence-oET5n61f.mjs +372 -0
- package/dist/piExtension.mjs +2 -1
- package/dist/piRunnerArgs-Bf7RUF__.cjs +66 -0
- package/dist/piRunnerArgs-Dk1ncwHF.mjs +59 -0
- package/dist/{piRuntime-NtF0YqjH.cjs → piRuntime-B_6TE9Eb.cjs} +4 -4
- package/dist/{piRuntime-kLZNpAAl.mjs → piRuntime-BwRdhK_N.mjs} +2 -2
- package/dist/{piTerminal-B0fEOV_2.mjs → piTerminal-Bap3R8Re.mjs} +32 -35
- package/dist/{piTerminal-9J0GAKzb.cjs → piTerminal-CagmEoez.cjs} +32 -35
- package/dist/previewPath-B0rBdhy1.cjs +83 -0
- package/dist/previewPath-CXN4Vag0.mjs +81 -0
- package/dist/projectPath-BTBIMB57.mjs +10 -0
- package/dist/projectPath-ChLhdOyt.cjs +13 -0
- package/dist/registerCommonHandlers-BjSV2Skz.cjs +538 -0
- package/dist/registerCommonHandlers-CrgvJsOy.mjs +536 -0
- package/dist/registerKillSessionHandler-6TC2S3lf.cjs +366 -0
- package/dist/registerKillSessionHandler-Cbc8Rgtd.mjs +363 -0
- package/dist/remoteSessionClient-BNx78NLb.cjs +634 -0
- package/dist/remoteSessionClient-CO7qOjR0.mjs +612 -0
- package/dist/resources-BdFBQi8r.mjs +117 -0
- package/dist/resources-JDPugcDq.cjs +120 -0
- package/dist/run-2l5vSt2h.mjs +5743 -0
- package/dist/run-D5xAEWSB.cjs +5766 -0
- package/dist/runClaude-BX6ixjtW.cjs +894 -0
- package/dist/runClaude-BgN6xo05.mjs +892 -0
- package/dist/runClaudeDeps-Bp20MScE.cjs +4168 -0
- package/dist/runClaudeDeps-COBBhD9o.mjs +4129 -0
- package/dist/{runGemini-F-mqx3_K.cjs → runGemini-CSMPkt0p.cjs} +202 -175
- package/dist/{runGemini-DV6Ouadt.mjs → runGemini-Cc2jq-uh.mjs} +58 -31
- package/dist/{runOpenClaw-D7MlPS5n.mjs → runOpenClaw-CIlCwdW2.mjs} +31 -32
- package/dist/{runOpenClaw-Qqo9j63M.cjs → runOpenClaw-YBwua4Rf.cjs} +64 -65
- package/dist/sandbox-C4gXLP1p.cjs +187 -0
- package/dist/sandbox-CNkwOtHI.mjs +180 -0
- package/dist/selfCheck-CU8xSDsl.cjs +87 -0
- package/dist/selfCheck-hJ6PCh7v.mjs +84 -0
- package/dist/{send-CLhYXNgy.mjs → send-CGHcKZ-w.mjs} +12 -5
- package/dist/{send-4qJTCAYW.cjs → send-DY9Nr-x4.cjs} +16 -9
- package/dist/server-II51Mimf.cjs +407 -0
- package/dist/server-m2b5pamZ.mjs +404 -0
- package/dist/sessionLock-7ePAYDOg.cjs +205 -0
- package/dist/sessionLock-Cw6FmbMv.mjs +197 -0
- package/dist/sessionModeFile-BVCV5f7E.cjs +36 -0
- package/dist/sessionModeFile-CDmDyRkD.mjs +32 -0
- package/dist/sessionOps-C_f1wKOr.mjs +760 -0
- package/dist/sessionOps-DJdExmBy.cjs +783 -0
- package/dist/sessionScanner-CaAHLyNV.mjs +206 -0
- package/dist/sessionScanner-D1goKdd3.cjs +209 -0
- package/dist/{sessions-Bh1pyddi.cjs → sessions-DGs6ALNo.cjs} +37 -28
- package/dist/{sessions-CzIH2rGp.mjs → sessions-DnvmWUel.mjs} +17 -8
- package/dist/setupOfflineReconnection-CVViqZft.mjs +72 -0
- package/dist/setupOfflineReconnection-R7XuR_sj.cjs +74 -0
- package/dist/smallFastModel-BLNgMrwR.mjs +18 -0
- package/dist/smallFastModel-D_uxGMo3.cjs +21 -0
- package/dist/{spawn-CmFOSVdW.cjs → spawn-CXTH7qg1.cjs} +61 -65
- package/dist/{spawn-CPIWlPOM.mjs → spawn-DNYSvFtC.mjs} +35 -40
- package/dist/spawnAgents-BGQiEF-w.cjs +52 -0
- package/dist/spawnAgents-DZzZ9jMI.mjs +45 -0
- package/dist/spawnHappyCLI-BhYj84rv.cjs +66 -0
- package/dist/spawnHappyCLI-cqWQbwsb.mjs +64 -0
- package/dist/specialCommands-BSqhYg5X.mjs +52 -0
- package/dist/specialCommands-DSsTeRej.cjs +54 -0
- package/dist/startFileWatcher-CKKx-8ZO.cjs +71 -0
- package/dist/startFileWatcher-CdegMbJj.mjs +69 -0
- package/dist/startupPlan-BLMbfQIH.cjs +12 -0
- package/dist/startupPlan-DFMyNgAs.mjs +10 -0
- package/dist/streamRelay-BiKw59Cc.cjs +233 -0
- package/dist/streamRelay-FPVbHpbM.mjs +231 -0
- package/dist/sync-CVk4QERO.cjs +71 -0
- package/dist/sync-CxD-w_40.mjs +69 -0
- package/dist/systemPrompt-BygJcmbi.cjs +97 -0
- package/dist/systemPrompt-DmtypUc7.mjs +94 -0
- package/dist/{teams-CEahj4cm.mjs → teams-BOC6nIPL.mjs} +9 -40
- package/dist/{teams-CB1R9-Z9.cjs → teams-BsvpOBfn.cjs} +14 -45
- package/dist/template-BaHAmH2K.cjs +129 -0
- package/dist/template-CiOPa2rd.mjs +121 -0
- package/dist/{terminalTitleSuggest-DKgX3osW.mjs → terminalTitleSuggest-CeKsX_V6.mjs} +1 -9
- package/dist/{terminalTitleSuggest-Dji8I0Jy.cjs → terminalTitleSuggest-DhvRFX3H.cjs} +0 -10
- package/dist/terminals-B_bfC9c2.cjs +250 -0
- package/dist/terminals-Dlg0x3nY.mjs +235 -0
- package/dist/time-BZNdNiIV.mjs +38 -0
- package/dist/time-CNRE4ChD.cjs +42 -0
- package/dist/titleGenerator-CFRhU-xq.cjs +172 -0
- package/dist/titleGenerator-ox6ptCsp.mjs +166 -0
- package/dist/tmuxSocket-B8Yx20sT.mjs +19 -0
- package/dist/tmuxSocket-D-Xt6Vn-.cjs +24 -0
- package/dist/{todo-DqyRI4wP.cjs → todo-B-KB7oAB.cjs} +15 -12
- package/dist/{todo-Cknp5D4U.mjs → todo-C5KBVbUw.mjs} +11 -8
- package/dist/tools-BZabNIid.mjs +177 -0
- package/dist/tools-BmQQUATY.mjs +60 -0
- package/dist/tools-CsSovyNp.cjs +180 -0
- package/dist/tools-lMDra4Pv.cjs +63 -0
- package/dist/trimIdent-4gvk9Wk6.cjs +22 -0
- package/dist/trimIdent-BAjmv_TG.mjs +20 -0
- package/dist/types-CRS-srZK.cjs +66 -0
- package/dist/types-CkqAACXx.mjs +64 -0
- package/dist/uninstall-B1WLtC1d.mjs +47 -0
- package/dist/uninstall-fvA5aWpU.cjs +49 -0
- package/package.json +7 -7
- package/scripts/session_hook_forwarder.cjs +9 -2
- package/dist/agentGuidance-BRDN1NoZ.cjs +0 -2217
- package/dist/agentGuidance-KgvYuF3R.mjs +0 -2145
- package/dist/index-BF6WfgV5.mjs +0 -19624
- package/dist/index-C-JazrMB.mjs +0 -52
- package/dist/index-blDSadUR.cjs +0 -58
- package/dist/index-rH3DrQ5B.cjs +0 -19688
- package/dist/limits-BH5CV5vR.cjs +0 -2683
- package/dist/limits-CjRj8IX5.mjs +0 -2558
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { c as commandOnPath } from './detectCLI-CSGMlV51.mjs';
|
|
6
|
+
|
|
7
|
+
const SUPPORTED_NODE_LABEL = "20.19+ within 20.x, 22.13+ within 22.x, or 24+";
|
|
8
|
+
const MIN_TMUX_ENV_MAJOR = 3;
|
|
9
|
+
const MIN_TMUX_ENV_MINOR = 2;
|
|
10
|
+
function enabled(value) {
|
|
11
|
+
return ["1", "true", "yes"].includes(value?.trim().toLowerCase() ?? "");
|
|
12
|
+
}
|
|
13
|
+
function resolveClaudeCredentialReadiness(env = process.env, fileExists = existsSync, readText = (path) => readFileSync(path, "utf8"), userHome = homedir()) {
|
|
14
|
+
if (enabled(env.CLAUDE_CODE_USE_BEDROCK)) return { configured: true, source: "Amazon Bedrock" };
|
|
15
|
+
if (enabled(env.CLAUDE_CODE_USE_VERTEX)) return { configured: true, source: "Google Vertex AI" };
|
|
16
|
+
if (enabled(env.CLAUDE_CODE_USE_FOUNDRY)) return { configured: true, source: "Microsoft Foundry" };
|
|
17
|
+
if (env.ANTHROPIC_AUTH_TOKEN?.trim()) return { configured: true, source: "ANTHROPIC_AUTH_TOKEN" };
|
|
18
|
+
if (env.ANTHROPIC_API_KEY?.trim()) return { configured: true, source: "ANTHROPIC_API_KEY" };
|
|
19
|
+
if (env.CLAUDE_CODE_OAUTH_TOKEN?.trim()) return { configured: true, source: "CLAUDE_CODE_OAUTH_TOKEN" };
|
|
20
|
+
const configDir = env.CLAUDE_CONFIG_DIR?.trim() || join(userHome, ".claude");
|
|
21
|
+
const settingsPath = join(configDir, "settings.json");
|
|
22
|
+
try {
|
|
23
|
+
if (fileExists(settingsPath)) {
|
|
24
|
+
const settings = JSON.parse(readText(settingsPath));
|
|
25
|
+
if (typeof settings.apiKeyHelper === "string" && settings.apiKeyHelper.trim()) {
|
|
26
|
+
return { configured: true, source: "Claude apiKeyHelper" };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
if (fileExists(join(configDir, ".credentials.json"))) {
|
|
32
|
+
return { configured: true, source: "Claude local credentials" };
|
|
33
|
+
}
|
|
34
|
+
return { configured: false };
|
|
35
|
+
}
|
|
36
|
+
function toolProbeLabel(tool) {
|
|
37
|
+
return tool.version ? `${tool.command} (${tool.version})` : tool.command;
|
|
38
|
+
}
|
|
39
|
+
function shareableSettingsSummary(value) {
|
|
40
|
+
const settings = value && typeof value === "object" ? value : {};
|
|
41
|
+
const sandbox = settings.sandboxConfig && typeof settings.sandboxConfig === "object" ? settings.sandboxConfig : {};
|
|
42
|
+
return {
|
|
43
|
+
...typeof settings.schemaVersion === "number" ? { schemaVersion: settings.schemaVersion } : {},
|
|
44
|
+
...typeof settings.onboardingCompleted === "boolean" ? { onboardingCompleted: settings.onboardingCompleted } : {},
|
|
45
|
+
machineIdConfigured: typeof settings.machineId === "string" && settings.machineId.length > 0,
|
|
46
|
+
serverUrlConfigured: typeof settings.serverUrl === "string" && settings.serverUrl.length > 0,
|
|
47
|
+
webappUrlConfigured: typeof settings.webappUrl === "string" && settings.webappUrl.length > 0,
|
|
48
|
+
sandboxEnabled: sandbox.enabled === true,
|
|
49
|
+
chromeMode: settings.chromeMode === true,
|
|
50
|
+
boardLlm: settings.boardLlm === true,
|
|
51
|
+
todoProviderConfigured: Boolean(settings.todoProvider),
|
|
52
|
+
terminalAutoRestore: typeof settings.terminalAutoRestore === "boolean" ? settings.terminalAutoRestore : "default"
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function firstUsefulLine(output) {
|
|
56
|
+
if (typeof output !== "string") return void 0;
|
|
57
|
+
const line = output.split(/\r?\n/).map((value) => value.trim()).find(Boolean);
|
|
58
|
+
if (!line) return void 0;
|
|
59
|
+
return Array.from(line).slice(0, 160).join("");
|
|
60
|
+
}
|
|
61
|
+
function nodeSupported(version) {
|
|
62
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
|
|
63
|
+
if (!match) return false;
|
|
64
|
+
const [major, minor, patch] = match.slice(1).map(Number);
|
|
65
|
+
if (major === 20) return minor >= 19;
|
|
66
|
+
if (major === 22) return minor >= 13;
|
|
67
|
+
return major >= 24;
|
|
68
|
+
}
|
|
69
|
+
function normalizedEndpoint(value) {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = new URL(value);
|
|
72
|
+
return `${parsed.origin}${parsed.pathname.replace(/\/+$/, "")}`;
|
|
73
|
+
} catch {
|
|
74
|
+
return value.replace(/\/+$/, "");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function daemonEndpointsMatch(stateServerUrl, stateWebappUrl, configuredServerUrl, configuredWebappUrl) {
|
|
78
|
+
return Boolean(
|
|
79
|
+
stateServerUrl && stateWebappUrl && normalizedEndpoint(stateServerUrl) === normalizedEndpoint(configuredServerUrl) && normalizedEndpoint(stateWebappUrl) === normalizedEndpoint(configuredWebappUrl)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
function tmuxSupportsSessionEnv(version) {
|
|
83
|
+
if (!version) return false;
|
|
84
|
+
if (/master/i.test(version)) return true;
|
|
85
|
+
const match = /(\d+)\.(\d+)/.exec(version);
|
|
86
|
+
if (!match) return false;
|
|
87
|
+
const major = Number(match[1]);
|
|
88
|
+
const minor = Number(match[2]);
|
|
89
|
+
return major > MIN_TMUX_ENV_MAJOR || major === MIN_TMUX_ENV_MAJOR && minor >= MIN_TMUX_ENV_MINOR;
|
|
90
|
+
}
|
|
91
|
+
function probe(command, run) {
|
|
92
|
+
try {
|
|
93
|
+
const result = run(command, command === "tmux" ? ["-V"] : ["--version"], {
|
|
94
|
+
encoding: "utf8",
|
|
95
|
+
timeout: 2e3,
|
|
96
|
+
windowsHide: true
|
|
97
|
+
});
|
|
98
|
+
const version = firstUsefulLine(result.stdout) ?? firstUsefulLine(result.stderr);
|
|
99
|
+
return {
|
|
100
|
+
command,
|
|
101
|
+
available: !result.error && result.status === 0,
|
|
102
|
+
...version ? { version } : {}
|
|
103
|
+
};
|
|
104
|
+
} catch {
|
|
105
|
+
return { command, available: false };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function daemonReadiness(authenticated, running, hasState) {
|
|
109
|
+
if (running && hasState) {
|
|
110
|
+
return { level: "ready", message: "\u2713 Daemon is running" };
|
|
111
|
+
}
|
|
112
|
+
if (hasState) {
|
|
113
|
+
return { level: "warning", message: "\u26A0\uFE0F Daemon state exists but process not running (stale)" };
|
|
114
|
+
}
|
|
115
|
+
if (!authenticated) {
|
|
116
|
+
return {
|
|
117
|
+
level: "next",
|
|
118
|
+
message: "\u25CB Daemon not started yet \u2014 pair this machine, then run `very-happy daemon start`"
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
level: "warning",
|
|
123
|
+
message: "\u26A0\uFE0F Daemon is not running \u2014 run `very-happy daemon start`"
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function collectRuntimeReadiness(run = spawnSync, currentNodeVersion = process.version, onPath = commandOnPath) {
|
|
127
|
+
const tmux = probe("tmux", run);
|
|
128
|
+
return {
|
|
129
|
+
node: {
|
|
130
|
+
version: currentNodeVersion,
|
|
131
|
+
supported: nodeSupported(currentNodeVersion)
|
|
132
|
+
},
|
|
133
|
+
tmux: {
|
|
134
|
+
...tmux,
|
|
135
|
+
supportsSessionEnv: tmux.available && tmuxSupportsSessionEnv(tmux.version)
|
|
136
|
+
},
|
|
137
|
+
agents: ["claude", "codex", "gemini", "opencode", "openclaw", "pi"].map((command) => probe(command, run)),
|
|
138
|
+
piAdapter: { available: onPath("pi-acp") }
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { SUPPORTED_NODE_LABEL as S, toolProbeLabel as a, daemonReadiness as b, collectRuntimeReadiness as c, daemonEndpointsMatch as d, resolveClaudeCredentialReadiness as r, shareableSettingsSummary as s, tmuxSupportsSessionEnv as t };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const CLAUDE_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
2
|
+
function pathOf(input) {
|
|
3
|
+
if (!input || typeof input !== "object") return null;
|
|
4
|
+
const record = input;
|
|
5
|
+
const candidate = record.file_path ?? record.notebook_path;
|
|
6
|
+
return typeof candidate === "string" && candidate.trim() !== "" ? candidate : null;
|
|
7
|
+
}
|
|
8
|
+
function extractClaudeEditPaths(body) {
|
|
9
|
+
if (!body || typeof body !== "object") return [];
|
|
10
|
+
const line = body;
|
|
11
|
+
if (line.type !== "assistant") return [];
|
|
12
|
+
const content = line.message?.content;
|
|
13
|
+
if (!Array.isArray(content)) return [];
|
|
14
|
+
const stamp = line.timestamp;
|
|
15
|
+
const at = typeof stamp === "string" ? Date.parse(stamp) : typeof stamp === "number" ? stamp : NaN;
|
|
16
|
+
const edits = [];
|
|
17
|
+
for (const block of content) {
|
|
18
|
+
if (!block || typeof block !== "object") continue;
|
|
19
|
+
const b = block;
|
|
20
|
+
if (b.type !== "tool_use" || typeof b.name !== "string" || !CLAUDE_EDIT_TOOLS.has(b.name)) continue;
|
|
21
|
+
const path = pathOf(b.input);
|
|
22
|
+
if (path) edits.push({ path, tool: b.name, ...Number.isFinite(at) ? { at } : {} });
|
|
23
|
+
}
|
|
24
|
+
return edits;
|
|
25
|
+
}
|
|
26
|
+
function extractCodexPatchPaths(changes) {
|
|
27
|
+
if (!changes || typeof changes !== "object" || Array.isArray(changes)) return [];
|
|
28
|
+
return Object.keys(changes).filter((path) => path.trim() !== "").map((path) => ({ path, tool: "CodexPatch" }));
|
|
29
|
+
}
|
|
30
|
+
function extractAcpEditPaths(toolKind, args) {
|
|
31
|
+
if (!args || typeof args !== "object") return [];
|
|
32
|
+
const a = args;
|
|
33
|
+
if (a.piTool === "write" || a.piTool === "edit") {
|
|
34
|
+
const raw = a.rawInput;
|
|
35
|
+
const path = raw && typeof raw.path === "string" && raw.path.trim() !== "" ? raw.path : null;
|
|
36
|
+
return path ? [{ path, tool: a.piTool }] : [];
|
|
37
|
+
}
|
|
38
|
+
if (typeof a.piTool === "string") return [];
|
|
39
|
+
if (toolKind !== "edit" || !Array.isArray(a.locations)) return [];
|
|
40
|
+
const edits = [];
|
|
41
|
+
for (const location of a.locations) {
|
|
42
|
+
const path = location && typeof location === "object" ? location.path : void 0;
|
|
43
|
+
if (typeof path === "string" && path.trim() !== "" && !edits.some((e) => e.path === path)) edits.push({ path, tool: "edit" });
|
|
44
|
+
}
|
|
45
|
+
return edits;
|
|
46
|
+
}
|
|
47
|
+
class EditReportThrottle {
|
|
48
|
+
constructor(intervalMs = 5e3) {
|
|
49
|
+
this.intervalMs = intervalMs;
|
|
50
|
+
}
|
|
51
|
+
lastReportedAt = /* @__PURE__ */ new Map();
|
|
52
|
+
/** Returns the edits that should be reported now and remembers them. */
|
|
53
|
+
take(edits, now = Date.now()) {
|
|
54
|
+
const due = [];
|
|
55
|
+
for (const edit of edits) {
|
|
56
|
+
const last = this.lastReportedAt.get(edit.path);
|
|
57
|
+
if (last !== void 0 && now - last < this.intervalMs) continue;
|
|
58
|
+
this.lastReportedAt.set(edit.path, now);
|
|
59
|
+
due.push(edit);
|
|
60
|
+
}
|
|
61
|
+
if (this.lastReportedAt.size > 512) {
|
|
62
|
+
for (const [path, at] of this.lastReportedAt) if (now - at > this.intervalMs * 10) this.lastReportedAt.delete(path);
|
|
63
|
+
}
|
|
64
|
+
return due;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { EditReportThrottle as E, extractCodexPatchPaths as a, extractClaudeEditPaths as b, extractAcpEditPaths as e };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const CLAUDE_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
4
|
+
function pathOf(input) {
|
|
5
|
+
if (!input || typeof input !== "object") return null;
|
|
6
|
+
const record = input;
|
|
7
|
+
const candidate = record.file_path ?? record.notebook_path;
|
|
8
|
+
return typeof candidate === "string" && candidate.trim() !== "" ? candidate : null;
|
|
9
|
+
}
|
|
10
|
+
function extractClaudeEditPaths(body) {
|
|
11
|
+
if (!body || typeof body !== "object") return [];
|
|
12
|
+
const line = body;
|
|
13
|
+
if (line.type !== "assistant") return [];
|
|
14
|
+
const content = line.message?.content;
|
|
15
|
+
if (!Array.isArray(content)) return [];
|
|
16
|
+
const stamp = line.timestamp;
|
|
17
|
+
const at = typeof stamp === "string" ? Date.parse(stamp) : typeof stamp === "number" ? stamp : NaN;
|
|
18
|
+
const edits = [];
|
|
19
|
+
for (const block of content) {
|
|
20
|
+
if (!block || typeof block !== "object") continue;
|
|
21
|
+
const b = block;
|
|
22
|
+
if (b.type !== "tool_use" || typeof b.name !== "string" || !CLAUDE_EDIT_TOOLS.has(b.name)) continue;
|
|
23
|
+
const path = pathOf(b.input);
|
|
24
|
+
if (path) edits.push({ path, tool: b.name, ...Number.isFinite(at) ? { at } : {} });
|
|
25
|
+
}
|
|
26
|
+
return edits;
|
|
27
|
+
}
|
|
28
|
+
function extractCodexPatchPaths(changes) {
|
|
29
|
+
if (!changes || typeof changes !== "object" || Array.isArray(changes)) return [];
|
|
30
|
+
return Object.keys(changes).filter((path) => path.trim() !== "").map((path) => ({ path, tool: "CodexPatch" }));
|
|
31
|
+
}
|
|
32
|
+
function extractAcpEditPaths(toolKind, args) {
|
|
33
|
+
if (!args || typeof args !== "object") return [];
|
|
34
|
+
const a = args;
|
|
35
|
+
if (a.piTool === "write" || a.piTool === "edit") {
|
|
36
|
+
const raw = a.rawInput;
|
|
37
|
+
const path = raw && typeof raw.path === "string" && raw.path.trim() !== "" ? raw.path : null;
|
|
38
|
+
return path ? [{ path, tool: a.piTool }] : [];
|
|
39
|
+
}
|
|
40
|
+
if (typeof a.piTool === "string") return [];
|
|
41
|
+
if (toolKind !== "edit" || !Array.isArray(a.locations)) return [];
|
|
42
|
+
const edits = [];
|
|
43
|
+
for (const location of a.locations) {
|
|
44
|
+
const path = location && typeof location === "object" ? location.path : void 0;
|
|
45
|
+
if (typeof path === "string" && path.trim() !== "" && !edits.some((e) => e.path === path)) edits.push({ path, tool: "edit" });
|
|
46
|
+
}
|
|
47
|
+
return edits;
|
|
48
|
+
}
|
|
49
|
+
class EditReportThrottle {
|
|
50
|
+
constructor(intervalMs = 5e3) {
|
|
51
|
+
this.intervalMs = intervalMs;
|
|
52
|
+
}
|
|
53
|
+
lastReportedAt = /* @__PURE__ */ new Map();
|
|
54
|
+
/** Returns the edits that should be reported now and remembers them. */
|
|
55
|
+
take(edits, now = Date.now()) {
|
|
56
|
+
const due = [];
|
|
57
|
+
for (const edit of edits) {
|
|
58
|
+
const last = this.lastReportedAt.get(edit.path);
|
|
59
|
+
if (last !== void 0 && now - last < this.intervalMs) continue;
|
|
60
|
+
this.lastReportedAt.set(edit.path, now);
|
|
61
|
+
due.push(edit);
|
|
62
|
+
}
|
|
63
|
+
if (this.lastReportedAt.size > 512) {
|
|
64
|
+
for (const [path, at] of this.lastReportedAt) if (now - at > this.intervalMs * 10) this.lastReportedAt.delete(path);
|
|
65
|
+
}
|
|
66
|
+
return due;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
exports.EditReportThrottle = EditReportThrottle;
|
|
71
|
+
exports.extractAcpEditPaths = extractAcpEditPaths;
|
|
72
|
+
exports.extractClaudeEditPaths = extractClaudeEditPaths;
|
|
73
|
+
exports.extractCodexPatchPaths = extractCodexPatchPaths;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { l as logger } from './logger-fXoNzNuB.mjs';
|
|
2
|
+
import { i as isDaemonRunningCurrentlyInstalledHappyVersion, c as checkIfDaemonRunningAndCleanupStaleState, a as stopDaemon } from './controlClient-DjoKTtgK.mjs';
|
|
3
|
+
import { s as spawnHappyCLI } from './spawnHappyCLI-cqWQbwsb.mjs';
|
|
4
|
+
import { readDaemonState } from './persistence-oET5n61f.mjs';
|
|
5
|
+
import { c as configuration } from './configuration-DoWFJUPm.mjs';
|
|
6
|
+
import { d as daemonEndpointsMatch } from './doctorReadiness-C208SMN9.mjs';
|
|
7
|
+
|
|
8
|
+
function extractNoSandboxFlag(args) {
|
|
9
|
+
let noSandbox = false;
|
|
10
|
+
const remainingArgs = [];
|
|
11
|
+
for (const arg of args) {
|
|
12
|
+
if (arg === "--no-sandbox") {
|
|
13
|
+
noSandbox = true;
|
|
14
|
+
} else {
|
|
15
|
+
remainingArgs.push(arg);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
noSandbox,
|
|
20
|
+
args: remainingArgs
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DAEMON_READY_TIMEOUT_MS = 5e3;
|
|
25
|
+
const DAEMON_READY_POLL_INTERVAL_MS = 100;
|
|
26
|
+
async function ensureDaemonRunning() {
|
|
27
|
+
logger.debug("Ensuring Happy background service is running & matches our version...");
|
|
28
|
+
const versionMatches = await isDaemonRunningCurrentlyInstalledHappyVersion();
|
|
29
|
+
const state = await readDaemonState();
|
|
30
|
+
const endpointsMatch = Boolean(state) && daemonEndpointsMatch(
|
|
31
|
+
state?.serverUrl,
|
|
32
|
+
state?.webappUrl,
|
|
33
|
+
configuration.serverUrl,
|
|
34
|
+
configuration.webappUrl
|
|
35
|
+
);
|
|
36
|
+
if (versionMatches && endpointsMatch) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (await checkIfDaemonRunningAndCleanupStaleState()) {
|
|
40
|
+
logger.debug("Stopping daemon whose version or configured relay no longer matches...");
|
|
41
|
+
await stopDaemon();
|
|
42
|
+
}
|
|
43
|
+
logger.debug("Starting Happy background service...");
|
|
44
|
+
const daemonProcess = spawnHappyCLI(["daemon", "start-sync"], {
|
|
45
|
+
detached: true,
|
|
46
|
+
stdio: "ignore",
|
|
47
|
+
env: process.env
|
|
48
|
+
});
|
|
49
|
+
daemonProcess.unref();
|
|
50
|
+
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
51
|
+
while (Date.now() < deadline) {
|
|
52
|
+
if (await checkIfDaemonRunningAndCleanupStaleState()) {
|
|
53
|
+
logger.debug("Happy background service is ready");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, DAEMON_READY_POLL_INTERVAL_MS));
|
|
57
|
+
}
|
|
58
|
+
logger.debug(`Happy background service did not become ready within ${DAEMON_READY_TIMEOUT_MS}ms; continuing anyway`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { extractNoSandboxFlag as a, ensureDaemonRunning as e };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var logger = require('./logger-Bc8-u1w0.cjs');
|
|
4
|
+
var controlClient = require('./controlClient-fEHE85bH.cjs');
|
|
5
|
+
var spawnHappyCLI = require('./spawnHappyCLI-BhYj84rv.cjs');
|
|
6
|
+
var persistence = require('./persistence-CBNMzSa8.cjs');
|
|
7
|
+
var configuration = require('./configuration-v9f8LM9T.cjs');
|
|
8
|
+
var doctorReadiness = require('./doctorReadiness-BCZGcN0a.cjs');
|
|
9
|
+
|
|
10
|
+
function extractNoSandboxFlag(args) {
|
|
11
|
+
let noSandbox = false;
|
|
12
|
+
const remainingArgs = [];
|
|
13
|
+
for (const arg of args) {
|
|
14
|
+
if (arg === "--no-sandbox") {
|
|
15
|
+
noSandbox = true;
|
|
16
|
+
} else {
|
|
17
|
+
remainingArgs.push(arg);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
noSandbox,
|
|
22
|
+
args: remainingArgs
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DAEMON_READY_TIMEOUT_MS = 5e3;
|
|
27
|
+
const DAEMON_READY_POLL_INTERVAL_MS = 100;
|
|
28
|
+
async function ensureDaemonRunning() {
|
|
29
|
+
logger.logger.debug("Ensuring Happy background service is running & matches our version...");
|
|
30
|
+
const versionMatches = await controlClient.isDaemonRunningCurrentlyInstalledHappyVersion();
|
|
31
|
+
const state = await persistence.readDaemonState();
|
|
32
|
+
const endpointsMatch = Boolean(state) && doctorReadiness.daemonEndpointsMatch(
|
|
33
|
+
state?.serverUrl,
|
|
34
|
+
state?.webappUrl,
|
|
35
|
+
configuration.configuration.serverUrl,
|
|
36
|
+
configuration.configuration.webappUrl
|
|
37
|
+
);
|
|
38
|
+
if (versionMatches && endpointsMatch) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (await controlClient.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
42
|
+
logger.logger.debug("Stopping daemon whose version or configured relay no longer matches...");
|
|
43
|
+
await controlClient.stopDaemon();
|
|
44
|
+
}
|
|
45
|
+
logger.logger.debug("Starting Happy background service...");
|
|
46
|
+
const daemonProcess = spawnHappyCLI.spawnHappyCLI(["daemon", "start-sync"], {
|
|
47
|
+
detached: true,
|
|
48
|
+
stdio: "ignore",
|
|
49
|
+
env: process.env
|
|
50
|
+
});
|
|
51
|
+
daemonProcess.unref();
|
|
52
|
+
const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
|
|
53
|
+
while (Date.now() < deadline) {
|
|
54
|
+
if (await controlClient.checkIfDaemonRunningAndCleanupStaleState()) {
|
|
55
|
+
logger.logger.debug("Happy background service is ready");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, DAEMON_READY_POLL_INTERVAL_MS));
|
|
59
|
+
}
|
|
60
|
+
logger.logger.debug(`Happy background service did not become ready within ${DAEMON_READY_TIMEOUT_MS}ms; continuing anyway`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
exports.ensureDaemonRunning = ensureDaemonRunning;
|
|
64
|
+
exports.extractNoSandboxFlag = extractNoSandboxFlag;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { s as spawnHappyCLI } from './spawnHappyCLI-cqWQbwsb.mjs';
|
|
3
|
+
import axios, { AxiosError } from 'axios';
|
|
4
|
+
import tweetnacl from 'tweetnacl';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { d as decodeBase64, a as decryptWithDataKey, b as decryptLegacy } from './authRelay-DSAvg1uv.mjs';
|
|
7
|
+
import { c as configuration } from './configuration-DoWFJUPm.mjs';
|
|
8
|
+
import { g as getLocalHappyAgentCredentialPath, r as readLocalHappyAgentCredentials } from './localHappyAgentAuth-DCsetiyG.mjs';
|
|
9
|
+
import 'cross-spawn';
|
|
10
|
+
import 'node:path';
|
|
11
|
+
import './projectPath-BTBIMB57.mjs';
|
|
12
|
+
import 'path';
|
|
13
|
+
import 'url';
|
|
14
|
+
import './logger-fXoNzNuB.mjs';
|
|
15
|
+
import 'chalk';
|
|
16
|
+
import 'node:util';
|
|
17
|
+
import 'node:crypto';
|
|
18
|
+
import 'node:os';
|
|
19
|
+
import './package-uOg9SCKi.mjs';
|
|
20
|
+
import 'node:fs/promises';
|
|
21
|
+
|
|
22
|
+
const ResumableMetadataSchema = z.object({
|
|
23
|
+
path: z.string().min(1),
|
|
24
|
+
flavor: z.string().optional(),
|
|
25
|
+
claudeSessionId: z.string().optional(),
|
|
26
|
+
codexThreadId: z.string().optional()
|
|
27
|
+
}).passthrough();
|
|
28
|
+
function resolveSessionRecordByPrefix(records, sessionId) {
|
|
29
|
+
const trimmed = sessionId.trim();
|
|
30
|
+
if (!trimmed) {
|
|
31
|
+
throw new Error("Happy session ID is required: very-happy resume <session-id>");
|
|
32
|
+
}
|
|
33
|
+
const matches = records.filter((record) => record.id.startsWith(trimmed));
|
|
34
|
+
if (matches.length === 0) {
|
|
35
|
+
throw new Error(`No Happy session found matching "${trimmed}"`);
|
|
36
|
+
}
|
|
37
|
+
if (matches.length > 1) {
|
|
38
|
+
throw new Error(`Ambiguous Happy session "${trimmed}" matches ${matches.length} sessions. Be more specific.`);
|
|
39
|
+
}
|
|
40
|
+
return matches[0];
|
|
41
|
+
}
|
|
42
|
+
function decryptBoxBundle(bundle, recipientSecretKey) {
|
|
43
|
+
if (bundle.length < 56) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const ephemeralPublicKey = bundle.slice(0, 32);
|
|
47
|
+
const nonce = bundle.slice(32, 56);
|
|
48
|
+
const ciphertext = bundle.slice(56);
|
|
49
|
+
const decrypted = tweetnacl.box.open(ciphertext, nonce, ephemeralPublicKey, recipientSecretKey);
|
|
50
|
+
return decrypted ? new Uint8Array(decrypted) : null;
|
|
51
|
+
}
|
|
52
|
+
function readAgentCredentials() {
|
|
53
|
+
const credentialPath = getLocalHappyAgentCredentialPath();
|
|
54
|
+
const credentials = readLocalHappyAgentCredentials();
|
|
55
|
+
if (!credentials) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Cannot use the legacy CLI resume path without ${credentialPath}. This credential is produced only by the source-only internal control-plane prototype; do not install the unrelated npm package named happy-agent. Use the Web session controls instead.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return credentials;
|
|
61
|
+
}
|
|
62
|
+
function resolveSessionEncryption(session, credentials) {
|
|
63
|
+
if (session.dataEncryptionKey) {
|
|
64
|
+
const encrypted = decodeBase64(session.dataEncryptionKey);
|
|
65
|
+
const sessionKey = decryptBoxBundle(encrypted.slice(1), credentials.contentKeyPair.secretKey);
|
|
66
|
+
if (!sessionKey) {
|
|
67
|
+
throw new Error(`Failed to decrypt data key for Happy session ${session.id}`);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
key: sessionKey,
|
|
71
|
+
variant: "dataKey"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
key: credentials.secret,
|
|
76
|
+
variant: "legacy"
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function decryptSessionMetadata(session, credentials) {
|
|
80
|
+
const encryption = resolveSessionEncryption(session, credentials);
|
|
81
|
+
const encryptedMetadata = decodeBase64(session.metadata);
|
|
82
|
+
const metadata = encryption.variant === "dataKey" ? decryptWithDataKey(encryptedMetadata, encryption.key) : decryptLegacy(encryptedMetadata, encryption.key);
|
|
83
|
+
if (!metadata) {
|
|
84
|
+
throw new Error(`Failed to decrypt metadata for Happy session ${session.id}`);
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
return ResumableMetadataSchema.parse(metadata);
|
|
88
|
+
} catch {
|
|
89
|
+
throw new Error(`Happy session ${session.id} is missing resumable metadata.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function fetchSessions(credentials) {
|
|
93
|
+
try {
|
|
94
|
+
const response = await axios.get(`${configuration.serverUrl}/v1/sessions`, {
|
|
95
|
+
headers: {
|
|
96
|
+
Authorization: `Bearer ${credentials.token}`,
|
|
97
|
+
"X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return response.data.sessions;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (error instanceof AxiosError) {
|
|
103
|
+
if (error.response?.status === 401) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"Legacy CLI resume authentication expired. Re-provision it from the source-only internal control-plane prototype; do not install the unrelated npm package named happy-agent."
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`Failed to load Happy sessions: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function resolveHappySession(sessionId) {
|
|
114
|
+
const credentials = readAgentCredentials();
|
|
115
|
+
const sessions = await fetchSessions(credentials);
|
|
116
|
+
const matched = resolveSessionRecordByPrefix(sessions, sessionId);
|
|
117
|
+
return {
|
|
118
|
+
id: matched.id,
|
|
119
|
+
active: matched.active,
|
|
120
|
+
metadata: decryptSessionMetadata(matched, credentials)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseResumeCommandArgs(args) {
|
|
125
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
126
|
+
return {
|
|
127
|
+
showHelp: true,
|
|
128
|
+
sessionId: ""
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (args.length === 0) {
|
|
132
|
+
throw new Error("Happy session ID is required: very-happy resume <session-id>");
|
|
133
|
+
}
|
|
134
|
+
if (args.length > 1) {
|
|
135
|
+
throw new Error(`Unexpected arguments for very-happy resume: ${args.slice(1).join(" ")}`);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
showHelp: false,
|
|
139
|
+
sessionId: args[0]
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function resolveFlavor(metadata) {
|
|
143
|
+
if (metadata.flavor === "codex" || metadata.codexThreadId) {
|
|
144
|
+
return "codex";
|
|
145
|
+
}
|
|
146
|
+
if (metadata.flavor === "claude" || metadata.claudeSessionId) {
|
|
147
|
+
return "claude";
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function buildResumeLaunch(session, options = {}) {
|
|
152
|
+
const { metadata } = session;
|
|
153
|
+
const flavor = resolveFlavor(metadata);
|
|
154
|
+
if (flavor === "codex") {
|
|
155
|
+
const args = ["codex"];
|
|
156
|
+
if (options.freshConversation) ; else {
|
|
157
|
+
if (!metadata.codexThreadId) {
|
|
158
|
+
throw new Error(`Happy session ${session.id} is missing its Codex thread ID.`);
|
|
159
|
+
}
|
|
160
|
+
args.push("--resume", metadata.codexThreadId);
|
|
161
|
+
}
|
|
162
|
+
if (options.startedBy) {
|
|
163
|
+
args.push("--started-by", options.startedBy);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
cwd: metadata.path,
|
|
167
|
+
args
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (flavor === "claude") {
|
|
171
|
+
const args = ["claude"];
|
|
172
|
+
if (options.claudeStartingMode) {
|
|
173
|
+
args.push("--happy-starting-mode", options.claudeStartingMode);
|
|
174
|
+
}
|
|
175
|
+
if (options.startedBy) {
|
|
176
|
+
args.push("--started-by", options.startedBy);
|
|
177
|
+
}
|
|
178
|
+
if (options.freshConversation) ; else {
|
|
179
|
+
if (!metadata.claudeSessionId) {
|
|
180
|
+
throw new Error(`Happy session ${session.id} is missing its Claude session ID.`);
|
|
181
|
+
}
|
|
182
|
+
args.push("--resume", metadata.claudeSessionId);
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
cwd: metadata.path,
|
|
186
|
+
args
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
throw new Error(`Happy session ${session.id} uses unsupported flavor "${metadata.flavor ?? "unknown"}".`);
|
|
190
|
+
}
|
|
191
|
+
function formatResumeHelp() {
|
|
192
|
+
return [
|
|
193
|
+
"very-happy resume - Resume a previous Happy session",
|
|
194
|
+
"",
|
|
195
|
+
"Usage:",
|
|
196
|
+
" very-happy resume <happy-session-id>",
|
|
197
|
+
"",
|
|
198
|
+
"Examples:",
|
|
199
|
+
" very-happy resume cmmij8olq00dp5jcxr3wtbpau",
|
|
200
|
+
" very-happy resume cmmij8",
|
|
201
|
+
"",
|
|
202
|
+
"This reuses the saved worktree/path and resumes the underlying agent session",
|
|
203
|
+
"when the backend supports it."
|
|
204
|
+
].join("\n");
|
|
205
|
+
}
|
|
206
|
+
function spawnResumeChild(launch) {
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
const child = spawnHappyCLI(launch.args, {
|
|
209
|
+
cwd: launch.cwd,
|
|
210
|
+
env: process.env,
|
|
211
|
+
stdio: "inherit"
|
|
212
|
+
});
|
|
213
|
+
child.once("error", reject);
|
|
214
|
+
child.once("exit", (code, signal) => {
|
|
215
|
+
if (signal) {
|
|
216
|
+
reject(new Error(`Resumed session exited via signal ${signal}`));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
resolve(code);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async function handleResumeCommand(args) {
|
|
224
|
+
const parsed = parseResumeCommandArgs(args);
|
|
225
|
+
if (parsed.showHelp) {
|
|
226
|
+
console.log(formatResumeHelp());
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const session = await resolveHappySession(parsed.sessionId);
|
|
230
|
+
const launch = buildResumeLaunch(session);
|
|
231
|
+
if (!existsSync(launch.cwd)) {
|
|
232
|
+
throw new Error(`Saved session path does not exist: ${launch.cwd}`);
|
|
233
|
+
}
|
|
234
|
+
const exitCode = await spawnResumeChild(launch);
|
|
235
|
+
if (typeof exitCode === "number" && exitCode !== 0) {
|
|
236
|
+
process.exit(exitCode);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export { buildResumeLaunch, formatResumeHelp, handleResumeCommand, parseResumeCommandArgs };
|