libretto 0.4.4 → 0.5.0

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.
Files changed (152) hide show
  1. package/dist/cli/cli.js +20 -19
  2. package/dist/cli/commands/ai.js +1 -1
  3. package/dist/cli/commands/browser.js +3 -3
  4. package/dist/cli/commands/execution.js +3 -3
  5. package/dist/cli/commands/logs.js +1 -1
  6. package/dist/cli/core/browser.js +11 -6
  7. package/dist/cli/core/context.js +4 -18
  8. package/dist/cli/core/session.js +2 -2
  9. package/dist/cli/core/snapshot-analyzer.js +2 -2
  10. package/dist/cli/router.js +1 -1
  11. package/dist/cli/workers/run-integration-runtime.js +2 -2
  12. package/dist/shared/paths/paths.js +2 -1
  13. package/dist/shared/paths/repo-root.d.ts +3 -0
  14. package/dist/shared/paths/repo-root.js +24 -0
  15. package/package.json +6 -7
  16. package/scripts/postinstall.mjs +12 -3
  17. package/skills/libretto/SKILL.md +93 -404
  18. package/skills/libretto/references/auth-profiles.md +30 -0
  19. package/skills/libretto/references/pages-and-page-targeting.md +29 -0
  20. package/skills/libretto/references/reverse-engineering-network-requests.md +39 -0
  21. package/skills/libretto/references/user-action-log.md +31 -0
  22. package/src/cli/cli.ts +173 -0
  23. package/src/cli/commands/ai.ts +35 -0
  24. package/src/cli/commands/browser.ts +165 -0
  25. package/src/cli/commands/execution.ts +691 -0
  26. package/src/cli/commands/init.ts +327 -0
  27. package/src/cli/commands/logs.ts +128 -0
  28. package/src/cli/commands/shared.ts +70 -0
  29. package/src/cli/commands/snapshot.ts +327 -0
  30. package/src/cli/core/ai-config.ts +255 -0
  31. package/src/cli/core/api-snapshot-analyzer.ts +97 -0
  32. package/src/cli/core/browser.ts +839 -0
  33. package/src/cli/core/context.ts +122 -0
  34. package/src/cli/core/pause-signals.ts +35 -0
  35. package/src/cli/core/session-telemetry.ts +553 -0
  36. package/src/cli/core/session.ts +209 -0
  37. package/src/cli/core/snapshot-analyzer.ts +875 -0
  38. package/src/cli/core/snapshot-api-config.ts +236 -0
  39. package/src/cli/core/telemetry.ts +446 -0
  40. package/src/cli/framework/simple-cli.ts +1273 -0
  41. package/src/cli/index.ts +13 -0
  42. package/src/cli/router.ts +28 -0
  43. package/src/cli/workers/run-integration-runtime.ts +311 -0
  44. package/src/cli/workers/run-integration-worker-protocol.ts +14 -0
  45. package/src/cli/workers/run-integration-worker.ts +75 -0
  46. package/src/index.ts +120 -0
  47. package/src/runtime/download/download.ts +100 -0
  48. package/src/runtime/download/index.ts +7 -0
  49. package/src/runtime/extract/extract.ts +92 -0
  50. package/src/runtime/extract/index.ts +1 -0
  51. package/src/runtime/network/index.ts +5 -0
  52. package/src/runtime/network/network.ts +113 -0
  53. package/src/runtime/recovery/agent.ts +256 -0
  54. package/src/runtime/recovery/errors.ts +152 -0
  55. package/src/runtime/recovery/index.ts +7 -0
  56. package/src/runtime/recovery/recovery.ts +50 -0
  57. package/{dist/shared/condense-dom/condense-dom.cjs → src/shared/condense-dom/condense-dom.ts} +243 -115
  58. package/src/shared/config/config.ts +22 -0
  59. package/src/shared/config/index.ts +5 -0
  60. package/src/shared/debug/index.ts +1 -0
  61. package/src/shared/debug/pause.ts +85 -0
  62. package/src/shared/instrumentation/errors.ts +82 -0
  63. package/src/shared/instrumentation/index.ts +9 -0
  64. package/src/shared/instrumentation/instrument.ts +276 -0
  65. package/src/shared/llm/ai-sdk-adapter.ts +78 -0
  66. package/src/shared/llm/client.ts +217 -0
  67. package/src/shared/llm/index.ts +3 -0
  68. package/src/shared/llm/types.ts +63 -0
  69. package/src/shared/logger/index.ts +6 -0
  70. package/src/shared/logger/logger.ts +352 -0
  71. package/src/shared/logger/sinks.ts +144 -0
  72. package/src/shared/paths/paths.ts +109 -0
  73. package/src/shared/paths/repo-root.ts +27 -0
  74. package/src/shared/run/api.ts +2 -0
  75. package/src/shared/run/browser.ts +98 -0
  76. package/src/shared/state/index.ts +11 -0
  77. package/src/shared/state/session-state.ts +74 -0
  78. package/src/shared/visualization/ghost-cursor.ts +200 -0
  79. package/src/shared/visualization/highlight.ts +146 -0
  80. package/src/shared/visualization/index.ts +18 -0
  81. package/src/shared/workflow/workflow.ts +42 -0
  82. package/dist/index.cjs +0 -144
  83. package/dist/index.d.cts +0 -21
  84. package/dist/runtime/download/download.cjs +0 -70
  85. package/dist/runtime/download/download.d.cts +0 -35
  86. package/dist/runtime/download/index.cjs +0 -30
  87. package/dist/runtime/download/index.d.cts +0 -3
  88. package/dist/runtime/extract/extract.cjs +0 -88
  89. package/dist/runtime/extract/extract.d.cts +0 -23
  90. package/dist/runtime/extract/index.cjs +0 -28
  91. package/dist/runtime/extract/index.d.cts +0 -5
  92. package/dist/runtime/network/index.cjs +0 -28
  93. package/dist/runtime/network/index.d.cts +0 -4
  94. package/dist/runtime/network/network.cjs +0 -91
  95. package/dist/runtime/network/network.d.cts +0 -28
  96. package/dist/runtime/recovery/agent.cjs +0 -223
  97. package/dist/runtime/recovery/agent.d.cts +0 -13
  98. package/dist/runtime/recovery/errors.cjs +0 -124
  99. package/dist/runtime/recovery/errors.d.cts +0 -31
  100. package/dist/runtime/recovery/index.cjs +0 -34
  101. package/dist/runtime/recovery/index.d.cts +0 -7
  102. package/dist/runtime/recovery/recovery.cjs +0 -55
  103. package/dist/runtime/recovery/recovery.d.cts +0 -12
  104. package/dist/shared/condense-dom/condense-dom.d.cts +0 -34
  105. package/dist/shared/config/config.cjs +0 -44
  106. package/dist/shared/config/config.d.cts +0 -10
  107. package/dist/shared/config/index.cjs +0 -32
  108. package/dist/shared/config/index.d.cts +0 -1
  109. package/dist/shared/debug/index.cjs +0 -28
  110. package/dist/shared/debug/index.d.cts +0 -1
  111. package/dist/shared/debug/pause.cjs +0 -86
  112. package/dist/shared/debug/pause.d.cts +0 -12
  113. package/dist/shared/instrumentation/errors.cjs +0 -81
  114. package/dist/shared/instrumentation/errors.d.cts +0 -12
  115. package/dist/shared/instrumentation/index.cjs +0 -35
  116. package/dist/shared/instrumentation/index.d.cts +0 -6
  117. package/dist/shared/instrumentation/instrument.cjs +0 -206
  118. package/dist/shared/instrumentation/instrument.d.cts +0 -32
  119. package/dist/shared/llm/ai-sdk-adapter.cjs +0 -71
  120. package/dist/shared/llm/ai-sdk-adapter.d.cts +0 -22
  121. package/dist/shared/llm/client.cjs +0 -218
  122. package/dist/shared/llm/client.d.cts +0 -13
  123. package/dist/shared/llm/index.cjs +0 -31
  124. package/dist/shared/llm/index.d.cts +0 -5
  125. package/dist/shared/llm/types.cjs +0 -16
  126. package/dist/shared/llm/types.d.cts +0 -67
  127. package/dist/shared/logger/index.cjs +0 -37
  128. package/dist/shared/logger/index.d.cts +0 -2
  129. package/dist/shared/logger/logger.cjs +0 -232
  130. package/dist/shared/logger/logger.d.cts +0 -86
  131. package/dist/shared/logger/sinks.cjs +0 -160
  132. package/dist/shared/logger/sinks.d.cts +0 -9
  133. package/dist/shared/paths/paths.cjs +0 -104
  134. package/dist/shared/paths/paths.d.cts +0 -10
  135. package/dist/shared/run/api.cjs +0 -28
  136. package/dist/shared/run/api.d.cts +0 -2
  137. package/dist/shared/run/browser.cjs +0 -98
  138. package/dist/shared/run/browser.d.cts +0 -22
  139. package/dist/shared/state/index.cjs +0 -38
  140. package/dist/shared/state/index.d.cts +0 -2
  141. package/dist/shared/state/session-state.cjs +0 -92
  142. package/dist/shared/state/session-state.d.cts +0 -40
  143. package/dist/shared/visualization/ghost-cursor.cjs +0 -174
  144. package/dist/shared/visualization/ghost-cursor.d.cts +0 -37
  145. package/dist/shared/visualization/highlight.cjs +0 -134
  146. package/dist/shared/visualization/highlight.d.cts +0 -22
  147. package/dist/shared/visualization/index.cjs +0 -45
  148. package/dist/shared/visualization/index.d.cts +0 -3
  149. package/dist/shared/workflow/workflow.cjs +0 -47
  150. package/dist/shared/workflow/workflow.d.cts +0 -21
  151. package/skills/libretto/code-generation-rules.md +0 -223
  152. package/skills/libretto/integration-approach-selection.md +0 -174
@@ -0,0 +1,39 @@
1
+ # Reverse Engineering Network Requests
2
+
3
+ Use this reference when the user wants to turn a browser workflow into direct network requests.
4
+
5
+ This is the default approach for new integrations when the site exposes a clear and stable HTTP request path.
6
+
7
+ ## When to Use This
8
+
9
+ - The page clearly loads or submits data through HTTP requests.
10
+ - The user can perform the workflow manually in a headed browser.
11
+ - Replaying the request is likely faster or more stable than reproducing every UI action.
12
+ - Fall back to browser automation when the request path is unclear, too dynamic, or blocked by anti-bot systems.
13
+
14
+ ## Workflow
15
+
16
+ - Open the page in headed mode.
17
+ - Let the user perform the relevant workflow manually.
18
+ - Read the network log after the relevant step.
19
+ - Identify the smallest set of requests that actually carries the data or performs the action.
20
+ - Confirm with the user before replaying any request that could mutate data.
21
+ - Recreate the request in code outside Libretto.
22
+ - Verify the resulting workflow with `npx libretto run ...`.
23
+
24
+ ## Commands
25
+
26
+ ```bash
27
+ npx libretto open https://target.example.com --headed
28
+ npx libretto network --last 20
29
+ npx libretto network --method POST --last 20
30
+ npx libretto network --filter 'referral|patient|search'
31
+ npx libretto exec "return await networkLog({ method: 'POST', last: 10 })"
32
+ ```
33
+
34
+ ## Notes
35
+
36
+ - Start with the request that returns the data you need, not every request on the page.
37
+ - Prefer captured requests over guessing payload shape.
38
+ - If the request format is opaque, highly dynamic, or heavily defended, fall back to UI automation for that part.
39
+ - Treat all replayed requests as potentially side-effectful until proven otherwise.
@@ -0,0 +1,31 @@
1
+ # User Action Log
2
+
3
+ Use this reference when the user performs steps manually in a headed Libretto session and you want to incorporate those steps into a workflow.
4
+
5
+ ## When to Use This
6
+
7
+ - The user demonstrates the workflow in the browser window.
8
+ - You want to know what they clicked, typed, or selected.
9
+ - You need to reconcile manual user actions with captured network requests.
10
+
11
+ ## Workflow
12
+
13
+ - Open the session in headed mode.
14
+ - Ask the user to perform the workflow.
15
+ - Read the user action log after they finish.
16
+ - Use the log to identify the important transitions in the workflow.
17
+ - Combine the action log with `snapshot` or `network` when the log alone is not enough.
18
+
19
+ ## Commands
20
+
21
+ ```bash
22
+ npx libretto actions --source user --last 20
23
+ npx libretto actions --source user --filter 'button|input|select'
24
+ npx libretto exec "return await actionLog({ source: 'user', last: 10 })"
25
+ ```
26
+
27
+ ## Notes
28
+
29
+ - The action log is most useful for reconstructing the sequence of a workflow, not for discovering every selector you need.
30
+ - If the user performed relevant manual steps, read the action log before writing or revising the workflow code.
31
+ - Use the action log to anchor your understanding, then inspect the current page state with `snapshot` or `exec`.
package/src/cli/cli.ts ADDED
@@ -0,0 +1,173 @@
1
+ import type { Logger } from "../shared/logger/index.js";
2
+ import {
3
+ closeLogger,
4
+ createLoggerForSession,
5
+ ensureLibrettoSetup,
6
+ } from "./core/context.js";
7
+ import {
8
+ SESSION_DEFAULT,
9
+ validateSessionName,
10
+ } from "./core/session.js";
11
+ import { createCLIApp } from "./router.js";
12
+
13
+ function renderUsage(app: ReturnType<typeof createCLIApp>): string {
14
+ return `${app.renderHelp()}
15
+
16
+ Options:
17
+ --session <name> Use a named session (default: "default")
18
+ Built-in sessions: default, dev-server, browser-agent
19
+
20
+ Examples:
21
+ libretto open https://linkedin.com
22
+
23
+ # ... manually log in ...
24
+ libretto save linkedin.com
25
+ # Next time you open linkedin.com, you'll be logged in automatically
26
+
27
+ libretto exec "await page.locator('button:has-text(\\"Sign in\\")').click()"
28
+ libretto exec "await page.fill('input[name=\\"email\\"]', 'test@example.com')"
29
+ libretto ai configure openai
30
+ libretto ai configure anthropic
31
+ libretto ai configure gemini
32
+ libretto ai configure vertex
33
+ libretto ai configure openai/gpt-4o
34
+ libretto snapshot
35
+ libretto snapshot --objective "Find the submit button" --context "Submitting a referral form, already filled in patient details"
36
+ libretto resume --session default
37
+ libretto close
38
+ libretto close --all
39
+ libretto close --all --force
40
+
41
+ # Multiple sessions
42
+ libretto open https://site1.com --session test1
43
+ libretto open https://site2.com --session test2
44
+ libretto exec "return await page.title()" --session test1
45
+
46
+ Available in exec:
47
+ page, context, state, browser, networkLog, actionLog
48
+
49
+ Profiles:
50
+ Profiles are saved to .libretto/profiles/<domain>.json (git-ignored)
51
+ They persist cookies, localStorage, and session data across browser launches.
52
+ Local profiles are machine-local and are not shared with other users/environments.
53
+ Sessions can expire; if run fails auth, log in again and re-save the profile.
54
+
55
+ Sessions:
56
+ Session state is stored in .libretto/sessions/<session>/state.json
57
+ CLI logs are stored in .libretto/sessions/<session>/logs.jsonl
58
+ Each session runs an isolated browser instance on a dynamic port.
59
+ `;
60
+ }
61
+
62
+ function readSessionArgBeforePassthrough(
63
+ rawArgs: readonly string[],
64
+ ): string | null | undefined {
65
+ for (let index = 0; index < rawArgs.length; index += 1) {
66
+ const token = rawArgs[index];
67
+ if (token === "--") return undefined;
68
+ if (token === "--session") {
69
+ const value = rawArgs[index + 1];
70
+ if (!value || value === "--" || value.startsWith("--")) {
71
+ return null;
72
+ }
73
+ return value;
74
+ }
75
+ if (!token.startsWith("--session=")) continue;
76
+
77
+ const value = token.slice("--session=".length);
78
+ if (value.length === 0 || value === "--" || value.startsWith("--")) {
79
+ return null;
80
+ }
81
+ return value;
82
+ }
83
+
84
+ return undefined;
85
+ }
86
+
87
+ function parseSessionForLog(rawArgs: string[]): string {
88
+ const value = readSessionArgBeforePassthrough(rawArgs);
89
+ if (value === undefined || value === null) {
90
+ return SESSION_DEFAULT;
91
+ }
92
+ try {
93
+ validateSessionName(value);
94
+ return value;
95
+ } catch {
96
+ return SESSION_DEFAULT;
97
+ }
98
+ }
99
+
100
+ function validateLegacySessionArg(rawArgs: string[]): void {
101
+ const value = readSessionArgBeforePassthrough(rawArgs);
102
+ if (value === undefined) return;
103
+ if (value === null) {
104
+ throw new Error(
105
+ `Usage: libretto <command> [--session <name>]\nMissing or invalid --session value.`,
106
+ );
107
+ }
108
+ validateSessionName(value);
109
+ }
110
+
111
+ function initializeLogger(rawArgs: string[]): Logger {
112
+ const sessionForLog = parseSessionForLog(rawArgs);
113
+ const logger = createLoggerForSession(sessionForLog);
114
+ logger.info("cli-start", {
115
+ args: rawArgs,
116
+ cwd: process.cwd(),
117
+ session: sessionForLog,
118
+ });
119
+ return logger;
120
+ }
121
+
122
+ async function withCliLogger<T>(
123
+ rawArgs: string[],
124
+ run: (logger: Logger) => Promise<T>,
125
+ ): Promise<T> {
126
+ const logger = initializeLogger(rawArgs);
127
+ try {
128
+ return await run(logger);
129
+ } finally {
130
+ await closeLogger(logger);
131
+ }
132
+ }
133
+
134
+ function isRootHelpRequest(rawArgs: readonly string[]): boolean {
135
+ if (rawArgs.length === 0) return true;
136
+ if (rawArgs[0] === "--help" || rawArgs[0] === "-h") return true;
137
+ return rawArgs[0] === "help" && rawArgs.length === 1;
138
+ }
139
+
140
+ export async function runLibrettoCLI(): Promise<void> {
141
+ const rawArgs = process.argv.slice(2);
142
+ let exitCode = 0;
143
+ ensureLibrettoSetup();
144
+ await withCliLogger(rawArgs, async (logger) => {
145
+ const app = createCLIApp(logger);
146
+
147
+ try {
148
+ validateLegacySessionArg(rawArgs);
149
+
150
+ if (isRootHelpRequest(rawArgs)) {
151
+ console.log(renderUsage(app));
152
+ return;
153
+ }
154
+
155
+ logger.info("cli-command", { args: rawArgs });
156
+ const result = await app.run(rawArgs);
157
+ if (typeof result === "string") {
158
+ console.log(result);
159
+ }
160
+ } catch (err) {
161
+ logger.error("cli-error", { error: err, args: rawArgs });
162
+ const message = err instanceof Error ? err.message : String(err);
163
+ if (message.startsWith("Unknown command: ")) {
164
+ console.error(`${message}\n`);
165
+ console.log(renderUsage(app));
166
+ } else {
167
+ console.error(message);
168
+ }
169
+ exitCode = 1;
170
+ }
171
+ });
172
+ process.exit(exitCode);
173
+ }
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { runAiConfigure } from "../core/ai-config.js";
3
+ import { SimpleCLI } from "../framework/simple-cli.js";
4
+
5
+ export const aiConfigureInput = SimpleCLI.input({
6
+ positionals: [
7
+ SimpleCLI.positional("preset", z.string().optional(), {
8
+ help: "Provider shorthand or provider/model-id",
9
+ }),
10
+ ],
11
+ named: {
12
+ clear: SimpleCLI.flag({ help: "Clear existing AI config" }),
13
+ },
14
+ });
15
+
16
+ export const aiCommands = SimpleCLI.group({
17
+ description: "AI commands",
18
+ routes: {
19
+ configure: SimpleCLI.command({
20
+ description: "Configure AI runtime",
21
+ })
22
+ .input(aiConfigureInput)
23
+ .handle(async ({ input }) => {
24
+ runAiConfigure(
25
+ {
26
+ clear: input.clear,
27
+ preset: input.preset,
28
+ },
29
+ {
30
+ configureCommandName: `libretto ai configure`,
31
+ },
32
+ );
33
+ }),
34
+ },
35
+ });
@@ -0,0 +1,165 @@
1
+ import { z } from "zod";
2
+ import type { LoggerApi } from "../../shared/logger/index.js";
3
+ import {
4
+ runClose as runCloseWithLogger,
5
+ runCloseAll as runCloseAllWithLogger,
6
+ runOpen,
7
+ runPages,
8
+ runSave,
9
+ } from "../core/browser.js";
10
+ import { withSessionLogger } from "../core/context.js";
11
+ import { assertSessionAvailableForStart } from "../core/session.js";
12
+ import { SimpleCLI } from "../framework/simple-cli.js";
13
+ import {
14
+ loadSessionStateMiddleware,
15
+ resolveSessionMiddleware,
16
+ sessionOption,
17
+ } from "./shared.js";
18
+
19
+ function parseViewportArg(
20
+ viewportArg: string | undefined,
21
+ ): { width: number; height: number } | undefined {
22
+ if (!viewportArg) return undefined;
23
+
24
+ const match = viewportArg.match(/^(\d+)x(\d+)$/i);
25
+ if (!match) {
26
+ throw new Error(
27
+ "Invalid --viewport format. Expected WIDTHxHEIGHT (e.g. 1920x1080).",
28
+ );
29
+ }
30
+
31
+ const width = Number(match[1]);
32
+ const height = Number(match[2]);
33
+ if (width < 1 || height < 1) {
34
+ throw new Error(
35
+ "Invalid --viewport dimensions. Width and height must be at least 1.",
36
+ );
37
+ }
38
+
39
+ return { width, height };
40
+ }
41
+
42
+ export const openInput = SimpleCLI.input({
43
+ positionals: [
44
+ SimpleCLI.positional("url", z.string().optional(), {
45
+ help: "URL to open",
46
+ }),
47
+ ],
48
+ named: {
49
+ session: sessionOption(),
50
+ headed: SimpleCLI.flag({ help: "Run browser in headed mode" }),
51
+ headless: SimpleCLI.flag({ help: "Run browser in headless mode" }),
52
+ viewport: SimpleCLI.option(z.string().optional(), {
53
+ help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)",
54
+ }),
55
+ },
56
+ })
57
+ .refine(
58
+ (input) => Boolean(input.url),
59
+ `Usage: libretto open <url> [--headless] [--viewport WxH] [--session <name>]`,
60
+ )
61
+ .refine(
62
+ (input) => !(input.headed && input.headless),
63
+ "Cannot pass both --headed and --headless.",
64
+ );
65
+
66
+ export function createOpenCommand(logger: LoggerApi) {
67
+ return SimpleCLI.command({
68
+ description: "Launch browser and open URL (headed by default)",
69
+ })
70
+ .input(openInput)
71
+ .use(resolveSessionMiddleware)
72
+ .handle(async ({ input, ctx }) => {
73
+ assertSessionAvailableForStart(ctx.session, logger);
74
+ const headed = input.headed || !input.headless;
75
+ const viewport = parseViewportArg(input.viewport);
76
+ await runOpen(input.url!, headed, ctx.session, logger, { viewport });
77
+ });
78
+ }
79
+
80
+ export const saveInput = SimpleCLI.input({
81
+ positionals: [
82
+ SimpleCLI.positional("urlOrDomain", z.string().optional(), {
83
+ help: "URL or domain to save",
84
+ }),
85
+ ],
86
+ named: {
87
+ session: sessionOption(),
88
+ },
89
+ }).refine(
90
+ (input) => Boolean(input.urlOrDomain),
91
+ `Usage: libretto save <url|domain> [--session <name>]`,
92
+ );
93
+
94
+ export function createSaveCommand(logger: LoggerApi) {
95
+ return SimpleCLI.command({
96
+ description: "Save current browser session",
97
+ })
98
+ .input(saveInput)
99
+ .use(resolveSessionMiddleware)
100
+ .use(loadSessionStateMiddleware)
101
+ .handle(async ({ input, ctx }) => {
102
+ await runSave(input.urlOrDomain!, ctx.session, logger);
103
+ });
104
+ }
105
+
106
+ export const pagesInput = SimpleCLI.input({
107
+ positionals: [],
108
+ named: {
109
+ session: sessionOption(),
110
+ },
111
+ });
112
+
113
+ export function createPagesCommand(logger: LoggerApi) {
114
+ return SimpleCLI.command({
115
+ description: "List open pages in the session",
116
+ })
117
+ .input(pagesInput)
118
+ .use(resolveSessionMiddleware)
119
+ .use(loadSessionStateMiddleware)
120
+ .handle(async ({ ctx }) => {
121
+ await runPages(ctx.session, logger);
122
+ });
123
+ }
124
+
125
+ export const closeInput = SimpleCLI.input({
126
+ positionals: [],
127
+ named: {
128
+ session: sessionOption(),
129
+ all: SimpleCLI.flag({ help: "Close all tracked sessions in this workspace" }),
130
+ force: SimpleCLI.flag({ help: "Force kill sessions that ignore SIGTERM (requires --all)" }),
131
+ },
132
+ });
133
+
134
+ export function createCloseCommand(logger: LoggerApi) {
135
+ return SimpleCLI.command({
136
+ description: "Close the browser",
137
+ })
138
+ .input(closeInput)
139
+ .use(resolveSessionMiddleware)
140
+ .handle(async ({ input, ctx }) => {
141
+ if (input.force && !input.all) {
142
+ throw new Error(`Usage: libretto close --all [--force]`);
143
+ }
144
+ if (input.all) {
145
+ await runCloseAllWithLogger(logger, { force: input.force });
146
+ return;
147
+ }
148
+ await runCloseWithLogger(ctx.session, logger);
149
+ });
150
+ }
151
+
152
+ export function createBrowserCommands(logger: LoggerApi) {
153
+ return {
154
+ open: createOpenCommand(logger),
155
+ save: createSaveCommand(logger),
156
+ pages: createPagesCommand(logger),
157
+ close: createCloseCommand(logger),
158
+ };
159
+ }
160
+
161
+ export async function runClose(session: string): Promise<void> {
162
+ await withSessionLogger(session, async (logger) => {
163
+ await runCloseWithLogger(session, logger);
164
+ });
165
+ }