@zonease/happy 0.6.37 → 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-DYCJXXdA.mjs → api-CIZKyfSn.mjs} +136 -1725
- package/dist/{types-BrTorNAy.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-D_g6ohaV.cjs → config-Draa-5XC.cjs} +21 -34
- package/dist/{config-DPn-cbpz.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.d.cts +16 -16
- package/dist/lib.d.mts +16 -16
- package/dist/lib.mjs +13 -7
- package/dist/{loadableToolOutput-lQH8Kb_w.mjs → loadableToolOutput-Cuygmd4K.mjs} +4 -2
- package/dist/{loadableToolOutput-CasonFIt.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-BTt3fxG4.cjs → runCodex-82UrZLme.cjs} +169 -153
- package/dist/{runCodex-BXCB5trB.mjs → runCodex-C-RTM0wA.mjs} +36 -20
- package/dist/{runGemini-37HHbp9n.mjs → runGemini-Bm6-4Xm2.mjs} +34 -17
- package/dist/{runGemini-C-0nSFBv.cjs → runGemini-CXMr4Jwb.cjs} +212 -195
- package/dist/{runOneShot-CXhbFJ2j.cjs → runOneShot-1B6RMYT7.cjs} +31 -55
- package/dist/{runOneShot-USX8QPfE.mjs → runOneShot-ioARRHrx.mjs} +14 -38
- package/dist/{serve-DZAq-_1F.cjs → serve-BtdAhkIE.cjs} +35 -58
- package/dist/{serve-BRedJPzL.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-BlkHOkB3.cjs +0 -11312
- package/dist/index-DJ2VpGiR.mjs +0 -11272
- package/dist/mcpToolSchemas-CdPXJQmb.mjs +0 -175
- package/dist/mcpToolSchemas-f-kttjFw.cjs +0 -186
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
import { readFile, open, stat, unlink, mkdir, writeFile, rename } from 'node:fs/promises';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, statSync, constants, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join, basename } from 'node:path';
|
|
5
|
+
import * as z from 'zod';
|
|
6
|
+
import { randomBytes, createHash, createCipheriv, createDecipheriv } from 'node:crypto';
|
|
7
|
+
import tweetnacl from 'tweetnacl';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { appendFileSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
const DEBUG_TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true"]);
|
|
12
|
+
function isDebug() {
|
|
13
|
+
return DEBUG_TRUE_VALUES.has(process.env.DEBUG?.toLowerCase() ?? "");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
var name = "@zonease/happy";
|
|
17
|
+
var version = "0.6.39";
|
|
18
|
+
var description = "Mobile and Web client for Claude Code, Codex, and Gemini";
|
|
19
|
+
var author = "ZonEaseTech";
|
|
20
|
+
var license = "MIT";
|
|
21
|
+
var type = "module";
|
|
22
|
+
var homepage = "https://github.com/ZonEaseTech/happy-wfs";
|
|
23
|
+
var bugs = "https://github.com/ZonEaseTech/happy-wfs/issues";
|
|
24
|
+
var repository = {
|
|
25
|
+
type: "git",
|
|
26
|
+
url: "git+https://github.com/ZonEaseTech/happy-wfs.git",
|
|
27
|
+
directory: "packages/happy-cli"
|
|
28
|
+
};
|
|
29
|
+
var bin = {
|
|
30
|
+
happy: "./bin/happy.mjs",
|
|
31
|
+
"happy-mcp": "./bin/happy-mcp.mjs"
|
|
32
|
+
};
|
|
33
|
+
var main = "./dist/index.cjs";
|
|
34
|
+
var module$1 = "./dist/index.mjs";
|
|
35
|
+
var types = "./dist/index.d.cts";
|
|
36
|
+
var exports$1 = {
|
|
37
|
+
".": {
|
|
38
|
+
require: {
|
|
39
|
+
types: "./dist/index.d.cts",
|
|
40
|
+
"default": "./dist/index.cjs"
|
|
41
|
+
},
|
|
42
|
+
"import": {
|
|
43
|
+
types: "./dist/index.d.mts",
|
|
44
|
+
"default": "./dist/index.mjs"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"./lib": {
|
|
48
|
+
require: {
|
|
49
|
+
types: "./dist/lib.d.cts",
|
|
50
|
+
"default": "./dist/lib.cjs"
|
|
51
|
+
},
|
|
52
|
+
"import": {
|
|
53
|
+
types: "./dist/lib.d.mts",
|
|
54
|
+
"default": "./dist/lib.mjs"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"./codex/happyMcpStdioBridge": {
|
|
58
|
+
require: {
|
|
59
|
+
types: "./dist/codex/happyMcpStdioBridge.d.cts",
|
|
60
|
+
"default": "./dist/codex/happyMcpStdioBridge.cjs"
|
|
61
|
+
},
|
|
62
|
+
"import": {
|
|
63
|
+
types: "./dist/codex/happyMcpStdioBridge.d.mts",
|
|
64
|
+
"default": "./dist/codex/happyMcpStdioBridge.mjs"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var files = [
|
|
69
|
+
"dist",
|
|
70
|
+
"bin",
|
|
71
|
+
"scripts",
|
|
72
|
+
"package.json"
|
|
73
|
+
];
|
|
74
|
+
var scripts = {
|
|
75
|
+
"why do we need to build before running tests / dev?": "We need the binary to be built so we run daemon commands which directly run the binary - we don't want them to go out of sync or have custom spawn logic depending how we started happy",
|
|
76
|
+
typecheck: "tsc --noEmit",
|
|
77
|
+
build: "shx rm -rf dist && tsc --noEmit && pkgroll",
|
|
78
|
+
test: "$npm_execpath run build && vitest run",
|
|
79
|
+
"test:integration": "$npm_execpath run build && vitest run --config vitest.integration.config.ts",
|
|
80
|
+
start: "$npm_execpath run build && node ./bin/happy.mjs",
|
|
81
|
+
dev: "tsx src/index.ts",
|
|
82
|
+
"dev:local-server": "$npm_execpath run build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
83
|
+
"dev:integration-test-env": "$npm_execpath run build && tsx --env-file .env.integration-test src/index.ts",
|
|
84
|
+
prepublishOnly: "$npm_execpath run build && $npm_execpath test",
|
|
85
|
+
release: "$npm_execpath install && release-it",
|
|
86
|
+
postinstall: "node scripts/unpack-tools.cjs",
|
|
87
|
+
"// ==== Dev/Stable Variant Management ====": "",
|
|
88
|
+
stable: "node scripts/env-wrapper.cjs stable",
|
|
89
|
+
"dev:variant": "node scripts/env-wrapper.cjs dev",
|
|
90
|
+
"// ==== Stable Version Quick Commands ====": "",
|
|
91
|
+
"stable:daemon:start": "node scripts/env-wrapper.cjs stable daemon start",
|
|
92
|
+
"stable:daemon:stop": "node scripts/env-wrapper.cjs stable daemon stop",
|
|
93
|
+
"stable:daemon:status": "node scripts/env-wrapper.cjs stable daemon status",
|
|
94
|
+
"stable:auth": "node scripts/env-wrapper.cjs stable auth",
|
|
95
|
+
"// ==== Development Version Quick Commands ====": "",
|
|
96
|
+
"dev:daemon:start": "node scripts/env-wrapper.cjs dev daemon start",
|
|
97
|
+
"dev:daemon:stop": "node scripts/env-wrapper.cjs dev daemon stop",
|
|
98
|
+
"dev:daemon:status": "node scripts/env-wrapper.cjs dev daemon status",
|
|
99
|
+
"dev:auth": "node scripts/env-wrapper.cjs dev auth",
|
|
100
|
+
"// ==== Setup ====": "",
|
|
101
|
+
"setup:dev": "node scripts/setup-dev.cjs",
|
|
102
|
+
doctor: "node scripts/env-wrapper.cjs stable doctor",
|
|
103
|
+
"// ==== Development Linking ====": "",
|
|
104
|
+
"link:dev": "node scripts/link-dev.cjs",
|
|
105
|
+
"unlink:dev": "node scripts/link-dev.cjs unlink"
|
|
106
|
+
};
|
|
107
|
+
var dependencies = {
|
|
108
|
+
"@agentclientprotocol/sdk": "^0.14.1",
|
|
109
|
+
"@paralleldrive/cuid2": "^2.2.2",
|
|
110
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
111
|
+
"@stablelib/base64": "^2.0.1",
|
|
112
|
+
"@stablelib/hex": "^2.0.1",
|
|
113
|
+
"@types/cross-spawn": "^6.0.6",
|
|
114
|
+
"@types/http-proxy": "^1.17.17",
|
|
115
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
116
|
+
"@types/react": "^19.2.7",
|
|
117
|
+
"@types/tmp": "^0.2.6",
|
|
118
|
+
"@types/ws": "^8.18.1",
|
|
119
|
+
ai: "^5.0.107",
|
|
120
|
+
axios: "^1.13.2",
|
|
121
|
+
chalk: "^5.6.2",
|
|
122
|
+
"cross-spawn": "^7.0.6",
|
|
123
|
+
"expo-server-sdk": "^3.15.0",
|
|
124
|
+
fastify: "^5.6.2",
|
|
125
|
+
"fastify-type-provider-zod": "4.0.2",
|
|
126
|
+
"http-proxy": "^1.18.1",
|
|
127
|
+
"http-proxy-middleware": "^3.0.5",
|
|
128
|
+
ink: "^6.5.1",
|
|
129
|
+
open: "^10.2.0",
|
|
130
|
+
"ps-list": "^8.1.1",
|
|
131
|
+
"qrcode-terminal": "^0.12.0",
|
|
132
|
+
react: "^19.2.0",
|
|
133
|
+
"socket.io-client": "^4.8.1",
|
|
134
|
+
tar: "^7.5.2",
|
|
135
|
+
tmp: "^0.2.5",
|
|
136
|
+
tweetnacl: "^1.0.3",
|
|
137
|
+
ws: "^8.19.0",
|
|
138
|
+
zod: "3.25.76"
|
|
139
|
+
};
|
|
140
|
+
var devDependencies = {
|
|
141
|
+
"@eslint/compat": "^1",
|
|
142
|
+
"@types/node": ">=20",
|
|
143
|
+
"cross-env": "^10.1.0",
|
|
144
|
+
dotenv: "^16.6.1",
|
|
145
|
+
eslint: "^9",
|
|
146
|
+
"eslint-config-prettier": "^10",
|
|
147
|
+
pkgroll: "^2.14.2",
|
|
148
|
+
"release-it": "^19.0.6",
|
|
149
|
+
shx: "^0.3.3",
|
|
150
|
+
"ts-node": "^10",
|
|
151
|
+
tsx: "^4.20.6",
|
|
152
|
+
typescript: "5.9.3",
|
|
153
|
+
vitest: "^3.2.4",
|
|
154
|
+
"happy-wire": "*"
|
|
155
|
+
};
|
|
156
|
+
var resolutions = {
|
|
157
|
+
"whatwg-url": "14.2.0",
|
|
158
|
+
"parse-path": "7.0.3",
|
|
159
|
+
"@types/parse-path": "7.0.3"
|
|
160
|
+
};
|
|
161
|
+
var publishConfig = {
|
|
162
|
+
registry: "https://registry.npmjs.org",
|
|
163
|
+
access: "public"
|
|
164
|
+
};
|
|
165
|
+
var packageManager = "yarn@1.22.22";
|
|
166
|
+
var optionalDependencies = {
|
|
167
|
+
"node-pty": "^1.0.0",
|
|
168
|
+
"@zonease/happy-tools-arm64-darwin": "1.0.0",
|
|
169
|
+
"@zonease/happy-tools-x64-darwin": "1.0.0",
|
|
170
|
+
"@zonease/happy-tools-arm64-linux": "1.0.0",
|
|
171
|
+
"@zonease/happy-tools-x64-linux": "1.0.0",
|
|
172
|
+
"@zonease/happy-tools-x64-win32": "1.0.0",
|
|
173
|
+
"@zonease/happy-tools-arm64-win32": "1.0.0"
|
|
174
|
+
};
|
|
175
|
+
var packageJson = {
|
|
176
|
+
name: name,
|
|
177
|
+
version: version,
|
|
178
|
+
description: description,
|
|
179
|
+
author: author,
|
|
180
|
+
license: license,
|
|
181
|
+
type: type,
|
|
182
|
+
homepage: homepage,
|
|
183
|
+
bugs: bugs,
|
|
184
|
+
repository: repository,
|
|
185
|
+
bin: bin,
|
|
186
|
+
main: main,
|
|
187
|
+
module: module$1,
|
|
188
|
+
types: types,
|
|
189
|
+
exports: exports$1,
|
|
190
|
+
files: files,
|
|
191
|
+
scripts: scripts,
|
|
192
|
+
dependencies: dependencies,
|
|
193
|
+
devDependencies: devDependencies,
|
|
194
|
+
resolutions: resolutions,
|
|
195
|
+
publishConfig: publishConfig,
|
|
196
|
+
packageManager: packageManager,
|
|
197
|
+
optionalDependencies: optionalDependencies
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
class Configuration {
|
|
201
|
+
serverUrl;
|
|
202
|
+
webappUrl;
|
|
203
|
+
isDaemonProcess;
|
|
204
|
+
// Directories and paths (from persistence)
|
|
205
|
+
happyHomeDir;
|
|
206
|
+
logsDir;
|
|
207
|
+
settingsFile;
|
|
208
|
+
privateKeyFile;
|
|
209
|
+
daemonStateFile;
|
|
210
|
+
daemonLockFile;
|
|
211
|
+
sessionKeysDir;
|
|
212
|
+
currentCliVersion;
|
|
213
|
+
isExperimentalEnabled;
|
|
214
|
+
disableCaffeinate;
|
|
215
|
+
constructor() {
|
|
216
|
+
this.serverUrl = process.env.HAPPY_SERVER_URL || "https://api-happy.zonease.org";
|
|
217
|
+
this.webappUrl = process.env.HAPPY_WEBAPP_URL || "https://happy.zonease.org";
|
|
218
|
+
const args = process.argv.slice(2);
|
|
219
|
+
this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
|
|
220
|
+
if (process.env.HAPPY_HOME_DIR) {
|
|
221
|
+
const expandedPath = process.env.HAPPY_HOME_DIR.replace(/^~/, homedir());
|
|
222
|
+
this.happyHomeDir = expandedPath;
|
|
223
|
+
} else {
|
|
224
|
+
this.happyHomeDir = join(homedir(), ".happy");
|
|
225
|
+
}
|
|
226
|
+
this.logsDir = join(this.happyHomeDir, "logs");
|
|
227
|
+
this.settingsFile = join(this.happyHomeDir, "settings.json");
|
|
228
|
+
this.privateKeyFile = join(this.happyHomeDir, "access.key");
|
|
229
|
+
this.daemonStateFile = join(this.happyHomeDir, "daemon.state.json");
|
|
230
|
+
this.daemonLockFile = join(this.happyHomeDir, "daemon.state.json.lock");
|
|
231
|
+
this.sessionKeysDir = join(this.happyHomeDir, "session-keys");
|
|
232
|
+
this.isExperimentalEnabled = ["true", "1", "yes"].includes(process.env.HAPPY_EXPERIMENTAL?.toLowerCase() || "");
|
|
233
|
+
this.disableCaffeinate = ["true", "1", "yes"].includes(process.env.HAPPY_DISABLE_CAFFEINATE?.toLowerCase() || "");
|
|
234
|
+
this.currentCliVersion = packageJson.version;
|
|
235
|
+
const variant = process.env.HAPPY_VARIANT || "stable";
|
|
236
|
+
if (variant === "dev" && !this.happyHomeDir.includes("dev")) {
|
|
237
|
+
console.warn('\u26A0\uFE0F WARNING: HAPPY_VARIANT=dev but HAPPY_HOME_DIR does not contain "dev"');
|
|
238
|
+
console.warn(` Current: ${this.happyHomeDir}`);
|
|
239
|
+
console.warn(` Expected: Should contain "dev" (e.g., ~/.happy-dev)`);
|
|
240
|
+
}
|
|
241
|
+
if (!this.isDaemonProcess && variant === "dev") {
|
|
242
|
+
console.log("\x1B[33m\u{1F527} DEV MODE\x1B[0m - Data: " + this.happyHomeDir);
|
|
243
|
+
}
|
|
244
|
+
if (!existsSync(this.happyHomeDir)) {
|
|
245
|
+
mkdirSync(this.happyHomeDir, { recursive: true });
|
|
246
|
+
}
|
|
247
|
+
if (!existsSync(this.logsDir)) {
|
|
248
|
+
mkdirSync(this.logsDir, { recursive: true });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const configuration = new Configuration();
|
|
253
|
+
|
|
254
|
+
function createTimestampForFilename(date = /* @__PURE__ */ new Date()) {
|
|
255
|
+
return date.toLocaleString("sv-SE", {
|
|
256
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
257
|
+
year: "numeric",
|
|
258
|
+
month: "2-digit",
|
|
259
|
+
day: "2-digit",
|
|
260
|
+
hour: "2-digit",
|
|
261
|
+
minute: "2-digit",
|
|
262
|
+
second: "2-digit"
|
|
263
|
+
}).replace(/[: ]/g, "-").replace(/,/g, "") + "-pid-" + process.pid;
|
|
264
|
+
}
|
|
265
|
+
function createTimestampForLogEntry(date = /* @__PURE__ */ new Date()) {
|
|
266
|
+
return date.toLocaleTimeString("en-US", {
|
|
267
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
268
|
+
hour12: false,
|
|
269
|
+
hour: "2-digit",
|
|
270
|
+
minute: "2-digit",
|
|
271
|
+
second: "2-digit",
|
|
272
|
+
fractionalSecondDigits: 3
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function getSessionLogPath() {
|
|
276
|
+
const timestamp = createTimestampForFilename();
|
|
277
|
+
const filename = configuration.isDaemonProcess ? `${timestamp}-daemon.log` : `${timestamp}.log`;
|
|
278
|
+
return join(configuration.logsDir, filename);
|
|
279
|
+
}
|
|
280
|
+
class Logger {
|
|
281
|
+
constructor(logFilePath = getSessionLogPath()) {
|
|
282
|
+
this.logFilePath = logFilePath;
|
|
283
|
+
if (process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING && process.env.HAPPY_SERVER_URL) {
|
|
284
|
+
this.dangerouslyUnencryptedServerLoggingUrl = process.env.HAPPY_SERVER_URL;
|
|
285
|
+
console.log(chalk.yellow("[REMOTE LOGGING] Sending logs to server for AI debugging"));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
dangerouslyUnencryptedServerLoggingUrl;
|
|
289
|
+
// Use local timezone for simplicity of locating the logs,
|
|
290
|
+
// in practice you will not need absolute timestamps
|
|
291
|
+
localTimezoneTimestamp() {
|
|
292
|
+
return createTimestampForLogEntry();
|
|
293
|
+
}
|
|
294
|
+
debug(message, ...args) {
|
|
295
|
+
this.logToFile(`[${this.localTimezoneTimestamp()}]`, message, ...args);
|
|
296
|
+
}
|
|
297
|
+
debugLargeJson(message, object, maxStringLength = 100, maxArrayLength = 10) {
|
|
298
|
+
if (!isDebug()) {
|
|
299
|
+
this.debug(`In production, skipping message inspection`);
|
|
300
|
+
}
|
|
301
|
+
const truncateStrings = (obj) => {
|
|
302
|
+
if (typeof obj === "string") {
|
|
303
|
+
return obj.length > maxStringLength ? obj.substring(0, maxStringLength) + "... [truncated for logs]" : obj;
|
|
304
|
+
}
|
|
305
|
+
if (Array.isArray(obj)) {
|
|
306
|
+
const truncatedArray = obj.map((item) => truncateStrings(item)).slice(0, maxArrayLength);
|
|
307
|
+
if (obj.length > maxArrayLength) {
|
|
308
|
+
truncatedArray.push(`... [truncated array for logs up to ${maxArrayLength} items]`);
|
|
309
|
+
}
|
|
310
|
+
return truncatedArray;
|
|
311
|
+
}
|
|
312
|
+
if (obj && typeof obj === "object") {
|
|
313
|
+
const result = {};
|
|
314
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
315
|
+
if (key === "usage") {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
result[key] = truncateStrings(value);
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
return obj;
|
|
323
|
+
};
|
|
324
|
+
const truncatedObject = truncateStrings(object);
|
|
325
|
+
const json = JSON.stringify(truncatedObject, null, 2);
|
|
326
|
+
this.logToFile(`[${this.localTimezoneTimestamp()}]`, message, "\n", json);
|
|
327
|
+
}
|
|
328
|
+
info(message, ...args) {
|
|
329
|
+
this.logToConsole("info", "", message, ...args);
|
|
330
|
+
this.debug(message, args);
|
|
331
|
+
}
|
|
332
|
+
infoDeveloper(message, ...args) {
|
|
333
|
+
this.debug(message, ...args);
|
|
334
|
+
if (isDebug()) {
|
|
335
|
+
this.logToConsole("info", "[DEV]", message, ...args);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
warn(message, ...args) {
|
|
339
|
+
this.logToConsole("warn", "", message, ...args);
|
|
340
|
+
this.debug(`[WARN] ${message}`, ...args);
|
|
341
|
+
}
|
|
342
|
+
getLogPath() {
|
|
343
|
+
return this.logFilePath;
|
|
344
|
+
}
|
|
345
|
+
logToConsole(level, prefix, message, ...args) {
|
|
346
|
+
switch (level) {
|
|
347
|
+
case "debug": {
|
|
348
|
+
console.log(chalk.gray(prefix), message, ...args);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
case "error": {
|
|
352
|
+
console.error(chalk.red(prefix), message, ...args);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
case "info": {
|
|
356
|
+
console.log(chalk.blue(prefix), message, ...args);
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case "warn": {
|
|
360
|
+
console.log(chalk.yellow(prefix), message, ...args);
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
default: {
|
|
364
|
+
this.debug("Unknown log level:", level);
|
|
365
|
+
console.log(chalk.blue(prefix), message, ...args);
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async sendToRemoteServer(level, message, ...args) {
|
|
371
|
+
if (!this.dangerouslyUnencryptedServerLoggingUrl) return;
|
|
372
|
+
try {
|
|
373
|
+
await fetch(this.dangerouslyUnencryptedServerLoggingUrl + "/logs-combined-from-cli-and-mobile-for-simple-ai-debugging", {
|
|
374
|
+
method: "POST",
|
|
375
|
+
headers: { "Content-Type": "application/json" },
|
|
376
|
+
body: JSON.stringify({
|
|
377
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
378
|
+
level,
|
|
379
|
+
message: `${message} ${args.map(
|
|
380
|
+
(a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)
|
|
381
|
+
).join(" ")}`,
|
|
382
|
+
source: "cli",
|
|
383
|
+
platform: process.platform
|
|
384
|
+
})
|
|
385
|
+
});
|
|
386
|
+
} catch (error) {
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
logToFile(prefix, message, ...args) {
|
|
390
|
+
const logLine = `${prefix} ${message} ${args.map((arg) => {
|
|
391
|
+
if (typeof arg === "string") return arg;
|
|
392
|
+
if (arg instanceof Error) return `${arg.message}
|
|
393
|
+
${arg.stack ?? ""}`;
|
|
394
|
+
return JSON.stringify(arg);
|
|
395
|
+
}).join(" ")}
|
|
396
|
+
`;
|
|
397
|
+
if (this.dangerouslyUnencryptedServerLoggingUrl) {
|
|
398
|
+
let level = "info";
|
|
399
|
+
if (prefix.includes(this.localTimezoneTimestamp())) {
|
|
400
|
+
level = "debug";
|
|
401
|
+
}
|
|
402
|
+
this.sendToRemoteServer(level, message, ...args).catch(() => {
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
appendFileSync(this.logFilePath, logLine);
|
|
407
|
+
} catch (appendError) {
|
|
408
|
+
if (isDebug()) {
|
|
409
|
+
console.error("[DEV MODE ONLY THROWING] Failed to append to log file:", appendError);
|
|
410
|
+
throw appendError;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
let logger = new Logger();
|
|
416
|
+
async function listDaemonLogFiles(limit = 50) {
|
|
417
|
+
try {
|
|
418
|
+
const logsDir = configuration.logsDir;
|
|
419
|
+
if (!existsSync(logsDir)) {
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
const logs = readdirSync(logsDir).filter((file) => file.endsWith("-daemon.log")).map((file) => {
|
|
423
|
+
const fullPath = join(logsDir, file);
|
|
424
|
+
const stats = statSync(fullPath);
|
|
425
|
+
return { file, path: fullPath, modified: stats.mtime };
|
|
426
|
+
}).sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
427
|
+
try {
|
|
428
|
+
const { readDaemonState } = await Promise.resolve().then(function () { return persistence; });
|
|
429
|
+
const state = await readDaemonState();
|
|
430
|
+
if (!state) {
|
|
431
|
+
return logs;
|
|
432
|
+
}
|
|
433
|
+
if (state.daemonLogPath && existsSync(state.daemonLogPath)) {
|
|
434
|
+
const stats = statSync(state.daemonLogPath);
|
|
435
|
+
const persisted = {
|
|
436
|
+
file: basename(state.daemonLogPath),
|
|
437
|
+
path: state.daemonLogPath,
|
|
438
|
+
modified: stats.mtime
|
|
439
|
+
};
|
|
440
|
+
const idx = logs.findIndex((l) => l.path === persisted.path);
|
|
441
|
+
if (idx >= 0) {
|
|
442
|
+
const [found] = logs.splice(idx, 1);
|
|
443
|
+
logs.unshift(found);
|
|
444
|
+
} else {
|
|
445
|
+
logs.unshift(persisted);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
return logs.slice(0, Math.max(0, limit));
|
|
451
|
+
} catch {
|
|
452
|
+
return [];
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
async function getLatestDaemonLog() {
|
|
456
|
+
const [latest] = await listDaemonLogFiles(1);
|
|
457
|
+
return latest || null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
var logger$1 = /*#__PURE__*/Object.freeze({
|
|
461
|
+
__proto__: null,
|
|
462
|
+
getLatestDaemonLog: getLatestDaemonLog,
|
|
463
|
+
listDaemonLogFiles: listDaemonLogFiles,
|
|
464
|
+
logger: logger
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
function encodeBase64(buffer, variant = "base64") {
|
|
468
|
+
if (variant === "base64url") {
|
|
469
|
+
return encodeBase64Url(buffer);
|
|
470
|
+
}
|
|
471
|
+
return Buffer.from(buffer).toString("base64");
|
|
472
|
+
}
|
|
473
|
+
function encodeBase64Url(buffer) {
|
|
474
|
+
return Buffer.from(buffer).toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
475
|
+
}
|
|
476
|
+
function decodeBase64(base64, variant = "base64") {
|
|
477
|
+
if (variant === "base64url") {
|
|
478
|
+
const base64Standard = base64.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - base64.length % 4) % 4);
|
|
479
|
+
return new Uint8Array(Buffer.from(base64Standard, "base64"));
|
|
480
|
+
}
|
|
481
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
482
|
+
}
|
|
483
|
+
function getRandomBytes(size) {
|
|
484
|
+
return new Uint8Array(randomBytes(size));
|
|
485
|
+
}
|
|
486
|
+
function libsodiumPublicKeyFromSecretKey(seed) {
|
|
487
|
+
const hashedSeed = new Uint8Array(createHash("sha512").update(seed).digest());
|
|
488
|
+
const secretKey = hashedSeed.slice(0, 32);
|
|
489
|
+
return new Uint8Array(tweetnacl.box.keyPair.fromSecretKey(secretKey).publicKey);
|
|
490
|
+
}
|
|
491
|
+
function libsodiumEncryptForPublicKey(data, recipientPublicKey) {
|
|
492
|
+
const ephemeralKeyPair = tweetnacl.box.keyPair();
|
|
493
|
+
const nonce = getRandomBytes(tweetnacl.box.nonceLength);
|
|
494
|
+
const encrypted = tweetnacl.box(data, nonce, recipientPublicKey, ephemeralKeyPair.secretKey);
|
|
495
|
+
const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
|
|
496
|
+
result.set(ephemeralKeyPair.publicKey, 0);
|
|
497
|
+
result.set(nonce, ephemeralKeyPair.publicKey.length);
|
|
498
|
+
result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
function encryptLegacy(data, secret) {
|
|
502
|
+
const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
|
|
503
|
+
const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
|
|
504
|
+
const result = new Uint8Array(nonce.length + encrypted.length);
|
|
505
|
+
result.set(nonce);
|
|
506
|
+
result.set(encrypted, nonce.length);
|
|
507
|
+
return result;
|
|
508
|
+
}
|
|
509
|
+
function decryptLegacy(data, secret) {
|
|
510
|
+
const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
|
|
511
|
+
const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
|
|
512
|
+
const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
|
|
513
|
+
if (!decrypted) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
517
|
+
}
|
|
518
|
+
function encryptWithDataKey(data, dataKey) {
|
|
519
|
+
const nonce = getRandomBytes(12);
|
|
520
|
+
const cipher = createCipheriv("aes-256-gcm", dataKey, nonce);
|
|
521
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(data));
|
|
522
|
+
const encrypted = Buffer.concat([
|
|
523
|
+
cipher.update(plaintext),
|
|
524
|
+
cipher.final()
|
|
525
|
+
]);
|
|
526
|
+
const authTag = cipher.getAuthTag();
|
|
527
|
+
const bundle = new Uint8Array(12 + encrypted.length + 16 + 1);
|
|
528
|
+
bundle.set([0], 0);
|
|
529
|
+
bundle.set(nonce, 1);
|
|
530
|
+
bundle.set(new Uint8Array(encrypted), 13);
|
|
531
|
+
bundle.set(new Uint8Array(authTag), 13 + encrypted.length);
|
|
532
|
+
return bundle;
|
|
533
|
+
}
|
|
534
|
+
function decryptWithDataKey(bundle, dataKey) {
|
|
535
|
+
if (bundle.length < 1) {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
if (bundle[0] !== 0) {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
if (bundle.length < 12 + 16 + 1) {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
const nonce = bundle.slice(1, 13);
|
|
545
|
+
const authTag = bundle.slice(bundle.length - 16);
|
|
546
|
+
const ciphertext = bundle.slice(13, bundle.length - 16);
|
|
547
|
+
try {
|
|
548
|
+
const decipher = createDecipheriv("aes-256-gcm", dataKey, nonce);
|
|
549
|
+
decipher.setAuthTag(authTag);
|
|
550
|
+
const decrypted = Buffer.concat([
|
|
551
|
+
decipher.update(ciphertext),
|
|
552
|
+
decipher.final()
|
|
553
|
+
]);
|
|
554
|
+
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
555
|
+
} catch (error) {
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function encrypt(key, variant, data) {
|
|
560
|
+
if (variant === "legacy") {
|
|
561
|
+
return encryptLegacy(data, key);
|
|
562
|
+
} else {
|
|
563
|
+
return encryptWithDataKey(data, key);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function decrypt(key, variant, data) {
|
|
567
|
+
if (variant === "legacy") {
|
|
568
|
+
return decryptLegacy(data, key);
|
|
569
|
+
} else {
|
|
570
|
+
return decryptWithDataKey(data, key);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const AnthropicConfigSchema = z.object({
|
|
575
|
+
baseUrl: z.string().url().optional(),
|
|
576
|
+
authToken: z.string().optional(),
|
|
577
|
+
model: z.string().optional()
|
|
578
|
+
});
|
|
579
|
+
const OpenAIConfigSchema = z.object({
|
|
580
|
+
apiKey: z.string().optional(),
|
|
581
|
+
baseUrl: z.string().url().optional(),
|
|
582
|
+
model: z.string().optional()
|
|
583
|
+
});
|
|
584
|
+
const AzureOpenAIConfigSchema = z.object({
|
|
585
|
+
apiKey: z.string().optional(),
|
|
586
|
+
endpoint: z.string().url().optional(),
|
|
587
|
+
apiVersion: z.string().optional(),
|
|
588
|
+
deploymentName: z.string().optional()
|
|
589
|
+
});
|
|
590
|
+
const TogetherAIConfigSchema = z.object({
|
|
591
|
+
apiKey: z.string().optional(),
|
|
592
|
+
model: z.string().optional()
|
|
593
|
+
});
|
|
594
|
+
const TmuxConfigSchema = z.object({
|
|
595
|
+
sessionName: z.string().optional(),
|
|
596
|
+
tmpDir: z.string().optional(),
|
|
597
|
+
updateEnvironment: z.boolean().optional()
|
|
598
|
+
});
|
|
599
|
+
const EnvironmentVariableSchema = z.object({
|
|
600
|
+
name: z.string().regex(/^[A-Z_][A-Z0-9_]*$/, "Invalid environment variable name"),
|
|
601
|
+
value: z.string()
|
|
602
|
+
});
|
|
603
|
+
const ProfileCompatibilitySchema = z.object({
|
|
604
|
+
claude: z.boolean().default(true),
|
|
605
|
+
codex: z.boolean().default(true),
|
|
606
|
+
gemini: z.boolean().default(true)
|
|
607
|
+
});
|
|
608
|
+
const AIBackendProfileSchema = z.object({
|
|
609
|
+
id: z.string().uuid(),
|
|
610
|
+
name: z.string().min(1).max(100),
|
|
611
|
+
description: z.string().max(500).optional(),
|
|
612
|
+
// Agent-specific configurations
|
|
613
|
+
anthropicConfig: AnthropicConfigSchema.optional(),
|
|
614
|
+
openaiConfig: OpenAIConfigSchema.optional(),
|
|
615
|
+
azureOpenAIConfig: AzureOpenAIConfigSchema.optional(),
|
|
616
|
+
togetherAIConfig: TogetherAIConfigSchema.optional(),
|
|
617
|
+
// Tmux configuration
|
|
618
|
+
tmuxConfig: TmuxConfigSchema.optional(),
|
|
619
|
+
// Environment variables (validated)
|
|
620
|
+
environmentVariables: z.array(EnvironmentVariableSchema).default([]),
|
|
621
|
+
// Default session type for this profile
|
|
622
|
+
defaultSessionType: z.enum(["simple", "worktree"]).optional(),
|
|
623
|
+
// Default permission mode for this profile (supports both Claude and Codex modes)
|
|
624
|
+
defaultPermissionMode: z.enum([
|
|
625
|
+
"default",
|
|
626
|
+
"acceptEdits",
|
|
627
|
+
"bypassPermissions",
|
|
628
|
+
"plan",
|
|
629
|
+
// Claude modes
|
|
630
|
+
"read-only",
|
|
631
|
+
"safe-yolo",
|
|
632
|
+
"yolo"
|
|
633
|
+
// Codex modes
|
|
634
|
+
]).optional(),
|
|
635
|
+
// Default model mode for this profile
|
|
636
|
+
defaultModelMode: z.string().optional(),
|
|
637
|
+
// Compatibility metadata
|
|
638
|
+
compatibility: ProfileCompatibilitySchema.default({ claude: true, codex: true, gemini: true }),
|
|
639
|
+
// Built-in profile indicator
|
|
640
|
+
isBuiltIn: z.boolean().default(false),
|
|
641
|
+
// Metadata
|
|
642
|
+
createdAt: z.number().default(() => Date.now()),
|
|
643
|
+
updatedAt: z.number().default(() => Date.now()),
|
|
644
|
+
version: z.string().default("1.0.0")
|
|
645
|
+
});
|
|
646
|
+
function validateProfileForAgent(profile, agent) {
|
|
647
|
+
return profile.compatibility[agent];
|
|
648
|
+
}
|
|
649
|
+
function getProfileEnvironmentVariables(profile) {
|
|
650
|
+
const envVars = {};
|
|
651
|
+
profile.environmentVariables.forEach((envVar) => {
|
|
652
|
+
envVars[envVar.name] = envVar.value;
|
|
653
|
+
});
|
|
654
|
+
if (profile.anthropicConfig) {
|
|
655
|
+
if (profile.anthropicConfig.baseUrl) envVars.ANTHROPIC_BASE_URL = profile.anthropicConfig.baseUrl;
|
|
656
|
+
if (profile.anthropicConfig.authToken) envVars.ANTHROPIC_AUTH_TOKEN = profile.anthropicConfig.authToken;
|
|
657
|
+
if (profile.anthropicConfig.model) envVars.ANTHROPIC_MODEL = profile.anthropicConfig.model;
|
|
658
|
+
}
|
|
659
|
+
if (profile.openaiConfig) {
|
|
660
|
+
if (profile.openaiConfig.apiKey) envVars.OPENAI_API_KEY = profile.openaiConfig.apiKey;
|
|
661
|
+
if (profile.openaiConfig.baseUrl) envVars.OPENAI_BASE_URL = profile.openaiConfig.baseUrl;
|
|
662
|
+
if (profile.openaiConfig.model) envVars.OPENAI_MODEL = profile.openaiConfig.model;
|
|
663
|
+
}
|
|
664
|
+
if (profile.azureOpenAIConfig) {
|
|
665
|
+
if (profile.azureOpenAIConfig.apiKey) envVars.AZURE_OPENAI_API_KEY = profile.azureOpenAIConfig.apiKey;
|
|
666
|
+
if (profile.azureOpenAIConfig.endpoint) envVars.AZURE_OPENAI_ENDPOINT = profile.azureOpenAIConfig.endpoint;
|
|
667
|
+
if (profile.azureOpenAIConfig.apiVersion) envVars.AZURE_OPENAI_API_VERSION = profile.azureOpenAIConfig.apiVersion;
|
|
668
|
+
if (profile.azureOpenAIConfig.deploymentName) envVars.AZURE_OPENAI_DEPLOYMENT_NAME = profile.azureOpenAIConfig.deploymentName;
|
|
669
|
+
}
|
|
670
|
+
if (profile.togetherAIConfig) {
|
|
671
|
+
if (profile.togetherAIConfig.apiKey) envVars.TOGETHER_API_KEY = profile.togetherAIConfig.apiKey;
|
|
672
|
+
if (profile.togetherAIConfig.model) envVars.TOGETHER_MODEL = profile.togetherAIConfig.model;
|
|
673
|
+
}
|
|
674
|
+
if (profile.tmuxConfig) {
|
|
675
|
+
if (profile.tmuxConfig.sessionName !== void 0) envVars.TMUX_SESSION_NAME = profile.tmuxConfig.sessionName;
|
|
676
|
+
if (profile.tmuxConfig.tmpDir) envVars.TMUX_TMPDIR = profile.tmuxConfig.tmpDir;
|
|
677
|
+
if (profile.tmuxConfig.updateEnvironment !== void 0) {
|
|
678
|
+
envVars.TMUX_UPDATE_ENVIRONMENT = profile.tmuxConfig.updateEnvironment.toString();
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return envVars;
|
|
682
|
+
}
|
|
683
|
+
const SUPPORTED_SCHEMA_VERSION = 2;
|
|
684
|
+
const defaultSettings = {
|
|
685
|
+
schemaVersion: SUPPORTED_SCHEMA_VERSION,
|
|
686
|
+
onboardingCompleted: false,
|
|
687
|
+
profiles: [],
|
|
688
|
+
localEnvironmentVariables: {}
|
|
689
|
+
};
|
|
690
|
+
function migrateSettings(raw, fromVersion) {
|
|
691
|
+
let migrated = { ...raw };
|
|
692
|
+
if (fromVersion < 2) {
|
|
693
|
+
if (!migrated.profiles) {
|
|
694
|
+
migrated.profiles = [];
|
|
695
|
+
}
|
|
696
|
+
if (!migrated.localEnvironmentVariables) {
|
|
697
|
+
migrated.localEnvironmentVariables = {};
|
|
698
|
+
}
|
|
699
|
+
migrated.schemaVersion = 2;
|
|
700
|
+
}
|
|
701
|
+
return migrated;
|
|
702
|
+
}
|
|
703
|
+
async function readSettings() {
|
|
704
|
+
if (!existsSync(configuration.settingsFile)) {
|
|
705
|
+
return { ...defaultSettings };
|
|
706
|
+
}
|
|
707
|
+
try {
|
|
708
|
+
const content = await readFile(configuration.settingsFile, "utf8");
|
|
709
|
+
const raw = JSON.parse(content);
|
|
710
|
+
const schemaVersion = raw.schemaVersion ?? 1;
|
|
711
|
+
if (schemaVersion > SUPPORTED_SCHEMA_VERSION) {
|
|
712
|
+
logger.warn(
|
|
713
|
+
`\u26A0\uFE0F Settings schema v${schemaVersion} > supported v${SUPPORTED_SCHEMA_VERSION}. Update happy-cli for full functionality.`
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
const migrated = migrateSettings(raw, schemaVersion);
|
|
717
|
+
if (migrated.profiles && Array.isArray(migrated.profiles)) {
|
|
718
|
+
const validProfiles = [];
|
|
719
|
+
for (const profile of migrated.profiles) {
|
|
720
|
+
try {
|
|
721
|
+
const validated = AIBackendProfileSchema.parse(profile);
|
|
722
|
+
validProfiles.push(validated);
|
|
723
|
+
} catch (error) {
|
|
724
|
+
logger.warn(
|
|
725
|
+
`\u26A0\uFE0F Invalid profile "${profile?.name || profile?.id || "unknown"}" - skipping. Error: ${error.message}`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
migrated.profiles = validProfiles;
|
|
730
|
+
}
|
|
731
|
+
return { ...defaultSettings, ...migrated };
|
|
732
|
+
} catch (error) {
|
|
733
|
+
logger.warn(`Failed to read settings: ${error.message}`);
|
|
734
|
+
return { ...defaultSettings };
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
async function updateSettings(updater) {
|
|
738
|
+
const LOCK_RETRY_INTERVAL_MS = 100;
|
|
739
|
+
const MAX_LOCK_ATTEMPTS = 50;
|
|
740
|
+
const STALE_LOCK_TIMEOUT_MS = 1e4;
|
|
741
|
+
const lockFile = configuration.settingsFile + ".lock";
|
|
742
|
+
const tmpFile = configuration.settingsFile + ".tmp";
|
|
743
|
+
let fileHandle;
|
|
744
|
+
let attempts = 0;
|
|
745
|
+
while (attempts < MAX_LOCK_ATTEMPTS) {
|
|
746
|
+
try {
|
|
747
|
+
fileHandle = await open(lockFile, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY);
|
|
748
|
+
break;
|
|
749
|
+
} catch (err) {
|
|
750
|
+
if (err.code === "EEXIST") {
|
|
751
|
+
attempts++;
|
|
752
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
|
|
753
|
+
try {
|
|
754
|
+
const stats = await stat(lockFile);
|
|
755
|
+
if (Date.now() - stats.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
|
|
756
|
+
await unlink(lockFile).catch(() => {
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
} else {
|
|
762
|
+
throw err;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (!fileHandle) {
|
|
767
|
+
throw new Error(`Failed to acquire settings lock after ${MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS / 1e3} seconds`);
|
|
768
|
+
}
|
|
769
|
+
try {
|
|
770
|
+
const current = await readSettings() || { ...defaultSettings };
|
|
771
|
+
const updated = await updater(current);
|
|
772
|
+
if (!existsSync(configuration.happyHomeDir)) {
|
|
773
|
+
await mkdir(configuration.happyHomeDir, { recursive: true });
|
|
774
|
+
}
|
|
775
|
+
await writeFile(tmpFile, JSON.stringify(updated, null, 2));
|
|
776
|
+
await rename(tmpFile, configuration.settingsFile);
|
|
777
|
+
return updated;
|
|
778
|
+
} finally {
|
|
779
|
+
await fileHandle.close();
|
|
780
|
+
await unlink(lockFile).catch(() => {
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
const credentialsSchema = z.object({
|
|
785
|
+
token: z.string(),
|
|
786
|
+
secret: z.string().base64().nullish(),
|
|
787
|
+
// Legacy
|
|
788
|
+
encryption: z.object({
|
|
789
|
+
publicKey: z.string().base64(),
|
|
790
|
+
machineKey: z.string().base64()
|
|
791
|
+
}).nullish()
|
|
792
|
+
});
|
|
793
|
+
async function readCredentials() {
|
|
794
|
+
if (!existsSync(configuration.privateKeyFile)) {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
const keyBase64 = await readFile(configuration.privateKeyFile, "utf8");
|
|
799
|
+
const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
|
|
800
|
+
if (credentials.secret) {
|
|
801
|
+
return {
|
|
802
|
+
token: credentials.token,
|
|
803
|
+
encryption: {
|
|
804
|
+
type: "legacy",
|
|
805
|
+
secret: new Uint8Array(Buffer.from(credentials.secret, "base64"))
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
} else if (credentials.encryption) {
|
|
809
|
+
return {
|
|
810
|
+
token: credentials.token,
|
|
811
|
+
encryption: {
|
|
812
|
+
type: "dataKey",
|
|
813
|
+
publicKey: new Uint8Array(Buffer.from(credentials.encryption.publicKey, "base64")),
|
|
814
|
+
machineKey: new Uint8Array(Buffer.from(credentials.encryption.machineKey, "base64"))
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
} catch {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
async function writeCredentialsLegacy(credentials) {
|
|
824
|
+
if (!existsSync(configuration.happyHomeDir)) {
|
|
825
|
+
await mkdir(configuration.happyHomeDir, { recursive: true });
|
|
826
|
+
}
|
|
827
|
+
await writeFile(configuration.privateKeyFile, JSON.stringify({
|
|
828
|
+
secret: encodeBase64(credentials.secret),
|
|
829
|
+
token: credentials.token
|
|
830
|
+
}, null, 2));
|
|
831
|
+
}
|
|
832
|
+
async function writeCredentialsDataKey(credentials) {
|
|
833
|
+
if (!existsSync(configuration.happyHomeDir)) {
|
|
834
|
+
await mkdir(configuration.happyHomeDir, { recursive: true });
|
|
835
|
+
}
|
|
836
|
+
await writeFile(configuration.privateKeyFile, JSON.stringify({
|
|
837
|
+
encryption: { publicKey: encodeBase64(credentials.publicKey), machineKey: encodeBase64(credentials.machineKey) },
|
|
838
|
+
token: credentials.token
|
|
839
|
+
}, null, 2));
|
|
840
|
+
}
|
|
841
|
+
async function clearCredentials() {
|
|
842
|
+
if (existsSync(configuration.privateKeyFile)) {
|
|
843
|
+
await unlink(configuration.privateKeyFile);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
async function clearMachineId() {
|
|
847
|
+
await updateSettings((settings) => ({
|
|
848
|
+
...settings,
|
|
849
|
+
machineId: void 0
|
|
850
|
+
}));
|
|
851
|
+
}
|
|
852
|
+
async function readDaemonState() {
|
|
853
|
+
try {
|
|
854
|
+
if (!existsSync(configuration.daemonStateFile)) {
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
const content = await readFile(configuration.daemonStateFile, "utf-8");
|
|
858
|
+
return JSON.parse(content);
|
|
859
|
+
} catch (error) {
|
|
860
|
+
console.error(`[PERSISTENCE] Daemon state file corrupted: ${configuration.daemonStateFile}`, error);
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function writeDaemonState(state) {
|
|
865
|
+
writeFileSync(configuration.daemonStateFile, JSON.stringify(state, null, 2), "utf-8");
|
|
866
|
+
}
|
|
867
|
+
async function clearDaemonState() {
|
|
868
|
+
if (existsSync(configuration.daemonStateFile)) {
|
|
869
|
+
await unlink(configuration.daemonStateFile);
|
|
870
|
+
}
|
|
871
|
+
if (existsSync(configuration.daemonLockFile)) {
|
|
872
|
+
try {
|
|
873
|
+
await unlink(configuration.daemonLockFile);
|
|
874
|
+
} catch {
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
async function acquireDaemonLock(maxAttempts = 5, delayIncrementMs = 200) {
|
|
879
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
880
|
+
try {
|
|
881
|
+
const fileHandle = await open(
|
|
882
|
+
configuration.daemonLockFile,
|
|
883
|
+
constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY
|
|
884
|
+
);
|
|
885
|
+
await fileHandle.writeFile(String(process.pid));
|
|
886
|
+
return fileHandle;
|
|
887
|
+
} catch (error) {
|
|
888
|
+
if (error.code === "EEXIST") {
|
|
889
|
+
try {
|
|
890
|
+
const lockPid = readFileSync(configuration.daemonLockFile, "utf-8").trim();
|
|
891
|
+
if (lockPid && !isNaN(Number(lockPid))) {
|
|
892
|
+
try {
|
|
893
|
+
process.kill(Number(lockPid), 0);
|
|
894
|
+
} catch {
|
|
895
|
+
unlinkSync(configuration.daemonLockFile);
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
} catch {
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
if (attempt === maxAttempts) {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
const delayMs = attempt * delayIncrementMs;
|
|
906
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
async function releaseDaemonLock(lockHandle) {
|
|
912
|
+
try {
|
|
913
|
+
await lockHandle.close();
|
|
914
|
+
} catch {
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
if (existsSync(configuration.daemonLockFile)) {
|
|
918
|
+
unlinkSync(configuration.daemonLockFile);
|
|
919
|
+
}
|
|
920
|
+
} catch {
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
var persistence = /*#__PURE__*/Object.freeze({
|
|
925
|
+
__proto__: null,
|
|
926
|
+
AIBackendProfileSchema: AIBackendProfileSchema,
|
|
927
|
+
SUPPORTED_SCHEMA_VERSION: SUPPORTED_SCHEMA_VERSION,
|
|
928
|
+
acquireDaemonLock: acquireDaemonLock,
|
|
929
|
+
clearCredentials: clearCredentials,
|
|
930
|
+
clearDaemonState: clearDaemonState,
|
|
931
|
+
clearMachineId: clearMachineId,
|
|
932
|
+
getProfileEnvironmentVariables: getProfileEnvironmentVariables,
|
|
933
|
+
readCredentials: readCredentials,
|
|
934
|
+
readDaemonState: readDaemonState,
|
|
935
|
+
readSettings: readSettings,
|
|
936
|
+
releaseDaemonLock: releaseDaemonLock,
|
|
937
|
+
updateSettings: updateSettings,
|
|
938
|
+
validateProfileForAgent: validateProfileForAgent,
|
|
939
|
+
writeCredentialsDataKey: writeCredentialsDataKey,
|
|
940
|
+
writeCredentialsLegacy: writeCredentialsLegacy,
|
|
941
|
+
writeDaemonState: writeDaemonState
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
export { getProfileEnvironmentVariables as A, encryptLegacy as B, getRandomBytes as C, libsodiumEncryptForPublicKey as D, logger$1 as E, persistence as F, readSettings as a, clearCredentials as b, configuration as c, clearMachineId as d, decodeBase64 as e, encodeBase64 as f, encrypt as g, decrypt as h, isDebug as i, readDaemonState as j, encryptWithDataKey as k, logger as l, decryptWithDataKey as m, clearDaemonState as n, libsodiumPublicKeyFromSecretKey as o, packageJson as p, decryptLegacy as q, readCredentials as r, encodeBase64Url as s, writeCredentialsLegacy as t, updateSettings as u, acquireDaemonLock as v, writeCredentialsDataKey as w, writeDaemonState as x, releaseDaemonLock as y, validateProfileForAgent as z };
|