libretto 0.5.5 → 0.6.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 (110) hide show
  1. package/README.md +23 -10
  2. package/README.template.md +23 -10
  3. package/dist/cli/cli.js +10 -0
  4. package/dist/cli/commands/ai.js +77 -2
  5. package/dist/cli/commands/browser.js +98 -8
  6. package/dist/cli/commands/execution.js +152 -56
  7. package/dist/cli/commands/setup.js +390 -0
  8. package/dist/cli/commands/snapshot.js +2 -2
  9. package/dist/cli/commands/status.js +62 -0
  10. package/dist/cli/core/{snapshot-api-config.js → ai-model.js} +81 -7
  11. package/dist/cli/core/api-snapshot-analyzer.js +7 -5
  12. package/dist/cli/core/browser.js +202 -36
  13. package/dist/cli/core/{ai-config.js → config.js} +14 -79
  14. package/dist/cli/core/context.js +1 -25
  15. package/dist/cli/core/deploy-artifact.js +121 -61
  16. package/dist/cli/core/providers/browserbase.js +53 -0
  17. package/dist/cli/core/providers/index.js +48 -0
  18. package/dist/cli/core/providers/kernel.js +46 -0
  19. package/dist/cli/core/providers/libretto-cloud.js +58 -0
  20. package/dist/cli/core/readonly-exec.js +231 -0
  21. package/dist/{shared/llm/client.js → cli/core/resolve-model.js} +4 -68
  22. package/dist/cli/core/session.js +53 -0
  23. package/dist/cli/core/skill-version.js +73 -0
  24. package/dist/cli/core/telemetry.js +1 -54
  25. package/dist/cli/index.js +1 -7
  26. package/dist/cli/router.js +4 -4
  27. package/dist/cli/workers/run-integration-runtime.js +19 -13
  28. package/dist/cli/workers/run-integration-worker-protocol.js +5 -2
  29. package/dist/index.d.ts +2 -4
  30. package/dist/index.js +2 -2
  31. package/dist/runtime/extract/extract.d.ts +2 -2
  32. package/dist/runtime/extract/extract.js +4 -2
  33. package/dist/runtime/extract/index.d.ts +1 -1
  34. package/dist/runtime/recovery/agent.d.ts +2 -3
  35. package/dist/runtime/recovery/agent.js +5 -3
  36. package/dist/runtime/recovery/errors.d.ts +2 -3
  37. package/dist/runtime/recovery/errors.js +4 -2
  38. package/dist/runtime/recovery/index.d.ts +1 -2
  39. package/dist/runtime/recovery/recovery.d.ts +2 -3
  40. package/dist/runtime/recovery/recovery.js +3 -3
  41. package/dist/shared/debug/pause.js +4 -21
  42. package/dist/shared/run/api.d.ts +2 -0
  43. package/dist/shared/run/browser.d.ts +9 -1
  44. package/dist/shared/run/browser.js +43 -3
  45. package/dist/shared/state/index.d.ts +1 -1
  46. package/dist/shared/state/index.js +2 -0
  47. package/dist/shared/state/session-state.d.ts +20 -1
  48. package/dist/shared/state/session-state.js +12 -2
  49. package/dist/shared/workflow/workflow.d.ts +2 -1
  50. package/dist/shared/workflow/workflow.js +16 -9
  51. package/package.json +17 -16
  52. package/scripts/postinstall.mjs +13 -11
  53. package/scripts/skills-libretto.mjs +14 -4
  54. package/skills/AGENTS.md +11 -0
  55. package/skills/libretto/SKILL.md +30 -9
  56. package/skills/libretto/references/auth-profiles.md +1 -1
  57. package/skills/libretto/references/code-generation-rules.md +3 -3
  58. package/skills/libretto/references/configuration-file-reference.md +11 -6
  59. package/skills/libretto-readonly/SKILL.md +95 -0
  60. package/src/cli/cli.ts +10 -0
  61. package/src/cli/commands/ai.ts +111 -1
  62. package/src/cli/commands/browser.ts +111 -9
  63. package/src/cli/commands/execution.ts +181 -74
  64. package/src/cli/commands/setup.ts +516 -0
  65. package/src/cli/commands/snapshot.ts +2 -2
  66. package/src/cli/commands/status.ts +79 -0
  67. package/src/cli/core/{snapshot-api-config.ts → ai-model.ts} +154 -14
  68. package/src/cli/core/api-snapshot-analyzer.ts +7 -5
  69. package/src/cli/core/browser.ts +242 -35
  70. package/src/cli/core/{ai-config.ts → config.ts} +14 -108
  71. package/src/cli/core/context.ts +1 -45
  72. package/src/cli/core/deploy-artifact.ts +141 -71
  73. package/src/cli/core/providers/browserbase.ts +57 -0
  74. package/src/cli/core/providers/index.ts +62 -0
  75. package/src/cli/core/providers/kernel.ts +49 -0
  76. package/src/cli/core/providers/libretto-cloud.ts +61 -0
  77. package/src/cli/core/providers/types.ts +9 -0
  78. package/src/cli/core/readonly-exec.ts +284 -0
  79. package/src/{shared/llm/client.ts → cli/core/resolve-model.ts} +3 -85
  80. package/src/cli/core/session.ts +75 -2
  81. package/src/cli/core/skill-version.ts +93 -0
  82. package/src/cli/core/telemetry.ts +0 -52
  83. package/src/cli/index.ts +0 -6
  84. package/src/cli/router.ts +4 -4
  85. package/src/cli/workers/run-integration-runtime.ts +18 -16
  86. package/src/cli/workers/run-integration-worker-protocol.ts +4 -1
  87. package/src/index.ts +1 -7
  88. package/src/runtime/extract/extract.ts +6 -5
  89. package/src/runtime/recovery/agent.ts +5 -4
  90. package/src/runtime/recovery/errors.ts +4 -3
  91. package/src/runtime/recovery/recovery.ts +4 -4
  92. package/src/shared/debug/pause.ts +4 -23
  93. package/src/shared/run/browser.ts +50 -1
  94. package/src/shared/state/index.ts +2 -0
  95. package/src/shared/state/session-state.ts +10 -0
  96. package/src/shared/workflow/workflow.ts +24 -13
  97. package/dist/cli/commands/init.js +0 -286
  98. package/dist/cli/commands/logs.js +0 -117
  99. package/dist/shared/llm/ai-sdk-adapter.d.ts +0 -22
  100. package/dist/shared/llm/ai-sdk-adapter.js +0 -49
  101. package/dist/shared/llm/client.d.ts +0 -13
  102. package/dist/shared/llm/index.d.ts +0 -5
  103. package/dist/shared/llm/index.js +0 -6
  104. package/dist/shared/llm/types.d.ts +0 -67
  105. package/src/cli/commands/init.ts +0 -331
  106. package/src/cli/commands/logs.ts +0 -128
  107. package/src/shared/llm/ai-sdk-adapter.ts +0 -81
  108. package/src/shared/llm/index.ts +0 -3
  109. package/src/shared/llm/types.ts +0 -63
  110. /package/dist/{shared/llm → cli/core/providers}/types.js +0 -0
@@ -6,7 +6,7 @@ Follow the user's existing codebase conventions, abstractions, and patterns when
6
6
 
7
7
  ## Workflow File Structure
8
8
 
9
- Generated files must export a `workflow()` instance so they can be run via `npx libretto run <file> <workflowName>`. Import `workflow` and its types from `"libretto"`:
9
+ Generated files must default-export a `workflow()` instance so they can be run via `npx libretto run <file>`. Import `workflow` and its types from `"libretto"`:
10
10
 
11
11
  ```typescript
12
12
  import { workflow, pause, type LibrettoWorkflowContext } from "libretto";
@@ -22,7 +22,7 @@ type Output = {
22
22
  results: Array<{ name: string; value: string }>;
23
23
  };
24
24
 
25
- export const myWorkflow = workflow<Input, Output>(
25
+ export default workflow<Input, Output>(
26
26
  "myWorkflow",
27
27
  async (ctx: LibrettoWorkflowContext, input): Promise<Output> => {
28
28
  const { session, page } = ctx;
@@ -39,7 +39,7 @@ export const myWorkflow = workflow<Input, Output>(
39
39
  Key points:
40
40
 
41
41
  - `workflow(name, handler)` takes a unique workflow name and returns the workflow object that Libretto can run.
42
- - `npx libretto run ./file.ts myWorkflow` resolves `myWorkflow` from the workflows exported by `./file.ts`, so export or re-export the workflow from that file directly or through a `workflows` object, and make sure the run argument matches the name passed to `workflow("myWorkflow", ...)`.
42
+ - `npx libretto run ./file.ts` executes the file's default-exported workflow, so always use `export default workflow(...)`.
43
43
  - `ctx` provides `session` and `page`. Use `console.log`/`console.warn`/`console.error` for logging — the runtime wraps these with structured metadata automatically.
44
44
  - `input` comes from `--params '{"query":"foo"}'` or `--params-file params.json` on the CLI
45
45
  - Use `await pause(ctx.session)` (or `await pause(session)`) to pause the workflow for debugging. It is a no-op in production.
@@ -12,8 +12,9 @@ Use this reference when you need to inspect or change the workspace configuratio
12
12
 
13
13
  Libretto reads workspace config from `.libretto/config.json`.
14
14
 
15
- - The file is usually created or updated by `npx libretto ai configure ...`.
15
+ - The file is created by `npx libretto setup` during first-time onboarding (auto-pins the default model for the detected provider) or by `npx libretto ai configure ...` for explicit overrides.
16
16
  - API credentials still come from your shell environment or `.env`. The config file stores the selected model, not the secret itself.
17
+ - Use `npx libretto status` to inspect the current AI configuration and open sessions without changing anything.
17
18
  - For first-time setup instructions, follow the main `SKILL.md` flow instead of expanding this reference.
18
19
 
19
20
  ## Supported Settings
@@ -21,6 +22,7 @@ Libretto reads workspace config from `.libretto/config.json`.
21
22
  - `ai.model` selects the configured analysis model for `snapshot`.
22
23
  - `viewport` is an optional top-level setting used by `open` and `run` when you do not pass `--viewport`.
23
24
  - Viewport precedence is: CLI `--viewport`, then `.libretto/config.json`, then the default `1366x768`.
25
+ - `sessionMode` sets the default session access mode for new sessions created by `open`, `connect`, and `run`. Must be `"read-only"` or `"write-access"`. When omitted, defaults to `"write-access"`. Pass `--read-only` or `--write-access` to `open`, `connect`, or `run` to override when creating a session.
24
26
 
25
27
  Example:
26
28
 
@@ -34,20 +36,23 @@ Example:
34
36
  "viewport": {
35
37
  "width": 1280,
36
38
  "height": 800
37
- }
39
+ },
40
+ "sessionMode": "write-access"
38
41
  }
39
42
  ```
40
43
 
41
44
  ## Common Commands
42
45
 
43
46
  ```bash
44
- npx libretto init
45
- npx libretto ai configure openai
47
+ npx libretto setup # first-time onboarding, auto-pins default model
48
+ npx libretto status # inspect AI config and open sessions
49
+ npx libretto ai configure openai # explicitly change provider/model
46
50
  npx libretto open https://app.example.com --viewport 1440x900
47
- npx libretto run ./integration.ts main --viewport 1440x900
51
+ npx libretto run ./integration.ts --viewport 1440x900
48
52
  ```
49
53
 
50
54
  ## Notes
51
55
 
52
56
  - If you want a persistent default viewport for the workspace, add `viewport` to `.libretto/config.json` instead of repeating `--viewport` on every command.
53
- - If `snapshot` analysis is not configured yet, return to the setup steps in the main `SKILL.md` flow.
57
+ - If `snapshot` analysis is not configured yet, run `npx libretto setup` to auto-configure, or see the main `SKILL.md` flow.
58
+ - Run `npx libretto status` at any time to check which model is active and whether credentials are present.
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: libretto-readonly
3
+ description: "Read-only Libretto workflow for diagnosing live browser state without clicks, typing, navigation, or mutation requests."
4
+ license: MIT
5
+ metadata:
6
+ author: saffron-health
7
+ version: "0.5.6"
8
+ ---
9
+
10
+ ## How Libretto Read-Only Works
11
+
12
+ - Use this skill when the browser session must stay strictly read-only.
13
+ - Libretto stores read-only vs write-access on the session itself.
14
+ - The primary inspection tools are `snapshot` and `readonly-exec`.
15
+ - `readonly-exec` reuses Libretto's normal execution pipeline, but it only exposes read-only helpers and denies mutating Playwright methods.
16
+ - Only a user can change the session mode for an existing session. Never change a session's mode on your own — the user must change it themselves manually.
17
+
18
+ ## Working Rules
19
+
20
+ - Announce which session you are using and what page you are inspecting.
21
+ - Do not use `exec`, `run`, or any direct Playwright action that could change browser or application state.
22
+ - Do not click, type, submit forms, navigate, upload files, dispatch DOM events, or send non-GET requests.
23
+ - Prefer `snapshot` first when the visible page state is unclear.
24
+ - Use `readonly-exec` for focused inspection: titles, HTML, locator text, counts, visibility checks, and GET requests.
25
+ - Keep snippets small and purpose-built. Do not run multiple `readonly-exec` commands at the same time.
26
+ - End with diagnosis and handoff guidance, not an attempted in-browser repair.
27
+
28
+ ## Commands
29
+
30
+ ### `connect`
31
+
32
+ - Use `connect` to attach to an existing CDP endpoint for a preserved browser session.
33
+ - Use `--read-only` when creating the Libretto session handle for a preserved browser session.
34
+ - Libretto read-only mode is enforced through Libretto commands; direct CDP clients that skip Libretto are outside this boundary.
35
+
36
+ ```bash
37
+ npx libretto connect http://127.0.0.1:9222 --read-only --session failed-job-debug
38
+ ```
39
+
40
+ ### `pages`
41
+
42
+ - Use `pages` when a popup, new tab, or second page exists.
43
+ - If `readonly-exec` or `snapshot` complains about multiple pages, list ids first and then pass `--page`.
44
+
45
+ ```bash
46
+ npx libretto pages --session failed-job-debug
47
+ ```
48
+
49
+ ### `snapshot`
50
+
51
+ - Use `snapshot` as the first high-level observation tool.
52
+ - Always provide both `--objective` and `--context`.
53
+
54
+ ```bash
55
+ npx libretto snapshot \
56
+ --session failed-job-debug \
57
+ --objective "Identify the visible failure state and likely blocking UI condition" \
58
+ --context "The workflow already failed and the preserved browser must remain read-only."
59
+ ```
60
+
61
+ ### `readonly-exec`
62
+
63
+ - Use `readonly-exec` for narrow inspection code only.
64
+ - Denied operations fail with `ReadonlyExecDenied: ...`.
65
+
66
+ #### Helpers
67
+
68
+ - `page` — a read-only Playwright `Page` proxy. Standard Playwright read methods work normally (`url()`, `title()`, `content()`, `getByRole()`, `locator()`, `textContent()`, `isVisible()`, `count()`, `scrollIntoViewIfNeeded()`, etc.). Anything that mutates the page (`click`, `fill`, `goto`, `evaluate`, `keyboard`, `mouse`) is blocked.
69
+ - `state` — the current Libretto session state object.
70
+ - `get(url, options?)` — HTTP client restricted to **GET and HEAD** requests. Replaces `fetch`, which is blocked in readonly mode. Any request with a body or a non-GET/HEAD method throws `ReadonlyExecDenied`.
71
+ - `scrollBy(deltaX, deltaY)` — scroll the viewport by pixel offset. Use this to inspect content below the fold without targeting a specific element.
72
+
73
+ Standard JS globals `console`, `URL`, `Buffer`, `setTimeout`, and `setInterval` are also available.
74
+
75
+ #### Examples
76
+
77
+ ```bash
78
+ npx libretto readonly-exec "return page.url()" --session failed-job-debug
79
+ npx libretto readonly-exec "return await page.getByRole('heading').first().textContent()" --session failed-job-debug
80
+
81
+ # HTTP GET inspection
82
+ echo "const r = await get('https://api.example.com/status'); return await r.json()" \
83
+ | npx libretto readonly-exec - --session failed-job-debug
84
+
85
+ # Scroll down to inspect below-the-fold content
86
+ npx libretto readonly-exec "await scrollBy(0, 500)" --session failed-job-debug
87
+ ```
88
+
89
+ ### `close`
90
+
91
+ - Use `close` when the inspection session is no longer needed.
92
+
93
+ ```bash
94
+ npx libretto close --session failed-job-debug
95
+ ```
package/src/cli/cli.ts CHANGED
@@ -16,6 +16,10 @@ Examples:
16
16
 
17
17
  libretto exec "await page.locator('button:has-text(\\"Sign in\\")').click()"
18
18
  libretto exec "await page.fill('input[name=\\"email\\"]', 'test@example.com')"
19
+ libretto readonly-exec "return await page.title()" --session test1
20
+ libretto connect http://127.0.0.1:9222 --read-only --session test1
21
+ libretto run ./integration.ts --read-only --session test1
22
+ libretto status
19
23
  libretto ai configure openai
20
24
  libretto ai configure anthropic
21
25
  libretto ai configure gemini
@@ -36,6 +40,9 @@ Examples:
36
40
  Available in exec:
37
41
  page, context, state, browser, networkLog, actionLog
38
42
 
43
+ Available in readonly-exec:
44
+ page, state, snapshot, scrollBy, get
45
+
39
46
  Profiles:
40
47
  Profiles are saved to .libretto/profiles/<domain>.json (git-ignored)
41
48
  They persist cookies, localStorage, and session data across browser launches.
@@ -46,6 +53,9 @@ Sessions:
46
53
  Session state is stored in .libretto/sessions/<session>/state.json
47
54
  CLI logs are stored in .libretto/sessions/<session>/logs.jsonl
48
55
  Each session runs an isolated browser instance on a dynamic port.
56
+ Session mode is stored per session as read-only or write-access.
57
+ Use --read-only on open, connect, or run to create a read-only session.
58
+ Session mode is enforced by Libretto commands, not by raw CDP clients outside Libretto.
49
59
  `;
50
60
  }
51
61
 
@@ -1,7 +1,117 @@
1
1
  import { z } from "zod";
2
- import { runAiConfigure } from "../core/ai-config.js";
2
+ import {
3
+ CURRENT_CONFIG_VERSION,
4
+ readAiConfig,
5
+ writeAiConfig,
6
+ clearAiConfig,
7
+ type AiConfig,
8
+ } from "../core/config.js";
9
+ import { LIBRETTO_CONFIG_PATH } from "../core/context.js";
10
+ import { DEFAULT_SNAPSHOT_MODELS } from "../core/ai-model.js";
3
11
  import { SimpleCLI } from "../framework/simple-cli.js";
4
12
 
13
+ const PROVIDER_ALIASES: Record<string, string> = {
14
+ claude: DEFAULT_SNAPSHOT_MODELS.anthropic,
15
+ gemini: DEFAULT_SNAPSHOT_MODELS.google,
16
+ google: DEFAULT_SNAPSHOT_MODELS.google,
17
+ };
18
+
19
+ const CONFIGURE_PROVIDERS = [
20
+ "openai",
21
+ "anthropic",
22
+ "gemini",
23
+ "vertex",
24
+ ] as const;
25
+
26
+ function formatConfigureProviders(separator = " | "): string {
27
+ return CONFIGURE_PROVIDERS.join(separator);
28
+ }
29
+
30
+ function printAiConfig(config: AiConfig, configPath: string): void {
31
+ console.log(`Model: ${config.model}`);
32
+ console.log(`Config file: ${configPath}`);
33
+ console.log(`Updated at: ${config.updatedAt}`);
34
+ }
35
+
36
+ /**
37
+ * Resolve the model string from a `ai configure` argument.
38
+ * Accepts a provider shorthand ("openai", "anthropic", "gemini", "vertex")
39
+ * or a full provider/model-id string ("openai/gpt-4o", "anthropic/claude-sonnet-4-6").
40
+ */
41
+ function resolveModelFromInput(input: string): string | null {
42
+ const trimmed = input.trim();
43
+ if (!trimmed) return null;
44
+
45
+ // Full model string (contains a slash)
46
+ if (trimmed.includes("/")) return trimmed;
47
+
48
+ // Provider shorthand
49
+ const normalized = trimmed.toLowerCase();
50
+ return (
51
+ (DEFAULT_SNAPSHOT_MODELS as Record<string, string>)[normalized] ??
52
+ PROVIDER_ALIASES[normalized] ??
53
+ null
54
+ );
55
+ }
56
+
57
+ export function runAiConfigure(
58
+ input: {
59
+ preset?: string;
60
+ clear?: boolean;
61
+ },
62
+ options: {
63
+ configureCommandName?: string;
64
+ configPath?: string;
65
+ } = {},
66
+ ): void {
67
+ const configureCommandName =
68
+ options.configureCommandName ?? "npx libretto ai configure";
69
+ const configPath = options.configPath ?? LIBRETTO_CONFIG_PATH;
70
+
71
+ const presetArg = input.preset?.trim();
72
+
73
+ if (!presetArg && !input.clear) {
74
+ const config = readAiConfig(configPath);
75
+ if (!config) {
76
+ console.log(
77
+ `No AI config set. Choose a default model: ${configureCommandName} ${formatConfigureProviders()}`,
78
+ );
79
+ console.log(
80
+ "Provider credentials still come from your shell or .env file.",
81
+ );
82
+ return;
83
+ }
84
+ printAiConfig(config, configPath);
85
+ return;
86
+ }
87
+
88
+ if (input.clear) {
89
+ const removed = clearAiConfig(configPath);
90
+ if (removed) {
91
+ console.log(`Cleared AI config: ${configPath}`);
92
+ } else {
93
+ console.log("No AI config was set.");
94
+ }
95
+ return;
96
+ }
97
+
98
+ const model = resolveModelFromInput(presetArg!);
99
+ if (!model) {
100
+ console.log(
101
+ `Usage: ${configureCommandName} <${CONFIGURE_PROVIDERS.join("|")}|provider/model-id>\n` +
102
+ ` ${configureCommandName}\n` +
103
+ ` ${configureCommandName} --clear`,
104
+ );
105
+ throw new Error(
106
+ `Invalid provider or model. Use one of: ${formatConfigureProviders()}, or a full model string like "openai/gpt-4o".`,
107
+ );
108
+ }
109
+
110
+ const config = writeAiConfig(model, configPath);
111
+ console.log("AI config saved.");
112
+ printAiConfig(config, configPath);
113
+ }
114
+
5
115
  export const aiConfigureInput = SimpleCLI.input({
6
116
  positionals: [
7
117
  SimpleCLI.positional("preset", z.string().optional(), {
@@ -1,17 +1,27 @@
1
1
  import { z } from "zod";
2
+ import { SessionAccessModeSchema } from "../../shared/state/index.js";
2
3
  import {
3
4
  runClose as runCloseWithLogger,
4
5
  runCloseAll as runCloseAllWithLogger,
5
6
  runConnect as runConnectWithLogger,
6
7
  runOpen,
8
+ runOpenWithProvider,
7
9
  runPages,
8
10
  runSave,
9
11
  } from "../core/browser.js";
12
+ import {
13
+ resolveProviderName,
14
+ getCloudProviderApi,
15
+ } from "../core/providers/index.js";
16
+ import { readLibrettoConfig } from "../core/config.js";
10
17
  import { createLoggerForSession, withSessionLogger } from "../core/context.js";
11
18
  import {
19
+ type SessionAccessMode,
12
20
  assertSessionAvailableForStart,
21
+ setSessionMode,
13
22
  validateSessionName,
14
23
  } from "../core/session.js";
24
+ import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
15
25
  import { SimpleCLI } from "../framework/simple-cli.js";
16
26
  import {
17
27
  sessionOption,
@@ -42,6 +52,16 @@ export function parseViewportArg(
42
52
  return { width, height };
43
53
  }
44
54
 
55
+ function resolveRequestedSessionMode(
56
+ readOnly: boolean | undefined,
57
+ writeAccess: boolean | undefined,
58
+ ): SessionAccessMode {
59
+ if (readOnly) return "read-only";
60
+ if (writeAccess) return "write-access";
61
+ const config = readLibrettoConfig();
62
+ return config.sessionMode ?? "write-access";
63
+ }
64
+
45
65
  export const openInput = SimpleCLI.input({
46
66
  positionals: [
47
67
  SimpleCLI.positional("url", z.string().optional(), {
@@ -52,18 +72,34 @@ export const openInput = SimpleCLI.input({
52
72
  session: sessionOption(),
53
73
  headed: SimpleCLI.flag({ help: "Run browser in headed mode" }),
54
74
  headless: SimpleCLI.flag({ help: "Run browser in headless mode" }),
75
+ readOnly: SimpleCLI.flag({
76
+ name: "read-only",
77
+ help: "Create the session in read-only mode",
78
+ }),
79
+ writeAccess: SimpleCLI.flag({
80
+ name: "write-access",
81
+ help: "Create the session in write-access mode (overrides config default)",
82
+ }),
55
83
  viewport: SimpleCLI.option(z.string().optional(), {
56
84
  help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)",
57
85
  }),
86
+ provider: SimpleCLI.option(z.string().optional(), {
87
+ help: "Browser provider (local, kernel, browserbase)",
88
+ aliases: ["-p"],
89
+ }),
58
90
  },
59
91
  })
60
92
  .refine(
61
93
  (input) => Boolean(input.url),
62
- `Usage: libretto open <url> [--headless] [--viewport WxH] [--session <name>]`,
94
+ `Usage: libretto open <url> [--headless] [--read-only|--write-access] [--viewport WxH] [--session <name>]`,
63
95
  )
64
96
  .refine(
65
97
  (input) => !(input.headed && input.headless),
66
98
  "Cannot pass both --headed and --headless.",
99
+ )
100
+ .refine(
101
+ (input) => !(input.readOnly && input.writeAccess),
102
+ "Cannot pass both --read-only and --write-access.",
67
103
  );
68
104
 
69
105
  export const openCommand = SimpleCLI.command({
@@ -72,10 +108,30 @@ export const openCommand = SimpleCLI.command({
72
108
  .input(openInput)
73
109
  .use(withAutoSession())
74
110
  .handle(async ({ input, ctx }) => {
111
+ warnIfInstalledSkillOutOfDate();
75
112
  assertSessionAvailableForStart(ctx.session, ctx.logger);
76
- const headed = input.headed || !input.headless;
77
- const viewport = parseViewportArg(input.viewport);
78
- await runOpen(input.url!, headed, ctx.session, ctx.logger, { viewport });
113
+ const providerName = resolveProviderName(input.provider);
114
+ if (providerName === "local") {
115
+ const headed = input.headed || !input.headless;
116
+ const viewport = parseViewportArg(input.viewport);
117
+ await runOpen(input.url!, headed, ctx.session, ctx.logger, {
118
+ viewport,
119
+ accessMode: resolveRequestedSessionMode(
120
+ input.readOnly,
121
+ input.writeAccess,
122
+ ),
123
+ });
124
+ } else {
125
+ const provider = getCloudProviderApi(providerName);
126
+ await runOpenWithProvider(
127
+ input.url!,
128
+ providerName,
129
+ provider,
130
+ ctx.session,
131
+ ctx.logger,
132
+ resolveRequestedSessionMode(input.readOnly, input.writeAccess),
133
+ );
134
+ }
79
135
  });
80
136
 
81
137
  export const connectInput = SimpleCLI.input({
@@ -86,11 +142,24 @@ export const connectInput = SimpleCLI.input({
86
142
  ],
87
143
  named: {
88
144
  session: sessionOption(),
145
+ readOnly: SimpleCLI.flag({
146
+ name: "read-only",
147
+ help: "Create the session in read-only mode",
148
+ }),
149
+ writeAccess: SimpleCLI.flag({
150
+ name: "write-access",
151
+ help: "Create the session in write-access mode (overrides config default)",
152
+ }),
89
153
  },
90
- }).refine(
91
- (input) => Boolean(input.cdpUrl),
92
- `Usage: libretto connect <cdp-url> --session <name>`,
93
- );
154
+ })
155
+ .refine(
156
+ (input) => Boolean(input.cdpUrl),
157
+ `Usage: libretto connect <cdp-url> [--read-only|--write-access] --session <name>`,
158
+ )
159
+ .refine(
160
+ (input) => !(input.readOnly && input.writeAccess),
161
+ "Cannot pass both --read-only and --write-access.",
162
+ );
94
163
 
95
164
  export const connectCommand = SimpleCLI.command({
96
165
  description: "Connect to an existing Chrome DevTools Protocol (CDP) endpoint",
@@ -98,7 +167,13 @@ export const connectCommand = SimpleCLI.command({
98
167
  .input(connectInput)
99
168
  .use(withAutoSession())
100
169
  .handle(async ({ input, ctx }) => {
101
- await runConnectWithLogger(input.cdpUrl!, ctx.session, ctx.logger);
170
+ warnIfInstalledSkillOutOfDate();
171
+ await runConnectWithLogger(
172
+ input.cdpUrl!,
173
+ ctx.session,
174
+ ctx.logger,
175
+ resolveRequestedSessionMode(input.readOnly, input.writeAccess),
176
+ );
102
177
  });
103
178
 
104
179
  export const saveInput = SimpleCLI.input({
@@ -140,6 +215,32 @@ export const pagesCommand = SimpleCLI.command({
140
215
  await runPages(ctx.session, ctx.logger);
141
216
  });
142
217
 
218
+ export const sessionModeInput = SimpleCLI.input({
219
+ positionals: [
220
+ SimpleCLI.positional("mode", SessionAccessModeSchema.optional(), {
221
+ help: "Session mode to set",
222
+ }),
223
+ ],
224
+ named: {
225
+ session: sessionOption(),
226
+ },
227
+ });
228
+
229
+ export const sessionModeCommand = SimpleCLI.command({
230
+ description: "View or set the session access mode",
231
+ })
232
+ .input(sessionModeInput)
233
+ .use(withRequiredSession())
234
+ .handle(async ({ input, ctx }) => {
235
+ if (!input.mode) {
236
+ console.log(`Session "${ctx.session}" mode: ${ctx.sessionState.mode}`);
237
+ return;
238
+ }
239
+
240
+ const nextState = setSessionMode(ctx.session, input.mode, ctx.logger);
241
+ console.log(`Session "${ctx.session}" mode set to ${nextState.mode}.`);
242
+ });
243
+
143
244
  export const closeInput = SimpleCLI.input({
144
245
  positionals: [],
145
246
  named: {
@@ -179,6 +280,7 @@ export const browserCommands = {
179
280
  connect: connectCommand,
180
281
  save: saveCommand,
181
282
  pages: pagesCommand,
283
+ "session-mode": sessionModeCommand,
182
284
  close: closeCommand,
183
285
  };
184
286