@vellumai/cli 0.8.12 → 0.9.0-dev.202606162243.4268db3
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/README.md +1 -1
- package/bun.lock +49 -56
- package/node_modules/@vellumai/local-mode/src/__tests__/status.test.ts +224 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/wake.test.ts +19 -0
- package/node_modules/@vellumai/local-mode/src/index.ts +8 -1
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.test.ts +0 -15
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.ts +8 -4
- package/node_modules/@vellumai/local-mode/src/sleep.ts +80 -0
- package/node_modules/@vellumai/local-mode/src/status.ts +342 -0
- package/node_modules/@vellumai/local-mode/src/wake.ts +12 -1
- package/package.json +3 -3
- package/src/__tests__/assistant-config.test.ts +1 -2
- package/src/__tests__/device-id.test.ts +6 -14
- package/src/__tests__/helpers/os-mock.ts +27 -0
- package/src/__tests__/login-loopback.test.ts +71 -0
- package/src/__tests__/multi-local.test.ts +2 -10
- package/src/__tests__/nginx-ingress-command.test.ts +69 -0
- package/src/__tests__/nginx-ingress.test.ts +403 -0
- package/src/__tests__/sleep.test.ts +4 -0
- package/src/__tests__/teleport.test.ts +6 -9
- package/src/__tests__/tunnel.test.ts +164 -0
- package/src/__tests__/wake.test.ts +15 -4
- package/src/__tests__/workos-pkce.test.ts +314 -0
- package/src/commands/flags.ts +1 -22
- package/src/commands/hatch.ts +90 -9
- package/src/commands/login.ts +123 -59
- package/src/commands/nginx-ingress.ts +291 -0
- package/src/commands/rollback.ts +0 -6
- package/src/commands/sleep.ts +17 -0
- package/src/commands/teleport.ts +23 -36
- package/src/commands/tunnel.ts +69 -11
- package/src/commands/upgrade.ts +0 -2
- package/src/commands/wake.ts +7 -5
- package/src/commands/workflows.ts +301 -0
- package/src/index.ts +8 -0
- package/src/lib/arg-utils.ts +48 -0
- package/src/lib/assistant-client.ts +2 -0
- package/src/lib/assistant-config.ts +0 -7
- package/src/lib/cloudflare-tunnel.ts +15 -2
- package/src/lib/docker.ts +103 -49
- package/src/lib/environments/resolve.ts +3 -4
- package/src/lib/feature-flags.test.ts +157 -0
- package/src/lib/feature-flags.ts +38 -0
- package/src/lib/hatch-local.ts +0 -1
- package/src/lib/local.ts +5 -0
- package/src/lib/nginx-ingress.ts +576 -0
- package/src/lib/ngrok.ts +26 -4
- package/src/lib/platform-client.ts +0 -1
- package/src/lib/retire-local.ts +5 -0
- package/src/lib/statefulset.ts +73 -21
- package/src/lib/sync-cloud-assistants.ts +4 -17
- package/src/lib/upgrade-lifecycle.ts +1 -2
- package/src/lib/workos-pkce.ts +160 -0
- package/src/__tests__/env-drift.test.ts +0 -53
package/src/commands/hatch.ts
CHANGED
|
@@ -5,7 +5,6 @@ import cliPkg from "../../package.json";
|
|
|
5
5
|
|
|
6
6
|
import { buildOpenclawStartupScript } from "../adapters/openclaw";
|
|
7
7
|
import {
|
|
8
|
-
normalizeVersion,
|
|
9
8
|
saveAssistantEntry,
|
|
10
9
|
setActiveAssistant,
|
|
11
10
|
} from "../lib/assistant-config";
|
|
@@ -183,6 +182,9 @@ interface HatchArgs {
|
|
|
183
182
|
flagEnvVars: Record<string, string>;
|
|
184
183
|
analyze: boolean;
|
|
185
184
|
disablePlatform: boolean;
|
|
185
|
+
netnsContainer: string | null;
|
|
186
|
+
gatewayPort: number | null;
|
|
187
|
+
assistantCaCert: string | null;
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
function parseArgs(): HatchArgs {
|
|
@@ -190,8 +192,10 @@ function parseArgs(): HatchArgs {
|
|
|
190
192
|
process.argv.slice(3),
|
|
191
193
|
);
|
|
192
194
|
const flagEnvVars = { ...readAmbientFlagEnvVars(), ...cliFlagVars };
|
|
193
|
-
const disablePlatformAmbient =
|
|
194
|
-
|
|
195
|
+
const disablePlatformAmbient =
|
|
196
|
+
process.env.VELLUM_DISABLE_PLATFORM?.trim().toLowerCase();
|
|
197
|
+
let disablePlatform =
|
|
198
|
+
disablePlatformAmbient === "true" || disablePlatformAmbient === "1";
|
|
195
199
|
let species: Species = DEFAULT_SPECIES;
|
|
196
200
|
let detached = false;
|
|
197
201
|
let keepAlive = false;
|
|
@@ -201,6 +205,9 @@ function parseArgs(): HatchArgs {
|
|
|
201
205
|
let sourcePath: string | null = null;
|
|
202
206
|
const configValues: Record<string, string> = {};
|
|
203
207
|
let analyze = false;
|
|
208
|
+
let netnsContainer: string | null = null;
|
|
209
|
+
let gatewayPort: number | null = null;
|
|
210
|
+
let assistantCaCert: string | null = null;
|
|
204
211
|
|
|
205
212
|
for (let i = 0; i < args.length; i++) {
|
|
206
213
|
const arg = args[i];
|
|
@@ -240,6 +247,15 @@ function parseArgs(): HatchArgs {
|
|
|
240
247
|
console.log(
|
|
241
248
|
" --disable-platform Suppress all outbound platform API calls",
|
|
242
249
|
);
|
|
250
|
+
console.log(
|
|
251
|
+
" --netns-container <name> Join an existing container's network namespace (docker target only) instead of creating a per-instance network. The namespace owner publishes host ports, so --gateway-port is required.",
|
|
252
|
+
);
|
|
253
|
+
console.log(
|
|
254
|
+
" --gateway-port <port> Use an explicit host port for the gateway runtime URL instead of auto-allocating. Required with --netns-container.",
|
|
255
|
+
);
|
|
256
|
+
console.log(
|
|
257
|
+
" --assistant-ca-cert <path> Trust an extra PEM CA bundle in the assistant container (NODE_EXTRA_CA_CERTS) from process start. Useful behind a TLS-terminating egress proxy.",
|
|
258
|
+
);
|
|
243
259
|
process.exit(0);
|
|
244
260
|
} else if (arg === "-d") {
|
|
245
261
|
detached = true;
|
|
@@ -302,11 +318,38 @@ function parseArgs(): HatchArgs {
|
|
|
302
318
|
i++;
|
|
303
319
|
} else if (arg === "--disable-platform") {
|
|
304
320
|
disablePlatform = true;
|
|
321
|
+
} else if (arg === "--netns-container") {
|
|
322
|
+
const next = args[i + 1];
|
|
323
|
+
if (!next || next.startsWith("-")) {
|
|
324
|
+
console.error("Error: --netns-container requires a container name");
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
netnsContainer = next;
|
|
328
|
+
i++;
|
|
329
|
+
} else if (arg === "--gateway-port") {
|
|
330
|
+
const next = args[i + 1];
|
|
331
|
+
const parsed = next ? Number(next) : NaN;
|
|
332
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
333
|
+
console.error(
|
|
334
|
+
"Error: --gateway-port requires an integer port in 1-65535",
|
|
335
|
+
);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
gatewayPort = parsed;
|
|
339
|
+
i++;
|
|
340
|
+
} else if (arg === "--assistant-ca-cert") {
|
|
341
|
+
const next = args[i + 1];
|
|
342
|
+
if (!next || next.startsWith("-")) {
|
|
343
|
+
console.error("Error: --assistant-ca-cert requires a path argument");
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
assistantCaCert = next;
|
|
347
|
+
i++;
|
|
305
348
|
} else if (VALID_SPECIES.includes(arg as Species)) {
|
|
306
349
|
species = arg as Species;
|
|
307
350
|
} else {
|
|
308
351
|
console.error(
|
|
309
|
-
`Error: Unknown argument '${arg}'. Valid options: ${VALID_SPECIES.join(", ")}, -d, --watch, --source <path>, --keep-alive, --name <name>, --remote <${VALID_REMOTE_HOSTS.join("|")}>, --config <key=value>, --flag <key=value>, --analyze, --disable-platform
|
|
352
|
+
`Error: Unknown argument '${arg}'. Valid options: ${VALID_SPECIES.join(", ")}, -d, --watch, --source <path>, --keep-alive, --name <name>, --remote <${VALID_REMOTE_HOSTS.join("|")}>, --config <key=value>, --flag <key=value>, --analyze, --disable-platform, --netns-container <name>, --gateway-port <port>, --assistant-ca-cert <path>`,
|
|
310
353
|
);
|
|
311
354
|
process.exit(1);
|
|
312
355
|
}
|
|
@@ -324,6 +367,9 @@ function parseArgs(): HatchArgs {
|
|
|
324
367
|
flagEnvVars,
|
|
325
368
|
analyze,
|
|
326
369
|
disablePlatform,
|
|
370
|
+
netnsContainer,
|
|
371
|
+
gatewayPort,
|
|
372
|
+
assistantCaCert,
|
|
327
373
|
};
|
|
328
374
|
}
|
|
329
375
|
|
|
@@ -560,6 +606,9 @@ export async function hatch(): Promise<void> {
|
|
|
560
606
|
flagEnvVars,
|
|
561
607
|
analyze,
|
|
562
608
|
disablePlatform,
|
|
609
|
+
netnsContainer,
|
|
610
|
+
gatewayPort,
|
|
611
|
+
assistantCaCert,
|
|
563
612
|
} = parseArgs();
|
|
564
613
|
|
|
565
614
|
if (disablePlatform) {
|
|
@@ -581,6 +630,25 @@ export async function hatch(): Promise<void> {
|
|
|
581
630
|
process.exit(1);
|
|
582
631
|
}
|
|
583
632
|
|
|
633
|
+
if (
|
|
634
|
+
(netnsContainer !== null ||
|
|
635
|
+
gatewayPort !== null ||
|
|
636
|
+
assistantCaCert !== null) &&
|
|
637
|
+
remote !== "docker"
|
|
638
|
+
) {
|
|
639
|
+
console.error(
|
|
640
|
+
"Error: --netns-container, --gateway-port, and --assistant-ca-cert are only supported for docker hatch targets.",
|
|
641
|
+
);
|
|
642
|
+
process.exit(1);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (netnsContainer !== null && gatewayPort === null) {
|
|
646
|
+
console.error(
|
|
647
|
+
"Error: --gateway-port is required with --netns-container (the namespace owner publishes the port before hatch runs).",
|
|
648
|
+
);
|
|
649
|
+
process.exit(1);
|
|
650
|
+
}
|
|
651
|
+
|
|
584
652
|
if (UNSUPPORTED_REMOTE_HATCH_TARGETS.has(remote)) {
|
|
585
653
|
console.error(
|
|
586
654
|
`Error: \`vellum hatch --remote ${remote}\` is not a supported provisioning target yet.`,
|
|
@@ -592,14 +660,30 @@ export async function hatch(): Promise<void> {
|
|
|
592
660
|
}
|
|
593
661
|
|
|
594
662
|
if (remote === "local") {
|
|
595
|
-
await hatchLocal(
|
|
663
|
+
await hatchLocal(
|
|
664
|
+
species,
|
|
665
|
+
name,
|
|
666
|
+
watch,
|
|
667
|
+
keepAlive,
|
|
668
|
+
configValues,
|
|
669
|
+
flagEnvVars,
|
|
670
|
+
);
|
|
596
671
|
return;
|
|
597
672
|
}
|
|
598
673
|
|
|
599
674
|
if (remote === "docker") {
|
|
600
|
-
await hatchDocker(
|
|
675
|
+
await hatchDocker({
|
|
676
|
+
species,
|
|
677
|
+
detached,
|
|
678
|
+
name,
|
|
679
|
+
watch,
|
|
680
|
+
configValues,
|
|
681
|
+
flagEnvVars,
|
|
601
682
|
sourcePath,
|
|
602
683
|
analyze,
|
|
684
|
+
netnsContainer: netnsContainer ?? undefined,
|
|
685
|
+
gatewayPort: gatewayPort ?? undefined,
|
|
686
|
+
assistantCaCertPath: assistantCaCert ?? undefined,
|
|
603
687
|
});
|
|
604
688
|
return;
|
|
605
689
|
}
|
|
@@ -639,9 +723,6 @@ async function hatchVellumPlatform(): Promise<void> {
|
|
|
639
723
|
cloud: "vellum",
|
|
640
724
|
species: "vellum",
|
|
641
725
|
hatchedAt: new Date().toISOString(),
|
|
642
|
-
...(result.current_release_version != null && {
|
|
643
|
-
version: normalizeVersion(result.current_release_version),
|
|
644
|
-
}),
|
|
645
726
|
});
|
|
646
727
|
setActiveAssistant(result.id);
|
|
647
728
|
|
package/src/commands/login.ts
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import { createServer } from "http";
|
|
2
1
|
import { spawn } from "child_process";
|
|
3
2
|
import { randomBytes } from "crypto";
|
|
3
|
+
import { createServer } from "http";
|
|
4
|
+
import type { AddressInfo } from "net";
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
getActiveAssistant,
|
|
7
|
-
resolveAssistant,
|
|
8
8
|
loadAllAssistants,
|
|
9
9
|
removeAssistantEntry,
|
|
10
|
+
resolveAssistant,
|
|
10
11
|
setActiveAssistant,
|
|
11
12
|
} from "../lib/assistant-config";
|
|
12
13
|
import { computeDeviceId } from "../lib/guardian-token";
|
|
13
|
-
import {
|
|
14
|
-
fetchAssistantIngressUrl,
|
|
15
|
-
fetchCurrentVersion,
|
|
16
|
-
} from "../lib/upgrade-lifecycle.js";
|
|
17
14
|
import {
|
|
18
15
|
clearPlatformToken,
|
|
19
16
|
ensureSelfHostedLocalRegistration,
|
|
@@ -21,7 +18,6 @@ import {
|
|
|
21
18
|
fetchOrganizationId,
|
|
22
19
|
fetchPlatformAssistants,
|
|
23
20
|
getPlatformUrl,
|
|
24
|
-
getWebUrl,
|
|
25
21
|
injectCredentialsIntoAssistant,
|
|
26
22
|
readGatewayCredential,
|
|
27
23
|
readPlatformToken,
|
|
@@ -29,6 +25,18 @@ import {
|
|
|
29
25
|
savePlatformToken,
|
|
30
26
|
} from "../lib/platform-client";
|
|
31
27
|
import { syncCloudAssistants } from "../lib/sync-cloud-assistants";
|
|
28
|
+
import {
|
|
29
|
+
fetchAssistantIngressUrl,
|
|
30
|
+
fetchCurrentVersion,
|
|
31
|
+
} from "../lib/upgrade-lifecycle.js";
|
|
32
|
+
import {
|
|
33
|
+
CALLBACK_PATH,
|
|
34
|
+
buildAuthorizeUrl,
|
|
35
|
+
exchangeAccessTokenForSession,
|
|
36
|
+
exchangeCodeWithWorkos,
|
|
37
|
+
fetchWorkosClientId,
|
|
38
|
+
generatePkcePair,
|
|
39
|
+
} from "../lib/workos-pkce";
|
|
32
40
|
|
|
33
41
|
const LOGIN_TIMEOUT_MS = 120_000; // 2 minutes
|
|
34
42
|
|
|
@@ -41,7 +49,11 @@ function escapeHtml(s: string): string {
|
|
|
41
49
|
.replace(/'/g, "'");
|
|
42
50
|
}
|
|
43
51
|
|
|
44
|
-
function renderLoginPage(
|
|
52
|
+
function renderLoginPage(
|
|
53
|
+
title: string,
|
|
54
|
+
subtitle: string,
|
|
55
|
+
success: boolean,
|
|
56
|
+
): string {
|
|
45
57
|
const checkmarkSvg = `<svg class="icon" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
46
58
|
<circle cx="28" cy="28" r="28" fill="var(--positive-bg)"/>
|
|
47
59
|
<path class="check" d="M17 28.5L24.5 36L39 21" stroke="var(--positive-fg)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
@@ -175,78 +187,131 @@ function openBrowser(url: string): void {
|
|
|
175
187
|
child.unref();
|
|
176
188
|
}
|
|
177
189
|
|
|
190
|
+
export interface LoopbackListener {
|
|
191
|
+
/** The full `http://127.0.0.1:<port>/auth/callback` redirect URI. */
|
|
192
|
+
redirectUri: string;
|
|
193
|
+
/** Resolves with the authorization code once the state-matched callback arrives. */
|
|
194
|
+
waitForCode: Promise<string>;
|
|
195
|
+
/** Tear down the server, rejecting any pending waiter with `reason`. */
|
|
196
|
+
close: (reason?: string) => void;
|
|
197
|
+
}
|
|
198
|
+
|
|
178
199
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
200
|
+
* Bind an ephemeral 127.0.0.1 listener and wait for the OAuth redirect.
|
|
201
|
+
* Exported for tests; production callers go through `workosPkceLogin`.
|
|
181
202
|
*/
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
|
|
203
|
+
export function startLoopbackListener(
|
|
204
|
+
expectedState: string,
|
|
205
|
+
): Promise<LoopbackListener> {
|
|
206
|
+
return new Promise((resolveListener, rejectListener) => {
|
|
207
|
+
let settle: {
|
|
208
|
+
resolve: (code: string) => void;
|
|
209
|
+
reject: (err: Error) => void;
|
|
210
|
+
};
|
|
211
|
+
const waitForCode = new Promise<string>((resolve, reject) => {
|
|
212
|
+
settle = { resolve, reject };
|
|
213
|
+
});
|
|
185
214
|
|
|
186
215
|
const server = createServer((req, res) => {
|
|
187
|
-
const url = new URL(req.url ?? "/",
|
|
188
|
-
|
|
189
|
-
|
|
216
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
217
|
+
if (
|
|
218
|
+
url.pathname !== CALLBACK_PATH ||
|
|
219
|
+
url.searchParams.get("state") !== expectedState
|
|
220
|
+
) {
|
|
190
221
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
191
222
|
res.end("Not found");
|
|
192
223
|
return;
|
|
193
224
|
}
|
|
194
225
|
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
if (receivedState !== state) {
|
|
226
|
+
const error = url.searchParams.get("error");
|
|
227
|
+
const code = url.searchParams.get("code");
|
|
228
|
+
if (error || !code) {
|
|
199
229
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
200
|
-
res.end(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
230
|
+
res.end(
|
|
231
|
+
renderLoginPage(
|
|
232
|
+
"Login Failed",
|
|
233
|
+
"Please try again from your terminal.",
|
|
234
|
+
false,
|
|
235
|
+
),
|
|
236
|
+
);
|
|
237
|
+
server.close();
|
|
238
|
+
settle.reject(
|
|
239
|
+
new Error(
|
|
240
|
+
`Authentication failed: ${error ?? "no authorization code received"}`,
|
|
241
|
+
),
|
|
242
|
+
);
|
|
209
243
|
return;
|
|
210
244
|
}
|
|
211
245
|
|
|
212
246
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
213
|
-
res.end(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
function cleanup(error: string | null, token?: string): void {
|
|
222
|
-
clearTimeout(timeout);
|
|
247
|
+
res.end(
|
|
248
|
+
renderLoginPage(
|
|
249
|
+
"Login Successful",
|
|
250
|
+
"You can close this window and return to your terminal.",
|
|
251
|
+
true,
|
|
252
|
+
),
|
|
253
|
+
);
|
|
223
254
|
server.close();
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
} else if (token) {
|
|
227
|
-
resolve(token);
|
|
228
|
-
} else {
|
|
229
|
-
reject(new Error("Unknown error during login."));
|
|
230
|
-
}
|
|
231
|
-
}
|
|
255
|
+
settle.resolve(code);
|
|
256
|
+
});
|
|
232
257
|
|
|
233
|
-
server.on("error",
|
|
258
|
+
server.on("error", rejectListener);
|
|
234
259
|
server.listen(0, "127.0.0.1", () => {
|
|
235
260
|
const addr = server.address();
|
|
236
261
|
if (!addr || typeof addr === "string") {
|
|
237
|
-
|
|
262
|
+
rejectListener(new Error("Failed to start local server."));
|
|
238
263
|
return;
|
|
239
264
|
}
|
|
265
|
+
const { port } = addr as AddressInfo;
|
|
266
|
+
resolveListener({
|
|
267
|
+
redirectUri: `http://127.0.0.1:${port}${CALLBACK_PATH}`,
|
|
268
|
+
waitForCode,
|
|
269
|
+
close: (reason?: string) => {
|
|
270
|
+
server.close();
|
|
271
|
+
settle.reject(new Error(reason ?? "Login cancelled."));
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** App-held WorkOS PKCE login */
|
|
279
|
+
async function workosPkceLogin(platformUrl: string): Promise<string> {
|
|
280
|
+
const clientId = await fetchWorkosClientId(platformUrl);
|
|
281
|
+
const { verifier, challenge } = generatePkcePair();
|
|
282
|
+
const state = randomBytes(32).toString("hex");
|
|
240
283
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
284
|
+
const listener = await startLoopbackListener(state);
|
|
285
|
+
const timeout = setTimeout(() => {
|
|
286
|
+
listener.close("Login timed out. Please try again.");
|
|
287
|
+
}, LOGIN_TIMEOUT_MS);
|
|
244
288
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
289
|
+
try {
|
|
290
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
291
|
+
clientId,
|
|
292
|
+
redirectUri: listener.redirectUri,
|
|
293
|
+
challenge,
|
|
294
|
+
state,
|
|
248
295
|
});
|
|
249
|
-
|
|
296
|
+
|
|
297
|
+
console.log("Opening browser for login...");
|
|
298
|
+
console.log(`If the browser doesn't open, visit: ${authorizeUrl}`);
|
|
299
|
+
openBrowser(authorizeUrl);
|
|
300
|
+
|
|
301
|
+
const code = await listener.waitForCode;
|
|
302
|
+
const accessToken = await exchangeCodeWithWorkos({
|
|
303
|
+
clientId,
|
|
304
|
+
code,
|
|
305
|
+
verifier,
|
|
306
|
+
});
|
|
307
|
+
return await exchangeAccessTokenForSession(
|
|
308
|
+
platformUrl,
|
|
309
|
+
clientId,
|
|
310
|
+
accessToken,
|
|
311
|
+
);
|
|
312
|
+
} finally {
|
|
313
|
+
clearTimeout(timeout);
|
|
314
|
+
}
|
|
250
315
|
}
|
|
251
316
|
|
|
252
317
|
export async function login(): Promise<void> {
|
|
@@ -306,11 +371,10 @@ export async function login(): Promise<void> {
|
|
|
306
371
|
}
|
|
307
372
|
}
|
|
308
373
|
|
|
309
|
-
// If no --token flag, use
|
|
374
|
+
// If no --token flag, use app-held WorkOS PKCE login.
|
|
310
375
|
if (!token) {
|
|
311
|
-
const webUrl = getWebUrl();
|
|
312
376
|
try {
|
|
313
|
-
token = await
|
|
377
|
+
token = await workosPkceLogin(getPlatformUrl());
|
|
314
378
|
} catch (error) {
|
|
315
379
|
console.error(`❌ ${error instanceof Error ? error.message : error}`);
|
|
316
380
|
process.exit(1);
|