@zonease/happy 0.6.38 → 0.6.40
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/bin/happy.mjs +10 -0
- package/dist/{types-DvTsFaqR.cjs → api-C7FyJfZ1.cjs} +400 -1964
- package/dist/{types-BWpRU1VN.mjs → api-D6WL40-U.mjs} +220 -1729
- package/dist/auth-C7ZScWEw.cjs +274 -0
- package/dist/auth-DCsIoZLT.cjs +183 -0
- package/dist/auth-DO9GexAR.mjs +181 -0
- package/dist/auth-DxJ3VvrX.mjs +270 -0
- package/dist/browser-AoVGRX7y.mjs +20 -0
- package/dist/browser-DzgO4oaD.cjs +22 -0
- package/dist/claudeLocal-C6BdzhDx.cjs +352 -0
- package/dist/claudeLocal-CWNGpJ0-.mjs +344 -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-CclB3QW9.cjs} +21 -34
- package/dist/{config-_GW4YBCP.mjs → config-DZk-mJ0K.mjs} +7 -20
- package/dist/connect-BXLUKJ5P.mjs +679 -0
- package/dist/connect-k9CqDJ-T.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-B23M-WPp.cjs +177 -0
- package/dist/controlClient-DB5tDU7V.mjs +167 -0
- package/dist/deviceKeys-BTdo34Uz.mjs +175 -0
- package/dist/deviceKeys-C540DcXl.cjs +182 -0
- package/dist/diagnose-B3D1a_v0.cjs +60 -0
- package/dist/diagnose-CuRKXxt1.mjs +58 -0
- package/dist/doctor-BQGLhrcI.cjs +214 -0
- package/dist/doctor-C08m5Z9f.cjs +78 -0
- package/dist/doctor-CU4JLGQ1.mjs +211 -0
- package/dist/doctor-ON7pN5G5.mjs +74 -0
- package/dist/downloadImage-DZXI3mXL.mjs +887 -0
- package/dist/downloadImage-cQ1Z7hkC.cjs +897 -0
- package/dist/enroll-B55BQR56.mjs +113 -0
- package/dist/enroll-Bo-CNio1.cjs +115 -0
- package/dist/evidence-DQp8QWcv.cjs +129 -0
- package/dist/evidence-DTYRAaOZ.mjs +127 -0
- package/dist/formatImageMessage-CixDYY6G.cjs +46 -0
- package/dist/formatImageMessage-DbKSSjMx.mjs +43 -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-B1e6rdy7.mjs +94 -0
- package/dist/install-CgUPGIoF.mjs +95 -0
- package/dist/install-D7Ir7ciZ.cjs +96 -0
- package/dist/install-DSDufaPd.cjs +98 -0
- package/dist/lib.cjs +18 -12
- package/dist/lib.d.cts +77 -0
- package/dist/lib.d.mts +77 -0
- package/dist/lib.mjs +13 -7
- package/dist/{loadableToolOutput-va3RLplh.mjs → loadableToolOutput-Bfei9wPq.mjs} +4 -2
- package/dist/{loadableToolOutput-5vdQB98M.cjs → loadableToolOutput-CbLGtvST.cjs} +63 -61
- package/dist/mcpToolSchemas-CjeTfrAs.mjs +62 -0
- package/dist/mcpToolSchemas-DK42yoZ_.cjs +64 -0
- package/dist/persistence-CBTtC_dm.cjs +996 -0
- package/dist/persistence-DKfF81pd.mjs +944 -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-BVZsMNwN.mjs +2164 -0
- package/dist/run-CURx7LhK.cjs +2188 -0
- package/dist/runClaude-6Gi0sYQy.mjs +3763 -0
- package/dist/runClaude-CL3Kt5ex.cjs +3766 -0
- package/dist/{runCodex-DiKzBf9q.mjs → runCodex-ClwEgJ7M.mjs} +36 -20
- package/dist/{runCodex-skq0j05d.cjs → runCodex-YKyVBkJ3.cjs} +169 -153
- package/dist/{runGemini-BeqrVq8R.mjs → runGemini-BPWxVr60.mjs} +34 -17
- package/dist/{runGemini-CVp-CEIh.cjs → runGemini-Xz2W1spn.cjs} +212 -195
- package/dist/{runOneShot-ltCpHbBB.mjs → runOneShot-CdCWiiLr.mjs} +14 -38
- package/dist/{runOneShot-BQNkNW61.cjs → runOneShot-DjZyCvDI.cjs} +31 -55
- package/dist/{serve-BNdoEDVx.mjs → serve-B48JttQ3.mjs} +12 -35
- package/dist/{serve-BECdtivY.cjs → serve-B8vaWQA0.cjs} +35 -58
- package/dist/session-BXboDn2V.mjs +108 -0
- package/dist/session-BZO25zxp.cjs +110 -0
- package/dist/sessionKeyCache-B6cGadYL.mjs +33 -0
- package/dist/sessionKeyCache-CDmexJ9p.cjs +36 -0
- package/dist/sessionList-CgAYZpNj.mjs +51 -0
- package/dist/sessionList-lQ-nqEhc.cjs +54 -0
- package/dist/sessionSummary-CcORiQSq.cjs +393 -0
- package/dist/sessionSummary-DCWQoaDP.mjs +387 -0
- package/dist/spawnHappyCLI-KiazE3kL.cjs +70 -0
- package/dist/spawnHappyCLI-yBIP_Oqi.mjs +66 -0
- package/dist/ssh-q5uZhqBf.mjs +359 -0
- package/dist/ssh-yMllSHu5.cjs +365 -0
- package/dist/systemdScope-BeOtAkhk.mjs +25 -0
- package/dist/systemdScope-DW3-UoKB.cjs +29 -0
- package/dist/task-_nr7J19j.cjs +167 -0
- package/dist/task-wNX1N2i4.mjs +165 -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-Buaz7d-X.cjs +77 -0
- package/dist/uninstall-Dxf6_4mQ.mjs +75 -0
- package/dist/update-CGlIfa8e.cjs +111 -0
- package/dist/update-CnnoX2cV.mjs +109 -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,679 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { r as readCredentials } from './persistence-DKfF81pd.mjs';
|
|
6
|
+
import { A as ApiClient } from './api-D6WL40-U.mjs';
|
|
7
|
+
import { createServer } from 'http';
|
|
8
|
+
import { randomBytes, createHash } from 'crypto';
|
|
9
|
+
import { o as openBrowser } from './browser-AoVGRX7y.mjs';
|
|
10
|
+
import { exec } from 'child_process';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
|
+
import 'node:fs/promises';
|
|
13
|
+
import 'node:fs';
|
|
14
|
+
import 'node:os';
|
|
15
|
+
import 'node:path';
|
|
16
|
+
import 'zod';
|
|
17
|
+
import 'node:crypto';
|
|
18
|
+
import 'tweetnacl';
|
|
19
|
+
import 'axios';
|
|
20
|
+
import 'node:events';
|
|
21
|
+
import 'socket.io-client';
|
|
22
|
+
import './index-CPKdvi7J.mjs';
|
|
23
|
+
import '@paralleldrive/cuid2';
|
|
24
|
+
import './time-DgsBLJA_.mjs';
|
|
25
|
+
import 'node:child_process';
|
|
26
|
+
import 'node:util';
|
|
27
|
+
import 'fs/promises';
|
|
28
|
+
import './projectPath-BTBIMB57.mjs';
|
|
29
|
+
import 'url';
|
|
30
|
+
import 'node:module';
|
|
31
|
+
import 'ws';
|
|
32
|
+
import '@stablelib/base64';
|
|
33
|
+
import 'node:readline';
|
|
34
|
+
import 'expo-server-sdk';
|
|
35
|
+
import './sessionKeyCache-B6cGadYL.mjs';
|
|
36
|
+
import 'open';
|
|
37
|
+
|
|
38
|
+
const CLIENT_ID$2 = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
39
|
+
const AUTH_BASE_URL = "https://auth.openai.com";
|
|
40
|
+
const DEFAULT_PORT$2 = 1455;
|
|
41
|
+
function generatePKCE$2() {
|
|
42
|
+
const verifier = randomBytes(32).toString("base64url").replace(/[^a-zA-Z0-9\-._~]/g, "");
|
|
43
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
44
|
+
return { verifier, challenge };
|
|
45
|
+
}
|
|
46
|
+
function generateState$2() {
|
|
47
|
+
return randomBytes(16).toString("hex");
|
|
48
|
+
}
|
|
49
|
+
function parseJWT(token) {
|
|
50
|
+
const parts = token.split(".");
|
|
51
|
+
if (parts.length !== 3) {
|
|
52
|
+
throw new Error("Invalid JWT format");
|
|
53
|
+
}
|
|
54
|
+
const payload = Buffer.from(parts[1], "base64url").toString();
|
|
55
|
+
return JSON.parse(payload);
|
|
56
|
+
}
|
|
57
|
+
async function findAvailablePort$2() {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const server = createServer();
|
|
60
|
+
server.listen(0, "127.0.0.1", () => {
|
|
61
|
+
const port = server.address().port;
|
|
62
|
+
server.close(() => resolve(port));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function isPortAvailable$2(port) {
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const testServer = createServer();
|
|
69
|
+
testServer.once("error", () => {
|
|
70
|
+
testServer.close();
|
|
71
|
+
resolve(false);
|
|
72
|
+
});
|
|
73
|
+
testServer.listen(port, "127.0.0.1", () => {
|
|
74
|
+
testServer.close(() => resolve(true));
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async function exchangeCodeForTokens$2(code, verifier, port) {
|
|
79
|
+
const response = await fetch(`${AUTH_BASE_URL}/oauth/token`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
83
|
+
},
|
|
84
|
+
body: new URLSearchParams({
|
|
85
|
+
grant_type: "authorization_code",
|
|
86
|
+
client_id: CLIENT_ID$2,
|
|
87
|
+
code,
|
|
88
|
+
code_verifier: verifier,
|
|
89
|
+
redirect_uri: `http://localhost:${port}/auth/callback`
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const error = await response.text();
|
|
94
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
95
|
+
}
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
const idTokenPayload = parseJWT(data.id_token);
|
|
98
|
+
let accountId = idTokenPayload.chatgpt_account_id;
|
|
99
|
+
if (!accountId) {
|
|
100
|
+
const authClaim = idTokenPayload["https://api.openai.com/auth"];
|
|
101
|
+
if (authClaim && typeof authClaim === "object") {
|
|
102
|
+
accountId = authClaim.chatgpt_account_id || authClaim.account_id;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
id_token: data.id_token,
|
|
107
|
+
access_token: data.access_token || data.id_token,
|
|
108
|
+
refresh_token: data.refresh_token,
|
|
109
|
+
account_id: accountId
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function startCallbackServer$2(state, verifier, port) {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const server = createServer(async (req, res) => {
|
|
115
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
116
|
+
if (url.pathname === "/auth/callback") {
|
|
117
|
+
const code = url.searchParams.get("code");
|
|
118
|
+
const receivedState = url.searchParams.get("state");
|
|
119
|
+
if (receivedState !== state) {
|
|
120
|
+
res.writeHead(400);
|
|
121
|
+
res.end("Invalid state parameter");
|
|
122
|
+
server.close();
|
|
123
|
+
reject(new Error("Invalid state parameter"));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!code) {
|
|
127
|
+
res.writeHead(400);
|
|
128
|
+
res.end("No authorization code received");
|
|
129
|
+
server.close();
|
|
130
|
+
reject(new Error("No authorization code received"));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const tokens = await exchangeCodeForTokens$2(code, verifier, port);
|
|
135
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
136
|
+
res.end(`
|
|
137
|
+
<html>
|
|
138
|
+
<body style="font-family: sans-serif; padding: 20px;">
|
|
139
|
+
<h2>\u2705 Authentication Successful!</h2>
|
|
140
|
+
<p>You can close this window and return to your terminal.</p>
|
|
141
|
+
<script>setTimeout(() => window.close(), 3000);<\/script>
|
|
142
|
+
</body>
|
|
143
|
+
</html>
|
|
144
|
+
`);
|
|
145
|
+
server.close();
|
|
146
|
+
resolve(tokens);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
res.writeHead(500);
|
|
149
|
+
res.end("Token exchange failed");
|
|
150
|
+
server.close();
|
|
151
|
+
reject(error);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
server.listen(port, "127.0.0.1", () => {
|
|
156
|
+
});
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
server.close();
|
|
159
|
+
reject(new Error("Authentication timeout"));
|
|
160
|
+
}, 5 * 60 * 1e3);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async function authenticateCodex() {
|
|
164
|
+
const { verifier, challenge } = generatePKCE$2();
|
|
165
|
+
const state = generateState$2();
|
|
166
|
+
let port = DEFAULT_PORT$2;
|
|
167
|
+
const portAvailable = await isPortAvailable$2(port);
|
|
168
|
+
if (!portAvailable) {
|
|
169
|
+
port = await findAvailablePort$2();
|
|
170
|
+
}
|
|
171
|
+
const serverPromise = startCallbackServer$2(state, verifier, port);
|
|
172
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
173
|
+
const redirect_uri = `http://localhost:${port}/auth/callback`;
|
|
174
|
+
const params = [
|
|
175
|
+
["response_type", "code"],
|
|
176
|
+
["client_id", CLIENT_ID$2],
|
|
177
|
+
["redirect_uri", redirect_uri],
|
|
178
|
+
["scope", "openid profile email offline_access"],
|
|
179
|
+
["code_challenge", challenge],
|
|
180
|
+
["code_challenge_method", "S256"],
|
|
181
|
+
["id_token_add_organizations", "true"],
|
|
182
|
+
["codex_cli_simplified_flow", "true"],
|
|
183
|
+
["state", state]
|
|
184
|
+
];
|
|
185
|
+
const queryString = params.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
|
|
186
|
+
const authUrl = `${AUTH_BASE_URL}/oauth/authorize?${queryString}`;
|
|
187
|
+
console.log("\u{1F4CB} Opening browser for authentication...");
|
|
188
|
+
console.log(`If browser doesn't open, visit:
|
|
189
|
+
${authUrl}
|
|
190
|
+
`);
|
|
191
|
+
await openBrowser(authUrl);
|
|
192
|
+
const tokens = await serverPromise;
|
|
193
|
+
console.log("\u{1F389} Authentication successful!");
|
|
194
|
+
return tokens;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const CLIENT_ID$1 = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
198
|
+
const CLAUDE_AI_AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
199
|
+
const TOKEN_URL$1 = "https://console.anthropic.com/v1/oauth/token";
|
|
200
|
+
const DEFAULT_PORT$1 = 54545;
|
|
201
|
+
const SCOPE = "user:inference";
|
|
202
|
+
function generatePKCE$1() {
|
|
203
|
+
const verifier = randomBytes(32).toString("base64url").replace(/[^a-zA-Z0-9\-._~]/g, "");
|
|
204
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
205
|
+
return { verifier, challenge };
|
|
206
|
+
}
|
|
207
|
+
function generateState$1() {
|
|
208
|
+
return randomBytes(32).toString("base64url");
|
|
209
|
+
}
|
|
210
|
+
async function findAvailablePort$1() {
|
|
211
|
+
return new Promise((resolve) => {
|
|
212
|
+
const server = createServer();
|
|
213
|
+
server.listen(0, "127.0.0.1", () => {
|
|
214
|
+
const port = server.address().port;
|
|
215
|
+
server.close(() => resolve(port));
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async function isPortAvailable$1(port) {
|
|
220
|
+
return new Promise((resolve) => {
|
|
221
|
+
const testServer = createServer();
|
|
222
|
+
testServer.once("error", () => {
|
|
223
|
+
testServer.close();
|
|
224
|
+
resolve(false);
|
|
225
|
+
});
|
|
226
|
+
testServer.listen(port, "127.0.0.1", () => {
|
|
227
|
+
testServer.close(() => resolve(true));
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
async function exchangeCodeForTokens$1(code, verifier, port, state) {
|
|
232
|
+
const tokenResponse = await fetch(TOKEN_URL$1, {
|
|
233
|
+
method: "POST",
|
|
234
|
+
headers: {
|
|
235
|
+
"Content-Type": "application/json"
|
|
236
|
+
},
|
|
237
|
+
body: JSON.stringify({
|
|
238
|
+
grant_type: "authorization_code",
|
|
239
|
+
code,
|
|
240
|
+
redirect_uri: `http://localhost:${port}/callback`,
|
|
241
|
+
client_id: CLIENT_ID$1,
|
|
242
|
+
code_verifier: verifier,
|
|
243
|
+
state
|
|
244
|
+
})
|
|
245
|
+
});
|
|
246
|
+
if (!tokenResponse.ok) {
|
|
247
|
+
throw new Error(`Token exchange failed: ${tokenResponse.statusText}`);
|
|
248
|
+
}
|
|
249
|
+
const tokenData = await tokenResponse.json();
|
|
250
|
+
return {
|
|
251
|
+
raw: tokenData,
|
|
252
|
+
token: tokenData.access_token,
|
|
253
|
+
expires: Date.now() + tokenData.expires_in * 1e3
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
async function startCallbackServer$1(state, verifier, port) {
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
const server = createServer(async (req, res) => {
|
|
259
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
260
|
+
if (url.pathname === "/callback") {
|
|
261
|
+
const code = url.searchParams.get("code");
|
|
262
|
+
const receivedState = url.searchParams.get("state");
|
|
263
|
+
if (receivedState !== state) {
|
|
264
|
+
res.writeHead(400);
|
|
265
|
+
res.end("Invalid state parameter");
|
|
266
|
+
server.close();
|
|
267
|
+
reject(new Error("Invalid state parameter"));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (!code) {
|
|
271
|
+
res.writeHead(400);
|
|
272
|
+
res.end("No authorization code received");
|
|
273
|
+
server.close();
|
|
274
|
+
reject(new Error("No authorization code received"));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const tokens = await exchangeCodeForTokens$1(code, verifier, port, state);
|
|
279
|
+
res.writeHead(302, {
|
|
280
|
+
"Location": "https://console.anthropic.com/oauth/code/success?app=claude-code"
|
|
281
|
+
});
|
|
282
|
+
res.end();
|
|
283
|
+
server.close();
|
|
284
|
+
resolve(tokens);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
res.writeHead(500);
|
|
287
|
+
res.end("Token exchange failed");
|
|
288
|
+
server.close();
|
|
289
|
+
reject(error);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
server.listen(port, "127.0.0.1", () => {
|
|
294
|
+
});
|
|
295
|
+
setTimeout(() => {
|
|
296
|
+
server.close();
|
|
297
|
+
reject(new Error("Authentication timeout"));
|
|
298
|
+
}, 5 * 60 * 1e3);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
async function authenticateClaude() {
|
|
302
|
+
console.log("\u{1F680} Starting Anthropic Claude authentication...");
|
|
303
|
+
const { verifier, challenge } = generatePKCE$1();
|
|
304
|
+
const state = generateState$1();
|
|
305
|
+
let port = DEFAULT_PORT$1;
|
|
306
|
+
const portAvailable = await isPortAvailable$1(port);
|
|
307
|
+
if (!portAvailable) {
|
|
308
|
+
console.log(`Port ${port} is in use, finding an available port...`);
|
|
309
|
+
port = await findAvailablePort$1();
|
|
310
|
+
}
|
|
311
|
+
console.log(`\u{1F4E1} Using callback port: ${port}`);
|
|
312
|
+
const serverPromise = startCallbackServer$1(state, verifier, port);
|
|
313
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
314
|
+
const redirect_uri = `http://localhost:${port}/callback`;
|
|
315
|
+
const params = new URLSearchParams({
|
|
316
|
+
code: "true",
|
|
317
|
+
// This tells Claude.ai to show the code AND redirect
|
|
318
|
+
client_id: CLIENT_ID$1,
|
|
319
|
+
response_type: "code",
|
|
320
|
+
redirect_uri,
|
|
321
|
+
scope: SCOPE,
|
|
322
|
+
code_challenge: challenge,
|
|
323
|
+
code_challenge_method: "S256",
|
|
324
|
+
state
|
|
325
|
+
});
|
|
326
|
+
const authUrl = `${CLAUDE_AI_AUTHORIZE_URL}?${params}`;
|
|
327
|
+
console.log("\u{1F4CB} Opening browser for authentication...");
|
|
328
|
+
console.log("If browser doesn't open, visit this URL:");
|
|
329
|
+
console.log();
|
|
330
|
+
console.log(`${authUrl}`);
|
|
331
|
+
console.log();
|
|
332
|
+
await openBrowser(authUrl);
|
|
333
|
+
try {
|
|
334
|
+
const tokens = await serverPromise;
|
|
335
|
+
console.log("\u{1F389} Authentication successful!");
|
|
336
|
+
console.log("\u2705 OAuth tokens received");
|
|
337
|
+
return tokens;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error("\n\u274C Failed to authenticate with Anthropic");
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const execAsync = promisify(exec);
|
|
345
|
+
const CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
|
|
346
|
+
const CLIENT_SECRET = "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";
|
|
347
|
+
const AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
348
|
+
const TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
349
|
+
const DEFAULT_PORT = 54545;
|
|
350
|
+
const SCOPES = [
|
|
351
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
352
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
353
|
+
"https://www.googleapis.com/auth/userinfo.profile"
|
|
354
|
+
].join(" ");
|
|
355
|
+
function generatePKCE() {
|
|
356
|
+
const verifier = randomBytes(32).toString("base64url").replace(/[^a-zA-Z0-9\-._~]/g, "");
|
|
357
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
358
|
+
return { verifier, challenge };
|
|
359
|
+
}
|
|
360
|
+
function generateState() {
|
|
361
|
+
return randomBytes(32).toString("hex");
|
|
362
|
+
}
|
|
363
|
+
async function findAvailablePort() {
|
|
364
|
+
return new Promise((resolve) => {
|
|
365
|
+
const server = createServer();
|
|
366
|
+
server.listen(0, "127.0.0.1", () => {
|
|
367
|
+
const port = server.address().port;
|
|
368
|
+
server.close(() => resolve(port));
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
async function isPortAvailable(port) {
|
|
373
|
+
return new Promise((resolve) => {
|
|
374
|
+
const testServer = createServer();
|
|
375
|
+
testServer.once("error", () => {
|
|
376
|
+
testServer.close();
|
|
377
|
+
resolve(false);
|
|
378
|
+
});
|
|
379
|
+
testServer.listen(port, "127.0.0.1", () => {
|
|
380
|
+
testServer.close(() => resolve(true));
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
async function exchangeCodeForTokens(code, verifier, port) {
|
|
385
|
+
const response = await fetch(TOKEN_URL, {
|
|
386
|
+
method: "POST",
|
|
387
|
+
headers: {
|
|
388
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
389
|
+
},
|
|
390
|
+
body: new URLSearchParams({
|
|
391
|
+
grant_type: "authorization_code",
|
|
392
|
+
client_id: CLIENT_ID,
|
|
393
|
+
client_secret: CLIENT_SECRET,
|
|
394
|
+
code,
|
|
395
|
+
code_verifier: verifier,
|
|
396
|
+
redirect_uri: `http://localhost:${port}/oauth2callback`
|
|
397
|
+
})
|
|
398
|
+
});
|
|
399
|
+
if (!response.ok) {
|
|
400
|
+
const error = await response.text();
|
|
401
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
402
|
+
}
|
|
403
|
+
const data = await response.json();
|
|
404
|
+
return data;
|
|
405
|
+
}
|
|
406
|
+
async function startCallbackServer(state, verifier, port) {
|
|
407
|
+
return new Promise((resolve, reject) => {
|
|
408
|
+
const server = createServer(async (req, res) => {
|
|
409
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
410
|
+
if (url.pathname === "/oauth2callback") {
|
|
411
|
+
const code = url.searchParams.get("code");
|
|
412
|
+
const receivedState = url.searchParams.get("state");
|
|
413
|
+
const error = url.searchParams.get("error");
|
|
414
|
+
if (error) {
|
|
415
|
+
res.writeHead(302, {
|
|
416
|
+
"Location": "https://developers.google.com/gemini-code-assist/auth_failure_gemini"
|
|
417
|
+
});
|
|
418
|
+
res.end();
|
|
419
|
+
server.close();
|
|
420
|
+
reject(new Error(`Authentication error: ${error}`));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (receivedState !== state) {
|
|
424
|
+
res.writeHead(400);
|
|
425
|
+
res.end("State mismatch. Possible CSRF attack");
|
|
426
|
+
server.close();
|
|
427
|
+
reject(new Error("Invalid state parameter"));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (!code) {
|
|
431
|
+
res.writeHead(400);
|
|
432
|
+
res.end("No authorization code received");
|
|
433
|
+
server.close();
|
|
434
|
+
reject(new Error("No authorization code received"));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
const tokens = await exchangeCodeForTokens(code, verifier, port);
|
|
439
|
+
res.writeHead(302, {
|
|
440
|
+
"Location": "https://developers.google.com/gemini-code-assist/auth_success_gemini"
|
|
441
|
+
});
|
|
442
|
+
res.end();
|
|
443
|
+
server.close();
|
|
444
|
+
resolve(tokens);
|
|
445
|
+
} catch (error2) {
|
|
446
|
+
res.writeHead(500);
|
|
447
|
+
res.end("Token exchange failed");
|
|
448
|
+
server.close();
|
|
449
|
+
reject(error2);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
server.listen(port, "127.0.0.1", () => {
|
|
454
|
+
});
|
|
455
|
+
setTimeout(() => {
|
|
456
|
+
server.close();
|
|
457
|
+
reject(new Error("Authentication timeout"));
|
|
458
|
+
}, 5 * 60 * 1e3);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
async function authenticateGemini() {
|
|
462
|
+
console.log("\u{1F680} Starting Google Gemini authentication...");
|
|
463
|
+
const { verifier, challenge } = generatePKCE();
|
|
464
|
+
const state = generateState();
|
|
465
|
+
let port = DEFAULT_PORT;
|
|
466
|
+
const portAvailable = await isPortAvailable(port);
|
|
467
|
+
if (!portAvailable) {
|
|
468
|
+
console.log(`Port ${port} is in use, finding an available port...`);
|
|
469
|
+
port = await findAvailablePort();
|
|
470
|
+
}
|
|
471
|
+
console.log(`\u{1F4E1} Using callback port: ${port}`);
|
|
472
|
+
const serverPromise = startCallbackServer(state, verifier, port);
|
|
473
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
474
|
+
const redirect_uri = `http://localhost:${port}/oauth2callback`;
|
|
475
|
+
const params = new URLSearchParams({
|
|
476
|
+
client_id: CLIENT_ID,
|
|
477
|
+
response_type: "code",
|
|
478
|
+
redirect_uri,
|
|
479
|
+
scope: SCOPES,
|
|
480
|
+
access_type: "offline",
|
|
481
|
+
// To get refresh token
|
|
482
|
+
code_challenge: challenge,
|
|
483
|
+
code_challenge_method: "S256",
|
|
484
|
+
state,
|
|
485
|
+
prompt: "consent"
|
|
486
|
+
// Force consent to get refresh token
|
|
487
|
+
});
|
|
488
|
+
const authUrl = `${AUTHORIZE_URL}?${params}`;
|
|
489
|
+
console.log("\n\u{1F4CB} Opening browser for authentication...");
|
|
490
|
+
console.log("If browser doesn't open, visit this URL:");
|
|
491
|
+
console.log(`
|
|
492
|
+
${authUrl}
|
|
493
|
+
`);
|
|
494
|
+
const platform = process.platform;
|
|
495
|
+
const openCommand = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
496
|
+
try {
|
|
497
|
+
await execAsync(`${openCommand} "${authUrl}"`);
|
|
498
|
+
} catch {
|
|
499
|
+
console.log("\u26A0\uFE0F Could not open browser automatically");
|
|
500
|
+
}
|
|
501
|
+
try {
|
|
502
|
+
const tokens = await serverPromise;
|
|
503
|
+
console.log("\n\u{1F389} Authentication successful!");
|
|
504
|
+
console.log("\u2705 OAuth tokens received");
|
|
505
|
+
return tokens;
|
|
506
|
+
} catch (error) {
|
|
507
|
+
console.error("\n\u274C Failed to authenticate with Google");
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function decodeJwtPayload(token) {
|
|
513
|
+
try {
|
|
514
|
+
const parts = token.split(".");
|
|
515
|
+
if (parts.length !== 3) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
const payload = parts[1];
|
|
519
|
+
const decoded = Buffer.from(payload, "base64url").toString("utf-8");
|
|
520
|
+
return JSON.parse(decoded);
|
|
521
|
+
} catch {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function handleConnectCommand(args) {
|
|
527
|
+
const subcommand = args[0];
|
|
528
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
529
|
+
showConnectHelp();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
switch (subcommand.toLowerCase()) {
|
|
533
|
+
case "codex":
|
|
534
|
+
await handleConnectVendor("codex", "OpenAI");
|
|
535
|
+
break;
|
|
536
|
+
case "claude":
|
|
537
|
+
await handleConnectVendor("claude", "Anthropic");
|
|
538
|
+
break;
|
|
539
|
+
case "gemini":
|
|
540
|
+
await handleConnectVendor("gemini", "Gemini");
|
|
541
|
+
break;
|
|
542
|
+
case "status":
|
|
543
|
+
await handleConnectStatus();
|
|
544
|
+
break;
|
|
545
|
+
default:
|
|
546
|
+
console.error(chalk.red(`Unknown connect target: ${subcommand}`));
|
|
547
|
+
showConnectHelp();
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function showConnectHelp() {
|
|
552
|
+
console.log(`
|
|
553
|
+
${chalk.bold("happy connect")} - Connect AI vendor API keys to Happy AI cloud
|
|
554
|
+
|
|
555
|
+
${chalk.bold("Usage:")}
|
|
556
|
+
happy connect codex Store your Codex API key in Happy AI cloud
|
|
557
|
+
happy connect claude Store your Anthropic API key in Happy AI cloud
|
|
558
|
+
happy connect gemini Store your Gemini API key in Happy AI cloud
|
|
559
|
+
happy connect status Show connection status for all vendors
|
|
560
|
+
happy connect help Show this help message
|
|
561
|
+
|
|
562
|
+
${chalk.bold("Description:")}
|
|
563
|
+
The connect command allows you to securely store your AI vendor API keys
|
|
564
|
+
in Happy AI cloud. This enables you to use these services through Happy
|
|
565
|
+
without exposing your API keys locally.
|
|
566
|
+
|
|
567
|
+
${chalk.bold("Examples:")}
|
|
568
|
+
happy connect codex
|
|
569
|
+
happy connect claude
|
|
570
|
+
happy connect gemini
|
|
571
|
+
happy connect status
|
|
572
|
+
|
|
573
|
+
${chalk.bold("Notes:")}
|
|
574
|
+
\u2022 You must be authenticated with Happy first (run 'happy auth login')
|
|
575
|
+
\u2022 API keys are encrypted and stored securely in Happy AI cloud
|
|
576
|
+
\u2022 You can manage your stored keys at happy.zonease.org
|
|
577
|
+
`);
|
|
578
|
+
}
|
|
579
|
+
async function handleConnectVendor(vendor, displayName) {
|
|
580
|
+
console.log(chalk.bold(`
|
|
581
|
+
\u{1F50C} Connecting ${displayName} to Happy AI cloud
|
|
582
|
+
`));
|
|
583
|
+
const credentials = await readCredentials();
|
|
584
|
+
if (!credentials) {
|
|
585
|
+
console.log(chalk.yellow("\u26A0\uFE0F Not authenticated with Happy"));
|
|
586
|
+
console.log(chalk.gray(' Please run "happy auth login" first'));
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
const api = await ApiClient.create(credentials);
|
|
590
|
+
if (vendor === "codex") {
|
|
591
|
+
console.log("\u{1F680} Registering Codex token with server");
|
|
592
|
+
const codexAuthTokens = await authenticateCodex();
|
|
593
|
+
await api.registerVendorToken("openai", { oauth: codexAuthTokens });
|
|
594
|
+
console.log("\u2705 Codex token registered with server");
|
|
595
|
+
process.exit(0);
|
|
596
|
+
} else if (vendor === "claude") {
|
|
597
|
+
console.log("\u{1F680} Registering Anthropic token with server");
|
|
598
|
+
const anthropicAuthTokens = await authenticateClaude();
|
|
599
|
+
await api.registerVendorToken("anthropic", { oauth: anthropicAuthTokens });
|
|
600
|
+
console.log("\u2705 Anthropic token registered with server");
|
|
601
|
+
process.exit(0);
|
|
602
|
+
} else if (vendor === "gemini") {
|
|
603
|
+
console.log("\u{1F680} Registering Gemini token with server");
|
|
604
|
+
const geminiAuthTokens = await authenticateGemini();
|
|
605
|
+
await api.registerVendorToken("gemini", { oauth: geminiAuthTokens });
|
|
606
|
+
console.log("\u2705 Gemini token registered with server");
|
|
607
|
+
updateLocalGeminiCredentials(geminiAuthTokens);
|
|
608
|
+
process.exit(0);
|
|
609
|
+
} else {
|
|
610
|
+
throw new Error(`Unsupported vendor: ${vendor}`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
async function handleConnectStatus() {
|
|
614
|
+
console.log(chalk.bold("\n\u{1F50C} Connection Status\n"));
|
|
615
|
+
const credentials = await readCredentials();
|
|
616
|
+
if (!credentials) {
|
|
617
|
+
console.log(chalk.yellow("\u26A0\uFE0F Not authenticated with Happy"));
|
|
618
|
+
console.log(chalk.gray(' Please run "happy auth login" first'));
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
const api = await ApiClient.create(credentials);
|
|
622
|
+
const vendors = [
|
|
623
|
+
{ key: "gemini", name: "Gemini", display: "Google Gemini" },
|
|
624
|
+
{ key: "openai", name: "Codex", display: "OpenAI Codex" },
|
|
625
|
+
{ key: "anthropic", name: "Claude", display: "Anthropic Claude" }
|
|
626
|
+
];
|
|
627
|
+
for (const vendor of vendors) {
|
|
628
|
+
try {
|
|
629
|
+
const token = await api.getVendorToken(vendor.key);
|
|
630
|
+
if (token?.oauth) {
|
|
631
|
+
let userInfo = "";
|
|
632
|
+
if (token.oauth.id_token) {
|
|
633
|
+
const payload = decodeJwtPayload(token.oauth.id_token);
|
|
634
|
+
if (payload?.email) {
|
|
635
|
+
userInfo = chalk.gray(` (${payload.email})`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const expiresAt = token.oauth.expires_at || (token.oauth.expires_in ? Date.now() + token.oauth.expires_in * 1e3 : null);
|
|
639
|
+
const isExpired = expiresAt && expiresAt < Date.now();
|
|
640
|
+
if (isExpired) {
|
|
641
|
+
console.log(` ${chalk.yellow("\u26A0\uFE0F")} ${vendor.display}: ${chalk.yellow("expired")}${userInfo}`);
|
|
642
|
+
} else {
|
|
643
|
+
console.log(` ${chalk.green("\u2713")} ${vendor.display}: ${chalk.green("connected")}${userInfo}`);
|
|
644
|
+
}
|
|
645
|
+
} else {
|
|
646
|
+
console.log(` ${chalk.gray("\u25CB")} ${vendor.display}: ${chalk.gray("not connected")}`);
|
|
647
|
+
}
|
|
648
|
+
} catch {
|
|
649
|
+
console.log(` ${chalk.gray("\u25CB")} ${vendor.display}: ${chalk.gray("not connected")}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
console.log("");
|
|
653
|
+
console.log(chalk.gray("To connect a vendor, run: happy connect <vendor>"));
|
|
654
|
+
console.log(chalk.gray("Example: happy connect gemini"));
|
|
655
|
+
console.log("");
|
|
656
|
+
}
|
|
657
|
+
function updateLocalGeminiCredentials(tokens) {
|
|
658
|
+
try {
|
|
659
|
+
const geminiDir = join(homedir(), ".gemini");
|
|
660
|
+
const credentialsPath = join(geminiDir, "oauth_creds.json");
|
|
661
|
+
if (!existsSync(geminiDir)) {
|
|
662
|
+
mkdirSync(geminiDir, { recursive: true });
|
|
663
|
+
}
|
|
664
|
+
const credentials = {
|
|
665
|
+
access_token: tokens.access_token,
|
|
666
|
+
token_type: tokens.token_type || "Bearer",
|
|
667
|
+
scope: tokens.scope || "https://www.googleapis.com/auth/cloud-platform",
|
|
668
|
+
...tokens.refresh_token && { refresh_token: tokens.refresh_token },
|
|
669
|
+
...tokens.id_token && { id_token: tokens.id_token },
|
|
670
|
+
...tokens.expires_in && { expires_in: tokens.expires_in }
|
|
671
|
+
};
|
|
672
|
+
writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), "utf-8");
|
|
673
|
+
console.log(chalk.gray(` Updated local credentials: ${credentialsPath}`));
|
|
674
|
+
} catch (error) {
|
|
675
|
+
console.log(chalk.yellow(` \u26A0\uFE0F Could not update local credentials: ${error}`));
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export { handleConnectCommand };
|