@zonease/happy 0.6.38 → 0.6.39
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/{types-BWpRU1VN.mjs → api-CIZKyfSn.mjs} +136 -1725
- package/dist/{types-DvTsFaqR.cjs → api-CwOCQeLH.cjs} +317 -1961
- package/dist/auth-0VnlXT9f.mjs +181 -0
- package/dist/auth-BE8dD20T.mjs +270 -0
- package/dist/auth-BIleVCw0.cjs +274 -0
- package/dist/auth-Cas9DR3b.cjs +183 -0
- package/dist/browser-Cc08m7of.cjs +22 -0
- package/dist/browser-Dll5hxOV.mjs +20 -0
- package/dist/claudeLocal-CPT3zcIU.mjs +344 -0
- package/dist/claudeLocal-WkgLbl1a.cjs +352 -0
- package/dist/codex/happyMcpStdioBridge.cjs +4 -1
- package/dist/codex/happyMcpStdioBridge.mjs +4 -1
- package/dist/common-BdNgghUJ.mjs +75 -0
- package/dist/common-CZvSRU4p.cjs +83 -0
- package/dist/{config-0Xnc8ARM.cjs → config-Draa-5XC.cjs} +21 -34
- package/dist/{config-_GW4YBCP.mjs → config-rTT4qTU5.mjs} +7 -20
- package/dist/connect-BfC1tYP_.mjs +679 -0
- package/dist/connect-CJZ73syK.cjs +681 -0
- package/dist/{constants-B4x1K7by.cjs → constants-E_UWYTo3.cjs} +2 -2
- package/dist/{constants-BO9OAFOT.mjs → constants-pmXZGibX.mjs} +1 -1
- package/dist/controlClient-CM_QcJnB.mjs +159 -0
- package/dist/controlClient-DZMIsKsT.cjs +168 -0
- package/dist/deviceExec-CU1R9CRb.cjs +84 -0
- package/dist/deviceExec-D93AOVV4.mjs +81 -0
- package/dist/diagnose-B3D1a_v0.cjs +60 -0
- package/dist/diagnose-CuRKXxt1.mjs +58 -0
- package/dist/doctor-C08m5Z9f.cjs +78 -0
- package/dist/doctor-Cya9YSA0.cjs +214 -0
- package/dist/doctor-DZYB6IVc.mjs +211 -0
- package/dist/doctor-ON7pN5G5.mjs +74 -0
- package/dist/downloadImage-DfSFLb-h.mjs +887 -0
- package/dist/downloadImage-MQMrUUxS.cjs +897 -0
- package/dist/enroll-BxlbVftP.cjs +115 -0
- package/dist/enroll-C52x8oly.mjs +113 -0
- package/dist/evidence-Ccd9iYcj.mjs +127 -0
- package/dist/evidence-CjWBG5rX.cjs +129 -0
- package/dist/formatImageMessage-B4lSxlk2.mjs +43 -0
- package/dist/formatImageMessage-DbyG8c2j.cjs +46 -0
- package/dist/github-DKcyF1zp.mjs +57 -0
- package/dist/github-h9GhJJ_C.cjs +59 -0
- package/dist/index-CDTNgfUf.cjs +578 -0
- package/dist/index-CPKdvi7J.mjs +554 -0
- package/dist/index.cjs +812 -40
- package/dist/index.mjs +813 -40
- package/dist/install-BokHRgFW.cjs +98 -0
- package/dist/install-C_cp8b2-.cjs +96 -0
- package/dist/install-DAeCn8ae.mjs +94 -0
- package/dist/install-oRAjVLjP.mjs +95 -0
- package/dist/lib.cjs +18 -12
- package/dist/lib.mjs +13 -7
- package/dist/{loadableToolOutput-va3RLplh.mjs → loadableToolOutput-Cuygmd4K.mjs} +4 -2
- package/dist/{loadableToolOutput-5vdQB98M.cjs → loadableToolOutput-DDCJjywt.cjs} +63 -61
- package/dist/mcpToolSchemas-CjeTfrAs.mjs +62 -0
- package/dist/mcpToolSchemas-DK42yoZ_.cjs +64 -0
- package/dist/persistence-BFjvtUad.mjs +944 -0
- package/dist/persistence-JLUn_Nou.cjs +996 -0
- package/dist/projectPath-BTBIMB57.mjs +10 -0
- package/dist/projectPath-ChLhdOyt.cjs +13 -0
- package/dist/prompt-BTWDZZO0.cjs +25 -0
- package/dist/prompt-BsAZsPbo.mjs +22 -0
- package/dist/release-BMEzNONE.mjs +168 -0
- package/dist/release-EhOJeAZe.cjs +171 -0
- package/dist/run-BDn2aE3Q.cjs +2152 -0
- package/dist/run-DBZqN6Kn.mjs +2128 -0
- package/dist/runClaude-DNHvERX0.mjs +3763 -0
- package/dist/runClaude-uaKxPZPm.cjs +3766 -0
- package/dist/{runCodex-skq0j05d.cjs → runCodex-82UrZLme.cjs} +169 -153
- package/dist/{runCodex-DiKzBf9q.mjs → runCodex-C-RTM0wA.mjs} +36 -20
- package/dist/{runGemini-BeqrVq8R.mjs → runGemini-Bm6-4Xm2.mjs} +34 -17
- package/dist/{runGemini-CVp-CEIh.cjs → runGemini-CXMr4Jwb.cjs} +212 -195
- package/dist/{runOneShot-BQNkNW61.cjs → runOneShot-1B6RMYT7.cjs} +31 -55
- package/dist/{runOneShot-ltCpHbBB.mjs → runOneShot-ioARRHrx.mjs} +14 -38
- package/dist/{serve-BECdtivY.cjs → serve-BtdAhkIE.cjs} +35 -58
- package/dist/{serve-BNdoEDVx.mjs → serve-CwibwU6N.mjs} +12 -35
- package/dist/session-2sU4Bu1R.mjs +108 -0
- package/dist/session-D--F87zL.cjs +110 -0
- package/dist/sessionKeyCache-4Tp8pJ3N.cjs +36 -0
- package/dist/sessionKeyCache-D2BwO91C.mjs +33 -0
- package/dist/sessionList-BCSw3qYt.mjs +51 -0
- package/dist/sessionList-ICJsjNtJ.cjs +54 -0
- package/dist/sessionSummary-Bkz52BPh.cjs +393 -0
- package/dist/sessionSummary-lHGBWQSb.mjs +387 -0
- package/dist/spawnHappyCLI-CoORX0YW.mjs +66 -0
- package/dist/spawnHappyCLI-D_V-AAoP.cjs +70 -0
- package/dist/ssh-BTOlhhxi.cjs +333 -0
- package/dist/ssh-DL_AkRAD.mjs +328 -0
- package/dist/systemdScope-BeOtAkhk.mjs +25 -0
- package/dist/systemdScope-DW3-UoKB.cjs +29 -0
- package/dist/task-CQzrHRZe.mjs +165 -0
- package/dist/task-CSpaC9HX.cjs +167 -0
- package/dist/time-Bj2HRkKH.cjs +33 -0
- package/dist/time-DgsBLJA_.mjs +29 -0
- package/dist/trimIdent-4gvk9Wk6.cjs +22 -0
- package/dist/trimIdent-BAjmv_TG.mjs +20 -0
- package/dist/types-BoJh4Qfv.mjs +57 -0
- package/dist/types-DWeB7aqh.cjs +59 -0
- package/dist/uninstall-BiKyRAb1.cjs +77 -0
- package/dist/uninstall-nFMFGUVM.mjs +75 -0
- package/dist/update-BcqRHXf2.mjs +109 -0
- package/dist/update-Dw8pgsAT.cjs +111 -0
- package/dist/workflowCommon-B0d01Y_a.mjs +56 -0
- package/dist/workflowCommon-Bl568-rt.cjs +62 -0
- package/dist/workflowReports-BEWuQesA.cjs +113 -0
- package/dist/workflowReports-Dd8-4-8f.mjs +108 -0
- package/package.json +1 -1
- package/dist/index-BbCDDvoB.cjs +0 -11376
- package/dist/index-vvZXdswy.mjs +0 -11336
- package/dist/mcpToolSchemas-CdPXJQmb.mjs +0 -175
- package/dist/mcpToolSchemas-f-kttjFw.cjs +0 -186
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { l as logger, j as readDaemonState, n as clearDaemonState } from './persistence-BFjvtUad.mjs';
|
|
2
|
+
import { p as projectPath } from './projectPath-BTBIMB57.mjs';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import 'node:fs/promises';
|
|
6
|
+
import 'node:fs';
|
|
7
|
+
import 'node:os';
|
|
8
|
+
import 'node:path';
|
|
9
|
+
import 'zod';
|
|
10
|
+
import 'node:crypto';
|
|
11
|
+
import 'tweetnacl';
|
|
12
|
+
import 'chalk';
|
|
13
|
+
import 'url';
|
|
14
|
+
|
|
15
|
+
async function daemonPost(path, body) {
|
|
16
|
+
const state = await readDaemonState();
|
|
17
|
+
if (!state?.httpPort) {
|
|
18
|
+
const errorMessage = "No daemon running, no state file found";
|
|
19
|
+
logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
20
|
+
return {
|
|
21
|
+
error: errorMessage
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
process.kill(state.pid, 0);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
const errorMessage = "Daemon is not running, file is stale";
|
|
28
|
+
logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
29
|
+
return {
|
|
30
|
+
error: errorMessage
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const timeout = process.env.HAPPY_DAEMON_HTTP_TIMEOUT ? parseInt(process.env.HAPPY_DAEMON_HTTP_TIMEOUT) : 1e4;
|
|
35
|
+
const response = await fetch(`http://127.0.0.1:${state.httpPort}${path}`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify(body || {}),
|
|
39
|
+
// Mostly increased for stress test
|
|
40
|
+
signal: AbortSignal.timeout(timeout)
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
const errorMessage = `Request failed: ${path}, HTTP ${response.status}`;
|
|
44
|
+
logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
45
|
+
return {
|
|
46
|
+
error: errorMessage
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return await response.json();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const errorMessage = `Request failed: ${path}, ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
52
|
+
logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
53
|
+
return {
|
|
54
|
+
error: errorMessage
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function notifyDaemonSessionStarted(sessionId, metadata) {
|
|
59
|
+
return await daemonPost("/session-started", {
|
|
60
|
+
sessionId,
|
|
61
|
+
metadata
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function listDaemonSessions() {
|
|
65
|
+
const result = await daemonPost("/list");
|
|
66
|
+
return result.children || [];
|
|
67
|
+
}
|
|
68
|
+
async function stopDaemonSession(sessionId) {
|
|
69
|
+
const result = await daemonPost("/stop-session", { sessionId });
|
|
70
|
+
return result.success || false;
|
|
71
|
+
}
|
|
72
|
+
async function stopDaemonHttp() {
|
|
73
|
+
await daemonPost("/stop");
|
|
74
|
+
}
|
|
75
|
+
async function checkIfDaemonRunningAndCleanupStaleState() {
|
|
76
|
+
const state = await readDaemonState();
|
|
77
|
+
if (!state) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
process.kill(state.pid, 0);
|
|
82
|
+
return true;
|
|
83
|
+
} catch {
|
|
84
|
+
logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
|
|
85
|
+
await cleanupDaemonState();
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function isDaemonRunningCurrentlyInstalledHappyVersion() {
|
|
90
|
+
logger.debug("[DAEMON CONTROL] Checking if daemon is running same version");
|
|
91
|
+
const runningDaemon = await checkIfDaemonRunningAndCleanupStaleState();
|
|
92
|
+
if (!runningDaemon) {
|
|
93
|
+
logger.debug("[DAEMON CONTROL] No daemon running, returning false");
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const state = await readDaemonState();
|
|
97
|
+
if (!state) {
|
|
98
|
+
logger.debug("[DAEMON CONTROL] No daemon state found, returning false");
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const packageJsonPath = join(projectPath(), "package.json");
|
|
103
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
104
|
+
const currentCliVersion = packageJson.version;
|
|
105
|
+
logger.debug(`[DAEMON CONTROL] Current CLI version: ${currentCliVersion}, Daemon started with version: ${state.startedWithCliVersion}`);
|
|
106
|
+
return currentCliVersion === state.startedWithCliVersion;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
logger.debug("[DAEMON CONTROL] Error checking daemon version", error);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function cleanupDaemonState() {
|
|
113
|
+
try {
|
|
114
|
+
await clearDaemonState();
|
|
115
|
+
logger.debug("[DAEMON RUN] Daemon state file removed");
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logger.debug("[DAEMON RUN] Error cleaning up daemon metadata", error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function stopDaemon() {
|
|
121
|
+
try {
|
|
122
|
+
const state = await readDaemonState();
|
|
123
|
+
if (!state) {
|
|
124
|
+
logger.debug("No daemon state found");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
logger.debug(`Stopping daemon with PID ${state.pid}`);
|
|
128
|
+
try {
|
|
129
|
+
await stopDaemonHttp();
|
|
130
|
+
await waitForProcessDeath(state.pid, 2e3);
|
|
131
|
+
logger.debug("Daemon stopped gracefully via HTTP");
|
|
132
|
+
return;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
logger.debug("HTTP stop failed, will force kill", error);
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
process.kill(state.pid, "SIGKILL");
|
|
138
|
+
logger.debug("Force killed daemon");
|
|
139
|
+
} catch (error) {
|
|
140
|
+
logger.debug("Daemon already dead");
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
logger.debug("Error stopping daemon", error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function waitForProcessDeath(pid, timeout) {
|
|
147
|
+
const start = Date.now();
|
|
148
|
+
while (Date.now() - start < timeout) {
|
|
149
|
+
try {
|
|
150
|
+
process.kill(pid, 0);
|
|
151
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
152
|
+
} catch {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
throw new Error("Process did not die within timeout");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { checkIfDaemonRunningAndCleanupStaleState, cleanupDaemonState, isDaemonRunningCurrentlyInstalledHappyVersion, listDaemonSessions, notifyDaemonSessionStarted, stopDaemon, stopDaemonHttp, stopDaemonSession };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var logger = require('./persistence-JLUn_Nou.cjs');
|
|
4
|
+
var projectPath = require('./projectPath-ChLhdOyt.cjs');
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
require('node:fs/promises');
|
|
8
|
+
require('node:fs');
|
|
9
|
+
require('node:os');
|
|
10
|
+
require('node:path');
|
|
11
|
+
require('zod');
|
|
12
|
+
require('node:crypto');
|
|
13
|
+
require('tweetnacl');
|
|
14
|
+
require('chalk');
|
|
15
|
+
require('url');
|
|
16
|
+
|
|
17
|
+
async function daemonPost(path, body) {
|
|
18
|
+
const state = await logger.readDaemonState();
|
|
19
|
+
if (!state?.httpPort) {
|
|
20
|
+
const errorMessage = "No daemon running, no state file found";
|
|
21
|
+
logger.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
22
|
+
return {
|
|
23
|
+
error: errorMessage
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
process.kill(state.pid, 0);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
const errorMessage = "Daemon is not running, file is stale";
|
|
30
|
+
logger.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
31
|
+
return {
|
|
32
|
+
error: errorMessage
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const timeout = process.env.HAPPY_DAEMON_HTTP_TIMEOUT ? parseInt(process.env.HAPPY_DAEMON_HTTP_TIMEOUT) : 1e4;
|
|
37
|
+
const response = await fetch(`http://127.0.0.1:${state.httpPort}${path}`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "Content-Type": "application/json" },
|
|
40
|
+
body: JSON.stringify(body || {}),
|
|
41
|
+
// Mostly increased for stress test
|
|
42
|
+
signal: AbortSignal.timeout(timeout)
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const errorMessage = `Request failed: ${path}, HTTP ${response.status}`;
|
|
46
|
+
logger.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
47
|
+
return {
|
|
48
|
+
error: errorMessage
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return await response.json();
|
|
52
|
+
} catch (error) {
|
|
53
|
+
const errorMessage = `Request failed: ${path}, ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
54
|
+
logger.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
55
|
+
return {
|
|
56
|
+
error: errorMessage
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function notifyDaemonSessionStarted(sessionId, metadata) {
|
|
61
|
+
return await daemonPost("/session-started", {
|
|
62
|
+
sessionId,
|
|
63
|
+
metadata
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function listDaemonSessions() {
|
|
67
|
+
const result = await daemonPost("/list");
|
|
68
|
+
return result.children || [];
|
|
69
|
+
}
|
|
70
|
+
async function stopDaemonSession(sessionId) {
|
|
71
|
+
const result = await daemonPost("/stop-session", { sessionId });
|
|
72
|
+
return result.success || false;
|
|
73
|
+
}
|
|
74
|
+
async function stopDaemonHttp() {
|
|
75
|
+
await daemonPost("/stop");
|
|
76
|
+
}
|
|
77
|
+
async function checkIfDaemonRunningAndCleanupStaleState() {
|
|
78
|
+
const state = await logger.readDaemonState();
|
|
79
|
+
if (!state) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
process.kill(state.pid, 0);
|
|
84
|
+
return true;
|
|
85
|
+
} catch {
|
|
86
|
+
logger.logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
|
|
87
|
+
await cleanupDaemonState();
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function isDaemonRunningCurrentlyInstalledHappyVersion() {
|
|
92
|
+
logger.logger.debug("[DAEMON CONTROL] Checking if daemon is running same version");
|
|
93
|
+
const runningDaemon = await checkIfDaemonRunningAndCleanupStaleState();
|
|
94
|
+
if (!runningDaemon) {
|
|
95
|
+
logger.logger.debug("[DAEMON CONTROL] No daemon running, returning false");
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const state = await logger.readDaemonState();
|
|
99
|
+
if (!state) {
|
|
100
|
+
logger.logger.debug("[DAEMON CONTROL] No daemon state found, returning false");
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const packageJsonPath = path.join(projectPath.projectPath(), "package.json");
|
|
105
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
106
|
+
const currentCliVersion = packageJson.version;
|
|
107
|
+
logger.logger.debug(`[DAEMON CONTROL] Current CLI version: ${currentCliVersion}, Daemon started with version: ${state.startedWithCliVersion}`);
|
|
108
|
+
return currentCliVersion === state.startedWithCliVersion;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
logger.logger.debug("[DAEMON CONTROL] Error checking daemon version", error);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function cleanupDaemonState() {
|
|
115
|
+
try {
|
|
116
|
+
await logger.clearDaemonState();
|
|
117
|
+
logger.logger.debug("[DAEMON RUN] Daemon state file removed");
|
|
118
|
+
} catch (error) {
|
|
119
|
+
logger.logger.debug("[DAEMON RUN] Error cleaning up daemon metadata", error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function stopDaemon() {
|
|
123
|
+
try {
|
|
124
|
+
const state = await logger.readDaemonState();
|
|
125
|
+
if (!state) {
|
|
126
|
+
logger.logger.debug("No daemon state found");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
logger.logger.debug(`Stopping daemon with PID ${state.pid}`);
|
|
130
|
+
try {
|
|
131
|
+
await stopDaemonHttp();
|
|
132
|
+
await waitForProcessDeath(state.pid, 2e3);
|
|
133
|
+
logger.logger.debug("Daemon stopped gracefully via HTTP");
|
|
134
|
+
return;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.logger.debug("HTTP stop failed, will force kill", error);
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
process.kill(state.pid, "SIGKILL");
|
|
140
|
+
logger.logger.debug("Force killed daemon");
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.logger.debug("Daemon already dead");
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
logger.logger.debug("Error stopping daemon", error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function waitForProcessDeath(pid, timeout) {
|
|
149
|
+
const start = Date.now();
|
|
150
|
+
while (Date.now() - start < timeout) {
|
|
151
|
+
try {
|
|
152
|
+
process.kill(pid, 0);
|
|
153
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
154
|
+
} catch {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
throw new Error("Process did not die within timeout");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
exports.checkIfDaemonRunningAndCleanupStaleState = checkIfDaemonRunningAndCleanupStaleState;
|
|
162
|
+
exports.cleanupDaemonState = cleanupDaemonState;
|
|
163
|
+
exports.isDaemonRunningCurrentlyInstalledHappyVersion = isDaemonRunningCurrentlyInstalledHappyVersion;
|
|
164
|
+
exports.listDaemonSessions = listDaemonSessions;
|
|
165
|
+
exports.notifyDaemonSessionStarted = notifyDaemonSessionStarted;
|
|
166
|
+
exports.stopDaemon = stopDaemon;
|
|
167
|
+
exports.stopDaemonHttp = stopDaemonHttp;
|
|
168
|
+
exports.stopDaemonSession = stopDaemonSession;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var axios = require('axios');
|
|
4
|
+
var logger = require('./persistence-JLUn_Nou.cjs');
|
|
5
|
+
var auth = require('./auth-BIleVCw0.cjs');
|
|
6
|
+
|
|
7
|
+
function resolveMachineKey(credentials, dataEncryptionKey) {
|
|
8
|
+
if (credentials.encryption.type === "legacy") {
|
|
9
|
+
return { key: credentials.encryption.secret, variant: "legacy" };
|
|
10
|
+
}
|
|
11
|
+
if (!dataEncryptionKey) return null;
|
|
12
|
+
const blob = logger.decodeBase64(dataEncryptionKey);
|
|
13
|
+
if (blob.length < 1 || blob[0] !== 0) return null;
|
|
14
|
+
const opened = auth.decryptWithEphemeralKey(blob.slice(1), credentials.encryption.machineKey);
|
|
15
|
+
return opened ? { key: opened, variant: "dataKey" } : null;
|
|
16
|
+
}
|
|
17
|
+
async function fetchMachines(credentials) {
|
|
18
|
+
const response = await axios.get(
|
|
19
|
+
`${logger.configuration.serverUrl}/v1/machines`,
|
|
20
|
+
{ headers: { Authorization: `Bearer ${credentials.token}` }, timeout: 15e3 }
|
|
21
|
+
);
|
|
22
|
+
const data = response.data;
|
|
23
|
+
return Array.isArray(data) ? data : data.machines ?? [];
|
|
24
|
+
}
|
|
25
|
+
async function listDevices(credentials) {
|
|
26
|
+
const rows = await fetchMachines(credentials);
|
|
27
|
+
return rows.map((row) => {
|
|
28
|
+
const resolved = resolveMachineKey(credentials, row.dataEncryptionKey);
|
|
29
|
+
let metadata = null;
|
|
30
|
+
if (resolved && row.metadata) {
|
|
31
|
+
try {
|
|
32
|
+
metadata = logger.decrypt(resolved.key, resolved.variant, logger.decodeBase64(row.metadata));
|
|
33
|
+
} catch {
|
|
34
|
+
metadata = null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
id: row.id,
|
|
39
|
+
name: row.displayName || metadata?.displayName || metadata?.host || row.id.slice(0, 12),
|
|
40
|
+
description: row.description ?? null,
|
|
41
|
+
platform: metadata?.platform ?? null,
|
|
42
|
+
active: row.active,
|
|
43
|
+
lastActiveAt: row.activeAt ?? row.lastActiveAt ?? 0
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function deviceExec(socket, credentials, deviceId, command, options = {}) {
|
|
48
|
+
const row = options.skipMachineLookup ? null : await (async () => {
|
|
49
|
+
const rows = await fetchMachines(credentials);
|
|
50
|
+
const found = rows.find((candidate) => candidate.id === deviceId);
|
|
51
|
+
if (!found) {
|
|
52
|
+
throw new Error(`Device ${deviceId} not found`);
|
|
53
|
+
}
|
|
54
|
+
if (!found.active) {
|
|
55
|
+
throw new Error(`Device ${deviceId} is offline`);
|
|
56
|
+
}
|
|
57
|
+
return found;
|
|
58
|
+
})();
|
|
59
|
+
const resolved = options.deviceKeyBase64 ? { key: logger.decodeBase64(options.deviceKeyBase64), variant: options.deviceKeyVariant ?? "dataKey" } : resolveMachineKey(credentials, row.dataEncryptionKey);
|
|
60
|
+
if (!resolved) {
|
|
61
|
+
throw new Error(`Cannot resolve encryption key for device ${deviceId}. Re-select the device in the app.`);
|
|
62
|
+
}
|
|
63
|
+
const timeout = options.timeout ?? 6e4;
|
|
64
|
+
const params = logger.encodeBase64(logger.encrypt(resolved.key, resolved.variant, {
|
|
65
|
+
command,
|
|
66
|
+
cwd: options.cwd,
|
|
67
|
+
timeout
|
|
68
|
+
}));
|
|
69
|
+
const answer = await socket.timeout(timeout + 5e3).emitWithAck("rpc-call", {
|
|
70
|
+
method: `${deviceId}:bash`,
|
|
71
|
+
params
|
|
72
|
+
});
|
|
73
|
+
if (!answer?.ok) {
|
|
74
|
+
throw new Error(answer?.error || "Device RPC failed");
|
|
75
|
+
}
|
|
76
|
+
const decrypted = logger.decrypt(resolved.key, resolved.variant, logger.decodeBase64(answer.result));
|
|
77
|
+
if (!decrypted) {
|
|
78
|
+
throw new Error("Failed to decrypt device response");
|
|
79
|
+
}
|
|
80
|
+
return decrypted;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
exports.deviceExec = deviceExec;
|
|
84
|
+
exports.listDevices = listDevices;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { e as decodeBase64, f as encodeBase64, g as encrypt, h as decrypt, c as configuration } from './persistence-BFjvtUad.mjs';
|
|
3
|
+
import { decryptWithEphemeralKey } from './auth-BE8dD20T.mjs';
|
|
4
|
+
|
|
5
|
+
function resolveMachineKey(credentials, dataEncryptionKey) {
|
|
6
|
+
if (credentials.encryption.type === "legacy") {
|
|
7
|
+
return { key: credentials.encryption.secret, variant: "legacy" };
|
|
8
|
+
}
|
|
9
|
+
if (!dataEncryptionKey) return null;
|
|
10
|
+
const blob = decodeBase64(dataEncryptionKey);
|
|
11
|
+
if (blob.length < 1 || blob[0] !== 0) return null;
|
|
12
|
+
const opened = decryptWithEphemeralKey(blob.slice(1), credentials.encryption.machineKey);
|
|
13
|
+
return opened ? { key: opened, variant: "dataKey" } : null;
|
|
14
|
+
}
|
|
15
|
+
async function fetchMachines(credentials) {
|
|
16
|
+
const response = await axios.get(
|
|
17
|
+
`${configuration.serverUrl}/v1/machines`,
|
|
18
|
+
{ headers: { Authorization: `Bearer ${credentials.token}` }, timeout: 15e3 }
|
|
19
|
+
);
|
|
20
|
+
const data = response.data;
|
|
21
|
+
return Array.isArray(data) ? data : data.machines ?? [];
|
|
22
|
+
}
|
|
23
|
+
async function listDevices(credentials) {
|
|
24
|
+
const rows = await fetchMachines(credentials);
|
|
25
|
+
return rows.map((row) => {
|
|
26
|
+
const resolved = resolveMachineKey(credentials, row.dataEncryptionKey);
|
|
27
|
+
let metadata = null;
|
|
28
|
+
if (resolved && row.metadata) {
|
|
29
|
+
try {
|
|
30
|
+
metadata = decrypt(resolved.key, resolved.variant, decodeBase64(row.metadata));
|
|
31
|
+
} catch {
|
|
32
|
+
metadata = null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id: row.id,
|
|
37
|
+
name: row.displayName || metadata?.displayName || metadata?.host || row.id.slice(0, 12),
|
|
38
|
+
description: row.description ?? null,
|
|
39
|
+
platform: metadata?.platform ?? null,
|
|
40
|
+
active: row.active,
|
|
41
|
+
lastActiveAt: row.activeAt ?? row.lastActiveAt ?? 0
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function deviceExec(socket, credentials, deviceId, command, options = {}) {
|
|
46
|
+
const row = options.skipMachineLookup ? null : await (async () => {
|
|
47
|
+
const rows = await fetchMachines(credentials);
|
|
48
|
+
const found = rows.find((candidate) => candidate.id === deviceId);
|
|
49
|
+
if (!found) {
|
|
50
|
+
throw new Error(`Device ${deviceId} not found`);
|
|
51
|
+
}
|
|
52
|
+
if (!found.active) {
|
|
53
|
+
throw new Error(`Device ${deviceId} is offline`);
|
|
54
|
+
}
|
|
55
|
+
return found;
|
|
56
|
+
})();
|
|
57
|
+
const resolved = options.deviceKeyBase64 ? { key: decodeBase64(options.deviceKeyBase64), variant: options.deviceKeyVariant ?? "dataKey" } : resolveMachineKey(credentials, row.dataEncryptionKey);
|
|
58
|
+
if (!resolved) {
|
|
59
|
+
throw new Error(`Cannot resolve encryption key for device ${deviceId}. Re-select the device in the app.`);
|
|
60
|
+
}
|
|
61
|
+
const timeout = options.timeout ?? 6e4;
|
|
62
|
+
const params = encodeBase64(encrypt(resolved.key, resolved.variant, {
|
|
63
|
+
command,
|
|
64
|
+
cwd: options.cwd,
|
|
65
|
+
timeout
|
|
66
|
+
}));
|
|
67
|
+
const answer = await socket.timeout(timeout + 5e3).emitWithAck("rpc-call", {
|
|
68
|
+
method: `${deviceId}:bash`,
|
|
69
|
+
params
|
|
70
|
+
});
|
|
71
|
+
if (!answer?.ok) {
|
|
72
|
+
throw new Error(answer?.error || "Device RPC failed");
|
|
73
|
+
}
|
|
74
|
+
const decrypted = decrypt(resolved.key, resolved.variant, decodeBase64(answer.result));
|
|
75
|
+
if (!decrypted) {
|
|
76
|
+
throw new Error("Failed to decrypt device response");
|
|
77
|
+
}
|
|
78
|
+
return decrypted;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { deviceExec as d, listDevices as l };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chalk = require('chalk');
|
|
4
|
+
var workflowReports = require('./workflowReports-BEWuQesA.cjs');
|
|
5
|
+
var workflowCommon = require('./workflowCommon-Bl568-rt.cjs');
|
|
6
|
+
require('node:path');
|
|
7
|
+
require('node:fs/promises');
|
|
8
|
+
|
|
9
|
+
function printDiagnoseHelp() {
|
|
10
|
+
console.log(`
|
|
11
|
+
${chalk.bold("happy diagnose")} - Diagnosis evidence helpers
|
|
12
|
+
|
|
13
|
+
${chalk.bold("Usage:")}
|
|
14
|
+
happy diagnose remote --host <host> [options]
|
|
15
|
+
|
|
16
|
+
${chalk.bold("Options:")}
|
|
17
|
+
--host <host> SSH host alias or user@host
|
|
18
|
+
--service <name> Service/container name for docker logs
|
|
19
|
+
--keyword <text> Keyword to grep in logs
|
|
20
|
+
--since <duration> Log window, e.g. "2 hours ago" or "2026-05-13 10:00"
|
|
21
|
+
--health-url <url> Health endpoint to curl on the host
|
|
22
|
+
--out <path> Write the evidence pack to a markdown file
|
|
23
|
+
--json Print machine-readable JSON
|
|
24
|
+
-h, --help Show this help
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
async function handleDiagnoseCommand(args) {
|
|
28
|
+
const action = args[0];
|
|
29
|
+
if (!action || action === "-h" || action === "--help") {
|
|
30
|
+
printDiagnoseHelp();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (action !== "remote") {
|
|
34
|
+
throw new Error(`Unknown diagnose command: ${action}. Use "happy diagnose remote".`);
|
|
35
|
+
}
|
|
36
|
+
const parsed = workflowCommon.parseWorkflowArgs(args.slice(1), /* @__PURE__ */ new Set(["--host", "--service", "--keyword", "--since", "--health-url", "--out"]));
|
|
37
|
+
if (workflowCommon.booleanFlag(parsed, "--help") || workflowCommon.booleanFlag(parsed, "-h")) {
|
|
38
|
+
printDiagnoseHelp();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const host = workflowCommon.stringFlag(parsed, "--host") ?? parsed.positionals[0];
|
|
42
|
+
if (!host) {
|
|
43
|
+
throw new Error("--host is required.");
|
|
44
|
+
}
|
|
45
|
+
const input = {
|
|
46
|
+
host,
|
|
47
|
+
service: workflowCommon.stringFlag(parsed, "--service"),
|
|
48
|
+
keyword: workflowCommon.stringFlag(parsed, "--keyword"),
|
|
49
|
+
since: workflowCommon.stringFlag(parsed, "--since") ?? "2 hours ago",
|
|
50
|
+
healthUrl: workflowCommon.stringFlag(parsed, "--health-url")
|
|
51
|
+
};
|
|
52
|
+
const report = workflowReports.buildRemoteDiagnoseReport(input);
|
|
53
|
+
if (workflowCommon.booleanFlag(parsed, "--json")) {
|
|
54
|
+
console.log(JSON.stringify({ ...input, report }, null, 2));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await workflowCommon.printOrWriteOutput(report, workflowCommon.stringFlag(parsed, "--out"));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
exports.handleDiagnoseCommand = handleDiagnoseCommand;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { a as buildRemoteDiagnoseReport } from './workflowReports-Dd8-4-8f.mjs';
|
|
3
|
+
import { p as parseWorkflowArgs, b as booleanFlag, s as stringFlag, a as printOrWriteOutput } from './workflowCommon-B0d01Y_a.mjs';
|
|
4
|
+
import 'node:path';
|
|
5
|
+
import 'node:fs/promises';
|
|
6
|
+
|
|
7
|
+
function printDiagnoseHelp() {
|
|
8
|
+
console.log(`
|
|
9
|
+
${chalk.bold("happy diagnose")} - Diagnosis evidence helpers
|
|
10
|
+
|
|
11
|
+
${chalk.bold("Usage:")}
|
|
12
|
+
happy diagnose remote --host <host> [options]
|
|
13
|
+
|
|
14
|
+
${chalk.bold("Options:")}
|
|
15
|
+
--host <host> SSH host alias or user@host
|
|
16
|
+
--service <name> Service/container name for docker logs
|
|
17
|
+
--keyword <text> Keyword to grep in logs
|
|
18
|
+
--since <duration> Log window, e.g. "2 hours ago" or "2026-05-13 10:00"
|
|
19
|
+
--health-url <url> Health endpoint to curl on the host
|
|
20
|
+
--out <path> Write the evidence pack to a markdown file
|
|
21
|
+
--json Print machine-readable JSON
|
|
22
|
+
-h, --help Show this help
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
async function handleDiagnoseCommand(args) {
|
|
26
|
+
const action = args[0];
|
|
27
|
+
if (!action || action === "-h" || action === "--help") {
|
|
28
|
+
printDiagnoseHelp();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (action !== "remote") {
|
|
32
|
+
throw new Error(`Unknown diagnose command: ${action}. Use "happy diagnose remote".`);
|
|
33
|
+
}
|
|
34
|
+
const parsed = parseWorkflowArgs(args.slice(1), /* @__PURE__ */ new Set(["--host", "--service", "--keyword", "--since", "--health-url", "--out"]));
|
|
35
|
+
if (booleanFlag(parsed, "--help") || booleanFlag(parsed, "-h")) {
|
|
36
|
+
printDiagnoseHelp();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const host = stringFlag(parsed, "--host") ?? parsed.positionals[0];
|
|
40
|
+
if (!host) {
|
|
41
|
+
throw new Error("--host is required.");
|
|
42
|
+
}
|
|
43
|
+
const input = {
|
|
44
|
+
host,
|
|
45
|
+
service: stringFlag(parsed, "--service"),
|
|
46
|
+
keyword: stringFlag(parsed, "--keyword"),
|
|
47
|
+
since: stringFlag(parsed, "--since") ?? "2 hours ago",
|
|
48
|
+
healthUrl: stringFlag(parsed, "--health-url")
|
|
49
|
+
};
|
|
50
|
+
const report = buildRemoteDiagnoseReport(input);
|
|
51
|
+
if (booleanFlag(parsed, "--json")) {
|
|
52
|
+
console.log(JSON.stringify({ ...input, report }, null, 2));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await printOrWriteOutput(report, stringFlag(parsed, "--out"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { handleDiagnoseCommand };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var psList = require('ps-list');
|
|
4
|
+
var spawn = require('cross-spawn');
|
|
5
|
+
|
|
6
|
+
async function findAllHappyProcesses() {
|
|
7
|
+
try {
|
|
8
|
+
const processes = await psList();
|
|
9
|
+
const allProcesses = [];
|
|
10
|
+
for (const proc of processes) {
|
|
11
|
+
const cmd = proc.cmd || "";
|
|
12
|
+
const name = proc.name || "";
|
|
13
|
+
const isHappy = name.includes("happy") || name === "node" && (cmd.includes("happy-cli") || cmd.includes("dist/index.mjs")) || cmd.includes("happy.mjs") || cmd.includes("@zonease/happy") || cmd.includes("tsx") && cmd.includes("src/index.ts") && cmd.includes("happy-cli");
|
|
14
|
+
if (!isHappy) continue;
|
|
15
|
+
let type = "unknown";
|
|
16
|
+
if (proc.pid === process.pid) {
|
|
17
|
+
type = "current";
|
|
18
|
+
} else if (cmd.includes("--version")) {
|
|
19
|
+
type = cmd.includes("tsx") ? "dev-daemon-version-check" : "daemon-version-check";
|
|
20
|
+
} else if (cmd.includes("daemon start-sync") || cmd.includes("daemon start")) {
|
|
21
|
+
type = cmd.includes("tsx") ? "dev-daemon" : "daemon";
|
|
22
|
+
} else if (cmd.includes("--started-by daemon")) {
|
|
23
|
+
type = cmd.includes("tsx") ? "dev-daemon-spawned" : "daemon-spawned-session";
|
|
24
|
+
} else if (cmd.includes("doctor")) {
|
|
25
|
+
type = cmd.includes("tsx") ? "dev-doctor" : "doctor";
|
|
26
|
+
} else if (cmd.includes("--yolo")) {
|
|
27
|
+
type = "dev-session";
|
|
28
|
+
} else {
|
|
29
|
+
type = cmd.includes("tsx") ? "dev-related" : "user-session";
|
|
30
|
+
}
|
|
31
|
+
allProcesses.push({ pid: proc.pid, command: cmd || name, type });
|
|
32
|
+
}
|
|
33
|
+
return allProcesses;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function findRunawayHappyProcesses() {
|
|
39
|
+
const allProcesses = await findAllHappyProcesses();
|
|
40
|
+
return allProcesses.filter(
|
|
41
|
+
(p) => p.pid !== process.pid && (p.type === "daemon" || p.type === "dev-daemon" || p.type === "daemon-spawned-session" || p.type === "dev-daemon-spawned" || p.type === "daemon-version-check" || p.type === "dev-daemon-version-check")
|
|
42
|
+
).map((p) => ({ pid: p.pid, command: p.command }));
|
|
43
|
+
}
|
|
44
|
+
async function killRunawayHappyProcesses() {
|
|
45
|
+
const runawayProcesses = await findRunawayHappyProcesses();
|
|
46
|
+
const errors = [];
|
|
47
|
+
let killed = 0;
|
|
48
|
+
for (const { pid, command } of runawayProcesses) {
|
|
49
|
+
try {
|
|
50
|
+
console.log(`Killing runaway process PID ${pid}: ${command}`);
|
|
51
|
+
if (process.platform === "win32") {
|
|
52
|
+
const result = spawn.sync("taskkill", ["/F", "/PID", pid.toString()], { stdio: "pipe" });
|
|
53
|
+
if (result.error) throw result.error;
|
|
54
|
+
if (result.status !== 0) throw new Error(`taskkill exited with code ${result.status}`);
|
|
55
|
+
} else {
|
|
56
|
+
process.kill(pid, "SIGTERM");
|
|
57
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
58
|
+
const processes = await psList();
|
|
59
|
+
const stillAlive = processes.find((p) => p.pid === pid);
|
|
60
|
+
if (stillAlive) {
|
|
61
|
+
console.log(`Process PID ${pid} ignored SIGTERM, using SIGKILL`);
|
|
62
|
+
process.kill(pid, "SIGKILL");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
console.log(`Successfully killed runaway process PID ${pid}`);
|
|
66
|
+
killed++;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const errorMessage = error.message;
|
|
69
|
+
errors.push({ pid, error: errorMessage });
|
|
70
|
+
console.log(`Failed to kill process PID ${pid}: ${errorMessage}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { killed, errors };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
exports.findAllHappyProcesses = findAllHappyProcesses;
|
|
77
|
+
exports.findRunawayHappyProcesses = findRunawayHappyProcesses;
|
|
78
|
+
exports.killRunawayHappyProcesses = killRunawayHappyProcesses;
|