libretto 0.6.16-experimental-zod.0 → 0.6.16

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 (39) hide show
  1. package/README.md +13 -9
  2. package/README.template.md +13 -9
  3. package/dist/cli/cli.js +14 -0
  4. package/dist/cli/commands/browser.js +5 -6
  5. package/dist/cli/commands/execution.js +9 -10
  6. package/dist/cli/commands/experiments.js +4 -5
  7. package/dist/cli/commands/setup.js +1 -2
  8. package/dist/cli/commands/snapshot.js +2 -3
  9. package/dist/cli/core/browser.js +16 -17
  10. package/dist/cli/core/daemon/daemon.js +2 -3
  11. package/dist/cli/core/daemon/snapshot.js +1 -2
  12. package/dist/cli/core/session.js +4 -5
  13. package/dist/cli/core/skill-version.js +1 -2
  14. package/dist/cli/router.js +1 -2
  15. package/dist/shared/debug/pause.js +2 -3
  16. package/dist/shared/package-manager.d.ts +1 -2
  17. package/dist/shared/package-manager.js +1 -18
  18. package/docs/experiments.md +4 -4
  19. package/package.json +1 -1
  20. package/skills/libretto/SKILL.md +38 -38
  21. package/skills/libretto/references/auth-profiles.md +3 -3
  22. package/skills/libretto/references/code-generation-rules.md +0 -1
  23. package/skills/libretto/references/configuration-file-reference.md +9 -9
  24. package/skills/libretto/references/pages-and-page-targeting.md +3 -3
  25. package/skills/libretto-readonly/SKILL.md +8 -8
  26. package/src/cli/cli.ts +17 -0
  27. package/src/cli/commands/browser.ts +4 -5
  28. package/src/cli/commands/execution.ts +7 -8
  29. package/src/cli/commands/experiments.ts +4 -5
  30. package/src/cli/commands/setup.ts +1 -2
  31. package/src/cli/commands/snapshot.ts +2 -3
  32. package/src/cli/core/browser.ts +17 -18
  33. package/src/cli/core/daemon/daemon.ts +2 -3
  34. package/src/cli/core/daemon/snapshot.ts +1 -2
  35. package/src/cli/core/session.ts +4 -5
  36. package/src/cli/core/skill-version.ts +1 -2
  37. package/src/cli/router.ts +1 -2
  38. package/src/shared/debug/pause.ts +2 -3
  39. package/src/shared/package-manager.ts +0 -21
package/README.md CHANGED
@@ -28,13 +28,17 @@ https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
28
28
  ## Installation
29
29
 
30
30
  ```bash
31
+ # Install the Libretto command once. Requires Node.js and npm.
32
+ curl -fsSL https://libretto.sh/install.sh | bash
33
+
34
+ # Add Libretto to your project
31
35
  npm install libretto
32
36
 
33
37
  # First-time onboarding: install skills and download Chromium
34
- npx libretto setup
38
+ libretto setup
35
39
 
36
40
  # Check workspace readiness at any time
37
- npx libretto status
41
+ libretto status
38
42
  ```
39
43
 
40
44
  `setup` creates the `.libretto/` directory, installs agent skills, and downloads Chromium unless you pass `--skip-browsers`.
@@ -72,17 +76,17 @@ Agents can use Libretto to reproduce the failure, pause the workflow at any poin
72
76
  You can also use Libretto directly from the command line. All commands accept `--session <name>` to target a specific session.
73
77
 
74
78
  ```bash
75
- npx libretto open <url> # launch browser and open a URL
76
- npx libretto run ./integration.ts --headless # run a workflow and close on success
77
- npx libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
78
- npx libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
79
- npx libretto exec "<code>" # execute Playwright TypeScript against the open page
80
- npx libretto close # close the browser
79
+ libretto open <url> # launch browser and open a URL
80
+ libretto run ./integration.ts --headless # run a workflow and close on success
81
+ libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
82
+ libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
83
+ libretto exec "<code>" # execute Playwright TypeScript against the open page
84
+ libretto close # close the browser
81
85
  ```
82
86
 
83
87
  `run` sessions are inspectable through the same daemon-backed commands as `open` sessions. Successful runs close the browser by default; pass `--stay-open-on-success` to keep the browser open for `pages`, `snapshot`, and `exec`. Failed or paused workflows keep the browser open so you can inspect the exact page state before fixing or resuming the workflow.
84
88
 
85
- Run `npx libretto help` for the full list of commands.
89
+ Run `libretto help` for the full list of commands.
86
90
 
87
91
  ## Configuration
88
92
 
@@ -26,13 +26,17 @@ https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
26
26
  ## Installation
27
27
 
28
28
  ```bash
29
+ # Install the Libretto command once. Requires Node.js and npm.
30
+ curl -fsSL https://libretto.sh/install.sh | bash
31
+
32
+ # Add Libretto to your project
29
33
  npm install libretto
30
34
 
31
35
  # First-time onboarding: install skills and download Chromium
32
- npx libretto setup
36
+ libretto setup
33
37
 
34
38
  # Check workspace readiness at any time
35
- npx libretto status
39
+ libretto status
36
40
  ```
37
41
 
38
42
  `setup` creates the `.libretto/` directory, installs agent skills, and downloads Chromium unless you pass `--skip-browsers`.
@@ -70,17 +74,17 @@ Agents can use Libretto to reproduce the failure, pause the workflow at any poin
70
74
  You can also use Libretto directly from the command line. All commands accept `--session <name>` to target a specific session.
71
75
 
72
76
  ```bash
73
- npx libretto open <url> # launch browser and open a URL
74
- npx libretto run ./integration.ts --headless # run a workflow and close on success
75
- npx libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
76
- npx libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
77
- npx libretto exec "<code>" # execute Playwright TypeScript against the open page
78
- npx libretto close # close the browser
77
+ libretto open <url> # launch browser and open a URL
78
+ libretto run ./integration.ts --headless # run a workflow and close on success
79
+ libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
80
+ libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
81
+ libretto exec "<code>" # execute Playwright TypeScript against the open page
82
+ libretto close # close the browser
79
83
  ```
80
84
 
81
85
  `run` sessions are inspectable through the same daemon-backed commands as `open` sessions. Successful runs close the browser by default; pass `--stay-open-on-success` to keep the browser open for `pages`, `snapshot`, and `exec`. Failed or paused workflows keep the browser open so you can inspect the exact page state before fixing or resuming the workflow.
82
86
 
83
- Run `npx libretto help` for the full list of commands.
87
+ Run `libretto help` for the full list of commands.
84
88
 
85
89
  ## Configuration
86
90
 
package/dist/cli/cli.js CHANGED
@@ -14,6 +14,19 @@ Docs (agent-friendly): https://libretto.sh/docs
14
14
  function printSetupAudit() {
15
15
  warnIfInstalledSkillOutOfDate();
16
16
  }
17
+ function isPackageManagerExec(env = process.env) {
18
+ return env.npm_command === "exec";
19
+ }
20
+ function warnIfPackageManagerExec() {
21
+ if (!isPackageManagerExec()) return;
22
+ console.error(
23
+ [
24
+ "Warning: running Libretto through a package manager is deprecated and will be removed in a future release.",
25
+ "Install the native command instead:",
26
+ " curl -fsSL https://libretto.sh/install.sh | bash"
27
+ ].join("\n")
28
+ );
29
+ }
17
30
  function isRootHelpRequest(rawArgs) {
18
31
  if (rawArgs.length === 0) return true;
19
32
  if (rawArgs[0] === "--help" || rawArgs[0] === "-h") return true;
@@ -23,6 +36,7 @@ async function runLibrettoCLI() {
23
36
  const rawArgs = process.argv.slice(2);
24
37
  let exitCode = 0;
25
38
  loadEnv();
39
+ warnIfPackageManagerExec();
26
40
  ensureLibrettoSetup();
27
41
  const app = createCLIApp();
28
42
  try {
@@ -12,7 +12,6 @@ import {
12
12
  import { resolveProviderName } from "../core/providers/index.js";
13
13
  import { readLibrettoConfig } from "../core/config.js";
14
14
  import { createLoggerForSession } from "../core/context.js";
15
- import { librettoCommand } from "../../shared/package-manager.js";
16
15
  import {
17
16
  assertSessionAvailableForStart,
18
17
  setSessionMode,
@@ -134,7 +133,7 @@ const connectInput = SimpleCLI.input({
134
133
  }
135
134
  }).refine(
136
135
  (input) => Boolean(input.cdpUrl),
137
- `Usage: ${librettoCommand("connect <cdp-url> [--read-only|--write-access] --session <name>")}`
136
+ `Usage: libretto connect <cdp-url> [--read-only|--write-access] --session <name>`
138
137
  ).refine(
139
138
  (input) => !(input.readOnly && input.writeAccess),
140
139
  "Cannot pass both --read-only and --write-access."
@@ -162,7 +161,7 @@ const saveInput = SimpleCLI.input({
162
161
  }
163
162
  }).refine(
164
163
  (input) => Boolean(input.urlOrDomain),
165
- `Usage: ${librettoCommand("save <url|domain> --session <name>")}`
164
+ `Usage: libretto save <url|domain> --session <name>`
166
165
  );
167
166
  const saveCommand = SimpleCLI.command({
168
167
  description: "Save current browser session"
@@ -217,14 +216,14 @@ const closeInput = SimpleCLI.input({
217
216
  }
218
217
  }).refine(
219
218
  (input) => input.all || input.session,
220
- `Usage: ${librettoCommand("close <session>")}
221
- Usage: ${librettoCommand("close --all [--force]")}`
219
+ `Usage: libretto close <session>
220
+ Usage: libretto close --all [--force]`
222
221
  );
223
222
  const closeCommand = SimpleCLI.command({
224
223
  description: "Close the browser"
225
224
  }).input(closeInput).handle(async ({ input }) => {
226
225
  if (input.force && !input.all) {
227
- throw new Error(`Usage: ${librettoCommand("close --all [--force]")}`);
226
+ throw new Error(`Usage: libretto close --all [--force]`);
228
227
  }
229
228
  if (input.all) {
230
229
  const logger2 = createLoggerForSession("cli");
@@ -25,7 +25,6 @@ import {
25
25
  } from "../core/session.js";
26
26
  import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
27
27
  import { readLibrettoConfig } from "../core/config.js";
28
- import { librettoCommand } from "../../shared/package-manager.js";
29
28
  import { renderSnapshotDiff } from "../../shared/snapshot/diff-snapshots.js";
30
29
  import {
31
30
  getProviderStartupTimeoutMs,
@@ -288,7 +287,7 @@ async function stopExistingFailedRunSession(session, logger) {
288
287
  pid: existingState.pid
289
288
  });
290
289
  throw new Error(
291
- `Existing failed workflow process for session "${session}" (pid ${existingState.pid}) is still running. Close it with: ${librettoCommand(`close --session ${session}`)}`
290
+ `Existing failed workflow process for session "${session}" (pid ${existingState.pid}) is still running. Close it with: libretto close --session ${session}`
292
291
  );
293
292
  }
294
293
  clearSessionState(session, logger);
@@ -373,7 +372,7 @@ async function runResume(session, logger, sessionState) {
373
372
  const status = await client.getWorkflowStatus();
374
373
  if (status.state !== "paused") {
375
374
  throw new Error(
376
- `Session "${session}" is not paused. Run "${librettoCommand(`run ... --session ${session}`)}" and call pause("${session}") first.`
375
+ `Session "${session}" is not paused. Run "libretto run ... --session ${session}" and call pause("${session}") first.`
377
376
  );
378
377
  }
379
378
  await client.resumeWorkflow();
@@ -427,9 +426,9 @@ async function runIntegrationFromFile(args, logger) {
427
426
  `Local auth profile not found for domain "${normalizedDomain}".`,
428
427
  `Expected profile file: ${profilePath}`,
429
428
  "To create it:",
430
- ` 1. ${librettoCommand(`open https://${normalizedDomain} --headed --session ${args.session}`)}`,
429
+ ` 1. libretto open https://${normalizedDomain} --headed --session ${args.session}`,
431
430
  " 2. Log in manually in the browser window.",
432
- ` 3. ${librettoCommand(`save ${normalizedDomain} --session ${args.session}`)}`
431
+ ` 3. libretto save ${normalizedDomain} --session ${args.session}`
433
432
  ].join("\n")
434
433
  );
435
434
  }
@@ -546,8 +545,8 @@ const execInput = SimpleCLI.input({
546
545
  }
547
546
  }).refine(
548
547
  (input) => input.code !== void 0,
549
- `Usage: ${librettoCommand("exec <code|-> [--session <name>] [--visualize]")}
550
- echo '<code>' | ${librettoCommand("exec - [--session <name>] [--visualize]")}`
548
+ `Usage: libretto exec <code|-> [--session <name>] [--visualize]
549
+ echo '<code>' | libretto exec - [--session <name>] [--visualize]`
551
550
  );
552
551
  const execCommand = SimpleCLI.command({
553
552
  description: "Execute Playwright TypeScript code"
@@ -583,8 +582,8 @@ const readonlyExecInput = SimpleCLI.input({
583
582
  }
584
583
  }).refine(
585
584
  (input) => input.code !== void 0,
586
- `Usage: ${librettoCommand("readonly-exec <code|-> [--session <name>] [--page <id>]")}
587
- echo '<code>' | ${librettoCommand("readonly-exec - [--session <name>] [--page <id>]")}`
585
+ `Usage: libretto readonly-exec <code|-> [--session <name>] [--page <id>]
586
+ echo '<code>' | libretto readonly-exec - [--session <name>] [--page <id>]`
588
587
  );
589
588
  const readonlyExecCommand = SimpleCLI.command({
590
589
  description: "Execute read-only Playwright inspection code"
@@ -601,7 +600,7 @@ const readonlyExecCommand = SimpleCLI.command({
601
600
  mode: "readonly-exec"
602
601
  });
603
602
  });
604
- const runUsage = `Usage: ${librettoCommand("run <integrationFile> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--read-only|--write-access] [--no-visualize] [--stay-open-on-success] [--viewport WxH] [--provider <provider>]")}`;
603
+ const runUsage = `Usage: libretto run <integrationFile> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--read-only|--write-access] [--no-visualize] [--stay-open-on-success] [--viewport WxH] [--provider <provider>]`;
605
604
  const runInput = SimpleCLI.input({
606
605
  positionals: [
607
606
  SimpleCLI.positional("integrationFile", z.string().optional(), {
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod";
2
- import { librettoCommand } from "../../shared/package-manager.js";
3
2
  import {
4
3
  EXPERIMENTS,
5
4
  isExperimentName,
@@ -10,10 +9,10 @@ import { SimpleCLI } from "affordance";
10
9
  const experimentNames = Object.keys(EXPERIMENTS);
11
10
  const experimentsUsage = [
12
11
  "Usage:",
13
- ` ${librettoCommand("experiments")}`,
14
- ` ${librettoCommand("experiments describe <experiment>")}`,
15
- ` ${librettoCommand("experiments enable <experiment>")}`,
16
- ` ${librettoCommand("experiments disable <experiment>")}`
12
+ ` libretto experiments`,
13
+ ` libretto experiments describe <experiment>`,
14
+ ` libretto experiments enable <experiment>`,
15
+ ` libretto experiments disable <experiment>`
17
16
  ].join("\n");
18
17
  const experimentsInput = SimpleCLI.input({
19
18
  positionals: [
@@ -7,7 +7,6 @@ import {
7
7
  LIBRETTO_CONFIG_PATH,
8
8
  REPO_ROOT
9
9
  } from "../core/context.js";
10
- import { librettoCommand } from "../../shared/package-manager.js";
11
10
  import { SimpleCLI } from "affordance";
12
11
  function installBrowsers() {
13
12
  console.log("Installing Playwright Chromium...");
@@ -47,7 +46,7 @@ function copySkills() {
47
46
  "\n\u26A0\uFE0F No .agents/ or .claude/ directory found. Libretto skills were not installed."
48
47
  );
49
48
  console.log(
50
- ` Create one of these directories in your repo root and rerun \`${librettoCommand("setup")}\` to install skills:`
49
+ ` Create one of these directories in your repo root and rerun \`libretto setup\` to install skills:`
51
50
  );
52
51
  console.log(` mkdir ${join(REPO_ROOT, ".claude")}`);
53
52
  return;
@@ -7,7 +7,6 @@ import {
7
7
  withRequiredSession
8
8
  } from "./shared.js";
9
9
  import { DaemonClient } from "../core/daemon/ipc.js";
10
- import { librettoCommand } from "../../shared/package-manager.js";
11
10
  import { renderSnapshot } from "../../shared/snapshot/render-snapshot.js";
12
11
  const FALLBACK_SNAPSHOT_VIEWPORT = { width: 1280, height: 800 };
13
12
  function isZeroViewport(value) {
@@ -67,7 +66,7 @@ async function forceSnapshotViewport(page, viewport, logger, session, pageId, re
67
66
  async function runCompactSnapshot(args) {
68
67
  if (!args.daemonSocketPath) {
69
68
  throw new Error(
70
- `Session "${args.session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session: ${librettoCommand(`close --session ${args.session}`)}`
69
+ `Session "${args.session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session: libretto close --session ${args.session}`
71
70
  );
72
71
  }
73
72
  args.logger.info("compact-snapshot-via-daemon", {
@@ -88,7 +87,7 @@ async function runCompactSnapshot(args) {
88
87
  console.log(`Screenshot at ${result.pngPath}`);
89
88
  console.log(renderSnapshot(result.snapshot, args.ref));
90
89
  console.log(
91
- `Hint: Use ${librettoCommand(`snapshot <ref> --session ${args.session}`)} to inspect a subtree.`
90
+ `Hint: Use libretto snapshot <ref> --session ${args.session} to inspect a subtree.`
92
91
  );
93
92
  }
94
93
  const snapshotInput = SimpleCLI.input({
@@ -8,7 +8,6 @@ import { createServer } from "node:net";
8
8
  import { isWindowsNamedPipePath } from "../../shared/ipc/socket-transport.js";
9
9
  import { getSessionProviderClosePath, PROFILES_DIR } from "./context.js";
10
10
  import { readLibrettoConfig } from "./config.js";
11
- import { librettoCommand } from "../../shared/package-manager.js";
12
11
  import {
13
12
  getCloudProviderApi,
14
13
  getProviderStartupTimeoutMs
@@ -161,13 +160,13 @@ async function connect(session, logger, timeoutMs = 1e4, options) {
161
160
  });
162
161
  if (state.provider) {
163
162
  throw new Error(
164
- `Could not connect to ${state.provider.name} session for "${session}" at ${endpoint}. The remote session may still be active. Try again, or close with: ${librettoCommand(`close --session ${session}`)}`
163
+ `Could not connect to ${state.provider.name} session for "${session}" at ${endpoint}. The remote session may still be active. Try again, or close with: libretto close --session ${session}`
165
164
  );
166
165
  }
167
166
  if (state.pid == null || !isPidRunning(state.pid)) {
168
167
  clearSessionState(session, logger);
169
168
  throw new Error(
170
- `No browser running for session "${session}". Run '${librettoCommand(`open <url> --session ${session}`)}' first.`
169
+ `No browser running for session "${session}". Run 'libretto open <url> --session ${session}' first.`
171
170
  );
172
171
  }
173
172
  throw new Error(
@@ -197,14 +196,14 @@ async function connect(session, logger, timeoutMs = 1e4, options) {
197
196
  }
198
197
  if (options?.requireSinglePage && !options.pageId && pages.length > 1) {
199
198
  throw new Error(
200
- `Multiple pages are open in session "${session}". Pass --page <id> to target a page (run "${librettoCommand(`pages --session ${session}`)}" to list ids).`
199
+ `Multiple pages are open in session "${session}". Pass --page <id> to target a page (run "libretto pages --session ${session}" to list ids).`
201
200
  );
202
201
  }
203
202
  const pageRefs = await resolvePageReferences(pages);
204
203
  const pageRef = options?.pageId ? pageRefs.find((ref) => ref.id === options.pageId) ?? null : pageRefs[pageRefs.length - 1];
205
204
  if (!pageRef) {
206
205
  throw new Error(
207
- `Page "${options?.pageId}" was not found in session "${session}". Run "${librettoCommand(`pages --session ${session}`)}" to list ids.`
206
+ `Page "${options?.pageId}" was not found in session "${session}". Run "libretto pages --session ${session}" to list ids.`
208
207
  );
209
208
  }
210
209
  const page = pageRef.page;
@@ -237,7 +236,7 @@ async function runPages(session, logger) {
237
236
  let pageSummaries;
238
237
  if (!state.daemonSocketPath) {
239
238
  throw new Error(
240
- `Session "${session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session: ${librettoCommand(`close --session ${session}`)}`
239
+ `Session "${session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session: libretto close --session ${session}`
241
240
  );
242
241
  }
243
242
  const client = await DaemonClient.connect(state.daemonSocketPath);
@@ -310,7 +309,7 @@ async function runOpen(rawUrl, headed, session, logger, options) {
310
309
  const authProfilePath = getProfilePath(authDomain);
311
310
  if (!existsSync(authProfilePath)) {
312
311
  throw new Error(
313
- `No saved auth profile for "${authDomain}". Save one first: ${librettoCommand(`open https://${authDomain} --headed --session <name>`)}, log in, then run: ${librettoCommand(`save ${authDomain} --session <name>`)}`
312
+ `No saved auth profile for "${authDomain}". Save one first: libretto open https://${authDomain} --headed --session <name>, log in, then run: libretto save ${authDomain} --session <name>`
314
313
  );
315
314
  }
316
315
  }
@@ -565,7 +564,7 @@ async function runClose(session, logger) {
565
564
  } else {
566
565
  writeSessionState({ ...state, status: "cleanup-failed" }, logger);
567
566
  throw new Error(
568
- `Failed to confirm remote ${state.provider.name} session cleanup for session "${session}". State preserved with status "cleanup-failed". Retry with: ${librettoCommand(`close --session ${session}`)}`
567
+ `Failed to confirm remote ${state.provider.name} session cleanup for session "${session}". State preserved with status "cleanup-failed". Retry with: libretto close --session ${session}`
569
568
  );
570
569
  }
571
570
  } else {
@@ -623,7 +622,7 @@ function formatDaemonCloseFailure(session, providerName, error) {
623
622
  const message = error instanceof Error ? error.message : String(error);
624
623
  const cleanupWarning = providerName ? ` State preserved with status "cleanup-failed" because remote ${providerName} cleanup could not be confirmed.` : " State preserved so you can retry or inspect the session.";
625
624
  return new Error(
626
- `Failed to close session "${session}" gracefully over daemon IPC: ${message}.${cleanupWarning} Retry with: ${librettoCommand(`close --session ${session}`)}`
625
+ `Failed to close session "${session}" gracefully over daemon IPC: ${message}.${cleanupWarning} Retry with: libretto close --session ${session}`
627
626
  );
628
627
  }
629
628
  function waitForCloseSignalWindow(ms) {
@@ -667,7 +666,7 @@ async function closeProviderSessionDirectly(session, providerState, logger) {
667
666
  error
668
667
  });
669
668
  throw new Error(
670
- `Failed to close remote ${providerState.name} session "${providerState.sessionId}" for session "${session}". State preserved with status "cleanup-failed". Retry with: ${librettoCommand(`close --session ${session}`)}`
669
+ `Failed to close remote ${providerState.name} session "${providerState.sessionId}" for session "${session}". State preserved with status "cleanup-failed". Retry with: libretto close --session ${session}`
671
670
  );
672
671
  }
673
672
  }
@@ -841,7 +840,7 @@ async function runCloseAll(logger, options) {
841
840
  [
842
841
  `Failed to close ${failedSessions.length} session(s) gracefully: ${formatSessionList(failedSessions)}.`,
843
842
  `Closed ${closed} session(s).`,
844
- `Retry with: ${librettoCommand("close --all --force")}`
843
+ `Retry with: libretto close --all --force`
845
844
  ].join("\n")
846
845
  );
847
846
  }
@@ -894,7 +893,7 @@ async function runCloseAll(logger, options) {
894
893
  console.log(`Closed ${closedCount} session(s).`);
895
894
  if (failedProviderSessions.size > 0) {
896
895
  console.warn(
897
- `Failed to confirm remote cleanup for ${failedProviderSessions.size} provider-backed session(s). State preserved with status "cleanup-failed". Retry with: ${librettoCommand("close --all")}`
896
+ `Failed to confirm remote cleanup for ${failedProviderSessions.size} provider-backed session(s). State preserved with status "cleanup-failed". Retry with: libretto close --all`
898
897
  );
899
898
  }
900
899
  for (const recording of replayUrls) {
@@ -918,11 +917,11 @@ async function runConnect(cdpUrl, session, logger, accessMode, experiments) {
918
917
  `Invalid CDP URL: ${cdpUrl}`,
919
918
  ``,
920
919
  `Expected an HTTP or WebSocket URL pointing to a Chrome DevTools Protocol endpoint, for example:`,
921
- ` ${librettoCommand("connect http://127.0.0.1:9222")}`,
922
- ` ${librettoCommand("connect http://remote-host:9222")}`,
923
- ` ${librettoCommand("connect http://remote-host:9222/devtools/browser/<id>")}`,
924
- ` ${librettoCommand("connect ws://remote-host:9222/devtools/browser/<id>")}`,
925
- ` ${librettoCommand("connect wss://remote-host/cdp-endpoint")}`
920
+ ` libretto connect http://127.0.0.1:9222`,
921
+ ` libretto connect http://remote-host:9222`,
922
+ ` libretto connect http://remote-host:9222/devtools/browser/<id>`,
923
+ ` libretto connect ws://remote-host:9222/devtools/browser/<id>`,
924
+ ` libretto connect wss://remote-host/cdp-endpoint`
926
925
  ].join("\n")
927
926
  );
928
927
  }
@@ -33,7 +33,6 @@ import { handlePages } from "./pages.js";
33
33
  import { handleExec, handleReadonlyExec } from "./exec.js";
34
34
  import { DaemonExecRepl } from "./exec-repl.js";
35
35
  import { handleCompactSnapshot } from "./snapshot.js";
36
- import { librettoCommand } from "../../../shared/package-manager.js";
37
36
  import { snapshot } from "../../../shared/snapshot/capture-snapshot.js";
38
37
  import { diffSnapshots } from "../../../shared/snapshot/diff-snapshots.js";
39
38
  import {
@@ -446,7 +445,7 @@ class BrowserDaemon {
446
445
  const openPages = Array.from(this.pageById.values());
447
446
  if (openPages.length === 1) return openPages[0];
448
447
  throw new Error(
449
- `The primary page for session "${this.session}" is closed. Run "${librettoCommand(`pages --session ${this.session}`)}" to choose a page id.`
448
+ `The primary page for session "${this.session}" is closed. Run "libretto pages --session ${this.session}" to choose a page id.`
450
449
  );
451
450
  }
452
451
  return this.page;
@@ -454,7 +453,7 @@ class BrowserDaemon {
454
453
  const page = this.pageById.get(pageId);
455
454
  if (!page) {
456
455
  throw new Error(
457
- `Page "${pageId}" was not found in session "${this.session}". Run "${librettoCommand(`pages --session ${this.session}`)}" to list ids.`
456
+ `Page "${pageId}" was not found in session "${this.session}". Run "libretto pages --session ${this.session}" to list ids.`
458
457
  );
459
458
  }
460
459
  return page;
@@ -4,7 +4,6 @@ import {
4
4
  snapshot
5
5
  } from "../../../shared/snapshot/capture-snapshot.js";
6
6
  import { waitForPageStable } from "../../../shared/snapshot/wait-for-page-stable.js";
7
- import { librettoCommand } from "../../../shared/package-manager.js";
8
7
  import {
9
8
  resolveSnapshotViewport,
10
9
  readSnapshotViewportMetrics,
@@ -17,7 +16,7 @@ async function handleCompactSnapshot(targetPage, session, logger, options = {})
17
16
  if (options.useCachedSnapshot) {
18
17
  if (!options.cachedSnapshot) {
19
18
  throw new Error(
20
- `No compact snapshot is cached for session "${session}". Run ${librettoCommand(`snapshot --session ${session}`)} first.`
19
+ `No compact snapshot is cached for session "${session}". Run libretto snapshot --session ${session} first.`
21
20
  );
22
21
  }
23
22
  const screenshot2 = await captureSnapshotScreenshot(
@@ -18,7 +18,6 @@ import {
18
18
  parseSessionStateContent,
19
19
  serializeSessionState
20
20
  } from "../../shared/state/index.js";
21
- import { librettoCommand } from "../../shared/package-manager.js";
22
21
  const SESSION_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
23
22
  const SESSION_DEV_SERVER = "dev-server";
24
23
  const SESSION_BROWSER_AGENT = "browser-agent";
@@ -120,7 +119,7 @@ function throwSessionNotFoundError(session) {
120
119
  }
121
120
  lines.push("");
122
121
  lines.push("Start one with:");
123
- lines.push(` ${librettoCommand(`open <url> --session ${session}`)}`);
122
+ lines.push(` libretto open <url> --session ${session}`);
124
123
  throw new Error(lines.join("\n"));
125
124
  }
126
125
  function assertSessionStateExistsOrThrow(session) {
@@ -177,7 +176,7 @@ function assertSessionAllowsCommand(state, commandName, allowedModes) {
177
176
  }
178
177
  const supportedModes = [...allowedModes].join(", ");
179
178
  throw new Error(
180
- `Command "${commandName}" is blocked for session "${state.session}" because it is in ${mode} mode. Allowed modes for this command: ${supportedModes}. Run \`${librettoCommand(`session-mode write-access --session ${state.session}`)}\` to unlock the session.`
179
+ `Command "${commandName}" is blocked for session "${state.session}" because it is in ${mode} mode. Allowed modes for this command: ${supportedModes}. Run \`libretto session-mode write-access --session ${state.session}\` to unlock the session.`
181
180
  );
182
181
  }
183
182
  function clearSessionState(session, logger) {
@@ -214,7 +213,7 @@ function assertSessionAvailableForStart(session, logger) {
214
213
  if (!existingState) return;
215
214
  if (existingState.provider && existingState.cdpEndpoint) {
216
215
  throw new Error(
217
- `Session "${session}" is already open via ${existingState.provider.name} provider. Close it first with: ${librettoCommand(`close --session ${session}`)}`
216
+ `Session "${session}" is already open via ${existingState.provider.name} provider. Close it first with: libretto close --session ${session}`
218
217
  );
219
218
  }
220
219
  if (existingState.pid == null || !isPidRunning(existingState.pid)) {
@@ -223,7 +222,7 @@ function assertSessionAvailableForStart(session, logger) {
223
222
  }
224
223
  const endpoint = `http://127.0.0.1:${existingState.port}`;
225
224
  throw new Error(
226
- `Session "${session}" is already open and connected to ${endpoint} (pid ${existingState.pid}). Create a new session or close the current one with: ${librettoCommand(`close --session ${session}`)}`
225
+ `Session "${session}" is already open and connected to ${endpoint} (pid ${existingState.pid}). Create a new session or close the current one with: libretto close --session ${session}`
227
226
  );
228
227
  }
229
228
  export {
@@ -2,7 +2,6 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { REPO_ROOT } from "./context.js";
5
- import { librettoCommand } from "../../shared/package-manager.js";
6
5
  const INSTALLED_SKILL_PATHS = [
7
6
  [".agents", "skills", "libretto", "SKILL.md"],
8
7
  [".claude", "skills", "libretto", "SKILL.md"]
@@ -64,7 +63,7 @@ function warnIfInstalledSkillOutOfDate() {
64
63
  return;
65
64
  }
66
65
  console.error(
67
- `Warning: Your agent skill (${mismatch.installedVersion}) is out of date with your Libretto CLI (${mismatch.cliVersion}). Please run \`${librettoCommand("setup")}\` to update your skills to the correct version.`
66
+ `Warning: Your agent skill (${mismatch.installedVersion}) is out of date with your Libretto CLI (${mismatch.cliVersion}). Please run \`libretto setup\` to update your skills to the correct version.`
68
67
  );
69
68
  } catch {
70
69
  }
@@ -7,7 +7,6 @@ import { experimentsCommand } from "./commands/experiments.js";
7
7
  import { setupCommand } from "./commands/setup.js";
8
8
  import { statusCommand } from "./commands/status.js";
9
9
  import { snapshotCommand } from "./commands/snapshot.js";
10
- import { librettoCommand } from "../shared/package-manager.js";
11
10
  import { SimpleCLI } from "affordance";
12
11
  const cliRoutes = {
13
12
  ...browserCommands,
@@ -26,7 +25,7 @@ const cliRoutes = {
26
25
  snapshot: snapshotCommand
27
26
  };
28
27
  function createCLIApp() {
29
- return SimpleCLI.define(librettoCommand(), cliRoutes);
28
+ return SimpleCLI.define("libretto", cliRoutes);
30
29
  }
31
30
  export {
32
31
  cliRoutes,
@@ -1,8 +1,7 @@
1
1
  import { getActivePauseHandler } from "./pause-handler.js";
2
- import { librettoCommand } from "../package-manager.js";
3
2
  function throwMissingSessionError() {
4
3
  throw new Error(
5
- `pause(session) requires a non-empty session ID. Pass ctx.session from inside your workflow: await pause(ctx.session). To list running sessions, run: ${librettoCommand("status")}.`
4
+ `pause(session) requires a non-empty session ID. Pass ctx.session from inside your workflow: await pause(ctx.session). To list running sessions, run: libretto status.`
6
5
  );
7
6
  }
8
7
  async function pause(session) {
@@ -15,7 +14,7 @@ async function pause(session) {
15
14
  const handler = getActivePauseHandler();
16
15
  if (!handler) {
17
16
  throw new Error(
18
- `pause(session) can only suspend an active Libretto workflow. Run the workflow with ${librettoCommand("run <integrationFile>")} and call pause(ctx.session) from inside the workflow.`
17
+ `pause(session) can only suspend an active Libretto workflow. Run the workflow with libretto run <integrationFile> and call pause(ctx.session) from inside the workflow.`
19
18
  );
20
19
  }
21
20
  await handler({
@@ -2,6 +2,5 @@ type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
2
2
  declare function detectPackageManager(root?: string, env?: NodeJS.ProcessEnv): PackageManager;
3
3
  declare function detectProjectPackageManager(root?: string): PackageManager;
4
4
  declare function installCommand(packageManager?: PackageManager): string;
5
- declare function librettoCommand(args?: string, packageManager?: PackageManager): string;
6
5
 
7
- export { type PackageManager, detectPackageManager, detectProjectPackageManager, installCommand, librettoCommand };
6
+ export { type PackageManager, detectPackageManager, detectProjectPackageManager, installCommand };
@@ -36,25 +36,8 @@ function installCommand(packageManager = detectProjectPackageManager()) {
36
36
  return "npm install";
37
37
  }
38
38
  }
39
- function librettoRunner(packageManager = detectPackageManager()) {
40
- switch (packageManager) {
41
- case "pnpm":
42
- return "pnpm exec";
43
- case "yarn":
44
- return "yarn";
45
- case "bun":
46
- return "bunx";
47
- default:
48
- return "npx";
49
- }
50
- }
51
- function librettoCommand(args = "", packageManager = detectPackageManager()) {
52
- const suffix = args.trim();
53
- return `${librettoRunner(packageManager)} libretto${suffix ? ` ${suffix}` : ""}`;
54
- }
55
39
  export {
56
40
  detectPackageManager,
57
41
  detectProjectPackageManager,
58
- installCommand,
59
- librettoCommand
42
+ installCommand
60
43
  };
@@ -35,10 +35,10 @@ Example workspace state:
35
35
  `libretto experiments` is the CLI surface for inspecting and changing workspace experiment overrides.
36
36
 
37
37
  ```bash
38
- npx libretto experiments
39
- npx libretto experiments describe <experiment>
40
- npx libretto experiments enable <experiment>
41
- npx libretto experiments disable <experiment>
38
+ libretto experiments
39
+ libretto experiments describe <experiment>
40
+ libretto experiments enable <experiment>
41
+ libretto experiments disable <experiment>
42
42
  ```
43
43
 
44
44
  The command is implemented in `packages/libretto/src/cli/commands/experiments.ts` and registered as a top-level command. Listing prints registered experiments in registry order with their enabled/disabled state and description. `describe` prints the experiment status and full instructions. `enable` persists the override to `.libretto/config.json` and prints the full description with a prelude that the enabled experiment changes expected Libretto usage from the skill. `disable` persists the override and prints deterministic success text.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libretto",
3
- "version": "0.6.16-experimental-zod.0",
3
+ "version": "0.6.16",
4
4
  "description": "AI-powered browser automation library and CLI built on Playwright",
5
5
  "license": "MIT",
6
6
  "homepage": "https://libretto.sh",