@zonease/happy 0.6.38 → 0.6.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{types-BWpRU1VN.mjs → api-CIZKyfSn.mjs} +136 -1725
- package/dist/{types-DvTsFaqR.cjs → api-CwOCQeLH.cjs} +317 -1961
- package/dist/auth-0VnlXT9f.mjs +181 -0
- package/dist/auth-BE8dD20T.mjs +270 -0
- package/dist/auth-BIleVCw0.cjs +274 -0
- package/dist/auth-Cas9DR3b.cjs +183 -0
- package/dist/browser-Cc08m7of.cjs +22 -0
- package/dist/browser-Dll5hxOV.mjs +20 -0
- package/dist/claudeLocal-CPT3zcIU.mjs +344 -0
- package/dist/claudeLocal-WkgLbl1a.cjs +352 -0
- package/dist/codex/happyMcpStdioBridge.cjs +4 -1
- package/dist/codex/happyMcpStdioBridge.mjs +4 -1
- package/dist/common-BdNgghUJ.mjs +75 -0
- package/dist/common-CZvSRU4p.cjs +83 -0
- package/dist/{config-0Xnc8ARM.cjs → config-Draa-5XC.cjs} +21 -34
- package/dist/{config-_GW4YBCP.mjs → config-rTT4qTU5.mjs} +7 -20
- package/dist/connect-BfC1tYP_.mjs +679 -0
- package/dist/connect-CJZ73syK.cjs +681 -0
- package/dist/{constants-B4x1K7by.cjs → constants-E_UWYTo3.cjs} +2 -2
- package/dist/{constants-BO9OAFOT.mjs → constants-pmXZGibX.mjs} +1 -1
- package/dist/controlClient-CM_QcJnB.mjs +159 -0
- package/dist/controlClient-DZMIsKsT.cjs +168 -0
- package/dist/deviceExec-CU1R9CRb.cjs +84 -0
- package/dist/deviceExec-D93AOVV4.mjs +81 -0
- package/dist/diagnose-B3D1a_v0.cjs +60 -0
- package/dist/diagnose-CuRKXxt1.mjs +58 -0
- package/dist/doctor-C08m5Z9f.cjs +78 -0
- package/dist/doctor-Cya9YSA0.cjs +214 -0
- package/dist/doctor-DZYB6IVc.mjs +211 -0
- package/dist/doctor-ON7pN5G5.mjs +74 -0
- package/dist/downloadImage-DfSFLb-h.mjs +887 -0
- package/dist/downloadImage-MQMrUUxS.cjs +897 -0
- package/dist/enroll-BxlbVftP.cjs +115 -0
- package/dist/enroll-C52x8oly.mjs +113 -0
- package/dist/evidence-Ccd9iYcj.mjs +127 -0
- package/dist/evidence-CjWBG5rX.cjs +129 -0
- package/dist/formatImageMessage-B4lSxlk2.mjs +43 -0
- package/dist/formatImageMessage-DbyG8c2j.cjs +46 -0
- package/dist/github-DKcyF1zp.mjs +57 -0
- package/dist/github-h9GhJJ_C.cjs +59 -0
- package/dist/index-CDTNgfUf.cjs +578 -0
- package/dist/index-CPKdvi7J.mjs +554 -0
- package/dist/index.cjs +812 -40
- package/dist/index.mjs +813 -40
- package/dist/install-BokHRgFW.cjs +98 -0
- package/dist/install-C_cp8b2-.cjs +96 -0
- package/dist/install-DAeCn8ae.mjs +94 -0
- package/dist/install-oRAjVLjP.mjs +95 -0
- package/dist/lib.cjs +18 -12
- package/dist/lib.mjs +13 -7
- package/dist/{loadableToolOutput-va3RLplh.mjs → loadableToolOutput-Cuygmd4K.mjs} +4 -2
- package/dist/{loadableToolOutput-5vdQB98M.cjs → loadableToolOutput-DDCJjywt.cjs} +63 -61
- package/dist/mcpToolSchemas-CjeTfrAs.mjs +62 -0
- package/dist/mcpToolSchemas-DK42yoZ_.cjs +64 -0
- package/dist/persistence-BFjvtUad.mjs +944 -0
- package/dist/persistence-JLUn_Nou.cjs +996 -0
- package/dist/projectPath-BTBIMB57.mjs +10 -0
- package/dist/projectPath-ChLhdOyt.cjs +13 -0
- package/dist/prompt-BTWDZZO0.cjs +25 -0
- package/dist/prompt-BsAZsPbo.mjs +22 -0
- package/dist/release-BMEzNONE.mjs +168 -0
- package/dist/release-EhOJeAZe.cjs +171 -0
- package/dist/run-BDn2aE3Q.cjs +2152 -0
- package/dist/run-DBZqN6Kn.mjs +2128 -0
- package/dist/runClaude-DNHvERX0.mjs +3763 -0
- package/dist/runClaude-uaKxPZPm.cjs +3766 -0
- package/dist/{runCodex-skq0j05d.cjs → runCodex-82UrZLme.cjs} +169 -153
- package/dist/{runCodex-DiKzBf9q.mjs → runCodex-C-RTM0wA.mjs} +36 -20
- package/dist/{runGemini-BeqrVq8R.mjs → runGemini-Bm6-4Xm2.mjs} +34 -17
- package/dist/{runGemini-CVp-CEIh.cjs → runGemini-CXMr4Jwb.cjs} +212 -195
- package/dist/{runOneShot-BQNkNW61.cjs → runOneShot-1B6RMYT7.cjs} +31 -55
- package/dist/{runOneShot-ltCpHbBB.mjs → runOneShot-ioARRHrx.mjs} +14 -38
- package/dist/{serve-BECdtivY.cjs → serve-BtdAhkIE.cjs} +35 -58
- package/dist/{serve-BNdoEDVx.mjs → serve-CwibwU6N.mjs} +12 -35
- package/dist/session-2sU4Bu1R.mjs +108 -0
- package/dist/session-D--F87zL.cjs +110 -0
- package/dist/sessionKeyCache-4Tp8pJ3N.cjs +36 -0
- package/dist/sessionKeyCache-D2BwO91C.mjs +33 -0
- package/dist/sessionList-BCSw3qYt.mjs +51 -0
- package/dist/sessionList-ICJsjNtJ.cjs +54 -0
- package/dist/sessionSummary-Bkz52BPh.cjs +393 -0
- package/dist/sessionSummary-lHGBWQSb.mjs +387 -0
- package/dist/spawnHappyCLI-CoORX0YW.mjs +66 -0
- package/dist/spawnHappyCLI-D_V-AAoP.cjs +70 -0
- package/dist/ssh-BTOlhhxi.cjs +333 -0
- package/dist/ssh-DL_AkRAD.mjs +328 -0
- package/dist/systemdScope-BeOtAkhk.mjs +25 -0
- package/dist/systemdScope-DW3-UoKB.cjs +29 -0
- package/dist/task-CQzrHRZe.mjs +165 -0
- package/dist/task-CSpaC9HX.cjs +167 -0
- package/dist/time-Bj2HRkKH.cjs +33 -0
- package/dist/time-DgsBLJA_.mjs +29 -0
- package/dist/trimIdent-4gvk9Wk6.cjs +22 -0
- package/dist/trimIdent-BAjmv_TG.mjs +20 -0
- package/dist/types-BoJh4Qfv.mjs +57 -0
- package/dist/types-DWeB7aqh.cjs +59 -0
- package/dist/uninstall-BiKyRAb1.cjs +77 -0
- package/dist/uninstall-nFMFGUVM.mjs +75 -0
- package/dist/update-BcqRHXf2.mjs +109 -0
- package/dist/update-Dw8pgsAT.cjs +111 -0
- package/dist/workflowCommon-B0d01Y_a.mjs +56 -0
- package/dist/workflowCommon-Bl568-rt.cjs +62 -0
- package/dist/workflowReports-BEWuQesA.cjs +113 -0
- package/dist/workflowReports-Dd8-4-8f.mjs +108 -0
- package/package.json +1 -1
- package/dist/index-BbCDDvoB.cjs +0 -11376
- package/dist/index-vvZXdswy.mjs +0 -11336
- package/dist/mcpToolSchemas-CdPXJQmb.mjs +0 -175
- package/dist/mcpToolSchemas-f-kttjFw.cjs +0 -186
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { r as readCredentials, a as readSettings, c as configuration, l as logger, b as clearCredentials, d as clearMachineId } from './persistence-BFjvtUad.mjs';
|
|
3
|
+
import { authAndSetupMachineIfNeeded } from './auth-BE8dD20T.mjs';
|
|
4
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
5
|
+
import { createInterface } from 'node:readline';
|
|
6
|
+
import { checkIfDaemonRunningAndCleanupStaleState, stopDaemon } from './controlClient-CM_QcJnB.mjs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import 'node:fs/promises';
|
|
9
|
+
import 'node:path';
|
|
10
|
+
import 'zod';
|
|
11
|
+
import 'node:crypto';
|
|
12
|
+
import 'tweetnacl';
|
|
13
|
+
import 'fs';
|
|
14
|
+
import 'axios';
|
|
15
|
+
import 'qrcode-terminal';
|
|
16
|
+
import './time-DgsBLJA_.mjs';
|
|
17
|
+
import './browser-Dll5hxOV.mjs';
|
|
18
|
+
import 'open';
|
|
19
|
+
import 'react';
|
|
20
|
+
import 'ink';
|
|
21
|
+
import './projectPath-BTBIMB57.mjs';
|
|
22
|
+
import 'path';
|
|
23
|
+
import 'url';
|
|
24
|
+
|
|
25
|
+
async function handleAuthCommand(args) {
|
|
26
|
+
const subcommand = args[0];
|
|
27
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
28
|
+
showAuthHelp();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
switch (subcommand) {
|
|
32
|
+
case "login":
|
|
33
|
+
await handleAuthLogin(args.slice(1));
|
|
34
|
+
break;
|
|
35
|
+
case "logout":
|
|
36
|
+
await handleAuthLogout();
|
|
37
|
+
break;
|
|
38
|
+
case "status":
|
|
39
|
+
await handleAuthStatus();
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
console.error(chalk.red(`Unknown auth subcommand: ${subcommand}`));
|
|
43
|
+
showAuthHelp();
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function showAuthHelp() {
|
|
48
|
+
console.log(`
|
|
49
|
+
${chalk.bold("happy auth")} - Authentication management
|
|
50
|
+
|
|
51
|
+
${chalk.bold("Usage:")}
|
|
52
|
+
happy auth login [--force] Authenticate with Happy
|
|
53
|
+
happy auth logout Remove authentication and machine data
|
|
54
|
+
happy auth status Show authentication status
|
|
55
|
+
happy auth help Show this help message
|
|
56
|
+
|
|
57
|
+
${chalk.bold("Options:")}
|
|
58
|
+
--force Clear credentials, machine ID, and stop daemon before re-auth
|
|
59
|
+
|
|
60
|
+
${chalk.gray("PS: Your master secret never leaves your mobile/web device. Each CLI machine")}
|
|
61
|
+
${chalk.gray("receives only a derived key for per-machine encryption, so backup codes")}
|
|
62
|
+
${chalk.gray("cannot be displayed from the CLI.")}
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
async function handleAuthLogin(args) {
|
|
66
|
+
const forceAuth = args.includes("--force") || args.includes("-f");
|
|
67
|
+
if (forceAuth) {
|
|
68
|
+
console.log(chalk.yellow("Force authentication requested."));
|
|
69
|
+
console.log(chalk.gray("This will:"));
|
|
70
|
+
console.log(chalk.gray(" \u2022 Clear existing credentials"));
|
|
71
|
+
console.log(chalk.gray(" \u2022 Clear machine ID"));
|
|
72
|
+
console.log(chalk.gray(" \u2022 Stop daemon if running"));
|
|
73
|
+
console.log(chalk.gray(" \u2022 Re-authenticate and register machine\n"));
|
|
74
|
+
try {
|
|
75
|
+
logger.debug("Stopping daemon for force auth...");
|
|
76
|
+
await stopDaemon();
|
|
77
|
+
console.log(chalk.gray("\u2713 Stopped daemon"));
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.debug("Daemon was not running or failed to stop:", error);
|
|
80
|
+
}
|
|
81
|
+
await clearCredentials();
|
|
82
|
+
console.log(chalk.gray("\u2713 Cleared credentials"));
|
|
83
|
+
await clearMachineId();
|
|
84
|
+
console.log(chalk.gray("\u2713 Cleared machine ID"));
|
|
85
|
+
console.log("");
|
|
86
|
+
}
|
|
87
|
+
if (!forceAuth) {
|
|
88
|
+
const existingCreds = await readCredentials();
|
|
89
|
+
const settings = await readSettings();
|
|
90
|
+
if (existingCreds && settings?.machineId) {
|
|
91
|
+
console.log(chalk.green("\u2713 Already authenticated"));
|
|
92
|
+
console.log(chalk.gray(` Machine ID: ${settings.machineId}`));
|
|
93
|
+
console.log(chalk.gray(` Host: ${os.hostname()}`));
|
|
94
|
+
console.log(chalk.gray(` Use 'happy auth login --force' to re-authenticate`));
|
|
95
|
+
return;
|
|
96
|
+
} else if (existingCreds && !settings?.machineId) {
|
|
97
|
+
console.log(chalk.yellow("\u26A0\uFE0F Credentials exist but machine ID is missing"));
|
|
98
|
+
console.log(chalk.gray(" This can happen if --auth flag was used previously"));
|
|
99
|
+
console.log(chalk.gray(" Fixing by setting up machine...\n"));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const result = await authAndSetupMachineIfNeeded();
|
|
104
|
+
console.log(chalk.green("\n\u2713 Authentication successful"));
|
|
105
|
+
console.log(chalk.gray(` Machine ID: ${result.machineId}`));
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(chalk.red("Authentication failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function handleAuthLogout() {
|
|
112
|
+
const happyDir = configuration.happyHomeDir;
|
|
113
|
+
const credentials = await readCredentials();
|
|
114
|
+
if (!credentials) {
|
|
115
|
+
console.log(chalk.yellow("Not currently authenticated"));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
console.log(chalk.blue("This will log you out of Happy"));
|
|
119
|
+
console.log(chalk.yellow("\u26A0\uFE0F You will need to re-authenticate to use Happy again"));
|
|
120
|
+
const rl = createInterface({
|
|
121
|
+
input: process.stdin,
|
|
122
|
+
output: process.stdout
|
|
123
|
+
});
|
|
124
|
+
const answer = await new Promise((resolve) => {
|
|
125
|
+
rl.question(chalk.yellow("Are you sure you want to log out? (y/N): "), resolve);
|
|
126
|
+
});
|
|
127
|
+
rl.close();
|
|
128
|
+
if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
|
|
129
|
+
try {
|
|
130
|
+
try {
|
|
131
|
+
await stopDaemon();
|
|
132
|
+
console.log(chalk.gray("Stopped daemon"));
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
if (existsSync(happyDir)) {
|
|
136
|
+
rmSync(happyDir, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
console.log(chalk.green("\u2713 Successfully logged out"));
|
|
139
|
+
console.log(chalk.gray(' Run "happy auth login" to authenticate again'));
|
|
140
|
+
} catch (error) {
|
|
141
|
+
throw new Error(`Failed to logout: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
console.log(chalk.blue("Logout cancelled"));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function handleAuthStatus() {
|
|
148
|
+
const credentials = await readCredentials();
|
|
149
|
+
const settings = await readSettings();
|
|
150
|
+
console.log(chalk.bold("\nAuthentication Status\n"));
|
|
151
|
+
if (!credentials) {
|
|
152
|
+
console.log(chalk.red("\u2717 Not authenticated"));
|
|
153
|
+
console.log(chalk.gray(' Run "happy auth login" to authenticate'));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
console.log(chalk.green("\u2713 Authenticated"));
|
|
157
|
+
const tokenPreview = credentials.token.substring(0, 30) + "...";
|
|
158
|
+
console.log(chalk.gray(` Token: ${tokenPreview}`));
|
|
159
|
+
if (settings?.machineId) {
|
|
160
|
+
console.log(chalk.green("\u2713 Machine registered"));
|
|
161
|
+
console.log(chalk.gray(` Machine ID: ${settings.machineId}`));
|
|
162
|
+
console.log(chalk.gray(` Host: ${os.hostname()}`));
|
|
163
|
+
} else {
|
|
164
|
+
console.log(chalk.yellow("\u26A0\uFE0F Machine not registered"));
|
|
165
|
+
console.log(chalk.gray(' Run "happy auth login --force" to fix this'));
|
|
166
|
+
}
|
|
167
|
+
console.log(chalk.gray(`
|
|
168
|
+
Data directory: ${configuration.happyHomeDir}`));
|
|
169
|
+
try {
|
|
170
|
+
const running = await checkIfDaemonRunningAndCleanupStaleState();
|
|
171
|
+
if (running) {
|
|
172
|
+
console.log(chalk.green("\u2713 Daemon running"));
|
|
173
|
+
} else {
|
|
174
|
+
console.log(chalk.gray("\u2717 Daemon not running"));
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
console.log(chalk.gray("\u2717 Daemon not running"));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export { handleAuthCommand };
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { f as encodeBase64, c as configuration, l as logger, r as readCredentials, u as updateSettings, i as isDebug, s as encodeBase64Url, e as decodeBase64, t as writeCredentialsLegacy, w as writeCredentialsDataKey } from './persistence-BFjvtUad.mjs';
|
|
2
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
3
|
+
import tweetnacl from 'tweetnacl';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import qrcode from 'qrcode-terminal';
|
|
6
|
+
import { d as delay } from './time-DgsBLJA_.mjs';
|
|
7
|
+
import { o as openBrowser } from './browser-Dll5hxOV.mjs';
|
|
8
|
+
import React, { useState } from 'react';
|
|
9
|
+
import { useInput, Box, Text, render } from 'ink';
|
|
10
|
+
import 'node:fs/promises';
|
|
11
|
+
import 'node:fs';
|
|
12
|
+
import 'node:os';
|
|
13
|
+
import 'node:path';
|
|
14
|
+
import 'zod';
|
|
15
|
+
import 'chalk';
|
|
16
|
+
import 'fs';
|
|
17
|
+
import 'open';
|
|
18
|
+
|
|
19
|
+
function displayQRCode(url) {
|
|
20
|
+
console.log("=".repeat(80));
|
|
21
|
+
console.log("\u{1F4F1} To authenticate, scan this QR code with your mobile device:");
|
|
22
|
+
console.log("=".repeat(80));
|
|
23
|
+
qrcode.generate(url, { small: true }, (qr) => {
|
|
24
|
+
for (let l of qr.split("\n")) {
|
|
25
|
+
console.log(" ".repeat(10) + l);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
console.log("=".repeat(80));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function generateWebAuthUrl(publicKey) {
|
|
32
|
+
const publicKeyBase64 = encodeBase64(publicKey, "base64url");
|
|
33
|
+
return `${configuration.webappUrl}/terminal/connect#key=${publicKeyBase64}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const AuthSelector = ({ onSelect, onCancel }) => {
|
|
37
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
38
|
+
const options = [
|
|
39
|
+
{
|
|
40
|
+
method: "mobile",
|
|
41
|
+
label: "Mobile App"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
method: "web",
|
|
45
|
+
label: "Web Browser"
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
useInput((input, key) => {
|
|
49
|
+
if (key.upArrow) {
|
|
50
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
51
|
+
} else if (key.downArrow) {
|
|
52
|
+
setSelectedIndex((prev) => Math.min(options.length - 1, prev + 1));
|
|
53
|
+
} else if (key.return) {
|
|
54
|
+
onSelect(options[selectedIndex].method);
|
|
55
|
+
} else if (key.escape || key.ctrl && input === "c") {
|
|
56
|
+
onCancel();
|
|
57
|
+
} else if (input === "1") {
|
|
58
|
+
setSelectedIndex(0);
|
|
59
|
+
onSelect("mobile");
|
|
60
|
+
} else if (input === "2") {
|
|
61
|
+
setSelectedIndex(1);
|
|
62
|
+
onSelect("web");
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, null, "How would you like to authenticate?")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, options.map((option, index) => {
|
|
66
|
+
const isSelected = selectedIndex === index;
|
|
67
|
+
return /* @__PURE__ */ React.createElement(Box, { key: option.method, marginY: 0 }, /* @__PURE__ */ React.createElement(Text, { color: isSelected ? "cyan" : "gray" }, isSelected ? "\u203A " : " ", index + 1, ". ", option.label));
|
|
68
|
+
})), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Use arrows or 1-2 to select, Enter to confirm")));
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
async function doAuth() {
|
|
72
|
+
console.clear();
|
|
73
|
+
const authMethod = await selectAuthenticationMethod();
|
|
74
|
+
if (!authMethod) {
|
|
75
|
+
console.log("\nAuthentication cancelled.\n");
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
const secret = new Uint8Array(randomBytes(32));
|
|
79
|
+
const keypair = tweetnacl.box.keyPair.fromSecretKey(secret);
|
|
80
|
+
try {
|
|
81
|
+
if (isDebug()) {
|
|
82
|
+
console.log(`[AUTH DEBUG] Sending auth request to: ${configuration.serverUrl}/v1/auth/request`);
|
|
83
|
+
console.log(`[AUTH DEBUG] Public key: ${encodeBase64(keypair.publicKey).substring(0, 20)}...`);
|
|
84
|
+
}
|
|
85
|
+
await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
|
|
86
|
+
publicKey: encodeBase64(keypair.publicKey),
|
|
87
|
+
supportsV2: true
|
|
88
|
+
});
|
|
89
|
+
if (isDebug()) {
|
|
90
|
+
console.log(`[AUTH DEBUG] Auth request sent successfully`);
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (isDebug()) {
|
|
94
|
+
console.log(`[AUTH DEBUG] Failed to send auth request:`, error);
|
|
95
|
+
}
|
|
96
|
+
console.log("Failed to create authentication request, please try again later.");
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (authMethod === "mobile") {
|
|
100
|
+
return await doMobileAuth(keypair);
|
|
101
|
+
} else {
|
|
102
|
+
return await doWebAuth(keypair);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function selectAuthenticationMethod() {
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
let hasResolved = false;
|
|
108
|
+
const onSelect = (method) => {
|
|
109
|
+
if (!hasResolved) {
|
|
110
|
+
hasResolved = true;
|
|
111
|
+
app.unmount();
|
|
112
|
+
resolve(method);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const onCancel = () => {
|
|
116
|
+
if (!hasResolved) {
|
|
117
|
+
hasResolved = true;
|
|
118
|
+
app.unmount();
|
|
119
|
+
resolve(null);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const app = render(React.createElement(AuthSelector, { onSelect, onCancel }), {
|
|
123
|
+
exitOnCtrlC: false,
|
|
124
|
+
patchConsole: false
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function doMobileAuth(keypair) {
|
|
129
|
+
console.clear();
|
|
130
|
+
console.log("\nMobile Authentication\n");
|
|
131
|
+
console.log("Scan this QR code with your Happy mobile app:\n");
|
|
132
|
+
const authUrl = "happy://terminal?" + encodeBase64Url(keypair.publicKey);
|
|
133
|
+
displayQRCode(authUrl);
|
|
134
|
+
console.log("\nOr manually enter this URL:");
|
|
135
|
+
console.log(authUrl);
|
|
136
|
+
console.log("");
|
|
137
|
+
return await waitForAuthentication(keypair);
|
|
138
|
+
}
|
|
139
|
+
async function doWebAuth(keypair) {
|
|
140
|
+
console.clear();
|
|
141
|
+
console.log("\nWeb Authentication\n");
|
|
142
|
+
const webUrl = generateWebAuthUrl(keypair.publicKey);
|
|
143
|
+
console.log("Opening your browser...");
|
|
144
|
+
const browserOpened = await openBrowser(webUrl);
|
|
145
|
+
if (browserOpened) {
|
|
146
|
+
console.log("\u2713 Browser opened\n");
|
|
147
|
+
console.log("Complete authentication in your browser window.");
|
|
148
|
+
} else {
|
|
149
|
+
console.log("Could not open browser automatically.");
|
|
150
|
+
}
|
|
151
|
+
console.log("\nIf the browser did not open, please copy and paste this URL:");
|
|
152
|
+
console.log(webUrl);
|
|
153
|
+
console.log("");
|
|
154
|
+
return await waitForAuthentication(keypair);
|
|
155
|
+
}
|
|
156
|
+
async function waitForAuthentication(keypair) {
|
|
157
|
+
process.stdout.write("Waiting for authentication");
|
|
158
|
+
let dots = 0;
|
|
159
|
+
let cancelled = false;
|
|
160
|
+
const handleInterrupt = () => {
|
|
161
|
+
cancelled = true;
|
|
162
|
+
console.log("\n\nAuthentication cancelled.");
|
|
163
|
+
process.exit(0);
|
|
164
|
+
};
|
|
165
|
+
process.on("SIGINT", handleInterrupt);
|
|
166
|
+
try {
|
|
167
|
+
while (!cancelled) {
|
|
168
|
+
try {
|
|
169
|
+
const response = await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
|
|
170
|
+
publicKey: encodeBase64(keypair.publicKey),
|
|
171
|
+
supportsV2: true
|
|
172
|
+
});
|
|
173
|
+
if (response.data.state === "authorized") {
|
|
174
|
+
let token = response.data.token;
|
|
175
|
+
let r = decodeBase64(response.data.response);
|
|
176
|
+
let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
|
|
177
|
+
if (decrypted) {
|
|
178
|
+
if (decrypted.length === 32) {
|
|
179
|
+
const credentials = {
|
|
180
|
+
secret: decrypted,
|
|
181
|
+
token
|
|
182
|
+
};
|
|
183
|
+
await writeCredentialsLegacy(credentials);
|
|
184
|
+
console.log("\n\n\u2713 Authentication successful\n");
|
|
185
|
+
return {
|
|
186
|
+
encryption: {
|
|
187
|
+
type: "legacy",
|
|
188
|
+
secret: decrypted
|
|
189
|
+
},
|
|
190
|
+
token
|
|
191
|
+
};
|
|
192
|
+
} else {
|
|
193
|
+
if (decrypted[0] === 0) {
|
|
194
|
+
const credentials = {
|
|
195
|
+
publicKey: decrypted.slice(1, 33),
|
|
196
|
+
machineKey: randomBytes(32),
|
|
197
|
+
token
|
|
198
|
+
};
|
|
199
|
+
await writeCredentialsDataKey(credentials);
|
|
200
|
+
console.log("\n\n\u2713 Authentication successful\n");
|
|
201
|
+
return {
|
|
202
|
+
encryption: {
|
|
203
|
+
type: "dataKey",
|
|
204
|
+
publicKey: credentials.publicKey,
|
|
205
|
+
machineKey: credentials.machineKey
|
|
206
|
+
},
|
|
207
|
+
token
|
|
208
|
+
};
|
|
209
|
+
} else {
|
|
210
|
+
console.log("\n\nFailed to decrypt response. Please try again.");
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
console.log("\n\nFailed to decrypt response. Please try again.");
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.log("\n\nFailed to check authentication status. Please try again.");
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
process.stdout.write("\rWaiting for authentication" + ".".repeat(dots % 3 + 1) + " ");
|
|
224
|
+
dots++;
|
|
225
|
+
await delay(1e3);
|
|
226
|
+
}
|
|
227
|
+
} finally {
|
|
228
|
+
process.off("SIGINT", handleInterrupt);
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
function decryptWithEphemeralKey(encryptedBundle, recipientSecretKey) {
|
|
233
|
+
const ephemeralPublicKey = encryptedBundle.slice(0, 32);
|
|
234
|
+
const nonce = encryptedBundle.slice(32, 32 + tweetnacl.box.nonceLength);
|
|
235
|
+
const encrypted = encryptedBundle.slice(32 + tweetnacl.box.nonceLength);
|
|
236
|
+
const decrypted = tweetnacl.box.open(encrypted, nonce, ephemeralPublicKey, recipientSecretKey);
|
|
237
|
+
if (!decrypted) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return decrypted;
|
|
241
|
+
}
|
|
242
|
+
async function authAndSetupMachineIfNeeded() {
|
|
243
|
+
logger.debug("[AUTH] Starting auth and machine setup...");
|
|
244
|
+
let credentials = await readCredentials();
|
|
245
|
+
let newAuth = false;
|
|
246
|
+
if (!credentials) {
|
|
247
|
+
logger.debug("[AUTH] No credentials found, starting authentication flow...");
|
|
248
|
+
const authResult = await doAuth();
|
|
249
|
+
if (!authResult) {
|
|
250
|
+
throw new Error("Authentication failed or was cancelled");
|
|
251
|
+
}
|
|
252
|
+
credentials = authResult;
|
|
253
|
+
newAuth = true;
|
|
254
|
+
} else {
|
|
255
|
+
logger.debug("[AUTH] Using existing credentials");
|
|
256
|
+
}
|
|
257
|
+
const settings = await updateSettings(async (s) => {
|
|
258
|
+
if (newAuth || !s.machineId) {
|
|
259
|
+
return {
|
|
260
|
+
...s,
|
|
261
|
+
machineId: randomUUID()
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return s;
|
|
265
|
+
});
|
|
266
|
+
logger.debug(`[AUTH] Machine ID: ${settings.machineId}`);
|
|
267
|
+
return { credentials, machineId: settings.machineId };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export { authAndSetupMachineIfNeeded, decryptWithEphemeralKey, doAuth };
|