libretto 0.6.13 → 0.6.14
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 +24 -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 +6 -3
- 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/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 +24 -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 +6 -3
- 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/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,15 @@
|
|
|
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 logout
|
|
7
|
+
* libretto cloud auth invite <email> [--role member|admin|owner]
|
|
8
|
+
* libretto cloud auth accept-invite <tenantSlug> <invitationId>
|
|
9
|
+
* libretto cloud auth api-key issue [--label <label>]
|
|
10
|
+
* libretto cloud auth api-key list
|
|
11
|
+
* libretto cloud auth api-key revoke <id>
|
|
12
|
+
* libretto cloud auth whoami
|
|
13
13
|
*
|
|
14
14
|
* Credentials live at ~/.libretto/auth.json (mode 0600). The CLI sends either
|
|
15
15
|
* the stored API key or the stored session cookie depending on what's
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { z } from "zod";
|
|
20
|
-
import { SimpleCLI } from "
|
|
20
|
+
import { SimpleCLI } from "affordance";
|
|
21
21
|
import {
|
|
22
22
|
ApiCallError,
|
|
23
23
|
betterAuthCall,
|
|
24
|
-
HOSTED_API_URL,
|
|
25
24
|
NOT_AUTHENTICATED_MESSAGE,
|
|
26
25
|
orpcCall,
|
|
27
26
|
pickCredential,
|
|
28
27
|
resolveApiUrl,
|
|
28
|
+
resolveHostedApiUrl,
|
|
29
29
|
} from "../core/auth-fetch.js";
|
|
30
30
|
import {
|
|
31
31
|
authStatePath,
|
|
@@ -198,11 +198,10 @@ async function issueApiKey(
|
|
|
198
198
|
|
|
199
199
|
export const signupCommand = SimpleCLI.command({
|
|
200
200
|
description: "Create a new hosted-platform account and organization",
|
|
201
|
-
experimental: true,
|
|
202
201
|
})
|
|
203
202
|
.input(SimpleCLI.input({ positionals: [], named: {} }))
|
|
204
203
|
.handle(async () => {
|
|
205
|
-
const apiUrl =
|
|
204
|
+
const apiUrl = resolveHostedApiUrl();
|
|
206
205
|
console.log("Sign up for libretto cloud");
|
|
207
206
|
console.log();
|
|
208
207
|
console.log("Heads up: a libretto user can only belong to one organization.");
|
|
@@ -215,7 +214,7 @@ export const signupCommand = SimpleCLI.command({
|
|
|
215
214
|
const name = await prompt("Your name:");
|
|
216
215
|
if (name.toLowerCase() === "q" || name.length === 0) {
|
|
217
216
|
console.log(
|
|
218
|
-
"OK — ask an existing teammate to run `libretto
|
|
217
|
+
"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
218
|
);
|
|
220
219
|
return;
|
|
221
220
|
}
|
|
@@ -294,7 +293,7 @@ export const signupCommand = SimpleCLI.command({
|
|
|
294
293
|
console.log(`Session saved to ${authStatePath()}`);
|
|
295
294
|
console.log();
|
|
296
295
|
console.log("To generate an API key, run:");
|
|
297
|
-
console.log(" libretto
|
|
296
|
+
console.log(" libretto cloud auth api-key issue --label <label>");
|
|
298
297
|
console.log("Then add LIBRETTO_API_KEY=<key> to your project's .env file.");
|
|
299
298
|
});
|
|
300
299
|
|
|
@@ -304,11 +303,10 @@ export const signupCommand = SimpleCLI.command({
|
|
|
304
303
|
|
|
305
304
|
export const loginCommand = SimpleCLI.command({
|
|
306
305
|
description: "Sign in to an existing hosted-platform account",
|
|
307
|
-
experimental: true,
|
|
308
306
|
})
|
|
309
307
|
.input(SimpleCLI.input({ positionals: [], named: {} }))
|
|
310
308
|
.handle(async () => {
|
|
311
|
-
const apiUrl =
|
|
309
|
+
const apiUrl = resolveHostedApiUrl();
|
|
312
310
|
|
|
313
311
|
const email = await prompt("Email:");
|
|
314
312
|
const password = await promptPassword("Password:");
|
|
@@ -375,7 +373,6 @@ export const loginCommand = SimpleCLI.command({
|
|
|
375
373
|
|
|
376
374
|
export const logoutCommand = SimpleCLI.command({
|
|
377
375
|
description: "Clear local libretto credentials",
|
|
378
|
-
experimental: true,
|
|
379
376
|
})
|
|
380
377
|
.handle(async () => {
|
|
381
378
|
const state = await readAuthState();
|
|
@@ -400,7 +397,6 @@ export const logoutCommand = SimpleCLI.command({
|
|
|
400
397
|
|
|
401
398
|
export const inviteCommand = SimpleCLI.command({
|
|
402
399
|
description: "Invite a teammate to your active organization",
|
|
403
|
-
experimental: true,
|
|
404
400
|
})
|
|
405
401
|
.input(
|
|
406
402
|
SimpleCLI.input({
|
|
@@ -475,7 +471,7 @@ export const inviteCommand = SimpleCLI.command({
|
|
|
475
471
|
console.log();
|
|
476
472
|
console.log("Tell them to run:");
|
|
477
473
|
console.log(
|
|
478
|
-
` libretto
|
|
474
|
+
` libretto cloud auth accept-invite ${orgSlug} ${data.id}`,
|
|
479
475
|
);
|
|
480
476
|
});
|
|
481
477
|
|
|
@@ -485,7 +481,6 @@ export const inviteCommand = SimpleCLI.command({
|
|
|
485
481
|
|
|
486
482
|
export const acceptInviteCommand = SimpleCLI.command({
|
|
487
483
|
description: "Accept an organization invitation",
|
|
488
|
-
experimental: true,
|
|
489
484
|
})
|
|
490
485
|
.input(
|
|
491
486
|
SimpleCLI.input({
|
|
@@ -514,7 +509,7 @@ export const acceptInviteCommand = SimpleCLI.command({
|
|
|
514
509
|
)
|
|
515
510
|
.handle(async ({ input }) => {
|
|
516
511
|
const stored = await readAuthState();
|
|
517
|
-
const apiUrl =
|
|
512
|
+
const apiUrl = resolveHostedApiUrl();
|
|
518
513
|
const credential = pickCredential(stored);
|
|
519
514
|
const expectedTenantSlug = input.tenantSlug;
|
|
520
515
|
|
|
@@ -535,7 +530,7 @@ export const acceptInviteCommand = SimpleCLI.command({
|
|
|
535
530
|
[
|
|
536
531
|
"You're already a member of an organization.",
|
|
537
532
|
"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).",
|
|
533
|
+
"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
534
|
].join("\n"),
|
|
540
535
|
);
|
|
541
536
|
}
|
|
@@ -611,7 +606,7 @@ export const acceptInviteCommand = SimpleCLI.command({
|
|
|
611
606
|
console.log();
|
|
612
607
|
console.log("Email verified. You're logged in and a member of the organization.");
|
|
613
608
|
console.log("To generate an API key, run:");
|
|
614
|
-
console.log(" libretto
|
|
609
|
+
console.log(" libretto cloud auth api-key issue --label <label>");
|
|
615
610
|
console.log("Then add LIBRETTO_API_KEY=<key> to your project's .env file.");
|
|
616
611
|
});
|
|
617
612
|
|
|
@@ -621,7 +616,6 @@ export const acceptInviteCommand = SimpleCLI.command({
|
|
|
621
616
|
|
|
622
617
|
export const apiKeyIssueCommand = SimpleCLI.command({
|
|
623
618
|
description: "Issue a new API key for the active organization",
|
|
624
|
-
experimental: true,
|
|
625
619
|
})
|
|
626
620
|
.input(
|
|
627
621
|
SimpleCLI.input({
|
|
@@ -658,7 +652,6 @@ export const apiKeyIssueCommand = SimpleCLI.command({
|
|
|
658
652
|
|
|
659
653
|
export const apiKeyListCommand = SimpleCLI.command({
|
|
660
654
|
description: "List API keys for the active organization",
|
|
661
|
-
experimental: true,
|
|
662
655
|
})
|
|
663
656
|
.handle(async () => {
|
|
664
657
|
const stored = await readAuthState();
|
|
@@ -691,13 +684,12 @@ export const apiKeyListCommand = SimpleCLI.command({
|
|
|
691
684
|
|
|
692
685
|
export const apiKeyRevokeCommand = SimpleCLI.command({
|
|
693
686
|
description: "Revoke an API key by id",
|
|
694
|
-
experimental: true,
|
|
695
687
|
})
|
|
696
688
|
.input(
|
|
697
689
|
SimpleCLI.input({
|
|
698
690
|
positionals: [
|
|
699
691
|
SimpleCLI.positional("id", z.string().min(1), {
|
|
700
|
-
help: "API key id (from `auth api-key list`).",
|
|
692
|
+
help: "API key id (from `libretto cloud auth api-key list`).",
|
|
701
693
|
}),
|
|
702
694
|
],
|
|
703
695
|
named: {},
|
|
@@ -720,7 +712,7 @@ export const apiKeyRevokeCommand = SimpleCLI.command({
|
|
|
720
712
|
|
|
721
713
|
console.log(`API key ${input.id} revoked.`);
|
|
722
714
|
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>`.",
|
|
715
|
+
"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
716
|
);
|
|
725
717
|
});
|
|
726
718
|
|
|
@@ -730,7 +722,6 @@ export const apiKeyRevokeCommand = SimpleCLI.command({
|
|
|
730
722
|
|
|
731
723
|
export const whoamiCommand = SimpleCLI.command({
|
|
732
724
|
description: "Print the active session and credential source",
|
|
733
|
-
experimental: true,
|
|
734
725
|
})
|
|
735
726
|
.handle(async () => {
|
|
736
727
|
const stored = await readAuthState();
|
|
@@ -740,13 +731,13 @@ export const whoamiCommand = SimpleCLI.command({
|
|
|
740
731
|
|
|
741
732
|
if (credential.source === "none") {
|
|
742
733
|
console.log(
|
|
743
|
-
"Not authenticated. Run `libretto
|
|
734
|
+
"Not authenticated. Run `libretto cloud auth signup`, `libretto cloud auth login`, or set LIBRETTO_API_KEY in your env.",
|
|
744
735
|
);
|
|
745
736
|
return;
|
|
746
737
|
}
|
|
747
738
|
|
|
748
739
|
console.log(`Auth source: ${credential.source}`);
|
|
749
|
-
console.log(`API URL: ${
|
|
740
|
+
console.log(`API URL: ${resolveHostedApiUrl()}`);
|
|
750
741
|
console.log(
|
|
751
742
|
`LIBRETTO_API_KEY: ${envKey ? `set in env (${envKey.slice(0, 6)}…)` : "not set in env"}`,
|
|
752
743
|
);
|
|
@@ -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
|
|
|
@@ -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) {
|