libretto 0.6.24 → 0.6.25
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 +9 -1
- package/README.template.md +9 -1
- package/dist/cli/commands/browser.js +17 -10
- package/dist/cli/commands/cloud-credentials.js +70 -0
- package/dist/cli/commands/deploy.js +24 -2
- package/dist/cli/commands/execution.js +9 -30
- package/dist/cli/commands/import-chrome-profiles.js +46 -0
- package/dist/cli/commands/profiles.js +71 -0
- package/dist/cli/commands/shared.js +1 -3
- package/dist/cli/core/browser.js +89 -75
- package/dist/cli/core/daemon/daemon.js +47 -35
- package/dist/cli/core/daemon/ipc.js +3 -0
- package/dist/cli/core/deploy-artifact.js +85 -22
- package/dist/cli/core/profiles.js +47 -0
- package/dist/cli/core/prompt.js +9 -0
- package/dist/cli/core/providers/libretto-cloud.js +6 -2
- package/dist/cli/core/session-logs.js +325 -0
- package/dist/cli/core/telemetry.js +83 -313
- package/dist/cli/core/workflow-runner/runner.js +65 -0
- package/dist/cli/router.js +9 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/shared/workflow/auth-profile-name.d.ts +3 -0
- package/dist/shared/workflow/auth-profile-name.js +29 -0
- package/dist/shared/workflow/auth-profile-state.d.ts +20 -0
- package/dist/shared/workflow/auth-profile-state.js +105 -0
- package/dist/shared/workflow/authenticate.d.ts +17 -0
- package/dist/shared/workflow/authenticate.js +37 -0
- package/dist/shared/workflow/credentials.d.ts +5 -0
- package/dist/shared/workflow/credentials.js +68 -0
- package/dist/shared/workflow/workflow.d.ts +16 -1
- package/dist/shared/workflow/workflow.js +56 -4
- package/package.json +1 -1
- package/skills/libretto/SKILL.md +3 -4
- package/skills/libretto/references/auth-profiles.md +61 -11
- package/skills/libretto/references/code-generation-rules.md +31 -1
- package/skills/libretto-readonly/SKILL.md +1 -1
- package/src/cli/commands/browser.ts +19 -11
- package/src/cli/commands/cloud-credentials.ts +82 -0
- package/src/cli/commands/deploy.ts +41 -2
- package/src/cli/commands/execution.ts +10 -31
- package/src/cli/commands/import-chrome-profiles.ts +46 -0
- package/src/cli/commands/profiles.ts +90 -0
- package/src/cli/commands/shared.ts +4 -8
- package/src/cli/core/browser.ts +102 -91
- package/src/cli/core/daemon/config.ts +4 -1
- package/src/cli/core/daemon/daemon.ts +52 -44
- package/src/cli/core/daemon/ipc.ts +15 -0
- package/src/cli/core/deploy-artifact.ts +131 -32
- package/src/cli/core/profiles.ts +53 -0
- package/src/cli/core/prompt.ts +15 -0
- package/src/cli/core/providers/libretto-cloud.ts +6 -2
- package/src/cli/core/providers/types.ts +4 -1
- package/src/cli/core/session-logs.ts +445 -0
- package/src/cli/core/telemetry.ts +105 -422
- package/src/cli/core/workflow-runner/runner.ts +86 -1
- package/src/cli/router.ts +8 -0
- package/src/index.ts +10 -0
- package/src/shared/workflow/auth-profile-name.ts +27 -0
- package/src/shared/workflow/auth-profile-state.ts +144 -0
- package/src/shared/workflow/authenticate.ts +63 -0
- package/src/shared/workflow/credentials.ts +91 -0
- package/src/shared/workflow/workflow.ts +89 -4
package/README.md
CHANGED
|
@@ -93,6 +93,14 @@ Run `npx libretto help` for the full list of commands.
|
|
|
93
93
|
|
|
94
94
|
All Libretto state lives in a `.libretto/` directory at your project root. See the [configuration docs](https://libretto.sh/docs/understand-libretto/configuration) for details on config files, sessions, and profiles.
|
|
95
95
|
|
|
96
|
+
## Telemetry
|
|
97
|
+
|
|
98
|
+
Libretto records anonymous CLI telemetry to help understand CLI usage and help us prioritize improvements. Each resolved command can send only an install id, timestamp, command event name such as `libretto run`, and an error boolean. Libretto does not send command arguments, URLs, project paths, auth state, API keys, error messages or details, or user identity.
|
|
99
|
+
|
|
100
|
+
The install id is stored in the telemetry file at `~/.libretto/telemetry.json`. The implementation lives in [`src/cli/core/telemetry.ts`](src/cli/core/telemetry.ts).
|
|
101
|
+
|
|
102
|
+
To disable telemetry, set `LIBRETTO_TELEMETRY_DISABLED=1`, set `DO_NOT_TRACK=1`, run with `CI=1`, or edit `~/.libretto/telemetry.json` and set `"enabled": false`.
|
|
103
|
+
|
|
96
104
|
## Join the Community
|
|
97
105
|
|
|
98
106
|
Join our Discord to connect with other developers, get help, and share what you've built:
|
|
@@ -119,7 +127,7 @@ pnpm test
|
|
|
119
127
|
Source layout:
|
|
120
128
|
|
|
121
129
|
- `src/cli/` — CLI commands
|
|
122
|
-
- `src/runtime/` — browser runtime (network, recovery, downloads
|
|
130
|
+
- `src/runtime/` — browser runtime (network, recovery, downloads)
|
|
123
131
|
- `src/shared/` — shared utilities (config, LLM client, logging, state)
|
|
124
132
|
- `test/` — test files (`*.spec.ts`)
|
|
125
133
|
- `README.template.md` — source of truth for the repo and package READMEs
|
package/README.template.md
CHANGED
|
@@ -91,6 +91,14 @@ Run `npx libretto help` for the full list of commands.
|
|
|
91
91
|
|
|
92
92
|
All Libretto state lives in a `.libretto/` directory at your project root. See the [configuration docs](https://libretto.sh/docs/understand-libretto/configuration) for details on config files, sessions, and profiles.
|
|
93
93
|
|
|
94
|
+
## Telemetry
|
|
95
|
+
|
|
96
|
+
Libretto records anonymous CLI telemetry to help understand CLI usage and help us prioritize improvements. Each resolved command can send only an install id, timestamp, command event name such as `libretto run`, and an error boolean. Libretto does not send command arguments, URLs, project paths, auth state, API keys, error messages or details, or user identity.
|
|
97
|
+
|
|
98
|
+
The install id is stored in the telemetry file at `~/.libretto/telemetry.json`. The implementation lives in [`{{LIBRETTO_PATH_PREFIX}}src/cli/core/telemetry.ts`]({{LIBRETTO_PATH_PREFIX}}src/cli/core/telemetry.ts).
|
|
99
|
+
|
|
100
|
+
To disable telemetry, set `LIBRETTO_TELEMETRY_DISABLED=1`, set `DO_NOT_TRACK=1`, run with `CI=1`, or edit `~/.libretto/telemetry.json` and set `"enabled": false`.
|
|
101
|
+
|
|
94
102
|
## Join the Community
|
|
95
103
|
|
|
96
104
|
Join our Discord to connect with other developers, get help, and share what you've built:
|
|
@@ -117,7 +125,7 @@ pnpm test
|
|
|
117
125
|
Source layout:
|
|
118
126
|
|
|
119
127
|
- `{{LIBRETTO_PATH_PREFIX}}src/cli/` — CLI commands
|
|
120
|
-
- `{{LIBRETTO_PATH_PREFIX}}src/runtime/` — browser runtime (network, recovery, downloads
|
|
128
|
+
- `{{LIBRETTO_PATH_PREFIX}}src/runtime/` — browser runtime (network, recovery, downloads)
|
|
121
129
|
- `{{LIBRETTO_PATH_PREFIX}}src/shared/` — shared utilities (config, LLM client, logging, state)
|
|
122
130
|
- `{{LIBRETTO_PATH_PREFIX}}test/` — test files (`*.spec.ts`)
|
|
123
131
|
- `{{LIBRETTO_PATH_PREFIX}}README.template.md` — source of truth for the repo and package READMEs
|
|
@@ -68,7 +68,7 @@ const openInput = SimpleCLI.input({
|
|
|
68
68
|
}),
|
|
69
69
|
authProfile: SimpleCLI.option(z.string().optional(), {
|
|
70
70
|
name: "auth-profile",
|
|
71
|
-
help: "
|
|
71
|
+
help: "Named auth profile to load before opening the browser"
|
|
72
72
|
}),
|
|
73
73
|
viewport: SimpleCLI.option(z.string().optional(), {
|
|
74
74
|
help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)"
|
|
@@ -100,10 +100,15 @@ const openCommand = SimpleCLI.command({
|
|
|
100
100
|
input.readOnly,
|
|
101
101
|
input.writeAccess
|
|
102
102
|
),
|
|
103
|
-
|
|
103
|
+
authProfileName: input.authProfile,
|
|
104
104
|
experiments: ctx.experiments
|
|
105
105
|
});
|
|
106
106
|
} else {
|
|
107
|
+
if (input.authProfile) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"--auth-profile is only supported for local browser sessions. Hosted provider sessions use workflow-declared authProfile settings."
|
|
110
|
+
);
|
|
111
|
+
}
|
|
107
112
|
await runOpenWithProvider(
|
|
108
113
|
input.url,
|
|
109
114
|
providerName,
|
|
@@ -152,21 +157,23 @@ const connectCommand = SimpleCLI.command({
|
|
|
152
157
|
});
|
|
153
158
|
const saveInput = SimpleCLI.input({
|
|
154
159
|
positionals: [
|
|
155
|
-
SimpleCLI.positional("
|
|
156
|
-
help: "
|
|
160
|
+
SimpleCLI.positional("profileName", z.string(), {
|
|
161
|
+
help: "Profile name to save"
|
|
157
162
|
})
|
|
158
163
|
],
|
|
159
164
|
named: {
|
|
160
|
-
session: sessionOption()
|
|
165
|
+
session: sessionOption(),
|
|
166
|
+
sites: SimpleCLI.option(z.string(), {
|
|
167
|
+
help: "Comma-separated sites whose auth state should be saved"
|
|
168
|
+
})
|
|
161
169
|
}
|
|
162
|
-
})
|
|
163
|
-
(input) => Boolean(input.urlOrDomain),
|
|
164
|
-
`Usage: libretto save <url|domain> --session <name>`
|
|
165
|
-
);
|
|
170
|
+
});
|
|
166
171
|
const saveCommand = SimpleCLI.command({
|
|
167
172
|
description: "Save current browser session"
|
|
168
173
|
}).input(saveInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
|
|
169
|
-
await runSave(input.
|
|
174
|
+
await runSave(input.profileName, ctx.session, ctx.logger, {
|
|
175
|
+
sites: input.sites
|
|
176
|
+
});
|
|
170
177
|
});
|
|
171
178
|
const pagesInput = SimpleCLI.input({
|
|
172
179
|
positionals: [],
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { SimpleCLI } from "affordance";
|
|
2
|
+
import { orpcCall, resolveApiUrl } from "../core/auth-fetch.js";
|
|
3
|
+
const CLOUD_CREDENTIAL_ENV_PREFIX = "LIBRETTO_CLOUD_";
|
|
4
|
+
function requireApiKeyCredential() {
|
|
5
|
+
const apiKey = process.env.LIBRETTO_API_KEY?.trim();
|
|
6
|
+
if (!apiKey) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
"LIBRETTO_API_KEY is required to manage Libretto Cloud credentials. Issue one with `libretto cloud auth api-key issue --label <label>`."
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
apiUrl: resolveApiUrl(null),
|
|
13
|
+
credential: { source: "env-api-key", apiKey }
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function parseEnvCredentials(prefix) {
|
|
17
|
+
const credentials = {};
|
|
18
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
19
|
+
if (!key.startsWith(prefix) || value === void 0) continue;
|
|
20
|
+
if (value.trim().length === 0) continue;
|
|
21
|
+
const fieldName = key.slice(prefix.length).toLowerCase();
|
|
22
|
+
if (!fieldName || fieldName === "api_key") continue;
|
|
23
|
+
credentials[fieldName] = value;
|
|
24
|
+
}
|
|
25
|
+
return Object.entries(credentials).map(([name, value]) => ({ name, value })).sort((left, right) => left.name.localeCompare(right.name));
|
|
26
|
+
}
|
|
27
|
+
const pushCredentialCommand = SimpleCLI.command({
|
|
28
|
+
description: "Push LIBRETTO_CLOUD-prefixed env credentials to Libretto Cloud"
|
|
29
|
+
}).input(SimpleCLI.input({
|
|
30
|
+
positionals: [],
|
|
31
|
+
named: {}
|
|
32
|
+
})).handle(async () => {
|
|
33
|
+
const credentials = parseEnvCredentials(CLOUD_CREDENTIAL_ENV_PREFIX);
|
|
34
|
+
if (credentials.length === 0) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`No non-empty env vars found with prefix ${CLOUD_CREDENTIAL_ENV_PREFIX}.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const { apiUrl, credential } = requireApiKeyCredential();
|
|
40
|
+
let created = 0;
|
|
41
|
+
let updated = 0;
|
|
42
|
+
for (const item of credentials) {
|
|
43
|
+
const response = await orpcCall({
|
|
44
|
+
apiUrl,
|
|
45
|
+
path: "/v1/credentials/upsert",
|
|
46
|
+
input: item,
|
|
47
|
+
credential
|
|
48
|
+
});
|
|
49
|
+
if (response.overwritten) {
|
|
50
|
+
updated += 1;
|
|
51
|
+
console.log(`Updated cloud credential: ${item.name}`);
|
|
52
|
+
} else {
|
|
53
|
+
created += 1;
|
|
54
|
+
console.log(`Created cloud credential: ${item.name}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
console.log(
|
|
58
|
+
`Pushed ${credentials.length} cloud ${credentials.length === 1 ? "credential" : "credentials"} (${created} created, ${updated} updated).`
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
const cloudCredentialCommands = SimpleCLI.group({
|
|
62
|
+
description: "Manage hosted credentials",
|
|
63
|
+
routes: {
|
|
64
|
+
push: pushCredentialCommand
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
export {
|
|
68
|
+
cloudCredentialCommands,
|
|
69
|
+
pushCredentialCommand
|
|
70
|
+
};
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
orpcCall,
|
|
5
5
|
resolveApiUrl
|
|
6
6
|
} from "../core/auth-fetch.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
buildHostedDeployTarball
|
|
9
|
+
} from "../core/deploy-artifact.js";
|
|
8
10
|
import { readAuthState } from "../core/auth-storage.js";
|
|
9
11
|
import { SimpleCLI } from "affordance";
|
|
10
12
|
function generateDeploymentName() {
|
|
@@ -79,6 +81,25 @@ async function pollDeployment(apiUrl, credential, deploymentId, pollIntervalMs,
|
|
|
79
81
|
}
|
|
80
82
|
return deployment;
|
|
81
83
|
}
|
|
84
|
+
async function ensureWorkflowAuthProfiles(args) {
|
|
85
|
+
const profileNames = [
|
|
86
|
+
...new Set(
|
|
87
|
+
args.workflows.map((workflow) => workflow.authProfileName?.trim()).filter((name) => Boolean(name))
|
|
88
|
+
)
|
|
89
|
+
];
|
|
90
|
+
if (profileNames.length === 0) return;
|
|
91
|
+
console.log(
|
|
92
|
+
`Ensuring cloud auth ${profileNames.length === 1 ? "profile" : "profiles"}: ${profileNames.join(", ")}`
|
|
93
|
+
);
|
|
94
|
+
for (const name of profileNames) {
|
|
95
|
+
await orpcCall({
|
|
96
|
+
apiUrl: args.apiUrl,
|
|
97
|
+
path: "/v1/browserProfiles/ensure",
|
|
98
|
+
input: { name },
|
|
99
|
+
credential: args.credential
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
82
103
|
const deployInput = SimpleCLI.input({
|
|
83
104
|
positionals: [
|
|
84
105
|
SimpleCLI.positional("sourceDir", z.string().default("."), {
|
|
@@ -113,12 +134,13 @@ const deployCommand = SimpleCLI.command({
|
|
|
113
134
|
const { apiUrl, credential } = await requireDeployApiKey();
|
|
114
135
|
const deploymentName = generateDeploymentName();
|
|
115
136
|
console.log("Bundling hosted deployment artifact...");
|
|
116
|
-
const { entryPoint, source } = await buildHostedDeployTarball({
|
|
137
|
+
const { entryPoint, source, workflows } = await buildHostedDeployTarball({
|
|
117
138
|
additionalExternals: input.external,
|
|
118
139
|
deploymentName,
|
|
119
140
|
entryPoint: input.entryPoint,
|
|
120
141
|
sourceDir: input.sourceDir
|
|
121
142
|
});
|
|
143
|
+
await ensureWorkflowAuthProfiles({ apiUrl, credential, workflows });
|
|
122
144
|
const createPayload = {
|
|
123
145
|
source,
|
|
124
146
|
entry_point: entryPoint
|
|
@@ -5,10 +5,6 @@ import { installInstrumentation } from "../../shared/instrumentation/index.js";
|
|
|
5
5
|
import {
|
|
6
6
|
connect,
|
|
7
7
|
disconnectBrowser,
|
|
8
|
-
getProfilePath,
|
|
9
|
-
hasProfile,
|
|
10
|
-
normalizeDomain,
|
|
11
|
-
normalizeUrl,
|
|
12
8
|
runClose,
|
|
13
9
|
resolveViewport
|
|
14
10
|
} from "../core/browser.js";
|
|
@@ -30,7 +26,9 @@ import {
|
|
|
30
26
|
getProviderStartupTimeoutMs,
|
|
31
27
|
resolveProviderName
|
|
32
28
|
} from "../core/providers/index.js";
|
|
33
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
getAbsoluteIntegrationPath
|
|
31
|
+
} from "../core/workflow-runtime.js";
|
|
34
32
|
import {
|
|
35
33
|
compileExecFunction,
|
|
36
34
|
stripEmptyCatchHandlers
|
|
@@ -43,7 +41,7 @@ import {
|
|
|
43
41
|
readActionLog,
|
|
44
42
|
readNetworkLog,
|
|
45
43
|
wrapPageForActionLogging
|
|
46
|
-
} from "../core/
|
|
44
|
+
} from "../core/session-logs.js";
|
|
47
45
|
import { SimpleCLI } from "affordance";
|
|
48
46
|
import {
|
|
49
47
|
pageOption,
|
|
@@ -417,22 +415,6 @@ async function runIntegrationFromFile(args, logger) {
|
|
|
417
415
|
const absoluteIntegrationPath = getAbsoluteIntegrationPath(
|
|
418
416
|
args.integrationPath
|
|
419
417
|
);
|
|
420
|
-
if (args.authProfileDomain) {
|
|
421
|
-
const normalizedDomain = normalizeDomain(normalizeUrl(args.authProfileDomain));
|
|
422
|
-
if (!hasProfile(normalizedDomain)) {
|
|
423
|
-
const profilePath = getProfilePath(normalizedDomain);
|
|
424
|
-
throw new Error(
|
|
425
|
-
[
|
|
426
|
-
`Local auth profile not found for domain "${normalizedDomain}".`,
|
|
427
|
-
`Expected profile file: ${profilePath}`,
|
|
428
|
-
"To create it:",
|
|
429
|
-
` 1. libretto open https://${normalizedDomain} --headed --session ${args.session}`,
|
|
430
|
-
" 2. Log in manually in the browser window.",
|
|
431
|
-
` 3. libretto save ${normalizedDomain} --session ${args.session}`
|
|
432
|
-
].join("\n")
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
418
|
const runLogPath = logFileForSession(args.session);
|
|
437
419
|
const workflowOutcome = createDeferred();
|
|
438
420
|
const handlers = createWorkflowHandlers(workflowOutcome.resolve);
|
|
@@ -445,7 +427,10 @@ async function runIntegrationFromFile(args, logger) {
|
|
|
445
427
|
config: {
|
|
446
428
|
session: args.session,
|
|
447
429
|
experiments: args.experiments,
|
|
448
|
-
browser: args.providerName ? {
|
|
430
|
+
browser: args.providerName ? {
|
|
431
|
+
kind: "provider",
|
|
432
|
+
providerName: args.providerName
|
|
433
|
+
} : {
|
|
449
434
|
kind: "launch",
|
|
450
435
|
headed: !args.headless,
|
|
451
436
|
viewport: args.viewport ?? { width: 1366, height: 768 }
|
|
@@ -455,8 +440,7 @@ async function runIntegrationFromFile(args, logger) {
|
|
|
455
440
|
params: args.params,
|
|
456
441
|
visualize: args.visualize,
|
|
457
442
|
stayOpenOnSuccess: args.stayOpenOnSuccess,
|
|
458
|
-
tsconfigPath: args.tsconfigPath
|
|
459
|
-
authProfileDomain: args.authProfileDomain
|
|
443
|
+
tsconfigPath: args.tsconfigPath
|
|
460
444
|
}
|
|
461
445
|
},
|
|
462
446
|
logger,
|
|
@@ -644,10 +628,6 @@ const runInput = SimpleCLI.input({
|
|
|
644
628
|
name: "stay-open-on-success",
|
|
645
629
|
help: "Keep the browser session open after the workflow completes successfully"
|
|
646
630
|
}),
|
|
647
|
-
authProfile: SimpleCLI.option(z.string().optional(), {
|
|
648
|
-
name: "auth-profile",
|
|
649
|
-
help: "Domain for local auth profile (e.g. apps.example.com)"
|
|
650
|
-
}),
|
|
651
631
|
viewport: SimpleCLI.option(z.string().optional(), {
|
|
652
632
|
help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)"
|
|
653
633
|
}),
|
|
@@ -718,7 +698,6 @@ const runCommand = SimpleCLI.command({
|
|
|
718
698
|
tsconfigPath: input.tsconfig,
|
|
719
699
|
headless: daemonProviderName ? true : headlessMode ?? false,
|
|
720
700
|
visualize,
|
|
721
|
-
authProfileDomain: input.authProfile,
|
|
722
701
|
viewport,
|
|
723
702
|
accessMode: input.readOnly ? "read-only" : input.writeAccess ? "write-access" : readLibrettoConfig().sessionMode ?? "write-access",
|
|
724
703
|
providerName: daemonProviderName,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SimpleCLI } from "affordance";
|
|
3
|
+
import { createLoggerForSession } from "../core/context.js";
|
|
4
|
+
import { runFetchChromeProfile } from "../core/browser.js";
|
|
5
|
+
import { promptConfirm } from "../core/prompt.js";
|
|
6
|
+
const importChromeProfilesCommand = SimpleCLI.command({
|
|
7
|
+
description: "Fetch scoped auth state from a Chrome CDP session into a local profile"
|
|
8
|
+
}).input(SimpleCLI.input({
|
|
9
|
+
positionals: [
|
|
10
|
+
SimpleCLI.positional("profileName", z.string(), {
|
|
11
|
+
help: "Profile name to save"
|
|
12
|
+
})
|
|
13
|
+
],
|
|
14
|
+
named: {
|
|
15
|
+
cdpUrl: SimpleCLI.option(z.string(), {
|
|
16
|
+
name: "cdp-url",
|
|
17
|
+
help: "Chrome DevTools Protocol endpoint for the Chrome instance"
|
|
18
|
+
}),
|
|
19
|
+
sites: SimpleCLI.option(z.string(), {
|
|
20
|
+
help: "Comma-separated sites whose auth state should be imported"
|
|
21
|
+
}),
|
|
22
|
+
yes: SimpleCLI.flag({
|
|
23
|
+
help: "Skip confirmation before attaching to and disconnecting from Chrome"
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
})).handle(async ({ input }) => {
|
|
27
|
+
const logger = createLoggerForSession(`profile-fetch-${Date.now()}`);
|
|
28
|
+
try {
|
|
29
|
+
if (!input.yes) {
|
|
30
|
+
const confirmed = await promptConfirm(
|
|
31
|
+
"Importing from an existing Chrome CDP session may cause that Chrome window to close or relaunch when Libretto disconnects. Continue?"
|
|
32
|
+
);
|
|
33
|
+
if (!confirmed) {
|
|
34
|
+
throw new Error("Aborted Chrome profile import.");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
await runFetchChromeProfile(input.profileName, input.cdpUrl, logger, {
|
|
38
|
+
sites: input.sites
|
|
39
|
+
});
|
|
40
|
+
} finally {
|
|
41
|
+
await logger.close();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
export {
|
|
45
|
+
importChromeProfilesCommand
|
|
46
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SimpleCLI } from "affordance";
|
|
3
|
+
import { orpcCall, resolveApiUrl } from "../core/auth-fetch.js";
|
|
4
|
+
import { normalizeProfileName } from "../core/profiles.js";
|
|
5
|
+
function requireApiKeyCredential() {
|
|
6
|
+
const apiKey = process.env.LIBRETTO_API_KEY?.trim();
|
|
7
|
+
if (!apiKey) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"LIBRETTO_API_KEY is required to manage Libretto Cloud profiles. Issue one with `libretto cloud auth api-key issue --label <label>`."
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
apiUrl: resolveApiUrl(null),
|
|
14
|
+
credential: { source: "env-api-key", apiKey }
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const listProfilesCommand = SimpleCLI.command({
|
|
18
|
+
description: "List Libretto Cloud auth profiles"
|
|
19
|
+
}).input(SimpleCLI.input({ positionals: [], named: {} })).handle(async () => {
|
|
20
|
+
const { apiUrl, credential } = requireApiKeyCredential();
|
|
21
|
+
const response = await orpcCall({
|
|
22
|
+
apiUrl,
|
|
23
|
+
path: "/v1/browserProfiles/list",
|
|
24
|
+
input: {},
|
|
25
|
+
credential
|
|
26
|
+
});
|
|
27
|
+
if (response.profiles.length === 0) {
|
|
28
|
+
console.log("No cloud profiles found.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
for (const profile of response.profiles) {
|
|
32
|
+
const providers = profile.providers.length ? ` (${profile.providers.join(", ")})` : "";
|
|
33
|
+
console.log(`${profile.name}${providers}`);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const deleteProfileCommand = SimpleCLI.command({
|
|
37
|
+
description: "Delete a Libretto Cloud auth profile"
|
|
38
|
+
}).input(SimpleCLI.input({
|
|
39
|
+
positionals: [
|
|
40
|
+
SimpleCLI.positional("profileName", z.string(), {
|
|
41
|
+
help: "Cloud profile name to delete"
|
|
42
|
+
})
|
|
43
|
+
],
|
|
44
|
+
named: {}
|
|
45
|
+
})).handle(async ({ input }) => {
|
|
46
|
+
const profileName = normalizeProfileName(input.profileName);
|
|
47
|
+
const { apiUrl, credential } = requireApiKeyCredential();
|
|
48
|
+
const response = await orpcCall({
|
|
49
|
+
apiUrl,
|
|
50
|
+
path: "/v1/browserProfiles/delete",
|
|
51
|
+
input: { name: profileName },
|
|
52
|
+
credential
|
|
53
|
+
});
|
|
54
|
+
if (!response.success || response.deleted_count === 0) {
|
|
55
|
+
console.log(`No cloud profile found for ${profileName}.`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
console.log(`Deleted cloud profile: ${profileName}`);
|
|
59
|
+
});
|
|
60
|
+
const profileCommands = SimpleCLI.group({
|
|
61
|
+
description: "Manage hosted browser auth profiles",
|
|
62
|
+
routes: {
|
|
63
|
+
list: listProfilesCommand,
|
|
64
|
+
delete: deleteProfileCommand
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
export {
|
|
68
|
+
deleteProfileCommand,
|
|
69
|
+
listProfilesCommand,
|
|
70
|
+
profileCommands
|
|
71
|
+
};
|
|
@@ -19,9 +19,7 @@ function integerOption(help) {
|
|
|
19
19
|
return SimpleCLI.option(z.coerce.number().int().optional(), { help });
|
|
20
20
|
}
|
|
21
21
|
function withExperiments() {
|
|
22
|
-
return async ({
|
|
23
|
-
ctx
|
|
24
|
-
}) => ({
|
|
22
|
+
return async ({ ctx }) => ({
|
|
25
23
|
...ctx,
|
|
26
24
|
experiments: resolveExperiments()
|
|
27
25
|
});
|