libretto 0.5.3-experimental.4 → 0.5.3-experimental.5
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 +37 -109
- package/dist/cli/cli.js +97 -22
- package/dist/cli/commands/browser.js +59 -86
- package/dist/cli/commands/execution.js +96 -228
- package/dist/cli/commands/init.js +29 -34
- package/dist/cli/commands/logs.js +5 -4
- package/dist/cli/commands/shared.js +29 -30
- package/dist/cli/commands/snapshot.js +39 -26
- package/dist/cli/core/ai-config.js +4 -21
- package/dist/cli/core/api-snapshot-analyzer.js +5 -15
- package/dist/cli/core/browser.js +37 -207
- package/dist/cli/core/context.js +1 -4
- package/dist/cli/core/session-telemetry.js +174 -434
- package/dist/cli/core/session.js +8 -21
- package/dist/cli/core/snapshot-analyzer.js +31 -14
- package/dist/cli/core/snapshot-api-config.js +6 -2
- package/dist/cli/core/telemetry.js +4 -20
- package/dist/cli/framework/simple-cli.js +25 -51
- package/dist/cli/router.js +21 -16
- package/dist/cli/workers/run-integration-runtime.js +44 -26
- package/dist/cli/workers/run-integration-worker-protocol.js +2 -4
- package/dist/cli/workers/run-integration-worker.js +4 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +10 -13
- package/dist/runtime/download/download.js +1 -5
- package/dist/runtime/extract/extract.js +2 -11
- package/dist/runtime/network/network.js +1 -8
- package/dist/runtime/recovery/agent.js +2 -6
- package/dist/runtime/recovery/errors.js +1 -3
- package/dist/runtime/recovery/recovery.js +1 -3
- package/dist/shared/condense-dom/condense-dom.js +69 -17
- package/dist/shared/config/config.d.ts +9 -1
- package/dist/shared/config/config.js +18 -0
- package/dist/shared/config/index.d.ts +1 -2
- package/dist/shared/config/index.js +10 -0
- package/dist/shared/debug/pause.js +3 -9
- package/dist/shared/instrumentation/instrument.js +5 -101
- package/dist/shared/llm/ai-sdk-adapter.js +1 -3
- package/dist/shared/llm/client.js +1 -3
- package/dist/shared/logger/index.js +1 -4
- package/dist/shared/run/api.js +1 -3
- package/dist/shared/run/browser.js +3 -47
- package/dist/shared/state/session-state.d.ts +1 -2
- package/dist/shared/state/session-state.js +2 -5
- package/dist/shared/visualization/ghost-cursor.js +14 -36
- package/dist/shared/visualization/highlight.js +6 -9
- package/dist/shared/workflow/workflow.d.ts +5 -15
- package/dist/shared/workflow/workflow.js +5 -50
- package/package.json +5 -11
- package/scripts/check-skills-sync.mjs +2 -4
- package/scripts/postinstall.mjs +3 -4
- package/scripts/prepare-release.sh +97 -0
- package/scripts/skills-libretto.mjs +3 -5
- package/scripts/summarize-evals.mjs +10 -32
- package/skills/libretto/SKILL.md +62 -132
- package/skills/libretto/references/auth-profiles.md +2 -1
- package/skills/libretto/references/pages-and-page-targeting.md +1 -1
- package/skills/libretto/references/reverse-engineering-network-requests.md +75 -0
- package/skills/libretto/references/user-action-log.md +31 -0
- package/src/cli/cli.ts +110 -23
- package/src/cli/commands/browser.ts +70 -94
- package/src/cli/commands/execution.ts +111 -263
- package/src/cli/commands/init.ts +33 -37
- package/src/cli/commands/logs.ts +7 -7
- package/src/cli/commands/shared.ts +37 -36
- package/src/cli/commands/snapshot.ts +59 -44
- package/src/cli/core/ai-config.ts +4 -24
- package/src/cli/core/api-snapshot-analyzer.ts +6 -17
- package/src/cli/core/browser.ts +49 -260
- package/src/cli/core/context.ts +2 -7
- package/src/cli/core/session-telemetry.ts +197 -449
- package/src/cli/core/session.ts +7 -21
- package/src/cli/core/snapshot-analyzer.ts +46 -26
- package/src/cli/core/snapshot-api-config.ts +175 -170
- package/src/cli/core/telemetry.ts +4 -39
- package/src/cli/framework/simple-cli.ts +77 -153
- package/src/cli/router.ts +21 -15
- package/src/cli/workers/run-integration-runtime.ts +56 -36
- package/src/cli/workers/run-integration-worker-protocol.ts +1 -3
- package/src/cli/workers/run-integration-worker.ts +4 -1
- package/src/index.ts +66 -77
- package/src/runtime/download/download.ts +58 -62
- package/src/runtime/download/index.ts +5 -5
- package/src/runtime/extract/extract.ts +61 -71
- package/src/runtime/network/index.ts +3 -3
- package/src/runtime/network/network.ts +93 -99
- package/src/runtime/recovery/agent.ts +212 -217
- package/src/runtime/recovery/errors.ts +104 -107
- package/src/runtime/recovery/index.ts +3 -3
- package/src/runtime/recovery/recovery.ts +35 -38
- package/src/shared/condense-dom/condense-dom.ts +82 -27
- package/src/shared/config/config.ts +19 -0
- package/src/shared/config/index.ts +5 -0
- package/src/shared/debug/pause.ts +51 -57
- package/src/shared/instrumentation/errors.ts +62 -64
- package/src/shared/instrumentation/index.ts +5 -5
- package/src/shared/instrumentation/instrument.ts +209 -339
- package/src/shared/llm/ai-sdk-adapter.ts +55 -58
- package/src/shared/llm/client.ts +174 -181
- package/src/shared/llm/types.ts +39 -39
- package/src/shared/logger/index.ts +4 -11
- package/src/shared/logger/logger.ts +306 -312
- package/src/shared/logger/sinks.ts +114 -118
- package/src/shared/paths/paths.ts +49 -50
- package/src/shared/paths/repo-root.ts +17 -17
- package/src/shared/run/api.ts +1 -5
- package/src/shared/run/browser.ts +3 -65
- package/src/shared/state/index.ts +9 -9
- package/src/shared/state/session-state.ts +43 -46
- package/src/shared/visualization/ghost-cursor.ts +149 -180
- package/src/shared/visualization/highlight.ts +86 -89
- package/src/shared/visualization/index.ts +13 -13
- package/src/shared/workflow/workflow.ts +27 -107
- package/dist/cli/commands/deploy.js +0 -149
- package/dist/cli/core/deploy-artifact.js +0 -608
- package/dist/shared/dom-semantics.d.ts +0 -8
- package/dist/shared/dom-semantics.js +0 -69
- package/scripts/generate-changelog.ts +0 -132
- package/scripts/sync-skills.mjs +0 -12
- package/skills/libretto/references/action-logs.md +0 -101
- package/skills/libretto/references/code-generation-rules.md +0 -212
- package/skills/libretto/references/configuration-file-reference.md +0 -53
- package/skills/libretto/references/site-security-review.md +0 -143
- package/src/cli/commands/deploy.ts +0 -200
- package/src/cli/core/deploy-artifact.ts +0 -823
- package/src/shared/dom-semantics.ts +0 -68
package/README.md
CHANGED
|
@@ -1,135 +1,70 @@
|
|
|
1
1
|
# Libretto
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/saffron-health/libretto/discussions)
|
|
3
|
+
Libretto gives your coding agent superpowers for building, debugging, and maintaining browser RPA integrations.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Inspect live pages with minimal context overhead
|
|
10
|
-
- Capture network traffic to reverse-engineer site APIs
|
|
11
|
-
- Record user actions and replay them as automation scripts
|
|
12
|
-
- Debug broken workflows interactively against the real site
|
|
13
|
-
|
|
14
|
-
We at [Saffron Health](https://saffron.health) built Libretto to help us maintain our browser integrations to common healthcare software. We're open-sourcing it so other teams have an easier time doing the same thing.
|
|
15
|
-
|
|
16
|
-
https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
|
|
5
|
+
It is designed for engineering teams that automate workflows in web apps and want to move from brittle browser-only scripts to faster, more reliable network-first integrations.
|
|
17
6
|
|
|
18
7
|
## Installation
|
|
19
8
|
|
|
20
9
|
```bash
|
|
21
|
-
npm install libretto
|
|
10
|
+
npm install --save-dev libretto
|
|
11
|
+
```
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
npx libretto init
|
|
13
|
+
Chromium is downloaded automatically via a `postinstall` script. If postinstall scripts are disabled (e.g. `--ignore-scripts`, common in monorepos), run init manually:
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
npx libretto
|
|
15
|
+
```bash
|
|
16
|
+
npx libretto init
|
|
28
17
|
```
|
|
29
18
|
|
|
30
|
-
|
|
19
|
+
This installs the Chromium browser binary and optionally configures an AI subagent (Gemini, Claude, or Codex) that can analyze page snapshots without consuming the coding agent's context window.
|
|
31
20
|
|
|
32
|
-
|
|
21
|
+
## Usage
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
Libretto is usually used through prompts with the Libretto skill.
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
### One-shot script generation
|
|
37
26
|
|
|
38
|
-
|
|
27
|
+
```text
|
|
28
|
+
Use the Libretto skill. Go on LinkedIn and scrape the first 10 posts for content, who posted it, the number of reactions, the first 25 comments, and the first 25 reposts.
|
|
29
|
+
```
|
|
39
30
|
|
|
40
31
|
### Interactive script building
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
```text
|
|
34
|
+
Use the Libretto skill. Let's interactively build a script to scrape scheduling info from the eClinicalWorks EHR.
|
|
35
|
+
```
|
|
45
36
|
|
|
46
37
|
### Convert browser automation to network requests
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Libretto can read network requests from the browser, which it can use to reverse engineer the API and create a script that directly calls those requests. Directly making API calls is faster, and more reliable, than UI automation. You can also ask Libretto to conduct a security analysis which analyzes the requests for common security cookies, so you can understand whether a network request approach will be safe.
|
|
51
|
-
|
|
52
|
-
### Fix broken integrations
|
|
53
|
-
|
|
54
|
-
> We have a browser script at ./integration.ts that is supposed to go to Availity and perform an eligibility check for a patient. But I'm getting a broken selector error when I run it. Fix it. Use the Libretto skill.
|
|
55
|
-
|
|
56
|
-
Agents can use Libretto to reproduce the failure, pause the workflow at any point, inspect the live page, and fix issues, all autonomously.
|
|
57
|
-
|
|
58
|
-
### CLI usage
|
|
59
|
-
|
|
60
|
-
You can also use Libretto directly from the command line. All commands accept `--session <name>` to target a specific session.
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npx libretto init # interactive; run yourself, not through an agent
|
|
64
|
-
npx libretto open <url> # launch browser and open a URL (headed by default)
|
|
65
|
-
npx libretto snapshot --objective "..." --context "..." # capture PNG + HTML and analyze with an LLM
|
|
66
|
-
npx libretto exec "<code>" # execute Playwright TypeScript against the open page (single quoted argument)
|
|
67
|
-
echo "<code>" | npx libretto exec - # intentionally read Playwright TypeScript from stdin
|
|
68
|
-
npx libretto run <file> <workflowName> # run an exported workflow from a file
|
|
69
|
-
npx libretto resume # resume a paused workflow
|
|
70
|
-
npx libretto network # view captured network requests
|
|
71
|
-
npx libretto actions # view captured user/agent actions
|
|
72
|
-
npx libretto pages # list open pages in the session
|
|
73
|
-
npx libretto save <domain> # save browser session (cookies, localStorage) for reuse
|
|
74
|
-
npx libretto close # close the browser
|
|
75
|
-
npx libretto ai configure <provider> # configure snapshot analysis model
|
|
39
|
+
```text
|
|
40
|
+
We have a browser script at ./integration.ts that automates going to Hacker News and getting the first 10 posts. Convert it to direct network scripts instead. Use the Libretto skill.
|
|
76
41
|
```
|
|
77
42
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
All Libretto state lives in a `.libretto/` directory at your project root. Configuration is stored in `.libretto/config.json`.
|
|
81
|
-
|
|
82
|
-
### Config file
|
|
83
|
-
|
|
84
|
-
`.libretto/config.json` controls snapshot analysis and viewport settings:
|
|
43
|
+
### Fix broken integrations
|
|
85
44
|
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
"version": 1,
|
|
89
|
-
"ai": {
|
|
90
|
-
"model": "openai/gpt-5.4",
|
|
91
|
-
"updatedAt": "2026-01-01T00:00:00.000Z"
|
|
92
|
-
},
|
|
93
|
-
"viewport": { "width": 1280, "height": 800 }
|
|
94
|
-
}
|
|
45
|
+
```text
|
|
46
|
+
We have a browser script at ./integration.ts that is supposed to go to Availity and perform an eligibility check for a patient. But I'm getting a broken selector error when I run it. Fix it. Use the Libretto skill.
|
|
95
47
|
```
|
|
96
48
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
The easiest way to set the model is through the CLI:
|
|
49
|
+
You can also run workflows directly from the CLI:
|
|
100
50
|
|
|
101
51
|
```bash
|
|
102
|
-
npx libretto
|
|
52
|
+
npx libretto help
|
|
53
|
+
npx libretto run ./integration.ts main
|
|
103
54
|
```
|
|
104
55
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
The `viewport` field sets the default browser viewport size. Both fields are optional.
|
|
56
|
+
## The `.libretto/` directory
|
|
108
57
|
|
|
109
|
-
|
|
58
|
+
Libretto stores local runtime state in a `.libretto/` directory at your project root. Sensitive directories (`sessions/` and `profiles/`) are automatically git-ignored via `.libretto/.gitignore`.
|
|
110
59
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
- `state.json` —
|
|
114
|
-
- `logs.jsonl` —
|
|
115
|
-
- `network.jsonl` —
|
|
116
|
-
- `actions.jsonl` —
|
|
117
|
-
- `snapshots/` —
|
|
118
|
-
|
|
119
|
-
### Profiles
|
|
120
|
-
|
|
121
|
-
Profiles save browser sessions (cookies, localStorage) so you can reuse authenticated state across runs. They are stored in `.libretto/profiles/<domain>.json`, created via `npx libretto save <domain>`. Profiles are machine-local and git-ignored.
|
|
122
|
-
|
|
123
|
-
## Community
|
|
124
|
-
|
|
125
|
-
Have a question, idea, or want to share what you've built? Join the conversation on [GitHub Discussions](https://github.com/saffron-health/libretto/discussions).
|
|
126
|
-
|
|
127
|
-
- **[Q&A](https://github.com/saffron-health/libretto/discussions/categories/q-a)** — Ask questions and get help
|
|
128
|
-
- **[Ideas](https://github.com/saffron-health/libretto/discussions/categories/ideas)** — Suggest new features or improvements
|
|
129
|
-
- **[Show and tell](https://github.com/saffron-health/libretto/discussions/categories/show-and-tell)** — Share your workflows and automations
|
|
130
|
-
- **[General](https://github.com/saffron-health/libretto/discussions/categories/general)** — Chat about anything Libretto-related
|
|
131
|
-
|
|
132
|
-
Found a bug? Please [open an issue](https://github.com/saffron-health/libretto/issues/new).
|
|
60
|
+
- **`profiles/<domain>.json`** — Saved browser sessions (cookies, localStorage) for authenticated sites. Created via `npx libretto save <domain>`. Machine-local and never committed.
|
|
61
|
+
- **`sessions/<name>/`** — Per-session runtime state:
|
|
62
|
+
- `state.json` — Session metadata (debug port, PID, status)
|
|
63
|
+
- `logs.jsonl` — Structured session logs
|
|
64
|
+
- `network.jsonl` — Captured network requests (URLs, methods, headers, response status)
|
|
65
|
+
- `actions.jsonl` — Recorded user actions (clicks, fills, navigations)
|
|
66
|
+
- `snapshots/` — Screenshot PNGs and HTML snapshots captured via `npx libretto snapshot`
|
|
67
|
+
- **`ai.json`** — AI runtime configuration set via `npx libretto ai configure`.
|
|
133
68
|
|
|
134
69
|
## Authors
|
|
135
70
|
|
|
@@ -141,17 +76,10 @@ For local development in this repository:
|
|
|
141
76
|
|
|
142
77
|
```bash
|
|
143
78
|
pnpm i
|
|
79
|
+
pnpm check:skills
|
|
144
80
|
pnpm build
|
|
145
81
|
pnpm type-check
|
|
146
82
|
pnpm test
|
|
147
83
|
```
|
|
148
84
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
- `src/cli/` — CLI commands
|
|
152
|
-
- `src/runtime/` — browser runtime (network, recovery, downloads, extraction)
|
|
153
|
-
- `src/shared/` — shared utilities (config, LLM client, logging, state)
|
|
154
|
-
- `test/` — test files (`*.spec.ts`)
|
|
155
|
-
- `skills/libretto/` — source of truth for the Libretto skill; mirrors are synced on `pnpm i`
|
|
156
|
-
|
|
157
|
-
To check that skill mirrors are in sync without fixing them, run `pnpm check:skills`. To release, run `pnpm prepare-release`.
|
|
85
|
+
If the mirrored Libretto skill copies drift, run `pnpm i`. In this repository, `postinstall` resyncs them.
|
package/dist/cli/cli.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
closeLogger,
|
|
3
|
+
createLoggerForSession,
|
|
4
|
+
ensureLibrettoSetup
|
|
5
|
+
} from "./core/context.js";
|
|
6
|
+
import {
|
|
7
|
+
SESSION_DEFAULT,
|
|
8
|
+
validateSessionName
|
|
9
|
+
} from "./core/session.js";
|
|
2
10
|
import { createCLIApp } from "./router.js";
|
|
3
11
|
function renderUsage(app) {
|
|
4
12
|
return `${app.renderHelp()}
|
|
5
13
|
|
|
6
14
|
Options:
|
|
7
|
-
--session <name> Use a named session (
|
|
15
|
+
--session <name> Use a named session (default: "default")
|
|
16
|
+
Built-in sessions: default, dev-server, browser-agent
|
|
8
17
|
|
|
9
18
|
Examples:
|
|
10
19
|
libretto open https://linkedin.com
|
|
@@ -22,7 +31,7 @@ Examples:
|
|
|
22
31
|
libretto ai configure openai/gpt-4o
|
|
23
32
|
libretto snapshot
|
|
24
33
|
libretto snapshot --objective "Find the submit button" --context "Submitting a referral form, already filled in patient details"
|
|
25
|
-
libretto resume --session
|
|
34
|
+
libretto resume --session default
|
|
26
35
|
libretto close
|
|
27
36
|
libretto close --all
|
|
28
37
|
libretto close --all --force
|
|
@@ -47,6 +56,67 @@ Sessions:
|
|
|
47
56
|
Each session runs an isolated browser instance on a dynamic port.
|
|
48
57
|
`;
|
|
49
58
|
}
|
|
59
|
+
function readSessionArgBeforePassthrough(rawArgs) {
|
|
60
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
61
|
+
const token = rawArgs[index];
|
|
62
|
+
if (token === "--") return void 0;
|
|
63
|
+
if (token === "--session") {
|
|
64
|
+
const value2 = rawArgs[index + 1];
|
|
65
|
+
if (!value2 || value2 === "--" || value2.startsWith("--")) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return value2;
|
|
69
|
+
}
|
|
70
|
+
if (!token.startsWith("--session=")) continue;
|
|
71
|
+
const value = token.slice("--session=".length);
|
|
72
|
+
if (value.length === 0 || value === "--" || value.startsWith("--")) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
function parseSessionForLog(rawArgs) {
|
|
80
|
+
const value = readSessionArgBeforePassthrough(rawArgs);
|
|
81
|
+
if (value === void 0 || value === null) {
|
|
82
|
+
return SESSION_DEFAULT;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
validateSessionName(value);
|
|
86
|
+
return value;
|
|
87
|
+
} catch {
|
|
88
|
+
return SESSION_DEFAULT;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function validateLegacySessionArg(rawArgs) {
|
|
92
|
+
const value = readSessionArgBeforePassthrough(rawArgs);
|
|
93
|
+
if (value === void 0) return;
|
|
94
|
+
if (value === null) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Usage: libretto <command> [--session <name>]
|
|
97
|
+
Missing or invalid --session value.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
validateSessionName(value);
|
|
101
|
+
}
|
|
102
|
+
function initializeLogger(rawArgs) {
|
|
103
|
+
const sessionForLog = parseSessionForLog(rawArgs);
|
|
104
|
+
const logger = createLoggerForSession(sessionForLog);
|
|
105
|
+
logger.info("cli-start", {
|
|
106
|
+
args: rawArgs,
|
|
107
|
+
cwd: process.cwd(),
|
|
108
|
+
session: sessionForLog
|
|
109
|
+
});
|
|
110
|
+
return logger;
|
|
111
|
+
}
|
|
112
|
+
async function withCliLogger(rawArgs, run) {
|
|
113
|
+
const logger = initializeLogger(rawArgs);
|
|
114
|
+
try {
|
|
115
|
+
return await run(logger);
|
|
116
|
+
} finally {
|
|
117
|
+
await closeLogger(logger);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
50
120
|
function isRootHelpRequest(rawArgs) {
|
|
51
121
|
if (rawArgs.length === 0) return true;
|
|
52
122
|
if (rawArgs[0] === "--help" || rawArgs[0] === "-h") return true;
|
|
@@ -56,27 +126,32 @@ async function runLibrettoCLI() {
|
|
|
56
126
|
const rawArgs = process.argv.slice(2);
|
|
57
127
|
let exitCode = 0;
|
|
58
128
|
ensureLibrettoSetup();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
129
|
+
await withCliLogger(rawArgs, async (logger) => {
|
|
130
|
+
const app = createCLIApp(logger);
|
|
131
|
+
try {
|
|
132
|
+
validateLegacySessionArg(rawArgs);
|
|
133
|
+
if (isRootHelpRequest(rawArgs)) {
|
|
134
|
+
console.log(renderUsage(app));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
logger.info("cli-command", { args: rawArgs });
|
|
138
|
+
const result = await app.run(rawArgs);
|
|
139
|
+
if (typeof result === "string") {
|
|
140
|
+
console.log(result);
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
logger.error("cli-error", { error: err, args: rawArgs });
|
|
144
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
145
|
+
if (message.startsWith("Unknown command: ")) {
|
|
146
|
+
console.error(`${message}
|
|
73
147
|
`);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
148
|
+
console.log(renderUsage(app));
|
|
149
|
+
} else {
|
|
150
|
+
console.error(message);
|
|
151
|
+
}
|
|
152
|
+
exitCode = 1;
|
|
77
153
|
}
|
|
78
|
-
|
|
79
|
-
}
|
|
154
|
+
});
|
|
80
155
|
process.exit(exitCode);
|
|
81
156
|
}
|
|
82
157
|
export {
|
|
@@ -2,21 +2,17 @@ import { z } from "zod";
|
|
|
2
2
|
import {
|
|
3
3
|
runClose as runCloseWithLogger,
|
|
4
4
|
runCloseAll as runCloseAllWithLogger,
|
|
5
|
-
runConnect as runConnectWithLogger,
|
|
6
5
|
runOpen,
|
|
7
6
|
runPages,
|
|
8
7
|
runSave
|
|
9
8
|
} from "../core/browser.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
assertSessionAvailableForStart,
|
|
13
|
-
validateSessionName
|
|
14
|
-
} from "../core/session.js";
|
|
9
|
+
import { withSessionLogger } from "../core/context.js";
|
|
10
|
+
import { assertSessionAvailableForStart } from "../core/session.js";
|
|
15
11
|
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
16
12
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
loadSessionStateMiddleware,
|
|
14
|
+
resolveSessionMiddleware,
|
|
15
|
+
sessionOption
|
|
20
16
|
} from "./shared.js";
|
|
21
17
|
function parseViewportArg(viewportArg) {
|
|
22
18
|
if (!viewportArg) return void 0;
|
|
@@ -56,32 +52,16 @@ const openInput = SimpleCLI.input({
|
|
|
56
52
|
(input) => !(input.headed && input.headless),
|
|
57
53
|
"Cannot pass both --headed and --headless."
|
|
58
54
|
);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
SimpleCLI.positional("cdpUrl", z.string().optional(), {
|
|
70
|
-
help: "CDP endpoint URL (e.g. http://127.0.0.1:9222)"
|
|
71
|
-
})
|
|
72
|
-
],
|
|
73
|
-
named: {
|
|
74
|
-
session: sessionOption()
|
|
75
|
-
}
|
|
76
|
-
}).refine(
|
|
77
|
-
(input) => Boolean(input.cdpUrl),
|
|
78
|
-
`Usage: libretto connect <cdp-url> --session <name>`
|
|
79
|
-
);
|
|
80
|
-
const connectCommand = SimpleCLI.command({
|
|
81
|
-
description: "Connect to an existing Chrome DevTools Protocol (CDP) endpoint"
|
|
82
|
-
}).input(connectInput).use(withAutoSession()).handle(async ({ input, ctx }) => {
|
|
83
|
-
await runConnectWithLogger(input.cdpUrl, ctx.session, ctx.logger);
|
|
84
|
-
});
|
|
55
|
+
function createOpenCommand(logger) {
|
|
56
|
+
return SimpleCLI.command({
|
|
57
|
+
description: "Launch browser and open URL (headed by default)"
|
|
58
|
+
}).input(openInput).use(resolveSessionMiddleware).handle(async ({ input, ctx }) => {
|
|
59
|
+
assertSessionAvailableForStart(ctx.session, logger);
|
|
60
|
+
const headed = input.headed || !input.headless;
|
|
61
|
+
const viewport = parseViewportArg(input.viewport);
|
|
62
|
+
await runOpen(input.url, headed, ctx.session, logger, { viewport });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
85
65
|
const saveInput = SimpleCLI.input({
|
|
86
66
|
positionals: [
|
|
87
67
|
SimpleCLI.positional("urlOrDomain", z.string().optional(), {
|
|
@@ -93,79 +73,72 @@ const saveInput = SimpleCLI.input({
|
|
|
93
73
|
}
|
|
94
74
|
}).refine(
|
|
95
75
|
(input) => Boolean(input.urlOrDomain),
|
|
96
|
-
`Usage: libretto save <url|domain> --session <name
|
|
76
|
+
`Usage: libretto save <url|domain> [--session <name>]`
|
|
97
77
|
);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
78
|
+
function createSaveCommand(logger) {
|
|
79
|
+
return SimpleCLI.command({
|
|
80
|
+
description: "Save current browser session"
|
|
81
|
+
}).input(saveInput).use(resolveSessionMiddleware).use(loadSessionStateMiddleware).handle(async ({ input, ctx }) => {
|
|
82
|
+
await runSave(input.urlOrDomain, ctx.session, logger);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
103
85
|
const pagesInput = SimpleCLI.input({
|
|
104
86
|
positionals: [],
|
|
105
87
|
named: {
|
|
106
88
|
session: sessionOption()
|
|
107
89
|
}
|
|
108
90
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
91
|
+
function createPagesCommand(logger) {
|
|
92
|
+
return SimpleCLI.command({
|
|
93
|
+
description: "List open pages in the session"
|
|
94
|
+
}).input(pagesInput).use(resolveSessionMiddleware).use(loadSessionStateMiddleware).handle(async ({ ctx }) => {
|
|
95
|
+
await runPages(ctx.session, logger);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
114
98
|
const closeInput = SimpleCLI.input({
|
|
115
99
|
positionals: [],
|
|
116
100
|
named: {
|
|
117
101
|
session: sessionOption(),
|
|
118
|
-
all: SimpleCLI.flag({
|
|
119
|
-
|
|
120
|
-
}),
|
|
121
|
-
force: SimpleCLI.flag({
|
|
122
|
-
help: "Force kill sessions that ignore SIGTERM (requires --all)"
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
}).refine(
|
|
126
|
-
(input) => input.all || input.session,
|
|
127
|
-
`Usage: libretto close --session <name>
|
|
128
|
-
Usage: libretto close --all [--force]`
|
|
129
|
-
);
|
|
130
|
-
const closeCommand = SimpleCLI.command({
|
|
131
|
-
description: "Close the browser"
|
|
132
|
-
}).input(closeInput).handle(async ({ input }) => {
|
|
133
|
-
if (input.force && !input.all) {
|
|
134
|
-
throw new Error(`Usage: libretto close --all [--force]`);
|
|
102
|
+
all: SimpleCLI.flag({ help: "Close all tracked sessions in this workspace" }),
|
|
103
|
+
force: SimpleCLI.flag({ help: "Force kill sessions that ignore SIGTERM (requires --all)" })
|
|
135
104
|
}
|
|
136
|
-
if (input.all) {
|
|
137
|
-
const logger2 = createLoggerForSession("cli");
|
|
138
|
-
await runCloseAllWithLogger(logger2, { force: input.force });
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
validateSessionName(input.session);
|
|
142
|
-
const logger = createLoggerForSession(input.session);
|
|
143
|
-
await runCloseWithLogger(input.session, logger);
|
|
144
105
|
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
106
|
+
function createCloseCommand(logger) {
|
|
107
|
+
return SimpleCLI.command({
|
|
108
|
+
description: "Close the browser"
|
|
109
|
+
}).input(closeInput).use(resolveSessionMiddleware).handle(async ({ input, ctx }) => {
|
|
110
|
+
if (input.force && !input.all) {
|
|
111
|
+
throw new Error(`Usage: libretto close --all [--force]`);
|
|
112
|
+
}
|
|
113
|
+
if (input.all) {
|
|
114
|
+
await runCloseAllWithLogger(logger, { force: input.force });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
await runCloseWithLogger(ctx.session, logger);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function createBrowserCommands(logger) {
|
|
121
|
+
return {
|
|
122
|
+
open: createOpenCommand(logger),
|
|
123
|
+
save: createSaveCommand(logger),
|
|
124
|
+
pages: createPagesCommand(logger),
|
|
125
|
+
close: createCloseCommand(logger)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
152
128
|
async function runClose(session) {
|
|
153
129
|
await withSessionLogger(session, async (logger) => {
|
|
154
130
|
await runCloseWithLogger(session, logger);
|
|
155
131
|
});
|
|
156
132
|
}
|
|
157
133
|
export {
|
|
158
|
-
browserCommands,
|
|
159
|
-
closeCommand,
|
|
160
134
|
closeInput,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
135
|
+
createBrowserCommands,
|
|
136
|
+
createCloseCommand,
|
|
137
|
+
createOpenCommand,
|
|
138
|
+
createPagesCommand,
|
|
139
|
+
createSaveCommand,
|
|
164
140
|
openInput,
|
|
165
|
-
pagesCommand,
|
|
166
141
|
pagesInput,
|
|
167
|
-
parseViewportArg,
|
|
168
142
|
runClose,
|
|
169
|
-
saveCommand,
|
|
170
143
|
saveInput
|
|
171
144
|
};
|