@keystrokehq/cli 0.0.1
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/AGENTS-blurb.md +123 -0
- package/LICENSE +42 -0
- package/README.md +177 -0
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/bin/keystroke.mjs +107 -0
- package/dist/_manifest-JSRE3H8k.mjs +385 -0
- package/dist/agent-bundle-package-DWV6B_5q-BtV7Xycc.mjs +2344 -0
- package/dist/agent-manifest-CDnbkR2f.mjs +245 -0
- package/dist/agents-CZJGxVqV.mjs +228 -0
- package/dist/api-keys-D2lgguuY.mjs +40 -0
- package/dist/auth-DN2VusyU.mjs +59 -0
- package/dist/auth.handler-CT1BQUvu.mjs +340 -0
- package/dist/browser-qwFrUH82.mjs +24 -0
- package/dist/build-agents-BmM_AsSd-BGi9wtzt.mjs +514 -0
- package/dist/build-metadata-BWS7uhd_-DR8gJjTX.mjs +1422 -0
- package/dist/build-progress-DgYKb4hB.mjs +183 -0
- package/dist/build-tasks-CdihpudT-D5r5HUHe.mjs +91 -0
- package/dist/build-workflows-CfxBnIWh-CdYPv8w2.mjs +370 -0
- package/dist/build.handler-4799CjWH.mjs +36 -0
- package/dist/chunk-CH6r78ws.mjs +37 -0
- package/dist/clear-cache.handler-B9tqSoSM.mjs +11 -0
- package/dist/clear.handler-BTIXXPTJ.mjs +42 -0
- package/dist/clear.handler-BydlX-zE.mjs +11 -0
- package/dist/commander-DfTVqQ-3.mjs +133 -0
- package/dist/concurrency-gXn9Rw8x-DNl2YtrS.mjs +20 -0
- package/dist/connect-BUXkeH0F.mjs +43 -0
- package/dist/connect.handler-CYel9cy6.mjs +430 -0
- package/dist/constants-CPpPdSNg.mjs +8 -0
- package/dist/context-T7HZuB97.mjs +138 -0
- package/dist/credential-env-map-CI8yWHVy.mjs +28 -0
- package/dist/credential-schema-mismatch-BKo5PjcQ.mjs +76 -0
- package/dist/credentials-CvmjU0lK.mjs +171 -0
- package/dist/credentials-OfVHOtG3.mjs +151216 -0
- package/dist/current-deployment-workflow-poHt27i3.mjs +94 -0
- package/dist/current.handler-B8zKzfPp.mjs +21 -0
- package/dist/delete.handler-bAu1iXVQ.mjs +17 -0
- package/dist/deploy-7Jjls436.mjs +26 -0
- package/dist/deploy-BOPIpRWm.mjs +74 -0
- package/dist/deploy-progress-BmGUNFKg.mjs +70 -0
- package/dist/deploy.handler-BAzgiNhd.mjs +370 -0
- package/dist/detect-env-access-CwkOYeYM-D_BCZqV6.mjs +209 -0
- package/dist/diff-utils-NEfcjqxt.mjs +185 -0
- package/dist/diff.handler-Du7SY8K4.mjs +47 -0
- package/dist/dist-BkJUoBiG.mjs +1116 -0
- package/dist/dist-CUK7yBM0.mjs +308 -0
- package/dist/env-91KwMKov.mjs +140 -0
- package/dist/env.handler-BAzBuMzQ.mjs +277 -0
- package/dist/error-boundary-VL-JLfIa.mjs +34 -0
- package/dist/file-metadata-D1vm-XY2.mjs +191 -0
- package/dist/get-intrinsic-zLxwtrLK.mjs +658 -0
- package/dist/import-module-CV84H5fZ-B_CBCmb4.mjs +1747 -0
- package/dist/init-DpMCotSK.mjs +45 -0
- package/dist/init.handler-CPRnif52.mjs +585 -0
- package/dist/inspect.handler-DT_cD036.mjs +146 -0
- package/dist/integration-catalog-Bt-L3GjF.mjs +104 -0
- package/dist/integrations-DlatPK4W.mjs +79 -0
- package/dist/keystroke.d.mts +3 -0
- package/dist/keystroke.mjs +707 -0
- package/dist/layout-CbMtQ2tm.mjs +67 -0
- package/dist/list-enrichment-y-cwizLr.mjs +189 -0
- package/dist/list.handler-BTWvCyjA.mjs +52 -0
- package/dist/list.handler-CWF_Dj15.mjs +24 -0
- package/dist/list.handler-CZ6G2x_G.mjs +75 -0
- package/dist/list.handler-DWaQkJaR.mjs +51 -0
- package/dist/list.handler-DqbFcBW7.mjs +180 -0
- package/dist/list.handler-lq3ZGAn4.mjs +104 -0
- package/dist/logs-BEg9L5l8.mjs +28 -0
- package/dist/logs.handler-6hoMBzqw.mjs +35 -0
- package/dist/logs.handler-BD_dXiL1.mjs +231 -0
- package/dist/metadata-layout-GUYIUo0i-_aG2zjue.mjs +5877 -0
- package/dist/normalize-path-CojS-CgQ-DLCOvnD1.mjs +20 -0
- package/dist/options-CeaTcFxP.mjs +43 -0
- package/dist/org-xLzBtt2_.mjs +41 -0
- package/dist/output-DM4b7KgY.mjs +72 -0
- package/dist/oxc-B3KI3rf_-n9d1hKNq.mjs +119 -0
- package/dist/paused.handler-BMFm9Cff.mjs +94 -0
- package/dist/project-config-D1qsQlO7.mjs +107 -0
- package/dist/projects-CHkRE9rS.mjs +1574 -0
- package/dist/projects-Cjb7sovS.mjs +30 -0
- package/dist/read-credential-keys-77a91T8M-KA0Iw0Z1.mjs +9 -0
- package/dist/register.handler-BPCdor1_.mjs +86 -0
- package/dist/requirements.handler-DPXdSks3.mjs +201 -0
- package/dist/resolve-project-DDJ29sCF.mjs +35 -0
- package/dist/rolldown-runtime-twds-ZHy-BWWzu8VG.mjs +15 -0
- package/dist/run-polling-CAgFRdK3.mjs +20 -0
- package/dist/runs-D9hNLb9A.mjs +259 -0
- package/dist/schedule-BXx3uXwr.mjs +1142 -0
- package/dist/schema-17qMfNyI.mjs +18 -0
- package/dist/schema-display-CgmeKigW.mjs +130 -0
- package/dist/schemas-CDib1RhE.mjs +125 -0
- package/dist/skills-sync.handler-DIy8GR16.mjs +34 -0
- package/dist/skills.command-CrjI2dN9.mjs +35 -0
- package/dist/skills.handler-Bz8bJKql.mjs +9 -0
- package/dist/source-analysis-Cj-ADyu--BJQcFPCG.mjs +144 -0
- package/dist/spinner-progress-DMVwgqO9.mjs +173 -0
- package/dist/src-C0X6u_Mw.mjs +1340 -0
- package/dist/src-eHwu-Gfw.mjs +369 -0
- package/dist/status.handler-BO4nwvWn.mjs +101 -0
- package/dist/switch.handler-D_9213Vf.mjs +51 -0
- package/dist/sync-BL_Mo5st.mjs +39 -0
- package/dist/sync-keystroke-agent-skills-Kx_H7UTd.mjs +70 -0
- package/dist/sync.handler-BUFPdzWz.mjs +82 -0
- package/dist/task-B2sZMaZu.mjs +8 -0
- package/dist/task-target-build-CBeCKbu2.mjs +432 -0
- package/dist/task-target-deploy-C5X-USeR.mjs +4 -0
- package/dist/task-target-deploy-CA6elFpF-BEr4gkol.mjs +271 -0
- package/dist/task-target-deploy-runner.d.mts +3 -0
- package/dist/task-target-deploy-runner.mjs +202 -0
- package/dist/test-BHTgR3UA.mjs +698 -0
- package/dist/test.handler-BcPQ8b74.mjs +13 -0
- package/dist/trigger-artifacts-DQPbQNqC-B4yeeFBY.mjs +239 -0
- package/dist/trigger-manifest-CY7brZeg.mjs +30 -0
- package/dist/try-deploy.handler-DqybNhXx.mjs +490 -0
- package/dist/upload-CkU--iDC.mjs +207 -0
- package/dist/upload.handler-DCtiznQp.mjs +441 -0
- package/dist/utils-CywxCDM7.mjs +14 -0
- package/dist/validate.handler-DOcTaJL0.mjs +280 -0
- package/dist/workflow-build-DBQaBfnn.mjs +1819 -0
- package/dist/workflow-bundler-BPiqVscj-X1PFFAuP.mjs +167 -0
- package/dist/workflows-g9z87AJJ.mjs +799 -0
- package/dist/writer-BG8poUm3-BbXlU2kI.mjs +426 -0
- package/package.json +87 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { _ as AUTH_URL_PATH, b as CALLBACK_PATH, h as toErrorMessage, i as logger, t as ui, v as CALLBACK_LOOPBACK_HOST, x as CLI_AUTH_COMMAND, y as CALLBACK_LOOPBACK_ORIGIN } from "./keystroke.mjs";
|
|
4
|
+
import { f as upsertOrgCredentials, i as getCredentialsFilePath } from "./dist-CUK7yBM0.mjs";
|
|
5
|
+
import { a as resolveCliWebUrl, i as resolveCliServerUrl } from "./env-91KwMKov.mjs";
|
|
6
|
+
import { t as openBrowser } from "./browser-qwFrUH82.mjs";
|
|
7
|
+
import { hostname } from "node:os";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { createServer } from "node:http";
|
|
11
|
+
//#region src/commands/auth/device-auth.ts
|
|
12
|
+
const deviceAuthCallbackSuccessSchema = z.object({
|
|
13
|
+
state: z.string().min(1),
|
|
14
|
+
apiKey: z.string().min(1),
|
|
15
|
+
apiKeyId: z.uuid().optional(),
|
|
16
|
+
serverUrl: z.url(),
|
|
17
|
+
userId: z.string().optional(),
|
|
18
|
+
userEmail: z.email().optional(),
|
|
19
|
+
userName: z.string().optional(),
|
|
20
|
+
organizationId: z.uuid().optional(),
|
|
21
|
+
organizationName: z.string().optional()
|
|
22
|
+
});
|
|
23
|
+
function createDeviceAuthState() {
|
|
24
|
+
return randomBytes(24).toString("base64url");
|
|
25
|
+
}
|
|
26
|
+
function buildDeviceAuthUrl(params) {
|
|
27
|
+
const authUrl = new URL(AUTH_URL_PATH, params.webUrl);
|
|
28
|
+
authUrl.searchParams.set("callbackUrl", params.callbackUrl);
|
|
29
|
+
authUrl.searchParams.set("state", params.state);
|
|
30
|
+
authUrl.searchParams.set("cli", params.cli);
|
|
31
|
+
authUrl.searchParams.set("serverUrl", params.serverUrl);
|
|
32
|
+
if (params.deviceName) authUrl.searchParams.set("deviceName", params.deviceName);
|
|
33
|
+
if (params.organizationId) authUrl.searchParams.set("organizationId", params.organizationId);
|
|
34
|
+
return authUrl.toString();
|
|
35
|
+
}
|
|
36
|
+
function parseDeviceAuthCallback(searchParams) {
|
|
37
|
+
const errorMessage = searchParams.get("error");
|
|
38
|
+
if (errorMessage) throw new Error(errorMessage);
|
|
39
|
+
const parseResult = deviceAuthCallbackSuccessSchema.safeParse({
|
|
40
|
+
state: searchParams.get("state") ?? void 0,
|
|
41
|
+
apiKey: searchParams.get("apiKey") ?? void 0,
|
|
42
|
+
apiKeyId: searchParams.get("apiKeyId") ?? void 0,
|
|
43
|
+
serverUrl: searchParams.get("serverUrl") ?? void 0,
|
|
44
|
+
userId: searchParams.get("userId") ?? void 0,
|
|
45
|
+
userEmail: searchParams.get("userEmail") ?? void 0,
|
|
46
|
+
userName: searchParams.get("userName") ?? void 0,
|
|
47
|
+
organizationId: searchParams.get("organizationId") ?? void 0,
|
|
48
|
+
organizationName: searchParams.get("organizationName") ?? void 0
|
|
49
|
+
});
|
|
50
|
+
if (!parseResult.success) throw new Error("Invalid callback payload from device auth flow");
|
|
51
|
+
return parseResult.data;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/commands/auth/callback-listener.ts
|
|
55
|
+
function escapeHtml(value) {
|
|
56
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
57
|
+
}
|
|
58
|
+
const CARD_HTML = `
|
|
59
|
+
<!doctype html>
|
|
60
|
+
<html lang="en">
|
|
61
|
+
<head>
|
|
62
|
+
<meta charset="utf-8" />
|
|
63
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
64
|
+
<title>{{TITLE}} | Keystroke</title>
|
|
65
|
+
<style>
|
|
66
|
+
* { box-sizing: border-box; }
|
|
67
|
+
html { color-scheme: light; }
|
|
68
|
+
body {
|
|
69
|
+
margin: 0;
|
|
70
|
+
min-height: 100vh;
|
|
71
|
+
display: grid;
|
|
72
|
+
place-items: center;
|
|
73
|
+
padding: 24px;
|
|
74
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
75
|
+
background: #f9fafb;
|
|
76
|
+
color: #111827;
|
|
77
|
+
}
|
|
78
|
+
.card {
|
|
79
|
+
width: 100%;
|
|
80
|
+
max-width: 28rem;
|
|
81
|
+
padding: 1.5rem;
|
|
82
|
+
border-radius: 0.75rem;
|
|
83
|
+
border: 1px solid #e5e7eb;
|
|
84
|
+
background: #fff;
|
|
85
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
86
|
+
}
|
|
87
|
+
.brand {
|
|
88
|
+
margin-bottom: 1rem;
|
|
89
|
+
font-size: 0.875rem;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
color: #374151;
|
|
92
|
+
}
|
|
93
|
+
h1 {
|
|
94
|
+
margin: 0 0 0.5rem;
|
|
95
|
+
font-size: 1.5rem;
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
}
|
|
98
|
+
p {
|
|
99
|
+
margin: 0;
|
|
100
|
+
font-size: 0.875rem;
|
|
101
|
+
line-height: 1.5;
|
|
102
|
+
color: #4b5563;
|
|
103
|
+
}
|
|
104
|
+
.hint {
|
|
105
|
+
margin-top: 1rem;
|
|
106
|
+
font-size: 0.8125rem;
|
|
107
|
+
color: #6b7280;
|
|
108
|
+
}
|
|
109
|
+
.success-icon {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
justify-content: center;
|
|
113
|
+
width: 2.5rem;
|
|
114
|
+
height: 2.5rem;
|
|
115
|
+
margin-bottom: 1rem;
|
|
116
|
+
border-radius: 9999px;
|
|
117
|
+
background: #dcfce7;
|
|
118
|
+
color: #16a34a;
|
|
119
|
+
}
|
|
120
|
+
.error-icon {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
width: 2.5rem;
|
|
125
|
+
height: 2.5rem;
|
|
126
|
+
margin-bottom: 1rem;
|
|
127
|
+
border-radius: 9999px;
|
|
128
|
+
background: #fee2e2;
|
|
129
|
+
color: #dc2626;
|
|
130
|
+
}
|
|
131
|
+
</style>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
<main class="card">
|
|
135
|
+
<div class="brand">Keystroke</div>
|
|
136
|
+
{{ICON}}
|
|
137
|
+
<h1>{{TITLE}}</h1>
|
|
138
|
+
<p>{{MESSAGE}}</p>
|
|
139
|
+
<p class="hint">{{HINT}}</p>
|
|
140
|
+
</main>
|
|
141
|
+
</body>
|
|
142
|
+
</html>`;
|
|
143
|
+
function respondHtml(res, statusCode, title, message, options) {
|
|
144
|
+
const safeTitle = escapeHtml(title);
|
|
145
|
+
const safeMessage = escapeHtml(message);
|
|
146
|
+
const safeHint = escapeHtml(options?.hint ?? "You can close this tab and return to your terminal.");
|
|
147
|
+
const icon = options?.success === true ? "<div class=\"success-icon\" aria-hidden=\"true\">✓</div>" : options?.success === false ? "<div class=\"error-icon\" aria-hidden=\"true\">✕</div>" : "";
|
|
148
|
+
const html = CARD_HTML.replace(/\{\{TITLE\}\}/g, safeTitle).replace(/\{\{MESSAGE\}\}/g, safeMessage).replace(/\{\{HINT\}\}/g, safeHint).replace(/\{\{ICON\}\}/g, icon);
|
|
149
|
+
res.writeHead(statusCode, {
|
|
150
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
151
|
+
Connection: "close"
|
|
152
|
+
});
|
|
153
|
+
res.end(html);
|
|
154
|
+
}
|
|
155
|
+
async function createDeviceCallbackListener(expectedState, timeoutMs) {
|
|
156
|
+
let closed = false;
|
|
157
|
+
let timeoutId;
|
|
158
|
+
const closeServerPromise = (server) => new Promise((resolve, reject) => {
|
|
159
|
+
if (!server.listening) {
|
|
160
|
+
resolve();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
server.close((error) => {
|
|
164
|
+
if (error) reject(error);
|
|
165
|
+
else resolve();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
let resolveResult = () => {};
|
|
169
|
+
let rejectResult = () => {};
|
|
170
|
+
const waitForResult = new Promise((resolve, reject) => {
|
|
171
|
+
resolveResult = resolve;
|
|
172
|
+
rejectResult = reject;
|
|
173
|
+
});
|
|
174
|
+
const server = createServer((req, res) => {
|
|
175
|
+
try {
|
|
176
|
+
const url = new URL(req.url ?? "/", CALLBACK_LOOPBACK_ORIGIN);
|
|
177
|
+
if (url.pathname !== "/callback") {
|
|
178
|
+
respondHtml(res, 404, "Not Found", "Invalid callback path.", {
|
|
179
|
+
success: false,
|
|
180
|
+
hint: "Run keystroke auth from your terminal to start over."
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const payload = parseDeviceAuthCallback(url.searchParams);
|
|
185
|
+
if (payload.state !== expectedState) {
|
|
186
|
+
respondHtml(res, 400, "Authentication Failed", "State mismatch. Try running keystroke auth again.", {
|
|
187
|
+
success: false,
|
|
188
|
+
hint: "Go back to your terminal and run keystroke auth again."
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
respondHtml(res, 200, "Authentication Complete", "You're successfully signed in to Keystroke.", { success: true });
|
|
193
|
+
if (!closed) {
|
|
194
|
+
closed = true;
|
|
195
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
196
|
+
resolveResult(payload);
|
|
197
|
+
closeServerPromise(server);
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
respondHtml(res, 400, "Authentication Failed", "Invalid callback payload.", {
|
|
201
|
+
success: false,
|
|
202
|
+
hint: "Go back to your terminal and run keystroke auth again."
|
|
203
|
+
});
|
|
204
|
+
if (!closed) {
|
|
205
|
+
closed = true;
|
|
206
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
207
|
+
rejectResult(error);
|
|
208
|
+
closeServerPromise(server);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
await new Promise((resolve, reject) => {
|
|
213
|
+
server.once("error", reject);
|
|
214
|
+
server.listen(0, CALLBACK_LOOPBACK_HOST, () => resolve());
|
|
215
|
+
});
|
|
216
|
+
const address = server.address();
|
|
217
|
+
if (!address || typeof address === "string") {
|
|
218
|
+
await closeServerPromise(server);
|
|
219
|
+
throw new Error("Failed to start local callback server");
|
|
220
|
+
}
|
|
221
|
+
timeoutId = setTimeout(() => {
|
|
222
|
+
if (closed) return;
|
|
223
|
+
closed = true;
|
|
224
|
+
rejectResult(/* @__PURE__ */ new Error("Timed out waiting for device authentication"));
|
|
225
|
+
closeServerPromise(server);
|
|
226
|
+
}, timeoutMs);
|
|
227
|
+
return {
|
|
228
|
+
callbackUrl: `${CALLBACK_LOOPBACK_ORIGIN}:${address.port}${CALLBACK_PATH}`,
|
|
229
|
+
waitForResult,
|
|
230
|
+
close: async () => {
|
|
231
|
+
if (closed) return;
|
|
232
|
+
closed = true;
|
|
233
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
234
|
+
await closeServerPromise(server);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/commands/auth/auth.handler.ts
|
|
240
|
+
function getVerificationCode(state) {
|
|
241
|
+
return state.slice(0, 8).toUpperCase();
|
|
242
|
+
}
|
|
243
|
+
function printAuthIntro(params) {
|
|
244
|
+
ui.br();
|
|
245
|
+
ui.header("Keystroke CLI device authentication");
|
|
246
|
+
ui.hint("1) Sign in with your browser");
|
|
247
|
+
ui.hint("2) Verify the browser code matches your terminal");
|
|
248
|
+
ui.hint("3) Click \"Connect device\" in the browser");
|
|
249
|
+
ui.br();
|
|
250
|
+
ui.success(`Verification code: ${params.verificationCode}`);
|
|
251
|
+
ui.br();
|
|
252
|
+
ui.hint("Only continue if both codes are identical.");
|
|
253
|
+
ui.hint(`Opening browser: ${params.authUrl}`);
|
|
254
|
+
ui.br();
|
|
255
|
+
}
|
|
256
|
+
function printAuthSuccess(email, organizationName) {
|
|
257
|
+
ui.success("Authentication successful.");
|
|
258
|
+
if (email) ui.hint(`Connected as: ${email}`);
|
|
259
|
+
if (organizationName) ui.hint(`Organization: ${organizationName}`);
|
|
260
|
+
ui.hint(`Credentials saved to ${getCredentialsFilePath()}`);
|
|
261
|
+
}
|
|
262
|
+
async function warnIfAlreadyAuthenticated(ctx) {
|
|
263
|
+
if (!ctx.client) return;
|
|
264
|
+
try {
|
|
265
|
+
await ctx.client.public.auth.validate();
|
|
266
|
+
} catch {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const identity = ctx.storedCredentials?.user?.email ?? "unknown user";
|
|
270
|
+
ui.br();
|
|
271
|
+
ui.warn("Already authenticated");
|
|
272
|
+
ui.hint(`Signed in as ${identity}`);
|
|
273
|
+
ui.hint("Continuing will add or replace credentials for the selected organization.");
|
|
274
|
+
ui.br();
|
|
275
|
+
}
|
|
276
|
+
async function handleAuth(options, ctx, overrides) {
|
|
277
|
+
await warnIfAlreadyAuthenticated(ctx);
|
|
278
|
+
const webUrl = resolveCliWebUrl(options.webUrl);
|
|
279
|
+
const serverUrl = resolveCliServerUrl(options.serverUrl);
|
|
280
|
+
const timeoutMs = options.timeout * 1e3;
|
|
281
|
+
const state = createDeviceAuthState();
|
|
282
|
+
logger.info("Starting auth flow", {
|
|
283
|
+
webUrl,
|
|
284
|
+
serverUrl,
|
|
285
|
+
timeoutMs
|
|
286
|
+
});
|
|
287
|
+
const listener = await createDeviceCallbackListener(state, timeoutMs);
|
|
288
|
+
const deviceName = hostname().trim() || void 0;
|
|
289
|
+
const authUrl = buildDeviceAuthUrl({
|
|
290
|
+
webUrl,
|
|
291
|
+
callbackUrl: listener.callbackUrl,
|
|
292
|
+
state,
|
|
293
|
+
cli: CLI_AUTH_COMMAND,
|
|
294
|
+
serverUrl,
|
|
295
|
+
deviceName,
|
|
296
|
+
organizationId: overrides?.organizationId
|
|
297
|
+
});
|
|
298
|
+
printAuthIntro({
|
|
299
|
+
authUrl,
|
|
300
|
+
verificationCode: getVerificationCode(state)
|
|
301
|
+
});
|
|
302
|
+
try {
|
|
303
|
+
await openBrowser(authUrl);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
logger.error("Failed to open browser", { error: String(error) });
|
|
306
|
+
ui.warn(`Could not open browser automatically: ${toErrorMessage(error)}`);
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
const callbackPayload = await listener.waitForResult;
|
|
310
|
+
const user = callbackPayload.userId && callbackPayload.userEmail ? {
|
|
311
|
+
id: callbackPayload.userId,
|
|
312
|
+
email: callbackPayload.userEmail,
|
|
313
|
+
name: callbackPayload.userName
|
|
314
|
+
} : void 0;
|
|
315
|
+
if (callbackPayload.organizationId) await upsertOrgCredentials({
|
|
316
|
+
orgEntry: {
|
|
317
|
+
organizationId: callbackPayload.organizationId,
|
|
318
|
+
organizationName: callbackPayload.organizationName ?? "Unknown",
|
|
319
|
+
apiKey: callbackPayload.apiKey,
|
|
320
|
+
apiKeyId: callbackPayload.apiKeyId,
|
|
321
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
322
|
+
},
|
|
323
|
+
serverUrl: callbackPayload.serverUrl,
|
|
324
|
+
webUrl,
|
|
325
|
+
user
|
|
326
|
+
});
|
|
327
|
+
logger.info("Auth successful", {
|
|
328
|
+
email: callbackPayload.userEmail,
|
|
329
|
+
organizationId: callbackPayload.organizationId
|
|
330
|
+
});
|
|
331
|
+
printAuthSuccess(callbackPayload.userEmail, callbackPayload.organizationName);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (toErrorMessage(error).includes("Timed out waiting for device authentication")) ui.error(`Authentication timed out after ${options.timeout}s. Re-run ${CLI_AUTH_COMMAND} and complete the browser prompt before timeout.`);
|
|
334
|
+
throw error;
|
|
335
|
+
} finally {
|
|
336
|
+
await listener.close();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
//#endregion
|
|
340
|
+
export { handleAuth };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
//#region src/lib/browser.ts
|
|
5
|
+
async function openBrowser(url) {
|
|
6
|
+
const child = process.platform === "darwin" ? spawn("open", [url], { stdio: "ignore" }) : process.platform === "win32" ? spawn("cmd", [
|
|
7
|
+
"/c",
|
|
8
|
+
"start",
|
|
9
|
+
"",
|
|
10
|
+
url
|
|
11
|
+
], {
|
|
12
|
+
stdio: "ignore",
|
|
13
|
+
windowsHide: true
|
|
14
|
+
}) : spawn("xdg-open", [url], { stdio: "ignore" });
|
|
15
|
+
await new Promise((resolve, reject) => {
|
|
16
|
+
child.once("error", reject);
|
|
17
|
+
child.once("spawn", () => {
|
|
18
|
+
child.unref();
|
|
19
|
+
resolve();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { openBrowser as t };
|