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,404 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync, rmSync, mkdirSync, readFileSync, writeFileSync, statSync, chmodSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { randomBytes } from 'node:crypto';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { createInterface } from 'node:readline/promises';
|
|
9
|
+
import { c as configuration } from './configuration-DoWFJUPm.mjs';
|
|
10
|
+
import { updateSettings } from './persistence-oET5n61f.mjs';
|
|
11
|
+
import 'node:os';
|
|
12
|
+
import './package-uOg9SCKi.mjs';
|
|
13
|
+
import 'node:fs/promises';
|
|
14
|
+
import 'zod';
|
|
15
|
+
import './authRelay-DSAvg1uv.mjs';
|
|
16
|
+
import 'tweetnacl';
|
|
17
|
+
import './logger-fXoNzNuB.mjs';
|
|
18
|
+
import 'node:util';
|
|
19
|
+
|
|
20
|
+
function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, generatedInviteCode) {
|
|
21
|
+
const signupMode = configuredMode?.trim() || "invite";
|
|
22
|
+
const inviteCodes = configuredInviteCodes?.trim();
|
|
23
|
+
if (signupMode !== "invite" || inviteCodes) {
|
|
24
|
+
return { signupMode, signupInviteCodes: inviteCodes || void 0 };
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
signupMode,
|
|
28
|
+
signupInviteCodes: generatedInviteCode,
|
|
29
|
+
generatedInviteCode
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
34
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
35
|
+
const require_ = createRequire(import.meta.url);
|
|
36
|
+
const PRISMA_QUERY_ENGINE_FILES = {
|
|
37
|
+
"arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
|
|
38
|
+
"x64-darwin": "libquery_engine-darwin.dylib.node",
|
|
39
|
+
"arm64-linux": "libquery_engine-linux-arm64-openssl-3.0.x.so.node",
|
|
40
|
+
"x64-linux": "libquery_engine-debian-openssl-3.0.x.so.node",
|
|
41
|
+
"x64-win32": "query_engine-windows.dll.node"
|
|
42
|
+
};
|
|
43
|
+
const SERVER_PACKAGE_NAME = "very-happy-server";
|
|
44
|
+
const SETTINGS_WRITE_CONFIRM_FLAG = "--i-understand-this-will-modify-default-happy-settings";
|
|
45
|
+
async function handleServerCommand(args) {
|
|
46
|
+
const opts = parseArgs(args);
|
|
47
|
+
if (opts === null) return;
|
|
48
|
+
const serverUrl = `http://${opts.host === "0.0.0.0" ? "127.0.0.1" : opts.host}:${opts.port}`;
|
|
49
|
+
await ensureSettingsWriteAllowed(opts, serverUrl);
|
|
50
|
+
const dataDir = path.join(configuration.happyHomeDir, "server-data");
|
|
51
|
+
const pgliteDir = path.join(dataDir, "pglite");
|
|
52
|
+
const secretFile = path.join(dataDir, "master-secret");
|
|
53
|
+
const bootstrapCodeFile = path.join(dataDir, "bootstrap-invite-code");
|
|
54
|
+
if (opts.reset && existsSync(dataDir)) {
|
|
55
|
+
console.log(chalk.yellow(`Wiping ${dataDir}...`));
|
|
56
|
+
rmSync(dataDir, { recursive: true, force: true });
|
|
57
|
+
}
|
|
58
|
+
mkdirSync(dataDir, { recursive: true });
|
|
59
|
+
const masterSecret = opts.masterSecret ?? loadOrCreateMasterSecret(secretFile);
|
|
60
|
+
const signup = resolveLocalSignupBootstrap(
|
|
61
|
+
process.env.SIGNUP_MODE,
|
|
62
|
+
process.env.SIGNUP_INVITE_CODES,
|
|
63
|
+
loadOrCreateBootstrapInviteCode(bootstrapCodeFile)
|
|
64
|
+
);
|
|
65
|
+
const artifacts = resolveServerArtifacts();
|
|
66
|
+
if (!artifacts) {
|
|
67
|
+
console.error(chalk.red("Could not locate happy-server."));
|
|
68
|
+
console.error(chalk.gray(" Expected one of:"));
|
|
69
|
+
console.error(chalk.gray(` - installed ${SERVER_PACKAGE_NAME} package`));
|
|
70
|
+
console.error(chalk.gray(` - legacy bundled binary at ${path.join(__dirname$1, "..", "..", "tools", "server", currentPlatform(), bundledBinaryName())}`));
|
|
71
|
+
console.error(chalk.gray(" - sibling packages/happy-server/sources/standalone.ts in the monorepo"));
|
|
72
|
+
console.error(chalk.gray(` The fork-owned ${SERVER_PACKAGE_NAME} artifact may not be published yet.`));
|
|
73
|
+
console.error(chalk.gray(" Use Dockerfile.server from the repository for the supported self-host path."));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const staticDir = artifacts.webappDir ?? findWebappDir();
|
|
77
|
+
console.log(chalk.cyan(`
|
|
78
|
+
very-happy server`));
|
|
79
|
+
console.log(chalk.gray(` data dir: ${dataDir}`));
|
|
80
|
+
console.log(chalk.gray(` server url: ${serverUrl}`));
|
|
81
|
+
console.log(chalk.gray(` mode: ${serverArtifactMode(artifacts)}`));
|
|
82
|
+
if (staticDir) {
|
|
83
|
+
console.log(chalk.gray(` webapp: ${staticDir}`));
|
|
84
|
+
} else {
|
|
85
|
+
console.log(chalk.yellow(" webapp: (no production Web V2 build) \u2014 API only. Run `pnpm bundle:webapp` to build."));
|
|
86
|
+
}
|
|
87
|
+
console.log();
|
|
88
|
+
const env = {
|
|
89
|
+
...process.env,
|
|
90
|
+
DB_PROVIDER: "pglite",
|
|
91
|
+
DATA_DIR: dataDir,
|
|
92
|
+
PGLITE_DIR: pgliteDir,
|
|
93
|
+
HANDY_MASTER_SECRET: masterSecret,
|
|
94
|
+
PORT: String(opts.port),
|
|
95
|
+
HOST: opts.host,
|
|
96
|
+
SIGNUP_MODE: signup.signupMode
|
|
97
|
+
};
|
|
98
|
+
if (signup.signupInviteCodes) env.SIGNUP_INVITE_CODES = signup.signupInviteCodes;
|
|
99
|
+
if (staticDir) env.HAPPY_STATIC_DIR = staticDir;
|
|
100
|
+
env.HAPPY_INJECT_HTML_CONFIG = JSON.stringify({
|
|
101
|
+
serverUrl,
|
|
102
|
+
disableAnalytics: true
|
|
103
|
+
});
|
|
104
|
+
if (signup.generatedInviteCode) {
|
|
105
|
+
console.log(chalk.yellow.bold(" First-account bootstrap"));
|
|
106
|
+
console.log(chalk.yellow(` Invite code: ${signup.generatedInviteCode}`));
|
|
107
|
+
console.log(chalk.gray(" Register the first operator, stop this server, then restart with:"));
|
|
108
|
+
console.log(chalk.cyan(" SIGNUP_MODE=closed very-happy server"));
|
|
109
|
+
console.log(chalk.gray(" The code remains valid until the server is restarted in closed mode."));
|
|
110
|
+
console.log();
|
|
111
|
+
}
|
|
112
|
+
if (artifacts.bundled) {
|
|
113
|
+
const prismaEngine = artifacts.prismaQueryEngineLibrary ?? resolvePrismaQueryEngineLibrary(artifacts.cwd);
|
|
114
|
+
if (!prismaEngine) {
|
|
115
|
+
console.error(chalk.red("Could not locate the Prisma query engine for this platform."));
|
|
116
|
+
if (artifacts.source === "package") {
|
|
117
|
+
console.error(chalk.gray(` Expected ${SERVER_PACKAGE_NAME} to install @prisma/engines.`));
|
|
118
|
+
console.error(chalk.gray(` Try reinstalling ${SERVER_PACKAGE_NAME}, then run \`very-happy server\` again.`));
|
|
119
|
+
} else {
|
|
120
|
+
console.error(chalk.gray(" Expected @prisma/engines to be available near the happy package."));
|
|
121
|
+
console.error(chalk.gray(" Try reinstalling happy, then run `very-happy server` again."));
|
|
122
|
+
}
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
env.PRISMA_QUERY_ENGINE_LIBRARY = prismaEngine;
|
|
126
|
+
}
|
|
127
|
+
console.log(chalk.gray("Running migrations..."));
|
|
128
|
+
await spawnAndWait(artifacts, env, ["migrate"]);
|
|
129
|
+
if (opts.persistServerUrl) {
|
|
130
|
+
await updateSettings((current) => ({ ...current, serverUrl, webappUrl: serverUrl }));
|
|
131
|
+
console.log(chalk.gray(`Wrote serverUrl + webappUrl=${serverUrl} to ${configuration.settingsFile}`));
|
|
132
|
+
}
|
|
133
|
+
console.log(chalk.gray("Starting server..."));
|
|
134
|
+
const child = spawnBackground(artifacts, env, ["serve"]);
|
|
135
|
+
console.log();
|
|
136
|
+
console.log(chalk.green.bold(`\u2713 happy-server starting at ${serverUrl}`));
|
|
137
|
+
if (staticDir) {
|
|
138
|
+
console.log(chalk.green(` Open ${serverUrl} in your browser.`));
|
|
139
|
+
}
|
|
140
|
+
if (opts.persistServerUrl) {
|
|
141
|
+
console.log(chalk.gray(" happy CLI + daemon will use this server automatically (settings.serverUrl)."));
|
|
142
|
+
}
|
|
143
|
+
console.log(chalk.gray(" Press Ctrl-C to stop."));
|
|
144
|
+
console.log();
|
|
145
|
+
const forwardSignal = (sig) => {
|
|
146
|
+
if (!child.killed) child.kill(sig);
|
|
147
|
+
};
|
|
148
|
+
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
|
149
|
+
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
|
150
|
+
const exitCode = await new Promise((resolve) => {
|
|
151
|
+
child.on("exit", (code) => {
|
|
152
|
+
console.log(chalk.gray(`
|
|
153
|
+
happy-server exited (code ${code ?? 0})`));
|
|
154
|
+
resolve(code ?? 0);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
process.exit(exitCode);
|
|
158
|
+
}
|
|
159
|
+
function parseArgs(args) {
|
|
160
|
+
let port = 3005;
|
|
161
|
+
let host = "127.0.0.1";
|
|
162
|
+
let reset = false;
|
|
163
|
+
let persistServerUrl = true;
|
|
164
|
+
let allowSettingsWrite = false;
|
|
165
|
+
let masterSecret;
|
|
166
|
+
for (let i = 0; i < args.length; i++) {
|
|
167
|
+
const arg = args[i];
|
|
168
|
+
if (arg === "-h" || arg === "--help") {
|
|
169
|
+
showHelp();
|
|
170
|
+
return null;
|
|
171
|
+
} else if (arg === "--port" || arg === "-p") {
|
|
172
|
+
port = parseInt(args[++i], 10);
|
|
173
|
+
if (Number.isNaN(port)) {
|
|
174
|
+
console.error(chalk.red("Invalid --port"));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
} else if (arg === "--host") {
|
|
178
|
+
host = args[++i];
|
|
179
|
+
} else if (arg === "--reset") {
|
|
180
|
+
reset = true;
|
|
181
|
+
} else if (arg === "--no-persist") {
|
|
182
|
+
persistServerUrl = false;
|
|
183
|
+
} else if (arg === SETTINGS_WRITE_CONFIRM_FLAG) {
|
|
184
|
+
allowSettingsWrite = true;
|
|
185
|
+
} else if (arg === "--master-secret") {
|
|
186
|
+
masterSecret = args[++i];
|
|
187
|
+
} else {
|
|
188
|
+
console.error(chalk.red(`Unknown arg: ${arg}`));
|
|
189
|
+
showHelp();
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { port, host, reset, persistServerUrl, allowSettingsWrite, masterSecret };
|
|
194
|
+
}
|
|
195
|
+
function showHelp() {
|
|
196
|
+
console.log(`
|
|
197
|
+
${chalk.bold("very-happy server")} - Run Happy sync server + web app locally (self-host)
|
|
198
|
+
|
|
199
|
+
${chalk.bold("Usage:")}
|
|
200
|
+
very-happy server [--port 3005] [--host 127.0.0.1] [--reset] [--no-persist]
|
|
201
|
+
|
|
202
|
+
${chalk.bold("Options:")}
|
|
203
|
+
--port, -p <n> Port to listen on (default: 3005)
|
|
204
|
+
--host <ip> Host to bind (default: 127.0.0.1)
|
|
205
|
+
--reset Wipe local server data before starting
|
|
206
|
+
--no-persist Don't write serverUrl into settings.json
|
|
207
|
+
${SETTINGS_WRITE_CONFIRM_FLAG}
|
|
208
|
+
Write settings.serverUrl/settings.webappUrl without prompting
|
|
209
|
+
--master-secret <hex> Use a specific master secret (default: auto-generated)
|
|
210
|
+
|
|
211
|
+
${chalk.bold("Notes:")}
|
|
212
|
+
- Stores data in ${chalk.cyan("$HAPPY_HOME_DIR/server-data/")}
|
|
213
|
+
- Packaged installs require ${chalk.cyan(SERVER_PACKAGE_NAME)} once that fork-owned artifact is published
|
|
214
|
+
- Until then, use the repository's ${chalk.cyan("Dockerfile.server")} self-host path
|
|
215
|
+
- By default, asks before writing ${chalk.cyan("settings.serverUrl")} and ${chalk.cyan("settings.webappUrl")}
|
|
216
|
+
- Use ${chalk.cyan("--no-persist")} to run without modifying default Happy settings
|
|
217
|
+
- Fresh installs start in invite mode and print a persisted bootstrap code
|
|
218
|
+
- After the first account, restart with ${chalk.cyan("SIGNUP_MODE=closed")} to disable signup
|
|
219
|
+
- Open ${chalk.cyan("http://127.0.0.1:<port>")} for the web app (if bundled)
|
|
220
|
+
`);
|
|
221
|
+
}
|
|
222
|
+
async function ensureSettingsWriteAllowed(opts, serverUrl) {
|
|
223
|
+
if (!opts.persistServerUrl || opts.allowSettingsWrite) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const message = `very-happy server will write settings.serverUrl and settings.webappUrl to ${serverUrl} in ${configuration.settingsFile}.`;
|
|
227
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
228
|
+
console.error(chalk.red("Refusing to modify default Happy settings from a non-interactive run."));
|
|
229
|
+
console.error(chalk.gray(message));
|
|
230
|
+
console.error(chalk.gray(`Re-run with --no-persist, or pass ${SETTINGS_WRITE_CONFIRM_FLAG}.`));
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
234
|
+
try {
|
|
235
|
+
const answer = await rl.question(`${chalk.yellow(message)} Continue? ${chalk.gray("[y/N]")} `);
|
|
236
|
+
const normalized = answer.trim().toLowerCase();
|
|
237
|
+
if (normalized !== "y" && normalized !== "yes") {
|
|
238
|
+
console.error(chalk.gray("Cancelled. Re-run with --no-persist to start without changing settings."));
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
} finally {
|
|
242
|
+
rl.close();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function loadOrCreateMasterSecret(file) {
|
|
246
|
+
if (existsSync(file)) {
|
|
247
|
+
return readFileSync(file, "utf8").trim();
|
|
248
|
+
}
|
|
249
|
+
const secret = randomBytes(32).toString("hex");
|
|
250
|
+
writeFileSync(file, secret, { mode: 384 });
|
|
251
|
+
return secret;
|
|
252
|
+
}
|
|
253
|
+
function loadOrCreateBootstrapInviteCode(file) {
|
|
254
|
+
if (existsSync(file)) {
|
|
255
|
+
return readFileSync(file, "utf8").trim();
|
|
256
|
+
}
|
|
257
|
+
const code = `vh-bootstrap-${randomBytes(18).toString("base64url")}`;
|
|
258
|
+
writeFileSync(file, code, { mode: 384 });
|
|
259
|
+
return code;
|
|
260
|
+
}
|
|
261
|
+
function currentPlatform() {
|
|
262
|
+
return `${process.arch}-${process.platform}`;
|
|
263
|
+
}
|
|
264
|
+
function resolvePrismaQueryEngineLibrary(bundledServerDir) {
|
|
265
|
+
const fromDependency = findPrismaEngineFromDependency();
|
|
266
|
+
if (fromDependency) {
|
|
267
|
+
return fromDependency;
|
|
268
|
+
}
|
|
269
|
+
const legacyBundledEngine = path.join(bundledServerDir, "libquery_engine.node");
|
|
270
|
+
return existsSync(legacyBundledEngine) ? legacyBundledEngine : void 0;
|
|
271
|
+
}
|
|
272
|
+
function findPrismaEngineFromDependency() {
|
|
273
|
+
const engineFile = PRISMA_QUERY_ENGINE_FILES[currentPlatform()];
|
|
274
|
+
if (!engineFile) {
|
|
275
|
+
return void 0;
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const enginesPackage = require_.resolve("@prisma/engines/package.json");
|
|
279
|
+
const candidate = path.join(path.dirname(enginesPackage), engineFile);
|
|
280
|
+
return existsSync(candidate) ? candidate : void 0;
|
|
281
|
+
} catch {
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function resolveToolsPath(name) {
|
|
286
|
+
let dir = __dirname$1;
|
|
287
|
+
for (let i = 0; i < 5; i++) {
|
|
288
|
+
const candidate = path.join(dir, "tools", name);
|
|
289
|
+
if (existsSync(candidate)) return candidate;
|
|
290
|
+
const parent = path.dirname(dir);
|
|
291
|
+
if (parent === dir) break;
|
|
292
|
+
dir = parent;
|
|
293
|
+
}
|
|
294
|
+
return path.resolve(__dirname$1, "..", "tools", name);
|
|
295
|
+
}
|
|
296
|
+
function bundledBinaryName() {
|
|
297
|
+
return process.platform === "win32" ? "happy-server.exe" : "happy-server";
|
|
298
|
+
}
|
|
299
|
+
function ensureExecutable(file) {
|
|
300
|
+
if (process.platform === "win32") return;
|
|
301
|
+
try {
|
|
302
|
+
const mode = statSync(file).mode;
|
|
303
|
+
if ((mode & 73) === 0) chmodSync(file, mode | 493);
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function serverArtifactMode(artifacts) {
|
|
308
|
+
if (artifacts.source === "package") return SERVER_PACKAGE_NAME;
|
|
309
|
+
if (artifacts.source === "legacy-bundled") return "legacy bundled";
|
|
310
|
+
return "source (dev)";
|
|
311
|
+
}
|
|
312
|
+
function resolveServerArtifacts() {
|
|
313
|
+
const packageArtifact = resolveInstalledServerPackage();
|
|
314
|
+
if (packageArtifact) return packageArtifact;
|
|
315
|
+
const toolsRoot = resolveToolsPath("server");
|
|
316
|
+
const binDir = path.join(toolsRoot, currentPlatform());
|
|
317
|
+
const binary = path.join(binDir, bundledBinaryName());
|
|
318
|
+
if (existsSync(binary)) {
|
|
319
|
+
ensureExecutable(binary);
|
|
320
|
+
return { command: binary, prefixArgs: [], cwd: binDir, bundled: true, source: "legacy-bundled" };
|
|
321
|
+
}
|
|
322
|
+
const sourceEntry = findSourceStandalone();
|
|
323
|
+
if (sourceEntry) {
|
|
324
|
+
const tsx = findTsxBinary(path.dirname(path.dirname(sourceEntry)));
|
|
325
|
+
const useNode = tsx !== "tsx";
|
|
326
|
+
return {
|
|
327
|
+
command: useNode ? process.execPath : "tsx",
|
|
328
|
+
prefixArgs: useNode ? [tsx, sourceEntry] : [sourceEntry],
|
|
329
|
+
cwd: path.dirname(path.dirname(sourceEntry)),
|
|
330
|
+
bundled: false,
|
|
331
|
+
source: "source"
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return void 0;
|
|
335
|
+
}
|
|
336
|
+
function resolveInstalledServerPackage() {
|
|
337
|
+
try {
|
|
338
|
+
const serverPackage = require_(SERVER_PACKAGE_NAME);
|
|
339
|
+
const artifact = serverPackage.resolveServerArtifact?.();
|
|
340
|
+
if (!artifact || !artifact.command || !existsSync(artifact.command)) {
|
|
341
|
+
return void 0;
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
command: artifact.command,
|
|
345
|
+
prefixArgs: artifact.prefixArgs ?? [],
|
|
346
|
+
cwd: artifact.cwd,
|
|
347
|
+
bundled: artifact.bundled ?? true,
|
|
348
|
+
source: "package",
|
|
349
|
+
prismaQueryEngineLibrary: artifact.prismaQueryEngineLibrary,
|
|
350
|
+
webappDir: artifact.webappDir
|
|
351
|
+
};
|
|
352
|
+
} catch {
|
|
353
|
+
return void 0;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function findSourceStandalone() {
|
|
357
|
+
const candidates = [
|
|
358
|
+
path.resolve(__dirname$1, "../../../happy-server/sources/standalone.ts"),
|
|
359
|
+
path.resolve(__dirname$1, "../../happy-server/sources/standalone.ts"),
|
|
360
|
+
path.resolve(process.cwd(), "packages/happy-server/sources/standalone.ts"),
|
|
361
|
+
path.resolve(process.cwd(), "../happy-server/sources/standalone.ts")
|
|
362
|
+
];
|
|
363
|
+
for (const c of candidates) {
|
|
364
|
+
if (existsSync(c)) return c;
|
|
365
|
+
}
|
|
366
|
+
return void 0;
|
|
367
|
+
}
|
|
368
|
+
function findWebappDir() {
|
|
369
|
+
const bundled = resolveToolsPath("webapp");
|
|
370
|
+
if (existsSync(path.join(bundled, "index.html"))) return bundled;
|
|
371
|
+
const candidates = [
|
|
372
|
+
path.resolve(__dirname$1, "../../../happy-web-v2/dist"),
|
|
373
|
+
path.resolve(__dirname$1, "../../happy-web-v2/dist"),
|
|
374
|
+
path.resolve(process.cwd(), "packages/happy-web-v2/dist")
|
|
375
|
+
];
|
|
376
|
+
for (const c of candidates) {
|
|
377
|
+
if (existsSync(path.join(c, "index.html"))) return c;
|
|
378
|
+
}
|
|
379
|
+
return void 0;
|
|
380
|
+
}
|
|
381
|
+
function findTsxBinary(cwd) {
|
|
382
|
+
try {
|
|
383
|
+
return require_.resolve("tsx/cli", { paths: [cwd] });
|
|
384
|
+
} catch {
|
|
385
|
+
return "tsx";
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
async function spawnAndWait(art, env, args) {
|
|
389
|
+
const cmdArgs = [...art.prefixArgs, ...args];
|
|
390
|
+
await new Promise((resolve, reject) => {
|
|
391
|
+
const child = spawn(art.command, cmdArgs, { cwd: art.cwd, env, stdio: "inherit" });
|
|
392
|
+
child.on("error", reject);
|
|
393
|
+
child.on("exit", (code) => {
|
|
394
|
+
if (code === 0) resolve();
|
|
395
|
+
else reject(new Error(`happy-server ${args[0]} exited with code ${code}`));
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function spawnBackground(art, env, args) {
|
|
400
|
+
const cmdArgs = [...art.prefixArgs, ...args];
|
|
401
|
+
return spawn(art.command, cmdArgs, { cwd: art.cwd, env, stdio: "inherit" });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export { handleServerCommand };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_fs = require('node:fs');
|
|
4
|
+
var path = require('node:path');
|
|
5
|
+
var configuration = require('./configuration-v9f8LM9T.cjs');
|
|
6
|
+
var logger = require('./logger-Bc8-u1w0.cjs');
|
|
7
|
+
|
|
8
|
+
const PROCESS_IDENTITY_FIELDS = [
|
|
9
|
+
"hostPid",
|
|
10
|
+
"version",
|
|
11
|
+
"startedBy",
|
|
12
|
+
"startedFromDaemon",
|
|
13
|
+
"happyHomeDir",
|
|
14
|
+
"happyLibDir",
|
|
15
|
+
"happyToolsDir",
|
|
16
|
+
"capabilities",
|
|
17
|
+
"attachmentKinds",
|
|
18
|
+
"queueCancellation",
|
|
19
|
+
"sandbox",
|
|
20
|
+
"dangerouslySkipPermissions",
|
|
21
|
+
"os",
|
|
22
|
+
"host",
|
|
23
|
+
"homeDir",
|
|
24
|
+
"permissionMode"
|
|
25
|
+
];
|
|
26
|
+
function processIdentityFields(local) {
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const key of PROCESS_IDENTITY_FIELDS) {
|
|
29
|
+
const value = local[key];
|
|
30
|
+
if (value !== void 0) out[key] = value;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
function mergeReconnectMetadata(server, local, now) {
|
|
35
|
+
return {
|
|
36
|
+
...server,
|
|
37
|
+
...processIdentityFields(local),
|
|
38
|
+
lifecycleState: "running",
|
|
39
|
+
lifecycleStateSince: now,
|
|
40
|
+
archivedBy: void 0,
|
|
41
|
+
archiveReason: void 0
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function applyServerSnapshot(response, snapshot) {
|
|
45
|
+
return {
|
|
46
|
+
...response,
|
|
47
|
+
seq: snapshot.seq,
|
|
48
|
+
metadata: snapshot.metadata,
|
|
49
|
+
metadataVersion: snapshot.metadataVersion,
|
|
50
|
+
agentState: snapshot.agentState,
|
|
51
|
+
agentStateVersion: snapshot.agentStateVersion
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function withoutServerSnapshot(response) {
|
|
55
|
+
return { ...response, metadataVersion: 0, agentStateVersion: 0 };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const runtime = {
|
|
59
|
+
signal: (pid, signal) => process.kill(pid, signal),
|
|
60
|
+
isAlive: (pid) => {
|
|
61
|
+
try {
|
|
62
|
+
process.kill(pid, 0);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
schedule: (callback, delayMs) => {
|
|
69
|
+
setTimeout(callback, delayMs);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function terminateProcess(pid, onSettled, impl = runtime, graceMs = 2e3) {
|
|
73
|
+
if (!impl.isAlive(pid)) {
|
|
74
|
+
onSettled(true);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
impl.signal(pid, "SIGTERM");
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
impl.schedule(() => {
|
|
83
|
+
if (impl.isAlive(pid)) {
|
|
84
|
+
try {
|
|
85
|
+
impl.signal(pid, "SIGKILL");
|
|
86
|
+
} catch {
|
|
87
|
+
onSettled(!impl.isAlive(pid));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
impl.schedule(() => onSettled(!impl.isAlive(pid)), 100);
|
|
92
|
+
}, graceMs);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const SESSION_LOCK_DIR = "session-locks";
|
|
97
|
+
const defaultRuntime = () => ({
|
|
98
|
+
dir: path.join(configuration.configuration.happyHomeDir, SESSION_LOCK_DIR),
|
|
99
|
+
selfPid: process.pid,
|
|
100
|
+
now: () => Date.now(),
|
|
101
|
+
signal: (pid, signal) => process.kill(pid, signal),
|
|
102
|
+
isAlive: (pid) => {
|
|
103
|
+
try {
|
|
104
|
+
process.kill(pid, 0);
|
|
105
|
+
return true;
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
schedule: (callback, delayMs) => {
|
|
111
|
+
setTimeout(callback, delayMs);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
function sessionLockPath(happySessionId, dir = path.join(configuration.configuration.happyHomeDir, SESSION_LOCK_DIR)) {
|
|
115
|
+
return path.join(dir, `${happySessionId}.json`);
|
|
116
|
+
}
|
|
117
|
+
function readSessionLock(happySessionId, dir) {
|
|
118
|
+
try {
|
|
119
|
+
const path = sessionLockPath(happySessionId, dir);
|
|
120
|
+
if (!node_fs.existsSync(path)) return null;
|
|
121
|
+
const parsed = JSON.parse(node_fs.readFileSync(path, "utf-8"));
|
|
122
|
+
if (typeof parsed?.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) return null;
|
|
123
|
+
return {
|
|
124
|
+
pid: parsed.pid,
|
|
125
|
+
startedAt: typeof parsed.startedAt === "number" ? parsed.startedAt : 0,
|
|
126
|
+
version: typeof parsed.version === "string" ? parsed.version : "",
|
|
127
|
+
...typeof parsed.flavor === "string" ? { flavor: parsed.flavor } : {}
|
|
128
|
+
};
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function liveSessionLockHolder(happySessionId, runtime = defaultRuntime()) {
|
|
134
|
+
const record = readSessionLock(happySessionId, runtime.dir);
|
|
135
|
+
if (!record || record.pid === runtime.selfPid) return null;
|
|
136
|
+
return runtime.isAlive(record.pid) ? record : null;
|
|
137
|
+
}
|
|
138
|
+
function writeSessionLock(happySessionId, record, dir) {
|
|
139
|
+
node_fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
140
|
+
const path = sessionLockPath(happySessionId, dir);
|
|
141
|
+
const tmp = `${path}.${record.pid}.tmp`;
|
|
142
|
+
configuration.writePrivateFileSync(tmp, JSON.stringify(record));
|
|
143
|
+
node_fs.renameSync(tmp, path);
|
|
144
|
+
}
|
|
145
|
+
function acquireSessionLock(happySessionId, options, runtime = defaultRuntime()) {
|
|
146
|
+
const record = () => ({
|
|
147
|
+
pid: runtime.selfPid,
|
|
148
|
+
startedAt: runtime.now(),
|
|
149
|
+
version: options.version,
|
|
150
|
+
...options.flavor ? { flavor: options.flavor } : {}
|
|
151
|
+
});
|
|
152
|
+
const holder = liveSessionLockHolder(happySessionId, runtime);
|
|
153
|
+
if (!holder) {
|
|
154
|
+
writeSessionLock(happySessionId, record(), runtime.dir);
|
|
155
|
+
return Promise.resolve({ ok: true, replaced: null });
|
|
156
|
+
}
|
|
157
|
+
if (!options.takeover) {
|
|
158
|
+
return Promise.resolve({ ok: false, holder });
|
|
159
|
+
}
|
|
160
|
+
return new Promise((resolve) => {
|
|
161
|
+
const requested = terminateProcess(holder.pid, (stopped) => {
|
|
162
|
+
if (!stopped) {
|
|
163
|
+
resolve({ ok: false, holder });
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
writeSessionLock(happySessionId, record(), runtime.dir);
|
|
167
|
+
resolve({ ok: true, replaced: holder });
|
|
168
|
+
}, runtime, options.graceMs ?? 2e3);
|
|
169
|
+
if (!requested) resolve({ ok: false, holder });
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function releaseSessionLock(happySessionId, runtime = defaultRuntime()) {
|
|
173
|
+
try {
|
|
174
|
+
const record = readSessionLock(happySessionId, runtime.dir);
|
|
175
|
+
if (record && record.pid === runtime.selfPid) {
|
|
176
|
+
node_fs.unlinkSync(sessionLockPath(happySessionId, runtime.dir));
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function claimSessionOrExit(happySessionId, options) {
|
|
182
|
+
const result = await acquireSessionLock(happySessionId, {
|
|
183
|
+
takeover: options.takeover,
|
|
184
|
+
version: configuration.configuration.currentCliVersion,
|
|
185
|
+
flavor: options.flavor
|
|
186
|
+
});
|
|
187
|
+
if (!result.ok) {
|
|
188
|
+
const message = `Session ${happySessionId} is already run by very-happy pid ${result.holder.pid} (v${result.holder.version || "?"}); this process yields.`;
|
|
189
|
+
logger.logger.debug(`[SESSION LOCK] ${message}`);
|
|
190
|
+
console.error(message);
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
if (result.replaced) {
|
|
194
|
+
logger.logger.debug(`[SESSION LOCK] Took over session ${happySessionId} from pid ${result.replaced.pid} (v${result.replaced.version || "?"})`);
|
|
195
|
+
}
|
|
196
|
+
process.on("exit", () => releaseSessionLock(happySessionId));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
exports.applyServerSnapshot = applyServerSnapshot;
|
|
200
|
+
exports.claimSessionOrExit = claimSessionOrExit;
|
|
201
|
+
exports.mergeReconnectMetadata = mergeReconnectMetadata;
|
|
202
|
+
exports.processIdentityFields = processIdentityFields;
|
|
203
|
+
exports.readSessionLock = readSessionLock;
|
|
204
|
+
exports.terminateProcess = terminateProcess;
|
|
205
|
+
exports.withoutServerSnapshot = withoutServerSnapshot;
|