@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.
Files changed (109) hide show
  1. package/dist/{types-BWpRU1VN.mjs → api-CIZKyfSn.mjs} +136 -1725
  2. package/dist/{types-DvTsFaqR.cjs → api-CwOCQeLH.cjs} +317 -1961
  3. package/dist/auth-0VnlXT9f.mjs +181 -0
  4. package/dist/auth-BE8dD20T.mjs +270 -0
  5. package/dist/auth-BIleVCw0.cjs +274 -0
  6. package/dist/auth-Cas9DR3b.cjs +183 -0
  7. package/dist/browser-Cc08m7of.cjs +22 -0
  8. package/dist/browser-Dll5hxOV.mjs +20 -0
  9. package/dist/claudeLocal-CPT3zcIU.mjs +344 -0
  10. package/dist/claudeLocal-WkgLbl1a.cjs +352 -0
  11. package/dist/codex/happyMcpStdioBridge.cjs +4 -1
  12. package/dist/codex/happyMcpStdioBridge.mjs +4 -1
  13. package/dist/common-BdNgghUJ.mjs +75 -0
  14. package/dist/common-CZvSRU4p.cjs +83 -0
  15. package/dist/{config-0Xnc8ARM.cjs → config-Draa-5XC.cjs} +21 -34
  16. package/dist/{config-_GW4YBCP.mjs → config-rTT4qTU5.mjs} +7 -20
  17. package/dist/connect-BfC1tYP_.mjs +679 -0
  18. package/dist/connect-CJZ73syK.cjs +681 -0
  19. package/dist/{constants-B4x1K7by.cjs → constants-E_UWYTo3.cjs} +2 -2
  20. package/dist/{constants-BO9OAFOT.mjs → constants-pmXZGibX.mjs} +1 -1
  21. package/dist/controlClient-CM_QcJnB.mjs +159 -0
  22. package/dist/controlClient-DZMIsKsT.cjs +168 -0
  23. package/dist/deviceExec-CU1R9CRb.cjs +84 -0
  24. package/dist/deviceExec-D93AOVV4.mjs +81 -0
  25. package/dist/diagnose-B3D1a_v0.cjs +60 -0
  26. package/dist/diagnose-CuRKXxt1.mjs +58 -0
  27. package/dist/doctor-C08m5Z9f.cjs +78 -0
  28. package/dist/doctor-Cya9YSA0.cjs +214 -0
  29. package/dist/doctor-DZYB6IVc.mjs +211 -0
  30. package/dist/doctor-ON7pN5G5.mjs +74 -0
  31. package/dist/downloadImage-DfSFLb-h.mjs +887 -0
  32. package/dist/downloadImage-MQMrUUxS.cjs +897 -0
  33. package/dist/enroll-BxlbVftP.cjs +115 -0
  34. package/dist/enroll-C52x8oly.mjs +113 -0
  35. package/dist/evidence-Ccd9iYcj.mjs +127 -0
  36. package/dist/evidence-CjWBG5rX.cjs +129 -0
  37. package/dist/formatImageMessage-B4lSxlk2.mjs +43 -0
  38. package/dist/formatImageMessage-DbyG8c2j.cjs +46 -0
  39. package/dist/github-DKcyF1zp.mjs +57 -0
  40. package/dist/github-h9GhJJ_C.cjs +59 -0
  41. package/dist/index-CDTNgfUf.cjs +578 -0
  42. package/dist/index-CPKdvi7J.mjs +554 -0
  43. package/dist/index.cjs +812 -40
  44. package/dist/index.mjs +813 -40
  45. package/dist/install-BokHRgFW.cjs +98 -0
  46. package/dist/install-C_cp8b2-.cjs +96 -0
  47. package/dist/install-DAeCn8ae.mjs +94 -0
  48. package/dist/install-oRAjVLjP.mjs +95 -0
  49. package/dist/lib.cjs +18 -12
  50. package/dist/lib.mjs +13 -7
  51. package/dist/{loadableToolOutput-va3RLplh.mjs → loadableToolOutput-Cuygmd4K.mjs} +4 -2
  52. package/dist/{loadableToolOutput-5vdQB98M.cjs → loadableToolOutput-DDCJjywt.cjs} +63 -61
  53. package/dist/mcpToolSchemas-CjeTfrAs.mjs +62 -0
  54. package/dist/mcpToolSchemas-DK42yoZ_.cjs +64 -0
  55. package/dist/persistence-BFjvtUad.mjs +944 -0
  56. package/dist/persistence-JLUn_Nou.cjs +996 -0
  57. package/dist/projectPath-BTBIMB57.mjs +10 -0
  58. package/dist/projectPath-ChLhdOyt.cjs +13 -0
  59. package/dist/prompt-BTWDZZO0.cjs +25 -0
  60. package/dist/prompt-BsAZsPbo.mjs +22 -0
  61. package/dist/release-BMEzNONE.mjs +168 -0
  62. package/dist/release-EhOJeAZe.cjs +171 -0
  63. package/dist/run-BDn2aE3Q.cjs +2152 -0
  64. package/dist/run-DBZqN6Kn.mjs +2128 -0
  65. package/dist/runClaude-DNHvERX0.mjs +3763 -0
  66. package/dist/runClaude-uaKxPZPm.cjs +3766 -0
  67. package/dist/{runCodex-skq0j05d.cjs → runCodex-82UrZLme.cjs} +169 -153
  68. package/dist/{runCodex-DiKzBf9q.mjs → runCodex-C-RTM0wA.mjs} +36 -20
  69. package/dist/{runGemini-BeqrVq8R.mjs → runGemini-Bm6-4Xm2.mjs} +34 -17
  70. package/dist/{runGemini-CVp-CEIh.cjs → runGemini-CXMr4Jwb.cjs} +212 -195
  71. package/dist/{runOneShot-BQNkNW61.cjs → runOneShot-1B6RMYT7.cjs} +31 -55
  72. package/dist/{runOneShot-ltCpHbBB.mjs → runOneShot-ioARRHrx.mjs} +14 -38
  73. package/dist/{serve-BECdtivY.cjs → serve-BtdAhkIE.cjs} +35 -58
  74. package/dist/{serve-BNdoEDVx.mjs → serve-CwibwU6N.mjs} +12 -35
  75. package/dist/session-2sU4Bu1R.mjs +108 -0
  76. package/dist/session-D--F87zL.cjs +110 -0
  77. package/dist/sessionKeyCache-4Tp8pJ3N.cjs +36 -0
  78. package/dist/sessionKeyCache-D2BwO91C.mjs +33 -0
  79. package/dist/sessionList-BCSw3qYt.mjs +51 -0
  80. package/dist/sessionList-ICJsjNtJ.cjs +54 -0
  81. package/dist/sessionSummary-Bkz52BPh.cjs +393 -0
  82. package/dist/sessionSummary-lHGBWQSb.mjs +387 -0
  83. package/dist/spawnHappyCLI-CoORX0YW.mjs +66 -0
  84. package/dist/spawnHappyCLI-D_V-AAoP.cjs +70 -0
  85. package/dist/ssh-BTOlhhxi.cjs +333 -0
  86. package/dist/ssh-DL_AkRAD.mjs +328 -0
  87. package/dist/systemdScope-BeOtAkhk.mjs +25 -0
  88. package/dist/systemdScope-DW3-UoKB.cjs +29 -0
  89. package/dist/task-CQzrHRZe.mjs +165 -0
  90. package/dist/task-CSpaC9HX.cjs +167 -0
  91. package/dist/time-Bj2HRkKH.cjs +33 -0
  92. package/dist/time-DgsBLJA_.mjs +29 -0
  93. package/dist/trimIdent-4gvk9Wk6.cjs +22 -0
  94. package/dist/trimIdent-BAjmv_TG.mjs +20 -0
  95. package/dist/types-BoJh4Qfv.mjs +57 -0
  96. package/dist/types-DWeB7aqh.cjs +59 -0
  97. package/dist/uninstall-BiKyRAb1.cjs +77 -0
  98. package/dist/uninstall-nFMFGUVM.mjs +75 -0
  99. package/dist/update-BcqRHXf2.mjs +109 -0
  100. package/dist/update-Dw8pgsAT.cjs +111 -0
  101. package/dist/workflowCommon-B0d01Y_a.mjs +56 -0
  102. package/dist/workflowCommon-Bl568-rt.cjs +62 -0
  103. package/dist/workflowReports-BEWuQesA.cjs +113 -0
  104. package/dist/workflowReports-Dd8-4-8f.mjs +108 -0
  105. package/package.json +1 -1
  106. package/dist/index-BbCDDvoB.cjs +0 -11376
  107. package/dist/index-vvZXdswy.mjs +0 -11336
  108. package/dist/mcpToolSchemas-CdPXJQmb.mjs +0 -175
  109. 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;