libretto 0.6.7 → 0.6.9

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 CHANGED
@@ -7,11 +7,6 @@
7
7
  [![GitHub Discussions](https://img.shields.io/github/discussions/saffron-health/libretto)](https://github.com/saffron-health/libretto/discussions)
8
8
  [![Discord](https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white)](https://discord.gg/NYrG56hVDt)
9
9
 
10
- - Website: [libretto.sh](https://libretto.sh)
11
- - Repository: [github.com/saffron-health/libretto](https://github.com/saffron-health/libretto)
12
- - Docs: [libretto.sh/docs](https://libretto.sh/docs)
13
- - Discord: [discord.gg/NYrG56hVDt](https://discord.gg/NYrG56hVDt)
14
-
15
10
  Libretto is a toolkit for building robust web integrations. It gives your coding agent a live browser and a token-efficient CLI to:
16
11
 
17
12
  - Inspect live pages with minimal context overhead
@@ -23,6 +18,13 @@ We at [Saffron Health](https://saffron.health) built Libretto to help us maintai
23
18
 
24
19
  https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
25
20
 
21
+ ### Quick Links
22
+
23
+ - Website: [libretto.sh](https://libretto.sh)
24
+ - Docs: [libretto.sh/docs](https://libretto.sh/docs)
25
+ - Repository: [github.com/saffron-health/libretto](https://github.com/saffron-health/libretto)
26
+ - Discord: [discord.gg/NYrG56hVDt](https://discord.gg/NYrG56hVDt)
27
+
26
28
  ## Installation
27
29
 
28
30
  ```bash
@@ -75,86 +77,29 @@ Agents can use Libretto to reproduce the failure, pause the workflow at any poin
75
77
  You can also use Libretto directly from the command line. All commands accept `--session <name>` to target a specific session.
76
78
 
77
79
  ```bash
78
- npx libretto setup # interactive first-run onboarding; run yourself, not through an agent
79
- npx libretto status # check AI config health and open sessions
80
- npx libretto open <url> # launch browser and open a URL (headed by default)
81
- npx libretto snapshot --objective "..." --context "..." # capture PNG + HTML and analyze with an LLM
82
- npx libretto exec "<code>" # execute Playwright TypeScript against the open page (single quoted argument)
83
- echo "<code>" | npx libretto exec - # intentionally read Playwright TypeScript from stdin
84
- npx libretto run <file> # run the file's default-exported workflow
85
- npx libretto resume # resume a paused workflow
86
- npx libretto pages # list open pages in the session
87
- npx libretto save <domain> # save browser session (cookies, localStorage) for reuse
80
+ npx libretto open <url> # launch browser and open a URL
81
+ npx libretto snapshot --objective "..." # capture PNG + HTML and analyze with an LLM
82
+ npx libretto exec "<code>" # execute Playwright TypeScript against the open page
88
83
  npx libretto close # close the browser
89
- npx libretto ai configure <provider> # manually change snapshot analysis model
90
- npx libretto status # show AI config and open sessions
91
- ```
92
-
93
- ## Configuration
94
-
95
- All Libretto state lives in a `.libretto/` directory at your project root. Configuration is stored in `.libretto/config.json`.
96
-
97
- ### Config file
98
-
99
- `.libretto/config.json` controls snapshot analysis and viewport settings:
100
-
101
- ```json
102
- {
103
- "version": 1,
104
- "ai": {
105
- "model": "openai/gpt-5.4",
106
- "updatedAt": "2026-01-01T00:00:00.000Z"
107
- },
108
- "viewport": { "width": 1280, "height": 800 }
109
- }
110
- ```
111
-
112
- The `ai` field configures which model Libretto uses for snapshot analysis — extracting selectors, identifying interactive elements, or diagnosing why a step failed. This keeps heavy visual context out of your coding agent's context window. Snapshot analysis is required.
113
-
114
- `npx libretto setup` automatically pins the default model for the first provider whose credentials it finds. To explicitly change the provider or model afterward:
115
-
116
- ```bash
117
- npx libretto ai configure <openai | anthropic | gemini | vertex>
118
- ```
119
-
120
- To inspect the current configuration without changing anything:
121
-
122
- ```bash
123
- npx libretto status
124
84
  ```
125
85
 
126
- Provider credentials are read from environment variables or a `.env` file at your **repository root** (next to your `.git` directory): `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` / `GOOGLE_GENERATIVE_AI_API_KEY`, or `GOOGLE_CLOUD_PROJECT` for Vertex. Set `LIBRETTO_DISABLE_DOTENV=1` to skip `.env` loading.
86
+ Run `npx libretto help` for the full list of commands.
127
87
 
128
- The `viewport` field sets the default browser viewport size. Both fields are optional.
129
-
130
- ### Sessions
131
-
132
- Each Libretto session gets its own directory under `.libretto/sessions/<name>/` containing runtime state. Sessions are git-ignored.
133
-
134
- - `state.json` — session metadata (debug port, PID, status)
135
- - `logs.jsonl` — structured session logs
136
- - `network.jsonl` — captured network requests
137
- - `actions.jsonl` — recorded user actions
138
- - `snapshots/` — screenshot PNGs and HTML snapshots
139
-
140
- ### Profiles
88
+ ## Configuration
141
89
 
142
- 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.
90
+ All Libretto state lives in a `.libretto/` directory at your project root. See the [configuration docs](https://libretto.sh/docs/configuration) for details on config files, sessions, and profiles.
143
91
 
144
- ## Community
92
+ ## Join the Community
145
93
 
146
- Have a question, idea, or want to share what you've built? Join the conversation on [Discord](https://discord.gg/NYrG56hVDt) for quick help or [GitHub Discussions](https://github.com/saffron-health/libretto/discussions) for longer-form threads.
94
+ Join our Discord to connect with other developers, get help, and share what you've built:
147
95
 
148
- - **[Q&A](https://github.com/saffron-health/libretto/discussions/categories/q-a)** — Ask questions and get help
149
- - **[Ideas](https://github.com/saffron-health/libretto/discussions/categories/ideas)** — Suggest new features or improvements
150
- - **[Show and tell](https://github.com/saffron-health/libretto/discussions/categories/show-and-tell)** — Share your workflows and automations
151
- - **[General](https://github.com/saffron-health/libretto/discussions/categories/general)** — Chat about anything Libretto-related
96
+ [![Discord](https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white)](https://discord.gg/NYrG56hVDt)
152
97
 
153
- Found a bug? Please [open an issue](https://github.com/saffron-health/libretto/issues/new).
98
+ For longer-form threads, head to [GitHub Discussions](https://github.com/saffron-health/libretto/discussions). Found a bug? [Open an issue](https://github.com/saffron-health/libretto/issues/new).
154
99
 
155
- ## Authors
100
+ ## License
156
101
 
157
- Maintained by the team at [Saffron Health](https://saffron.health).
102
+ [MIT License](LICENSE) use it freely in commercial and open-source projects.
158
103
 
159
104
  ## Development
160
105
 
@@ -179,3 +124,10 @@ Source layout:
179
124
  Run `pnpm sync:mirrors` after editing `README.template.md` or anything under `skills/libretto/`.
180
125
 
181
126
  To check that generated READMEs, skill mirrors, and skill version metadata are in sync without fixing them, run `pnpm check:mirrors`. To release, run `pnpm prepare-release`.
127
+
128
+ ---
129
+
130
+ > [!NOTE]
131
+ > This is an early-stage project under active development. APIs may change before version 1.0. We recommend pinning to specific versions in production.
132
+
133
+ Built by the team at [Saffron Health](https://saffron.health).
@@ -5,11 +5,6 @@
5
5
  [![GitHub Discussions](https://img.shields.io/github/discussions/saffron-health/libretto)](https://github.com/saffron-health/libretto/discussions)
6
6
  [![Discord](https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white)](https://discord.gg/NYrG56hVDt)
7
7
 
8
- - Website: [libretto.sh](https://libretto.sh)
9
- - Repository: [github.com/saffron-health/libretto](https://github.com/saffron-health/libretto)
10
- - Docs: [libretto.sh/docs](https://libretto.sh/docs)
11
- - Discord: [discord.gg/NYrG56hVDt](https://discord.gg/NYrG56hVDt)
12
-
13
8
  Libretto is a toolkit for building robust web integrations. It gives your coding agent a live browser and a token-efficient CLI to:
14
9
 
15
10
  - Inspect live pages with minimal context overhead
@@ -21,6 +16,13 @@ We at [Saffron Health](https://saffron.health) built Libretto to help us maintai
21
16
 
22
17
  https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
23
18
 
19
+ ### Quick Links
20
+
21
+ - Website: [libretto.sh](https://libretto.sh)
22
+ - Docs: [libretto.sh/docs](https://libretto.sh/docs)
23
+ - Repository: [github.com/saffron-health/libretto](https://github.com/saffron-health/libretto)
24
+ - Discord: [discord.gg/NYrG56hVDt](https://discord.gg/NYrG56hVDt)
25
+
24
26
  ## Installation
25
27
 
26
28
  ```bash
@@ -73,86 +75,29 @@ Agents can use Libretto to reproduce the failure, pause the workflow at any poin
73
75
  You can also use Libretto directly from the command line. All commands accept `--session <name>` to target a specific session.
74
76
 
75
77
  ```bash
76
- npx libretto setup # interactive first-run onboarding; run yourself, not through an agent
77
- npx libretto status # check AI config health and open sessions
78
- npx libretto open <url> # launch browser and open a URL (headed by default)
79
- npx libretto snapshot --objective "..." --context "..." # capture PNG + HTML and analyze with an LLM
80
- npx libretto exec "<code>" # execute Playwright TypeScript against the open page (single quoted argument)
81
- echo "<code>" | npx libretto exec - # intentionally read Playwright TypeScript from stdin
82
- npx libretto run <file> # run the file's default-exported workflow
83
- npx libretto resume # resume a paused workflow
84
- npx libretto pages # list open pages in the session
85
- npx libretto save <domain> # save browser session (cookies, localStorage) for reuse
78
+ npx libretto open <url> # launch browser and open a URL
79
+ npx libretto snapshot --objective "..." # capture PNG + HTML and analyze with an LLM
80
+ npx libretto exec "<code>" # execute Playwright TypeScript against the open page
86
81
  npx libretto close # close the browser
87
- npx libretto ai configure <provider> # manually change snapshot analysis model
88
- npx libretto status # show AI config and open sessions
89
- ```
90
-
91
- ## Configuration
92
-
93
- All Libretto state lives in a `.libretto/` directory at your project root. Configuration is stored in `.libretto/config.json`.
94
-
95
- ### Config file
96
-
97
- `.libretto/config.json` controls snapshot analysis and viewport settings:
98
-
99
- ```json
100
- {
101
- "version": 1,
102
- "ai": {
103
- "model": "openai/gpt-5.4",
104
- "updatedAt": "2026-01-01T00:00:00.000Z"
105
- },
106
- "viewport": { "width": 1280, "height": 800 }
107
- }
108
- ```
109
-
110
- The `ai` field configures which model Libretto uses for snapshot analysis — extracting selectors, identifying interactive elements, or diagnosing why a step failed. This keeps heavy visual context out of your coding agent's context window. Snapshot analysis is required.
111
-
112
- `npx libretto setup` automatically pins the default model for the first provider whose credentials it finds. To explicitly change the provider or model afterward:
113
-
114
- ```bash
115
- npx libretto ai configure <openai | anthropic | gemini | vertex>
116
- ```
117
-
118
- To inspect the current configuration without changing anything:
119
-
120
- ```bash
121
- npx libretto status
122
82
  ```
123
83
 
124
- Provider credentials are read from environment variables or a `.env` file at your **repository root** (next to your `.git` directory): `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` / `GOOGLE_GENERATIVE_AI_API_KEY`, or `GOOGLE_CLOUD_PROJECT` for Vertex. Set `LIBRETTO_DISABLE_DOTENV=1` to skip `.env` loading.
84
+ Run `npx libretto help` for the full list of commands.
125
85
 
126
- The `viewport` field sets the default browser viewport size. Both fields are optional.
127
-
128
- ### Sessions
129
-
130
- Each Libretto session gets its own directory under `.libretto/sessions/<name>/` containing runtime state. Sessions are git-ignored.
131
-
132
- - `state.json` — session metadata (debug port, PID, status)
133
- - `logs.jsonl` — structured session logs
134
- - `network.jsonl` — captured network requests
135
- - `actions.jsonl` — recorded user actions
136
- - `snapshots/` — screenshot PNGs and HTML snapshots
137
-
138
- ### Profiles
86
+ ## Configuration
139
87
 
140
- 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.
88
+ All Libretto state lives in a `.libretto/` directory at your project root. See the [configuration docs](https://libretto.sh/docs/configuration) for details on config files, sessions, and profiles.
141
89
 
142
- ## Community
90
+ ## Join the Community
143
91
 
144
- Have a question, idea, or want to share what you've built? Join the conversation on [Discord](https://discord.gg/NYrG56hVDt) for quick help or [GitHub Discussions](https://github.com/saffron-health/libretto/discussions) for longer-form threads.
92
+ Join our Discord to connect with other developers, get help, and share what you've built:
145
93
 
146
- - **[Q&A](https://github.com/saffron-health/libretto/discussions/categories/q-a)** — Ask questions and get help
147
- - **[Ideas](https://github.com/saffron-health/libretto/discussions/categories/ideas)** — Suggest new features or improvements
148
- - **[Show and tell](https://github.com/saffron-health/libretto/discussions/categories/show-and-tell)** — Share your workflows and automations
149
- - **[General](https://github.com/saffron-health/libretto/discussions/categories/general)** — Chat about anything Libretto-related
94
+ [![Discord](https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white)](https://discord.gg/NYrG56hVDt)
150
95
 
151
- Found a bug? Please [open an issue](https://github.com/saffron-health/libretto/issues/new).
96
+ For longer-form threads, head to [GitHub Discussions](https://github.com/saffron-health/libretto/discussions). Found a bug? [Open an issue](https://github.com/saffron-health/libretto/issues/new).
152
97
 
153
- ## Authors
98
+ ## License
154
99
 
155
- Maintained by the team at [Saffron Health](https://saffron.health).
100
+ [MIT License](LICENSE) use it freely in commercial and open-source projects.
156
101
 
157
102
  ## Development
158
103
 
@@ -177,3 +122,10 @@ Source layout:
177
122
  Run `pnpm sync:mirrors` after editing `{{LIBRETTO_PATH_PREFIX}}README.template.md` or anything under `{{LIBRETTO_PATH_PREFIX}}skills/libretto/`.
178
123
 
179
124
  To check that generated READMEs, skill mirrors, and skill version metadata are in sync without fixing them, run `pnpm check:mirrors`. To release, run `pnpm prepare-release`.
125
+
126
+ ---
127
+
128
+ > [!NOTE]
129
+ > This is an early-stage project under active development. APIs may change before version 1.0. We recommend pinning to specific versions in production.
130
+
131
+ Built by the team at [Saffron Health](https://saffron.health).
@@ -687,6 +687,15 @@ const runCommand = SimpleCLI.command({
687
687
  `Creating ${providerName} browser session (session: ${ctx.session})...`
688
688
  );
689
689
  const providerSession = await provider.createSession();
690
+ ctx.logger.info("run-provider-session-created", {
691
+ provider: providerName,
692
+ sessionId: providerSession.sessionId,
693
+ cdpEndpoint: providerSession.cdpEndpoint,
694
+ liveViewUrl: providerSession.liveViewUrl
695
+ });
696
+ if (providerSession.liveViewUrl) {
697
+ console.log(`View live session: ${providerSession.liveViewUrl}`);
698
+ }
690
699
  console.log(`Connecting to ${providerName} browser...`);
691
700
  cdpEndpoint = providerSession.cdpEndpoint;
692
701
  providerInfo = {
@@ -714,7 +723,10 @@ const runCommand = SimpleCLI.command({
714
723
  } finally {
715
724
  if (provider && providerInfo) {
716
725
  try {
717
- await provider.closeSession(providerInfo.sessionId);
726
+ const result = await provider.closeSession(providerInfo.sessionId);
727
+ if (result.replayUrl) {
728
+ console.log(`View recording: ${result.replayUrl}`);
729
+ }
718
730
  } catch (cleanupErr) {
719
731
  console.error(
720
732
  `Failed to clean up ${providerInfo.name} session ${providerInfo.sessionId}:`,
@@ -23,7 +23,8 @@ const PROVIDER_SDK_PACKAGES = {
23
23
  openai: "@ai-sdk/openai",
24
24
  anthropic: "@ai-sdk/anthropic",
25
25
  google: "@ai-sdk/google",
26
- vertex: "@ai-sdk/google-vertex"
26
+ vertex: "@ai-sdk/google-vertex",
27
+ openrouter: "@ai-sdk/openai"
27
28
  };
28
29
  function detectPackageManager() {
29
30
  if (existsSync(join(REPO_ROOT, "pnpm-lock.yaml"))) return "pnpm";
@@ -102,6 +103,13 @@ const PROVIDER_CHOICES = [
102
103
  provider: "vertex",
103
104
  envVar: "GOOGLE_CLOUD_PROJECT",
104
105
  envHint: "Requires `gcloud auth application-default login` and a GCP project ID"
106
+ },
107
+ {
108
+ key: "5",
109
+ label: "OpenRouter",
110
+ provider: "openrouter",
111
+ envVar: "OPENROUTER_API_KEY",
112
+ envHint: "Get your key at https://openrouter.ai/settings/keys"
105
113
  }
106
114
  ];
107
115
  function promptUser(rl, question) {
@@ -136,7 +144,7 @@ function printHealthySummary(status) {
136
144
  console.log(`\u2713 Using ${providerLabel(status.provider)} (${status.model}).`);
137
145
  }
138
146
  console.log(
139
- "To change: npx libretto ai configure openai | anthropic | gemini | vertex"
147
+ "To change: npx libretto ai configure openai | anthropic | gemini | vertex | openrouter"
140
148
  );
141
149
  }
142
150
  function printInvalidAiConfigWarning(status) {
@@ -200,7 +208,7 @@ function printSnapshotApiStatus() {
200
208
  " GOOGLE_CLOUD_PROJECT=... # plus application default credentials for Vertex"
201
209
  );
202
210
  console.log(
203
- " Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model."
211
+ " Or run `npx libretto ai configure openai | anthropic | gemini | vertex | openrouter` to set a specific model."
204
212
  );
205
213
  console.log(
206
214
  " Run `npx libretto setup` interactively to set up credentials."
@@ -245,7 +253,7 @@ function printSkipMessage() {
245
253
  console.log(" ANTHROPIC_API_KEY=...");
246
254
  console.log(" GEMINI_API_KEY=...");
247
255
  console.log(
248
- " Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model."
256
+ " Or run `npx libretto ai configure openai | anthropic | gemini | vertex | openrouter` to set a specific model."
249
257
  );
250
258
  }
251
259
  async function runInteractiveApiSetup() {
@@ -13,7 +13,7 @@ function printAiStatus(status) {
13
13
  console.log(` Source: ${status.source}`);
14
14
  }
15
15
  console.log(
16
- " To change: npx libretto ai configure openai | anthropic | gemini | vertex"
16
+ " To change: npx libretto ai configure openai | anthropic | gemini | vertex | openrouter"
17
17
  );
18
18
  break;
19
19
  case "configured-missing-credentials":
@@ -10,7 +10,8 @@ const DEFAULT_SNAPSHOT_MODELS = {
10
10
  openai: "openai/gpt-5.4",
11
11
  anthropic: "anthropic/claude-sonnet-4-6",
12
12
  google: "google/gemini-3-flash-preview",
13
- vertex: "vertex/gemini-2.5-flash"
13
+ vertex: "vertex/gemini-2.5-flash",
14
+ openrouter: "openrouter/free"
14
15
  };
15
16
  function detectProviderEnvVar(provider, env = process.env) {
16
17
  switch (provider) {
@@ -27,6 +28,8 @@ function detectProviderEnvVar(provider, env = process.env) {
27
28
  if (env.GOOGLE_CLOUD_PROJECT?.trim()) return "GOOGLE_CLOUD_PROJECT";
28
29
  if (env.GCLOUD_PROJECT?.trim()) return "GCLOUD_PROJECT";
29
30
  return null;
31
+ case "openrouter":
32
+ return env.OPENROUTER_API_KEY?.trim() ? "OPENROUTER_API_KEY" : null;
30
33
  }
31
34
  }
32
35
  class SnapshotApiUnavailableError extends Error {
@@ -45,10 +48,12 @@ function providerSetupSentence(provider) {
45
48
  return "Add GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY to .env or as a shell environment variable.";
46
49
  case "vertex":
47
50
  return "Add GOOGLE_CLOUD_PROJECT or GCLOUD_PROJECT to .env or as a shell environment variable, and make sure application default credentials are configured.";
51
+ case "openrouter":
52
+ return "Add OPENROUTER_API_KEY to .env or as a shell environment variable.";
48
53
  }
49
54
  }
50
55
  function defaultModelCommandLine() {
51
- return "npx libretto ai configure openai | anthropic | gemini | vertex";
56
+ return "npx libretto ai configure openai | anthropic | gemini | vertex | openrouter";
52
57
  }
53
58
  function providerMissingCredentialSummary(provider) {
54
59
  switch (provider) {
@@ -60,12 +65,14 @@ function providerMissingCredentialSummary(provider) {
60
65
  return "GEMINI_API_KEY and GOOGLE_GENERATIVE_AI_API_KEY are missing";
61
66
  case "vertex":
62
67
  return "GOOGLE_CLOUD_PROJECT and GCLOUD_PROJECT are missing";
68
+ case "openrouter":
69
+ return "OPENROUTER_API_KEY is missing";
63
70
  }
64
71
  }
65
72
  function noSnapshotApiConfiguredMessage() {
66
73
  return [
67
74
  "Failed to analyze snapshot because no snapshot analyzer is configured.",
68
- `Add OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY, or GOOGLE_CLOUD_PROJECT to .env or as a shell environment variable, or choose a default model with \`${defaultModelCommandLine()}\`.`,
75
+ `Add OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY, GOOGLE_CLOUD_PROJECT, or OPENROUTER_API_KEY to .env or as a shell environment variable, or choose a default model with \`${defaultModelCommandLine()}\`.`,
69
76
  "For more info, run `npx libretto setup`."
70
77
  ].join(" ");
71
78
  }
@@ -82,7 +89,8 @@ function inferAutoSnapshotModel() {
82
89
  "openai",
83
90
  "anthropic",
84
91
  "google",
85
- "vertex"
92
+ "vertex",
93
+ "openrouter"
86
94
  ];
87
95
  for (const provider of providersInPriorityOrder) {
88
96
  const envVar = detectProviderEnvVar(provider);
@@ -0,0 +1,122 @@
1
+ import { chromium } from "playwright";
2
+ import { mkdir, unlink } from "node:fs/promises";
3
+ import { appendFileSync } from "node:fs";
4
+ import { installSessionTelemetry } from "./session-telemetry.js";
5
+ import {
6
+ getSessionDir,
7
+ getSessionLogsPath,
8
+ getSessionNetworkLogPath,
9
+ getSessionActionsLogPath,
10
+ getSessionStatePath
11
+ } from "./context.js";
12
+ const config = JSON.parse(process.argv[2]);
13
+ const sessionDir = getSessionDir(config.session);
14
+ await mkdir(sessionDir, { recursive: true });
15
+ const logFile = getSessionLogsPath(config.session);
16
+ const networkLogFile = getSessionNetworkLogPath(config.session);
17
+ const actionsLogFile = getSessionActionsLogPath(config.session);
18
+ function childLog(level, event, data = {}) {
19
+ const entry = JSON.stringify({
20
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
21
+ id: Math.random().toString(36).slice(2, 10),
22
+ level,
23
+ scope: "libretto.child",
24
+ event,
25
+ data
26
+ });
27
+ appendFileSync(logFile, entry + "\n");
28
+ }
29
+ function logAction(entry) {
30
+ appendFileSync(actionsLogFile, JSON.stringify(entry) + "\n");
31
+ }
32
+ function logNetwork(entry) {
33
+ appendFileSync(networkLogFile, JSON.stringify(entry) + "\n");
34
+ }
35
+ const windowPositionArg = config.windowPosition ? `--window-position=${config.windowPosition.x},${config.windowPosition.y}` : void 0;
36
+ const launchArgs = [
37
+ "--disable-blink-features=AutomationControlled",
38
+ `--remote-debugging-port=${config.port}`,
39
+ "--remote-debugging-address=127.0.0.1",
40
+ "--no-focus-on-check",
41
+ ...windowPositionArg ? [windowPositionArg] : []
42
+ ];
43
+ const browser = await chromium.launch({
44
+ headless: !config.headed,
45
+ args: launchArgs
46
+ });
47
+ async function cleanupSessionState() {
48
+ const sessionStatePath = getSessionStatePath(config.session);
49
+ try {
50
+ await unlink(sessionStatePath);
51
+ } catch (err) {
52
+ if (err.code !== "ENOENT") throw err;
53
+ }
54
+ }
55
+ let shuttingDown = false;
56
+ let wakeDaemon;
57
+ const sleepPromise = new Promise((resolve) => {
58
+ wakeDaemon = resolve;
59
+ });
60
+ async function shutdown(reason, closeBrowser) {
61
+ if (shuttingDown) return;
62
+ shuttingDown = true;
63
+ try {
64
+ childLog("info", reason, { port: config.port });
65
+ await cleanupSessionState();
66
+ if (closeBrowser) await browser.close();
67
+ } finally {
68
+ wakeDaemon();
69
+ }
70
+ }
71
+ browser.on("disconnected", () => {
72
+ void shutdown("browser-disconnected-exiting", false);
73
+ });
74
+ const context = await browser.newContext({
75
+ ...config.storageStatePath ? { storageState: config.storageStatePath } : {},
76
+ viewport: {
77
+ width: config.viewport.width,
78
+ height: config.viewport.height
79
+ },
80
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
81
+ });
82
+ const page = await context.newPage();
83
+ page.setDefaultTimeout(3e4);
84
+ page.setDefaultNavigationTimeout(45e3);
85
+ await installSessionTelemetry({
86
+ context,
87
+ initialPage: page,
88
+ includeUserDomActions: true,
89
+ logAction,
90
+ logNetwork
91
+ });
92
+ await page.goto(config.url);
93
+ process.on("SIGTERM", () => {
94
+ void shutdown("child-sigterm", true);
95
+ });
96
+ process.on("SIGINT", () => {
97
+ void shutdown("child-sigint", true);
98
+ });
99
+ process.on("uncaughtException", (err) => {
100
+ childLog("error", "uncaught-exception", {
101
+ message: err.message,
102
+ stack: err.stack
103
+ });
104
+ process.exit(1);
105
+ });
106
+ process.on("unhandledRejection", (reason) => {
107
+ childLog("warn", "unhandled-rejection", { reason: String(reason) });
108
+ });
109
+ process.on("exit", (code) => {
110
+ childLog("info", "child-exit", {
111
+ code,
112
+ pid: process.pid,
113
+ port: config.port
114
+ });
115
+ });
116
+ childLog("info", "child-launched", {
117
+ port: config.port,
118
+ pid: process.pid,
119
+ session: config.session
120
+ });
121
+ await sleepPromise;
122
+ process.exit(0);