libretto 0.6.13 → 0.6.15
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/cli/commands/auth.js +43 -33
- package/dist/cli/commands/billing.js +3 -5
- package/dist/cli/commands/browser.js +3 -6
- package/dist/cli/commands/deploy.js +54 -45
- package/dist/cli/commands/execution.js +7 -4
- package/dist/cli/commands/experiments.js +1 -1
- package/dist/cli/commands/setup.js +1 -1
- package/dist/cli/commands/shared.js +1 -1
- package/dist/cli/commands/snapshot.js +1 -1
- package/dist/cli/commands/status.js +1 -1
- package/dist/cli/core/auth-fetch.js +11 -6
- package/dist/cli/core/browser.js +10 -5
- package/dist/cli/core/daemon/daemon.js +63 -10
- package/dist/cli/core/daemon/exec-repl.js +133 -0
- package/dist/cli/core/daemon/exec.js +6 -21
- package/dist/cli/core/daemon/ipc.js +47 -4
- package/dist/cli/core/daemon/ipc.spec.js +21 -0
- package/dist/cli/core/exec-compiler.js +8 -3
- package/dist/cli/core/providers/index.js +13 -4
- package/dist/cli/core/providers/kernel.js +3 -3
- package/dist/cli/core/providers/libretto-cloud.js +178 -26
- package/dist/cli/router.js +9 -4
- package/dist/shared/ipc/socket-transport.d.ts +2 -1
- package/dist/shared/ipc/socket-transport.js +16 -5
- package/dist/shared/ipc/socket-transport.spec.js +5 -0
- package/package.json +2 -2
- package/skills/libretto/SKILL.md +33 -29
- package/skills/libretto/references/code-generation-rules.md +6 -0
- package/skills/libretto/references/configuration-file-reference.md +8 -0
- package/skills/libretto/references/site-security-review.md +6 -6
- package/skills/libretto-readonly/SKILL.md +1 -1
- package/src/cli/commands/auth.ts +46 -33
- package/src/cli/commands/billing.ts +3 -5
- package/src/cli/commands/browser.ts +5 -9
- package/src/cli/commands/deploy.ts +55 -49
- package/src/cli/commands/execution.ts +7 -4
- package/src/cli/commands/experiments.ts +1 -1
- package/src/cli/commands/setup.ts +1 -1
- package/src/cli/commands/shared.ts +1 -1
- package/src/cli/commands/snapshot.ts +1 -1
- package/src/cli/commands/status.ts +1 -1
- package/src/cli/core/auth-fetch.ts +9 -4
- package/src/cli/core/browser.ts +12 -5
- package/src/cli/core/daemon/daemon.ts +81 -9
- package/src/cli/core/daemon/exec-repl.ts +189 -0
- package/src/cli/core/daemon/exec.ts +8 -43
- package/src/cli/core/daemon/ipc.spec.ts +27 -0
- package/src/cli/core/daemon/ipc.ts +76 -7
- package/src/cli/core/exec-compiler.ts +8 -3
- package/src/cli/core/providers/index.ts +17 -4
- package/src/cli/core/providers/kernel.ts +4 -3
- package/src/cli/core/providers/libretto-cloud.ts +224 -36
- package/src/cli/router.ts +9 -4
- package/src/shared/ipc/socket-transport.spec.ts +6 -0
- package/src/shared/ipc/socket-transport.ts +20 -5
- package/dist/cli/framework/simple-cli.js +0 -880
- package/src/cli/framework/simple-cli.ts +0 -1459
package/src/cli/commands/auth.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Hosted-platform auth commands.
|
|
3
3
|
*
|
|
4
|
-
* libretto
|
|
5
|
-
* libretto
|
|
6
|
-
* libretto
|
|
7
|
-
* libretto
|
|
8
|
-
* libretto
|
|
9
|
-
* libretto
|
|
10
|
-
* libretto
|
|
11
|
-
* libretto
|
|
12
|
-
* libretto
|
|
4
|
+
* libretto cloud auth signup
|
|
5
|
+
* libretto cloud auth login
|
|
6
|
+
* libretto cloud auth forgot-password
|
|
7
|
+
* libretto cloud auth logout
|
|
8
|
+
* libretto cloud auth invite <email> [--role member|admin|owner]
|
|
9
|
+
* libretto cloud auth accept-invite <tenantSlug> <invitationId>
|
|
10
|
+
* libretto cloud auth api-key issue [--label <label>]
|
|
11
|
+
* libretto cloud auth api-key list
|
|
12
|
+
* libretto cloud auth api-key revoke <id>
|
|
13
|
+
* libretto cloud auth whoami
|
|
13
14
|
*
|
|
14
15
|
* Credentials live at ~/.libretto/auth.json (mode 0600). The CLI sends either
|
|
15
16
|
* the stored API key or the stored session cookie depending on what's
|
|
@@ -17,15 +18,15 @@
|
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
import { z } from "zod";
|
|
20
|
-
import { SimpleCLI } from "
|
|
21
|
+
import { SimpleCLI } from "affordance";
|
|
21
22
|
import {
|
|
22
23
|
ApiCallError,
|
|
23
24
|
betterAuthCall,
|
|
24
|
-
HOSTED_API_URL,
|
|
25
25
|
NOT_AUTHENTICATED_MESSAGE,
|
|
26
26
|
orpcCall,
|
|
27
27
|
pickCredential,
|
|
28
28
|
resolveApiUrl,
|
|
29
|
+
resolveHostedApiUrl,
|
|
29
30
|
} from "../core/auth-fetch.js";
|
|
30
31
|
import {
|
|
31
32
|
authStatePath,
|
|
@@ -198,11 +199,10 @@ async function issueApiKey(
|
|
|
198
199
|
|
|
199
200
|
export const signupCommand = SimpleCLI.command({
|
|
200
201
|
description: "Create a new hosted-platform account and organization",
|
|
201
|
-
experimental: true,
|
|
202
202
|
})
|
|
203
203
|
.input(SimpleCLI.input({ positionals: [], named: {} }))
|
|
204
204
|
.handle(async () => {
|
|
205
|
-
const apiUrl =
|
|
205
|
+
const apiUrl = resolveHostedApiUrl();
|
|
206
206
|
console.log("Sign up for libretto cloud");
|
|
207
207
|
console.log();
|
|
208
208
|
console.log("Heads up: a libretto user can only belong to one organization.");
|
|
@@ -215,7 +215,7 @@ export const signupCommand = SimpleCLI.command({
|
|
|
215
215
|
const name = await prompt("Your name:");
|
|
216
216
|
if (name.toLowerCase() === "q" || name.length === 0) {
|
|
217
217
|
console.log(
|
|
218
|
-
"OK — ask an existing teammate to run `libretto
|
|
218
|
+
"OK — ask an existing teammate to run `libretto cloud auth invite <your-email>` and then run `libretto cloud auth accept-invite <slug> <invitation-id>` from this machine.",
|
|
219
219
|
);
|
|
220
220
|
return;
|
|
221
221
|
}
|
|
@@ -294,7 +294,7 @@ export const signupCommand = SimpleCLI.command({
|
|
|
294
294
|
console.log(`Session saved to ${authStatePath()}`);
|
|
295
295
|
console.log();
|
|
296
296
|
console.log("To generate an API key, run:");
|
|
297
|
-
console.log(" libretto
|
|
297
|
+
console.log(" libretto cloud auth api-key issue --label <label>");
|
|
298
298
|
console.log("Then add LIBRETTO_API_KEY=<key> to your project's .env file.");
|
|
299
299
|
});
|
|
300
300
|
|
|
@@ -304,11 +304,10 @@ export const signupCommand = SimpleCLI.command({
|
|
|
304
304
|
|
|
305
305
|
export const loginCommand = SimpleCLI.command({
|
|
306
306
|
description: "Sign in to an existing hosted-platform account",
|
|
307
|
-
experimental: true,
|
|
308
307
|
})
|
|
309
308
|
.input(SimpleCLI.input({ positionals: [], named: {} }))
|
|
310
309
|
.handle(async () => {
|
|
311
|
-
const apiUrl =
|
|
310
|
+
const apiUrl = resolveHostedApiUrl();
|
|
312
311
|
|
|
313
312
|
const email = await prompt("Email:");
|
|
314
313
|
const password = await promptPassword("Password:");
|
|
@@ -369,13 +368,32 @@ export const loginCommand = SimpleCLI.command({
|
|
|
369
368
|
}
|
|
370
369
|
});
|
|
371
370
|
|
|
371
|
+
export const forgotPasswordCommand = SimpleCLI.command({
|
|
372
|
+
description: "Send a password reset email",
|
|
373
|
+
})
|
|
374
|
+
.input(SimpleCLI.input({ positionals: [], named: {} }))
|
|
375
|
+
.handle(async () => {
|
|
376
|
+
const apiUrl = resolveHostedApiUrl();
|
|
377
|
+
const email = await prompt("Email:");
|
|
378
|
+
const result = await orpcCall<{ status: "sent" | "not_found" }>({
|
|
379
|
+
apiUrl,
|
|
380
|
+
path: "/v1/auth/requestPasswordReset",
|
|
381
|
+
input: { email },
|
|
382
|
+
unauthenticated: true,
|
|
383
|
+
});
|
|
384
|
+
if (result.status === "not_found") {
|
|
385
|
+
console.log(`No Libretto account exists for ${email}.`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
console.log(`Password reset link sent to ${email}.`);
|
|
389
|
+
});
|
|
390
|
+
|
|
372
391
|
// ---------------------------------------------------------------------------
|
|
373
392
|
// logout
|
|
374
393
|
// ---------------------------------------------------------------------------
|
|
375
394
|
|
|
376
395
|
export const logoutCommand = SimpleCLI.command({
|
|
377
396
|
description: "Clear local libretto credentials",
|
|
378
|
-
experimental: true,
|
|
379
397
|
})
|
|
380
398
|
.handle(async () => {
|
|
381
399
|
const state = await readAuthState();
|
|
@@ -400,7 +418,6 @@ export const logoutCommand = SimpleCLI.command({
|
|
|
400
418
|
|
|
401
419
|
export const inviteCommand = SimpleCLI.command({
|
|
402
420
|
description: "Invite a teammate to your active organization",
|
|
403
|
-
experimental: true,
|
|
404
421
|
})
|
|
405
422
|
.input(
|
|
406
423
|
SimpleCLI.input({
|
|
@@ -475,7 +492,7 @@ export const inviteCommand = SimpleCLI.command({
|
|
|
475
492
|
console.log();
|
|
476
493
|
console.log("Tell them to run:");
|
|
477
494
|
console.log(
|
|
478
|
-
` libretto
|
|
495
|
+
` libretto cloud auth accept-invite ${orgSlug} ${data.id}`,
|
|
479
496
|
);
|
|
480
497
|
});
|
|
481
498
|
|
|
@@ -485,7 +502,6 @@ export const inviteCommand = SimpleCLI.command({
|
|
|
485
502
|
|
|
486
503
|
export const acceptInviteCommand = SimpleCLI.command({
|
|
487
504
|
description: "Accept an organization invitation",
|
|
488
|
-
experimental: true,
|
|
489
505
|
})
|
|
490
506
|
.input(
|
|
491
507
|
SimpleCLI.input({
|
|
@@ -514,7 +530,7 @@ export const acceptInviteCommand = SimpleCLI.command({
|
|
|
514
530
|
)
|
|
515
531
|
.handle(async ({ input }) => {
|
|
516
532
|
const stored = await readAuthState();
|
|
517
|
-
const apiUrl =
|
|
533
|
+
const apiUrl = resolveHostedApiUrl();
|
|
518
534
|
const credential = pickCredential(stored);
|
|
519
535
|
const expectedTenantSlug = input.tenantSlug;
|
|
520
536
|
|
|
@@ -535,7 +551,7 @@ export const acceptInviteCommand = SimpleCLI.command({
|
|
|
535
551
|
[
|
|
536
552
|
"You're already a member of an organization.",
|
|
537
553
|
"A libretto user can only belong to one organization at a time.",
|
|
538
|
-
"To accept this invite: log out, delete the existing account, and re-run `auth accept-invite` with a new account (or a fresh email).",
|
|
554
|
+
"To accept this invite: log out, delete the existing account, and re-run `libretto cloud auth accept-invite` with a new account (or a fresh email).",
|
|
539
555
|
].join("\n"),
|
|
540
556
|
);
|
|
541
557
|
}
|
|
@@ -611,7 +627,7 @@ export const acceptInviteCommand = SimpleCLI.command({
|
|
|
611
627
|
console.log();
|
|
612
628
|
console.log("Email verified. You're logged in and a member of the organization.");
|
|
613
629
|
console.log("To generate an API key, run:");
|
|
614
|
-
console.log(" libretto
|
|
630
|
+
console.log(" libretto cloud auth api-key issue --label <label>");
|
|
615
631
|
console.log("Then add LIBRETTO_API_KEY=<key> to your project's .env file.");
|
|
616
632
|
});
|
|
617
633
|
|
|
@@ -621,7 +637,6 @@ export const acceptInviteCommand = SimpleCLI.command({
|
|
|
621
637
|
|
|
622
638
|
export const apiKeyIssueCommand = SimpleCLI.command({
|
|
623
639
|
description: "Issue a new API key for the active organization",
|
|
624
|
-
experimental: true,
|
|
625
640
|
})
|
|
626
641
|
.input(
|
|
627
642
|
SimpleCLI.input({
|
|
@@ -658,7 +673,6 @@ export const apiKeyIssueCommand = SimpleCLI.command({
|
|
|
658
673
|
|
|
659
674
|
export const apiKeyListCommand = SimpleCLI.command({
|
|
660
675
|
description: "List API keys for the active organization",
|
|
661
|
-
experimental: true,
|
|
662
676
|
})
|
|
663
677
|
.handle(async () => {
|
|
664
678
|
const stored = await readAuthState();
|
|
@@ -691,13 +705,12 @@ export const apiKeyListCommand = SimpleCLI.command({
|
|
|
691
705
|
|
|
692
706
|
export const apiKeyRevokeCommand = SimpleCLI.command({
|
|
693
707
|
description: "Revoke an API key by id",
|
|
694
|
-
experimental: true,
|
|
695
708
|
})
|
|
696
709
|
.input(
|
|
697
710
|
SimpleCLI.input({
|
|
698
711
|
positionals: [
|
|
699
712
|
SimpleCLI.positional("id", z.string().min(1), {
|
|
700
|
-
help: "API key id (from `auth api-key list`).",
|
|
713
|
+
help: "API key id (from `libretto cloud auth api-key list`).",
|
|
701
714
|
}),
|
|
702
715
|
],
|
|
703
716
|
named: {},
|
|
@@ -720,7 +733,7 @@ export const apiKeyRevokeCommand = SimpleCLI.command({
|
|
|
720
733
|
|
|
721
734
|
console.log(`API key ${input.id} revoked.`);
|
|
722
735
|
console.log(
|
|
723
|
-
"If this key was in your .env, remove the LIBRETTO_API_KEY value and issue a new one with `auth api-key issue --label <label>`.",
|
|
736
|
+
"If this key was in your .env, remove the LIBRETTO_API_KEY value and issue a new one with `libretto cloud auth api-key issue --label <label>`.",
|
|
724
737
|
);
|
|
725
738
|
});
|
|
726
739
|
|
|
@@ -730,7 +743,6 @@ export const apiKeyRevokeCommand = SimpleCLI.command({
|
|
|
730
743
|
|
|
731
744
|
export const whoamiCommand = SimpleCLI.command({
|
|
732
745
|
description: "Print the active session and credential source",
|
|
733
|
-
experimental: true,
|
|
734
746
|
})
|
|
735
747
|
.handle(async () => {
|
|
736
748
|
const stored = await readAuthState();
|
|
@@ -740,13 +752,13 @@ export const whoamiCommand = SimpleCLI.command({
|
|
|
740
752
|
|
|
741
753
|
if (credential.source === "none") {
|
|
742
754
|
console.log(
|
|
743
|
-
"Not authenticated. Run `libretto
|
|
755
|
+
"Not authenticated. Run `libretto cloud auth signup`, `libretto cloud auth login`, or set LIBRETTO_API_KEY in your env.",
|
|
744
756
|
);
|
|
745
757
|
return;
|
|
746
758
|
}
|
|
747
759
|
|
|
748
760
|
console.log(`Auth source: ${credential.source}`);
|
|
749
|
-
console.log(`API URL: ${
|
|
761
|
+
console.log(`API URL: ${resolveHostedApiUrl()}`);
|
|
750
762
|
console.log(
|
|
751
763
|
`LIBRETTO_API_KEY: ${envKey ? `set in env (${envKey.slice(0, 6)}…)` : "not set in env"}`,
|
|
752
764
|
);
|
|
@@ -771,6 +783,7 @@ export const authCommands = SimpleCLI.group({
|
|
|
771
783
|
routes: {
|
|
772
784
|
signup: signupCommand,
|
|
773
785
|
login: loginCommand,
|
|
786
|
+
"forgot-password": forgotPasswordCommand,
|
|
774
787
|
logout: logoutCommand,
|
|
775
788
|
invite: inviteCommand,
|
|
776
789
|
"accept-invite": acceptInviteCommand,
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* them switch between any of the configured Subscription Update
|
|
7
7
|
* products (Free / Pro / Team).
|
|
8
8
|
*
|
|
9
|
-
* libretto
|
|
10
|
-
* libretto
|
|
9
|
+
* libretto cloud billing portal → Stripe Customer Portal
|
|
10
|
+
* libretto cloud billing status → plan + usage + period end
|
|
11
11
|
*
|
|
12
12
|
* `libretto init` is unchanged. New tenants start on Free automatically
|
|
13
13
|
* (with a real Stripe Customer + Free Subscription created at signup).
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Auth: requires a session cookie (or LIBRETTO_API_KEY).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { SimpleCLI } from "
|
|
18
|
+
import { SimpleCLI } from "affordance";
|
|
19
19
|
import {
|
|
20
20
|
NOT_AUTHENTICATED_MESSAGE,
|
|
21
21
|
orpcCall,
|
|
@@ -71,7 +71,6 @@ function formatLimit(limit: number | null): string {
|
|
|
71
71
|
|
|
72
72
|
export const billingPortalCommand = SimpleCLI.command({
|
|
73
73
|
description: "Open the libretto plans page (current plan + switch options)",
|
|
74
|
-
experimental: true,
|
|
75
74
|
})
|
|
76
75
|
.handle(async () => {
|
|
77
76
|
const { apiUrl, credential } = await requireAuth();
|
|
@@ -97,7 +96,6 @@ export const billingPortalCommand = SimpleCLI.command({
|
|
|
97
96
|
|
|
98
97
|
export const billingStatusCommand = SimpleCLI.command({
|
|
99
98
|
description: "Print the current plan, status, and browser-hour usage",
|
|
100
|
-
experimental: true,
|
|
101
99
|
})
|
|
102
100
|
.handle(async () => {
|
|
103
101
|
const { apiUrl, credential } = await requireAuth();
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
validateSessionName,
|
|
21
21
|
} from "../core/session.js";
|
|
22
22
|
import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
|
|
23
|
-
import { SimpleCLI } from "
|
|
23
|
+
import { SimpleCLI } from "affordance";
|
|
24
24
|
import {
|
|
25
25
|
sessionOption,
|
|
26
26
|
withAutoSession,
|
|
@@ -63,8 +63,8 @@ function resolveRequestedSessionMode(
|
|
|
63
63
|
|
|
64
64
|
export const openInput = SimpleCLI.input({
|
|
65
65
|
positionals: [
|
|
66
|
-
SimpleCLI.positional("url", z.string().
|
|
67
|
-
help: "URL to open",
|
|
66
|
+
SimpleCLI.positional("url", z.string().default("about:blank"), {
|
|
67
|
+
help: "URL to open (defaults to about:blank)",
|
|
68
68
|
}),
|
|
69
69
|
],
|
|
70
70
|
named: {
|
|
@@ -92,10 +92,6 @@ export const openInput = SimpleCLI.input({
|
|
|
92
92
|
}),
|
|
93
93
|
},
|
|
94
94
|
})
|
|
95
|
-
.refine(
|
|
96
|
-
(input) => Boolean(input.url),
|
|
97
|
-
`Usage: ${librettoCommand("open <url> [--headless] [--read-only|--write-access] [--auth-profile <domain>] [--viewport WxH] [--session <name>]")}`,
|
|
98
|
-
)
|
|
99
95
|
.refine(
|
|
100
96
|
(input) => !(input.headed && input.headless),
|
|
101
97
|
"Cannot pass both --headed and --headless.",
|
|
@@ -119,7 +115,7 @@ export const openCommand = SimpleCLI.command({
|
|
|
119
115
|
if (providerName === "local") {
|
|
120
116
|
const headed = input.headed || !input.headless;
|
|
121
117
|
const viewport = parseViewportArg(input.viewport);
|
|
122
|
-
await runOpen(input.url
|
|
118
|
+
await runOpen(input.url, headed, ctx.session, ctx.logger, {
|
|
123
119
|
viewport,
|
|
124
120
|
accessMode: resolveRequestedSessionMode(
|
|
125
121
|
input.readOnly,
|
|
@@ -130,7 +126,7 @@ export const openCommand = SimpleCLI.command({
|
|
|
130
126
|
});
|
|
131
127
|
} else {
|
|
132
128
|
await runOpenWithProvider(
|
|
133
|
-
input.url
|
|
129
|
+
input.url,
|
|
134
130
|
providerName,
|
|
135
131
|
ctx.session,
|
|
136
132
|
ctx.logger,
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
orpcCall,
|
|
5
|
+
resolveApiUrl,
|
|
6
|
+
} from "../core/auth-fetch.js";
|
|
4
7
|
import { buildHostedDeployTarball } from "../core/deploy-artifact.js";
|
|
5
|
-
import {
|
|
8
|
+
import { readAuthState } from "../core/auth-storage.js";
|
|
9
|
+
import { SimpleCLI } from "affordance";
|
|
6
10
|
|
|
7
11
|
type DeploymentStatus = "building" | "ready" | "failed";
|
|
8
12
|
|
|
@@ -19,37 +23,50 @@ function generateDeploymentName(): string {
|
|
|
19
23
|
return `deploy-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
function
|
|
23
|
-
|
|
26
|
+
function deployApiKeyRequiredMessage(hasStoredSession: boolean): string {
|
|
27
|
+
if (hasStoredSession) {
|
|
28
|
+
return [
|
|
29
|
+
"LIBRETTO_API_KEY is required to deploy to Libretto Cloud.",
|
|
30
|
+
"You are logged in locally, but deploy endpoints require API-key auth.",
|
|
31
|
+
" • Generate a key: run `libretto cloud auth api-key issue --label <label>`.",
|
|
32
|
+
" • Add it to your project .env file: `LIBRETTO_API_KEY=<issued-key>`.",
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return [
|
|
37
|
+
"LIBRETTO_API_KEY is required to deploy to Libretto Cloud.",
|
|
38
|
+
"No local cloud session was found.",
|
|
39
|
+
" • New account: run `libretto cloud auth signup`, then verify your email.",
|
|
40
|
+
" • Existing account: run `libretto cloud auth login`.",
|
|
41
|
+
" • Generate a key: run `libretto cloud auth api-key issue --label <label>`.",
|
|
42
|
+
" • Add it to your project .env file: `LIBRETTO_API_KEY=<issued-key>`.",
|
|
43
|
+
].join("\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function requireDeployApiKey() {
|
|
47
|
+
const apiKey = process.env.LIBRETTO_API_KEY?.trim();
|
|
24
48
|
|
|
25
49
|
if (!apiKey) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
"LIBRETTO_API_KEY environment variable is required.",
|
|
28
|
-
);
|
|
50
|
+
throw new Error(deployApiKeyRequiredMessage(await hasStoredCloudSession()));
|
|
29
51
|
}
|
|
30
52
|
|
|
31
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
apiUrl: resolveApiUrl(null),
|
|
55
|
+
credential: { source: "env-api-key" as const, apiKey },
|
|
56
|
+
};
|
|
32
57
|
}
|
|
33
58
|
|
|
34
|
-
async function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return fetch(`${apiUrl}${path}`, {
|
|
41
|
-
method: "POST",
|
|
42
|
-
headers: {
|
|
43
|
-
"x-api-key": apiKey,
|
|
44
|
-
"Content-Type": "application/json",
|
|
45
|
-
},
|
|
46
|
-
body: JSON.stringify({ json: input }),
|
|
47
|
-
});
|
|
59
|
+
async function hasStoredCloudSession(): Promise<boolean> {
|
|
60
|
+
try {
|
|
61
|
+
return Boolean((await readAuthState())?.session);
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
async function pollDeployment(
|
|
51
68
|
apiUrl: string,
|
|
52
|
-
apiKey: string,
|
|
69
|
+
credential: { source: "env-api-key"; apiKey: string },
|
|
53
70
|
deploymentId: string,
|
|
54
71
|
pollIntervalMs: number,
|
|
55
72
|
maxWaitMs: number,
|
|
@@ -68,18 +85,14 @@ async function pollDeployment(
|
|
|
68
85
|
|
|
69
86
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
70
87
|
|
|
71
|
-
|
|
72
|
-
|
|
88
|
+
deployment = await orpcCall<DeploymentResponse["json"]>({
|
|
89
|
+
apiUrl,
|
|
90
|
+
path: "/v1/deployments/sync",
|
|
91
|
+
input: { id: deploymentId },
|
|
92
|
+
credential,
|
|
73
93
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
throw new Error(
|
|
77
|
-
`Failed to sync deployment status (${res.status}): ${JSON.stringify(body)}`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
status = body.json.status;
|
|
81
|
-
workflows = body.json.workflows;
|
|
82
|
-
deployment = body.json;
|
|
94
|
+
status = deployment.status;
|
|
95
|
+
workflows = deployment.workflows;
|
|
83
96
|
if (status === "ready" && readyAt === null) readyAt = Date.now();
|
|
84
97
|
process.stdout.write(".");
|
|
85
98
|
}
|
|
@@ -132,11 +145,10 @@ export const deployInput = SimpleCLI.input({
|
|
|
132
145
|
|
|
133
146
|
export const deployCommand = SimpleCLI.command({
|
|
134
147
|
description: "Deploy workflows to the hosted platform",
|
|
135
|
-
experimental: true,
|
|
136
148
|
})
|
|
137
149
|
.input(deployInput)
|
|
138
150
|
.handle(async ({ input }) => {
|
|
139
|
-
const { apiUrl,
|
|
151
|
+
const { apiUrl, credential } = await requireDeployApiKey();
|
|
140
152
|
const deploymentName = generateDeploymentName();
|
|
141
153
|
|
|
142
154
|
// Hosted deploy uploads a generated artifact with a deploy entrypoint and
|
|
@@ -157,20 +169,14 @@ export const deployCommand = SimpleCLI.command({
|
|
|
157
169
|
if (input.description) createPayload.description = input.description;
|
|
158
170
|
|
|
159
171
|
console.log("Uploading deployment...");
|
|
160
|
-
const
|
|
172
|
+
const body = await orpcCall<DeploymentResponse["json"]>({
|
|
161
173
|
apiUrl,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
);
|
|
166
|
-
const body = (await res.json()) as DeploymentResponse;
|
|
167
|
-
if (res.status !== 200) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
`Failed to create deployment (${res.status}): ${JSON.stringify(body)}`,
|
|
170
|
-
);
|
|
171
|
-
}
|
|
174
|
+
path: "/v1/deployments/create",
|
|
175
|
+
input: createPayload,
|
|
176
|
+
credential,
|
|
177
|
+
});
|
|
172
178
|
|
|
173
|
-
const { deployment_id, status } = body
|
|
179
|
+
const { deployment_id, status } = body;
|
|
174
180
|
console.log(`Deployment created: ${deployment_id}`);
|
|
175
181
|
console.log(`Status: ${status}`);
|
|
176
182
|
|
|
@@ -178,7 +184,7 @@ export const deployCommand = SimpleCLI.command({
|
|
|
178
184
|
process.stdout.write("Waiting for build");
|
|
179
185
|
const deployment = await pollDeployment(
|
|
180
186
|
apiUrl,
|
|
181
|
-
|
|
187
|
+
credential,
|
|
182
188
|
deployment_id,
|
|
183
189
|
10_000,
|
|
184
190
|
5 * 60 * 1000,
|
|
@@ -29,7 +29,10 @@ import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
|
|
|
29
29
|
import { readLibrettoConfig } from "../core/config.js";
|
|
30
30
|
import { librettoCommand } from "../../shared/package-manager.js";
|
|
31
31
|
import { renderSnapshotDiff } from "../../shared/snapshot/diff-snapshots.js";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
getProviderStartupTimeoutMs,
|
|
34
|
+
resolveProviderName,
|
|
35
|
+
} from "../core/providers/index.js";
|
|
33
36
|
import { getAbsoluteIntegrationPath } from "../core/workflow-runtime.js";
|
|
34
37
|
import {
|
|
35
38
|
compileExecFunction,
|
|
@@ -49,7 +52,7 @@ import {
|
|
|
49
52
|
} from "../core/telemetry.js";
|
|
50
53
|
import type { SessionAccessMode } from "../../shared/state/index.js";
|
|
51
54
|
import type { Experiments } from "../core/experiments.js";
|
|
52
|
-
import { SimpleCLI } from "
|
|
55
|
+
import { SimpleCLI } from "affordance";
|
|
53
56
|
import {
|
|
54
57
|
pageOption,
|
|
55
58
|
sessionOption,
|
|
@@ -609,7 +612,7 @@ async function runIntegrationFromFile(
|
|
|
609
612
|
},
|
|
610
613
|
logger,
|
|
611
614
|
logPath: runLogPath,
|
|
612
|
-
startupTimeoutMs:
|
|
615
|
+
startupTimeoutMs: getProviderStartupTimeoutMs(args.providerName),
|
|
613
616
|
handlers,
|
|
614
617
|
});
|
|
615
618
|
|
|
@@ -762,7 +765,7 @@ export const readonlyExecCommand = SimpleCLI.command({
|
|
|
762
765
|
});
|
|
763
766
|
});
|
|
764
767
|
|
|
765
|
-
const runUsage = `Usage: ${librettoCommand("run <integrationFile> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--read-only|--write-access] [--no-visualize] [--stay-open-on-success] [--viewport WxH]")}`;
|
|
768
|
+
const runUsage = `Usage: ${librettoCommand("run <integrationFile> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--read-only|--write-access] [--no-visualize] [--stay-open-on-success] [--viewport WxH] [--provider <provider>]")}`;
|
|
766
769
|
|
|
767
770
|
export const runInput = SimpleCLI.input({
|
|
768
771
|
positionals: [
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type ExperimentName,
|
|
9
9
|
type Experiments,
|
|
10
10
|
} from "../core/experiments.js";
|
|
11
|
-
import { SimpleCLI } from "
|
|
11
|
+
import { SimpleCLI } from "affordance";
|
|
12
12
|
|
|
13
13
|
const experimentNames = Object.keys(EXPERIMENTS) as ExperimentName[];
|
|
14
14
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
REPO_ROOT,
|
|
9
9
|
} from "../core/context.js";
|
|
10
10
|
import { librettoCommand } from "../../shared/package-manager.js";
|
|
11
|
-
import { SimpleCLI } from "
|
|
11
|
+
import { SimpleCLI } from "affordance";
|
|
12
12
|
|
|
13
13
|
function installBrowsers(): void {
|
|
14
14
|
console.log("Installing Playwright Chromium...");
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type SimpleCLIMiddlewareArgs,
|
|
15
15
|
type SimpleCLIContext,
|
|
16
16
|
type SimpleCLIMiddleware,
|
|
17
|
-
} from "
|
|
17
|
+
} from "affordance";
|
|
18
18
|
|
|
19
19
|
export function sessionOption(help = "Session name") {
|
|
20
20
|
return SimpleCLI.option(z.string().optional(), { help });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { LoggerApi } from "../../shared/logger/index.js";
|
|
3
3
|
import { readSessionState } from "../core/session.js";
|
|
4
|
-
import { SimpleCLI } from "
|
|
4
|
+
import { SimpleCLI } from "affordance";
|
|
5
5
|
import {
|
|
6
6
|
pageOption,
|
|
7
7
|
sessionOption,
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
import { readAuthState, writeAuthState, type AuthState } from "./auth-storage.js";
|
|
13
13
|
|
|
14
|
-
export const
|
|
14
|
+
export const DEFAULT_HOSTED_API_URL = "https://api.libretto.sh";
|
|
15
|
+
|
|
16
|
+
export function resolveHostedApiUrl(): string {
|
|
17
|
+
return process.env.LIBRETTO_API_URL?.trim() || DEFAULT_HOSTED_API_URL;
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
21
|
* Shared "you have no usable credential" message. Pointed at the two
|
|
@@ -19,8 +23,9 @@ export const HOSTED_API_URL = "https://api.libretto.sh";
|
|
|
19
23
|
*/
|
|
20
24
|
export const NOT_AUTHENTICATED_MESSAGE = [
|
|
21
25
|
"Not authenticated.",
|
|
22
|
-
" •
|
|
23
|
-
" •
|
|
26
|
+
" • New account: run `libretto cloud auth signup`.",
|
|
27
|
+
" • Existing account: run `libretto cloud auth login`.",
|
|
28
|
+
" • Automation: set LIBRETTO_API_KEY in your env (issue one with `libretto cloud auth api-key issue --label <label>` after signing in).",
|
|
24
29
|
].join("\n");
|
|
25
30
|
|
|
26
31
|
export type CredentialSource = "env-api-key" | "cookie" | "none";
|
|
@@ -41,7 +46,7 @@ export function pickCredential(state: AuthState | null): CredentialChoice {
|
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
export function resolveApiUrl(_state: AuthState | null): string {
|
|
44
|
-
return
|
|
49
|
+
return resolveHostedApiUrl();
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
type FetchOptions = {
|
package/src/cli/core/browser.ts
CHANGED
|
@@ -9,13 +9,17 @@ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
|
9
9
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
11
|
import { createServer } from "node:net";
|
|
12
|
+
import { isWindowsNamedPipePath } from "../../shared/ipc/socket-transport.js";
|
|
12
13
|
import type { LoggerApi } from "../../shared/logger/index.js";
|
|
13
14
|
import type { SessionAccessMode } from "../../shared/state/index.js";
|
|
14
15
|
import type { Experiments } from "./experiments.js";
|
|
15
16
|
import { getSessionProviderClosePath, PROFILES_DIR } from "./context.js";
|
|
16
17
|
import { readLibrettoConfig } from "./config.js";
|
|
17
18
|
import { librettoCommand } from "../../shared/package-manager.js";
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
getCloudProviderApi,
|
|
21
|
+
getProviderStartupTimeoutMs,
|
|
22
|
+
} from "./providers/index.js";
|
|
19
23
|
import {
|
|
20
24
|
assertSessionAvailableForStart,
|
|
21
25
|
clearSessionState,
|
|
@@ -89,7 +93,8 @@ export function normalizeUrl(url: string): URL {
|
|
|
89
93
|
if (
|
|
90
94
|
parsedUrl.protocol === "http:" ||
|
|
91
95
|
parsedUrl.protocol === "https:" ||
|
|
92
|
-
parsedUrl.protocol === "file:"
|
|
96
|
+
parsedUrl.protocol === "file:" ||
|
|
97
|
+
parsedUrl.href === "about:blank"
|
|
93
98
|
) {
|
|
94
99
|
return parsedUrl;
|
|
95
100
|
}
|
|
@@ -99,7 +104,7 @@ export function normalizeUrl(url: string): URL {
|
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
throw new Error(
|
|
102
|
-
`Unsupported URL protocol: ${parsedUrl.protocol}. Use http://, https://, or
|
|
107
|
+
`Unsupported URL protocol: ${parsedUrl.protocol}. Use http://, https://, file://, or about:blank.`,
|
|
103
108
|
);
|
|
104
109
|
}
|
|
105
110
|
|
|
@@ -548,8 +553,8 @@ export async function runOpenWithProvider(
|
|
|
548
553
|
},
|
|
549
554
|
logger,
|
|
550
555
|
logPath: runLogPath,
|
|
551
|
-
// Remote
|
|
552
|
-
startupTimeoutMs:
|
|
556
|
+
// Remote provider creation can wait for cloud capacity before CDP exists.
|
|
557
|
+
startupTimeoutMs: getProviderStartupTimeoutMs(providerName),
|
|
553
558
|
});
|
|
554
559
|
client.destroy();
|
|
555
560
|
|
|
@@ -974,6 +979,8 @@ function unlinkDaemonSocket(
|
|
|
974
979
|
session: string,
|
|
975
980
|
): void {
|
|
976
981
|
if (!socketPath) return;
|
|
982
|
+
if (isWindowsNamedPipePath(socketPath)) return;
|
|
983
|
+
|
|
977
984
|
try {
|
|
978
985
|
unlinkSync(socketPath);
|
|
979
986
|
} catch (err) {
|