libretto 0.3.2 → 0.4.1

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 (31) hide show
  1. package/dist/cli/cli.js +83 -223
  2. package/dist/cli/commands/ai.js +32 -18
  3. package/dist/cli/commands/browser.js +126 -85
  4. package/dist/cli/commands/execution.js +147 -108
  5. package/dist/cli/commands/init.js +234 -131
  6. package/dist/cli/commands/logs.js +90 -65
  7. package/dist/cli/commands/shared.js +50 -0
  8. package/dist/cli/commands/snapshot.js +62 -37
  9. package/dist/cli/core/ai-config.js +29 -44
  10. package/dist/cli/core/api-snapshot-analyzer.js +74 -0
  11. package/dist/cli/core/context.js +1 -1
  12. package/dist/cli/core/snapshot-analyzer.js +200 -87
  13. package/dist/cli/core/snapshot-api-config.js +137 -0
  14. package/dist/cli/framework/simple-cli.js +776 -0
  15. package/dist/cli/router.js +29 -0
  16. package/dist/shared/condense-dom/condense-dom.cjs +462 -0
  17. package/dist/shared/condense-dom/condense-dom.d.cts +34 -0
  18. package/dist/shared/condense-dom/condense-dom.d.ts +34 -0
  19. package/dist/shared/condense-dom/condense-dom.js +438 -0
  20. package/dist/shared/llm/ai-sdk-adapter.cjs +5 -1
  21. package/dist/shared/llm/ai-sdk-adapter.js +5 -1
  22. package/dist/shared/llm/client.cjs +106 -27
  23. package/dist/shared/llm/client.d.cts +8 -1
  24. package/dist/shared/llm/client.d.ts +8 -1
  25. package/dist/shared/llm/client.js +89 -23
  26. package/dist/shared/llm/types.d.cts +2 -1
  27. package/dist/shared/llm/types.d.ts +2 -1
  28. package/package.json +7 -4
  29. /package/{.agents/skills → skills}/libretto/SKILL.md +0 -0
  30. /package/{.agents/skills → skills}/libretto/code-generation-rules.md +0 -0
  31. /package/{.agents/skills → skills}/libretto/integration-approach-selection.md +0 -0
package/dist/cli/cli.js CHANGED
@@ -1,61 +1,18 @@
1
- import yargs from "yargs";
2
- import { hideBin } from "yargs/helpers";
3
- import { registerAICommands } from "./commands/ai.js";
4
- import { registerBrowserCommands } from "./commands/browser.js";
5
- import { registerExecutionCommands } from "./commands/execution.js";
6
- import { registerLogCommands } from "./commands/logs.js";
7
- import { registerInitCommand } from "./commands/init.js";
8
- import { registerSnapshotCommands } from "./commands/snapshot.js";
9
1
  import {
10
2
  closeLogger,
11
3
  createLoggerForSession,
12
4
  ensureLibrettoSetup
13
5
  } from "./core/context.js";
14
6
  import {
15
- listSessionsWithStateFile,
7
+ SESSION_DEFAULT,
16
8
  validateSessionName
17
9
  } from "./core/session.js";
18
- const AUTO_SESSION_COMMANDS = /* @__PURE__ */ new Set(["open", "run"]);
19
- const SESSION_OPTIONAL_COMMANDS = /* @__PURE__ */ new Set(["help", "--help", "-h", "init", "ai"]);
20
- const CLI_COMMANDS = /* @__PURE__ */ new Set([
21
- "open",
22
- "run",
23
- "ai",
24
- "save",
25
- "exec",
26
- "snapshot",
27
- "network",
28
- "actions",
29
- "pages",
30
- "resume",
31
- "close",
32
- "init",
33
- "--help",
34
- "-h",
35
- "help"
36
- ]);
37
- function printUsage() {
38
- console.log(`Usage: libretto-cli <command> [--session <name>]
39
-
40
- Commands:
41
- init [--skip-browsers] Initialize libretto (install browsers, check AI setup)
42
- open <url> [--headless] Launch browser and open URL (headed by default)
43
- Automatically loads saved profile if available
44
- run <integrationFile> <integrationExport> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] Run an exported Libretto workflow from a file
45
- ai configure [preset] [-- <command prefix...>] Configure AI runtime for analysis commands
46
- save <url|domain> Save current browser session (cookies, localStorage, etc.)
47
- exec <code> [--visualize] Execute Playwright typescript code (--visualize enables ghost cursor + highlight)
48
- snapshot [--objective <text> --context <text>] Capture PNG + HTML; analyze when objective is provided (context optional)
49
- network [--last N] [--filter regex] [--method M] [--clear] View captured network requests
50
- actions [--last N] [--filter regex] [--action TYPE] [--source SOURCE] [--clear] View captured actions
51
- pages List open pages in the active session
52
- resume Resume a paused workflow in the active session
53
- close [--all] [--force] Close the browser for the session, or all tracked sessions with --all
10
+ import { createCLIApp } from "./router.js";
11
+ function renderUsage(app) {
12
+ return `${app.renderHelp()}
54
13
 
55
14
  Options:
56
- --session <name> Use a named session
57
- If omitted for open/run, a session id is auto-generated
58
- All other stateful commands require --session
15
+ --session <name> Use a named session (default: "default")
59
16
  Built-in sessions: default, dev-server, browser-agent
60
17
 
61
18
  Examples:
@@ -67,10 +24,11 @@ Examples:
67
24
 
68
25
  libretto-cli exec "await page.locator('button:has-text(\\"Sign in\\")').click()"
69
26
  libretto-cli exec "await page.fill('input[name=\\"email\\"]', 'test@example.com')"
70
- libretto-cli ai configure codex
71
- libretto-cli ai configure claude
27
+ libretto-cli ai configure openai
28
+ libretto-cli ai configure anthropic
72
29
  libretto-cli ai configure gemini
73
- libretto-cli ai configure <codex|claude|gemini> -- <command prefix...>
30
+ libretto-cli ai configure vertex
31
+ libretto-cli ai configure openai/gpt-4o
74
32
  libretto-cli snapshot
75
33
  libretto-cli snapshot --objective "Find the submit button" --context "Submitting a referral form, already filled in patient details"
76
34
  libretto-cli resume --session default
@@ -96,201 +54,103 @@ Sessions:
96
54
  Session state is stored in .libretto/sessions/<session>/state.json
97
55
  CLI logs are stored in .libretto/sessions/<session>/logs.jsonl
98
56
  Each session runs an isolated browser instance on a dynamic port.
99
- `);
57
+ `;
100
58
  }
101
- function filterSessionArgs(args) {
102
- const result = [];
103
- for (let i = 0; i < args.length; i++) {
104
- if (args[i] === "--session") {
105
- i++;
106
- } else {
107
- result.push(args[i]);
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;
108
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;
109
76
  }
110
- return result;
77
+ return void 0;
111
78
  }
112
79
  function parseSessionForLog(rawArgs) {
113
- const idx = rawArgs.indexOf("--session");
114
- if (idx < 0) return null;
115
- const value = rawArgs[idx + 1];
116
- if (!value || value.startsWith("--") || CLI_COMMANDS.has(value)) {
117
- return null;
80
+ const value = readSessionArgBeforePassthrough(rawArgs);
81
+ if (value === void 0 || value === null) {
82
+ return SESSION_DEFAULT;
118
83
  }
119
84
  try {
120
85
  validateSessionName(value);
121
86
  return value;
122
87
  } catch {
123
- return null;
124
- }
125
- }
126
- function hasExplicitSession(rawArgs) {
127
- return rawArgs.includes("--session");
128
- }
129
- function randomSessionId() {
130
- const digits = Math.floor(Math.random() * 1e4).toString().padStart(4, "0");
131
- return `ses-${digits}`;
132
- }
133
- function generateSessionId() {
134
- const activeSessions = new Set(listSessionsWithStateFile());
135
- for (let attempt = 0; attempt < 1e4; attempt += 1) {
136
- const candidate = randomSessionId();
137
- if (!activeSessions.has(candidate)) {
138
- return candidate;
139
- }
88
+ return SESSION_DEFAULT;
140
89
  }
141
- throw new Error(
142
- "Could not generate an available session id. Close an existing session and try again."
143
- );
144
90
  }
145
- function hasExecCodeArg(filteredArgs) {
146
- for (let i = 1; i < filteredArgs.length; i += 1) {
147
- const token = filteredArgs[i];
148
- if (!token) continue;
149
- if (token === "--") {
150
- return filteredArgs.length > i + 1;
151
- }
152
- if (token === "--visualize") {
153
- continue;
154
- }
155
- if (token === "--page") {
156
- const maybeValue = filteredArgs[i + 1];
157
- if (maybeValue && !maybeValue.startsWith("--")) {
158
- i += 1;
159
- }
160
- continue;
161
- }
162
- if (token.startsWith("--page=")) {
163
- continue;
164
- }
165
- if (token.startsWith("-")) {
166
- continue;
167
- }
168
- return true;
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-cli <command> [--session <name>]\nMissing or invalid --session value."
97
+ );
169
98
  }
170
- return false;
99
+ validateSessionName(value);
171
100
  }
172
- function commandNeedsSession(command, rawArgs, filteredArgs) {
173
- if (AUTO_SESSION_COMMANDS.has(command)) return false;
174
- if (SESSION_OPTIONAL_COMMANDS.has(command)) return false;
175
- if (command === "close" && rawArgs.includes("--all")) return false;
176
- if (command === "close" && rawArgs.includes("--force")) return false;
177
- if (command === "exec" && !hasExecCodeArg(filteredArgs)) return false;
178
- if (command === "save" && filteredArgs.length <= 1) return false;
179
- if (!CLI_COMMANDS.has(command)) return false;
180
- return true;
101
+ function initializeLogger(rawArgs) {
102
+ const sessionForLog = parseSessionForLog(rawArgs);
103
+ const logger = createLoggerForSession(sessionForLog);
104
+ logger.info("cli-start", {
105
+ args: rawArgs,
106
+ cwd: process.cwd(),
107
+ session: sessionForLog
108
+ });
109
+ return logger;
181
110
  }
182
- function resolveSessionArgs(rawArgs) {
183
- const filtered = filterSessionArgs(rawArgs);
184
- const command = filtered[0];
185
- const explicitSession = parseSessionForLog(rawArgs);
186
- if (!command) {
187
- return {
188
- args: rawArgs,
189
- generatedSession: null,
190
- resolvedSession: explicitSession
191
- };
192
- }
193
- if (hasExplicitSession(rawArgs)) {
194
- return {
195
- args: rawArgs,
196
- generatedSession: null,
197
- resolvedSession: explicitSession
198
- };
199
- }
200
- if (!AUTO_SESSION_COMMANDS.has(command)) {
201
- return {
202
- args: rawArgs,
203
- generatedSession: null,
204
- resolvedSession: null
205
- };
111
+ async function withCliLogger(rawArgs, run) {
112
+ const logger = initializeLogger(rawArgs);
113
+ try {
114
+ return await run(logger);
115
+ } finally {
116
+ await closeLogger(logger);
206
117
  }
207
- const generatedSession = generateSessionId();
208
- return {
209
- args: [...rawArgs, "--session", generatedSession],
210
- generatedSession,
211
- resolvedSession: generatedSession
212
- };
213
118
  }
214
- function createParser(logger) {
215
- let parser = yargs(hideBin(process.argv)).scriptName("libretto-cli").parserConfiguration({ "populate--": true }).option("session", {
216
- type: "string",
217
- describe: "Use a named session",
218
- global: true,
219
- requiresArg: true
220
- }).middleware((argv) => {
221
- if (argv.session !== void 0) {
222
- validateSessionName(String(argv.session));
223
- }
224
- }).exitProcess(false).help(false).version(false).fail((msg, err) => {
225
- if (err) throw err;
226
- throw new Error(msg || "Command failed");
227
- });
228
- parser = registerBrowserCommands(parser, logger);
229
- parser = registerExecutionCommands(parser, logger);
230
- parser = registerLogCommands(parser);
231
- parser = registerAICommands(parser);
232
- parser = registerSnapshotCommands(parser, logger);
233
- parser = registerInitCommand(parser);
234
- parser = parser.command("help", "Show usage", () => {
235
- }, () => {
236
- printUsage();
237
- });
238
- return parser;
119
+ function isRootHelpRequest(rawArgs) {
120
+ if (rawArgs.length === 0) return true;
121
+ if (rawArgs[0] === "--help" || rawArgs[0] === "-h") return true;
122
+ return rawArgs[0] === "help" && rawArgs.length === 1;
239
123
  }
240
124
  async function runLibrettoCLI() {
241
125
  const rawArgs = process.argv.slice(2);
242
126
  let exitCode = 0;
243
- let effectiveArgs = rawArgs;
244
- let generatedSession = null;
245
- let resolvedSession = null;
246
- ({
247
- args: effectiveArgs,
248
- generatedSession,
249
- resolvedSession
250
- } = resolveSessionArgs(rawArgs));
251
127
  ensureLibrettoSetup();
252
- const args = filterSessionArgs(effectiveArgs);
253
- const command = args[0];
254
- if (!command || command === "--help" || command === "-h" || command === "help") {
255
- printUsage();
256
- process.exit(exitCode);
257
- return;
258
- }
259
- if (!CLI_COMMANDS.has(command)) {
260
- console.error(`Unknown command: ${command}
128
+ await withCliLogger(rawArgs, async (logger) => {
129
+ const app = createCLIApp(logger);
130
+ try {
131
+ validateLegacySessionArg(rawArgs);
132
+ if (isRootHelpRequest(rawArgs)) {
133
+ console.log(renderUsage(app));
134
+ return;
135
+ }
136
+ logger.info("cli-command", { args: rawArgs });
137
+ const result = await app.run(rawArgs);
138
+ if (typeof result === "string") {
139
+ console.log(result);
140
+ }
141
+ } catch (err) {
142
+ logger.error("cli-error", { error: err, args: rawArgs });
143
+ const message = err instanceof Error ? err.message : String(err);
144
+ if (message.startsWith("Unknown command: ")) {
145
+ console.error(`${message}
261
146
  `);
262
- printUsage();
263
- process.exit(1);
264
- return;
265
- }
266
- if (!hasExplicitSession(effectiveArgs) && commandNeedsSession(command, effectiveArgs, args)) {
267
- console.error(
268
- [
269
- `Missing required --session for "${command}".`,
270
- "Pass --session <name>, or use open/run without --session to auto-create one."
271
- ].join("\n")
272
- );
273
- process.exit(1);
274
- return;
275
- }
276
- const sessionForLogger = resolvedSession ?? "cli";
277
- const logger = createLoggerForSession(sessionForLogger);
278
- try {
279
- const parser = createParser(logger);
280
- await parser.parseAsync(effectiveArgs);
281
- } catch (err) {
282
- logger.error("cli-error", {
283
- error: err,
284
- args: rawArgs,
285
- effectiveArgs,
286
- generatedSession
287
- });
288
- const message = err instanceof Error ? err.message : String(err);
289
- console.error(message);
290
- exitCode = 1;
291
- } finally {
292
- await closeLogger(logger);
293
- }
147
+ console.log(renderUsage(app));
148
+ } else {
149
+ console.error(message);
150
+ }
151
+ exitCode = 1;
152
+ }
153
+ });
294
154
  process.exit(exitCode);
295
155
  }
296
156
  export {
@@ -1,21 +1,35 @@
1
+ import { z } from "zod";
1
2
  import { runAiConfigure } from "../core/ai-config.js";
2
- function registerAICommands(yargs) {
3
- return yargs.command(
4
- "ai configure [preset]",
5
- "Configure AI runtime",
6
- (cmd) => cmd.option("clear", { type: "boolean", default: false }),
7
- (argv) => {
8
- const customPrefix = Array.isArray(argv["--"]) ? argv["--"] : [];
9
- runAiConfigure({
10
- clear: Boolean(argv.clear),
11
- preset: argv.preset,
12
- customPrefix
13
- }, {
14
- configureCommandName: "libretto-cli ai configure"
15
- });
16
- }
17
- );
18
- }
3
+ import { SimpleCLI } from "../framework/simple-cli.js";
4
+ const aiConfigureInput = SimpleCLI.input({
5
+ positionals: [
6
+ SimpleCLI.positional("preset", z.string().optional(), {
7
+ help: "Provider shorthand or provider/model-id"
8
+ })
9
+ ],
10
+ named: {
11
+ clear: SimpleCLI.flag({ help: "Clear existing AI config" })
12
+ }
13
+ });
14
+ const aiCommands = SimpleCLI.group({
15
+ description: "AI commands",
16
+ routes: {
17
+ configure: SimpleCLI.command({
18
+ description: "Configure AI runtime"
19
+ }).input(aiConfigureInput).handle(async ({ input }) => {
20
+ runAiConfigure(
21
+ {
22
+ clear: input.clear,
23
+ preset: input.preset
24
+ },
25
+ {
26
+ configureCommandName: "libretto-cli ai configure"
27
+ }
28
+ );
29
+ })
30
+ }
31
+ });
19
32
  export {
20
- registerAICommands
33
+ aiCommands,
34
+ aiConfigureInput
21
35
  };
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import {
2
3
  runClose as runCloseWithLogger,
3
4
  runCloseAll as runCloseAllWithLogger,
@@ -6,91 +7,123 @@ import {
6
7
  runSave
7
8
  } from "../core/browser.js";
8
9
  import { withSessionLogger } from "../core/context.js";
9
- function registerBrowserCommands(yargs, logger) {
10
- return yargs.command(
11
- "open [url]",
12
- "Launch browser and open URL (headed by default)",
13
- (cmd) => cmd.option("headed", {
14
- type: "boolean",
15
- default: false
16
- }).option("headless", {
17
- type: "boolean",
18
- default: false
19
- }).option("viewport", {
20
- type: "string",
21
- describe: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)"
22
- }),
23
- async (argv) => {
24
- const hasHeadedFlag = Boolean(argv.headed);
25
- const hasHeadlessFlag = Boolean(argv.headless);
26
- if (hasHeadedFlag && hasHeadlessFlag) {
27
- throw new Error("Cannot pass both --headed and --headless.");
28
- }
29
- const headed = hasHeadedFlag || !hasHeadlessFlag;
30
- const url = argv.url;
31
- if (!url) {
32
- throw new Error(
33
- "Usage: libretto-cli open <url> [--headless] [--viewport WxH] [--session <name>]"
34
- );
35
- }
36
- const viewportArg = argv.viewport;
37
- let viewport;
38
- if (viewportArg) {
39
- const match = viewportArg.match(/^(\d+)x(\d+)$/i);
40
- if (!match) {
41
- throw new Error(
42
- "Invalid --viewport format. Expected WIDTHxHEIGHT (e.g. 1920x1080)."
43
- );
44
- }
45
- const w = Number(match[1]);
46
- const h = Number(match[2]);
47
- if (w < 1 || h < 1) {
48
- throw new Error(
49
- "Invalid --viewport dimensions. Width and height must be at least 1."
50
- );
51
- }
52
- viewport = { width: w, height: h };
53
- }
54
- await runOpen(url, headed, String(argv.session), logger, { viewport });
55
- }
56
- ).command(
57
- "save [urlOrDomain]",
58
- "Save current browser session",
59
- (cmd) => cmd,
60
- async (argv) => {
61
- const urlOrDomain = argv.urlOrDomain;
62
- if (!urlOrDomain) {
63
- throw new Error("Usage: libretto-cli save <url|domain> [--session <name>]");
64
- }
65
- await runSave(urlOrDomain, String(argv.session), logger);
10
+ import { assertSessionAvailableForStart } from "../core/session.js";
11
+ import { SimpleCLI } from "../framework/simple-cli.js";
12
+ import {
13
+ loadSessionStateMiddleware,
14
+ resolveSessionMiddleware,
15
+ sessionOption
16
+ } from "./shared.js";
17
+ function parseViewportArg(viewportArg) {
18
+ if (!viewportArg) return void 0;
19
+ const match = viewportArg.match(/^(\d+)x(\d+)$/i);
20
+ if (!match) {
21
+ throw new Error(
22
+ "Invalid --viewport format. Expected WIDTHxHEIGHT (e.g. 1920x1080)."
23
+ );
24
+ }
25
+ const width = Number(match[1]);
26
+ const height = Number(match[2]);
27
+ if (width < 1 || height < 1) {
28
+ throw new Error(
29
+ "Invalid --viewport dimensions. Width and height must be at least 1."
30
+ );
31
+ }
32
+ return { width, height };
33
+ }
34
+ const openInput = SimpleCLI.input({
35
+ positionals: [
36
+ SimpleCLI.positional("url", z.string().optional(), {
37
+ help: "URL to open"
38
+ })
39
+ ],
40
+ named: {
41
+ session: sessionOption(),
42
+ headed: SimpleCLI.flag({ help: "Run browser in headed mode" }),
43
+ headless: SimpleCLI.flag({ help: "Run browser in headless mode" }),
44
+ viewport: SimpleCLI.option(z.string().optional(), {
45
+ help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)"
46
+ })
47
+ }
48
+ }).refine(
49
+ (input) => Boolean(input.url),
50
+ "Usage: libretto-cli open <url> [--headless] [--viewport WxH] [--session <name>]"
51
+ ).refine(
52
+ (input) => !(input.headed && input.headless),
53
+ "Cannot pass both --headed and --headless."
54
+ );
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
+ }
65
+ const saveInput = SimpleCLI.input({
66
+ positionals: [
67
+ SimpleCLI.positional("urlOrDomain", z.string().optional(), {
68
+ help: "URL or domain to save"
69
+ })
70
+ ],
71
+ named: {
72
+ session: sessionOption()
73
+ }
74
+ }).refine(
75
+ (input) => Boolean(input.urlOrDomain),
76
+ "Usage: libretto-cli save <url|domain> [--session <name>]"
77
+ );
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
+ }
85
+ const pagesInput = SimpleCLI.input({
86
+ positionals: [],
87
+ named: {
88
+ session: sessionOption()
89
+ }
90
+ });
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
+ }
98
+ const closeInput = SimpleCLI.input({
99
+ positionals: [],
100
+ named: {
101
+ session: sessionOption(),
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)" })
104
+ }
105
+ });
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-cli close --all [--force]");
66
112
  }
67
- ).command("pages", "List open pages in the session", (cmd) => cmd, async (argv) => {
68
- await runPages(String(argv.session), logger);
69
- }).command(
70
- "close",
71
- "Close the browser",
72
- (cmd) => cmd.option("all", {
73
- type: "boolean",
74
- default: false,
75
- describe: "Close all tracked sessions in this workspace"
76
- }).option("force", {
77
- type: "boolean",
78
- default: false,
79
- describe: "Force kill sessions that ignore SIGTERM (requires --all)"
80
- }),
81
- async (argv) => {
82
- const closeAll = Boolean(argv.all);
83
- const force = Boolean(argv.force);
84
- if (force && !closeAll) {
85
- throw new Error("Usage: libretto-cli close --all [--force]");
86
- }
87
- if (closeAll) {
88
- await runCloseAllWithLogger(logger, { force });
89
- return;
90
- }
91
- await runCloseWithLogger(String(argv.session), logger);
113
+ if (input.all) {
114
+ await runCloseAllWithLogger(logger, { force: input.force });
115
+ return;
92
116
  }
93
- );
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
+ };
94
127
  }
95
128
  async function runClose(session) {
96
129
  await withSessionLogger(session, async (logger) => {
@@ -98,6 +131,14 @@ async function runClose(session) {
98
131
  });
99
132
  }
100
133
  export {
101
- registerBrowserCommands,
102
- runClose
134
+ closeInput,
135
+ createBrowserCommands,
136
+ createCloseCommand,
137
+ createOpenCommand,
138
+ createPagesCommand,
139
+ createSaveCommand,
140
+ openInput,
141
+ pagesInput,
142
+ runClose,
143
+ saveInput
103
144
  };