libretto 0.5.6 → 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 (35) hide show
  1. package/dist/cli/commands/browser.js +31 -6
  2. package/dist/cli/commands/execution.js +54 -15
  3. package/dist/cli/commands/setup.js +78 -64
  4. package/dist/cli/commands/status.js +1 -1
  5. package/dist/cli/core/browser.js +163 -10
  6. package/dist/cli/core/config.js +1 -0
  7. package/dist/cli/core/providers/browserbase.js +53 -0
  8. package/dist/cli/core/providers/index.js +48 -0
  9. package/dist/cli/core/providers/kernel.js +46 -0
  10. package/dist/cli/core/providers/libretto-cloud.js +58 -0
  11. package/dist/cli/core/providers/types.js +0 -0
  12. package/dist/cli/core/session.js +9 -0
  13. package/dist/cli/workers/run-integration-runtime.js +3 -1
  14. package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
  15. package/dist/shared/run/browser.d.ts +6 -1
  16. package/dist/shared/run/browser.js +39 -1
  17. package/dist/shared/state/session-state.d.ts +11 -1
  18. package/dist/shared/state/session-state.js +9 -2
  19. package/package.json +1 -1
  20. package/src/cli/commands/browser.ts +35 -7
  21. package/src/cli/commands/execution.ts +54 -14
  22. package/src/cli/commands/setup.ts +81 -64
  23. package/src/cli/commands/status.ts +3 -1
  24. package/src/cli/core/browser.ts +197 -9
  25. package/src/cli/core/config.ts +1 -0
  26. package/src/cli/core/providers/browserbase.ts +57 -0
  27. package/src/cli/core/providers/index.ts +62 -0
  28. package/src/cli/core/providers/kernel.ts +49 -0
  29. package/src/cli/core/providers/libretto-cloud.ts +61 -0
  30. package/src/cli/core/providers/types.ts +9 -0
  31. package/src/cli/core/session.ts +13 -0
  32. package/src/cli/workers/run-integration-runtime.ts +2 -0
  33. package/src/cli/workers/run-integration-worker-protocol.ts +2 -0
  34. package/src/shared/run/browser.ts +45 -0
  35. package/src/shared/state/session-state.ts +7 -0
@@ -5,9 +5,14 @@ import {
5
5
  runCloseAll as runCloseAllWithLogger,
6
6
  runConnect as runConnectWithLogger,
7
7
  runOpen,
8
+ runOpenWithProvider,
8
9
  runPages,
9
10
  runSave,
10
11
  } from "../core/browser.js";
12
+ import {
13
+ resolveProviderName,
14
+ getCloudProviderApi,
15
+ } from "../core/providers/index.js";
11
16
  import { readLibrettoConfig } from "../core/config.js";
12
17
  import { createLoggerForSession, withSessionLogger } from "../core/context.js";
13
18
  import {
@@ -47,7 +52,10 @@ export function parseViewportArg(
47
52
  return { width, height };
48
53
  }
49
54
 
50
- function resolveRequestedSessionMode(readOnly: boolean | undefined, writeAccess: boolean | undefined): SessionAccessMode {
55
+ function resolveRequestedSessionMode(
56
+ readOnly: boolean | undefined,
57
+ writeAccess: boolean | undefined,
58
+ ): SessionAccessMode {
51
59
  if (readOnly) return "read-only";
52
60
  if (writeAccess) return "write-access";
53
61
  const config = readLibrettoConfig();
@@ -75,6 +83,10 @@ export const openInput = SimpleCLI.input({
75
83
  viewport: SimpleCLI.option(z.string().optional(), {
76
84
  help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)",
77
85
  }),
86
+ provider: SimpleCLI.option(z.string().optional(), {
87
+ help: "Browser provider (local, kernel, browserbase)",
88
+ aliases: ["-p"],
89
+ }),
78
90
  },
79
91
  })
80
92
  .refine(
@@ -98,12 +110,28 @@ export const openCommand = SimpleCLI.command({
98
110
  .handle(async ({ input, ctx }) => {
99
111
  warnIfInstalledSkillOutOfDate();
100
112
  assertSessionAvailableForStart(ctx.session, ctx.logger);
101
- const headed = input.headed || !input.headless;
102
- const viewport = parseViewportArg(input.viewport);
103
- await runOpen(input.url!, headed, ctx.session, ctx.logger, {
104
- viewport,
105
- accessMode: resolveRequestedSessionMode(input.readOnly, input.writeAccess),
106
- });
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
+ }
107
135
  });
108
136
 
109
137
  export const connectInput = SimpleCLI.input({
@@ -27,6 +27,7 @@ import {
27
27
  wrapPageForActionLogging,
28
28
  } from "../core/telemetry.js";
29
29
  import { readLibrettoConfig } from "../core/config.js";
30
+ import { resolveProviderName, getCloudProviderApi } from "../core/providers/index.js";
30
31
  import { createReadonlyExecHelpers } from "../core/readonly-exec.js";
31
32
  import type { RunIntegrationWorkerRequest } from "../workers/run-integration-worker-protocol.js";
32
33
  import { SimpleCLI } from "../framework/simple-cli.js";
@@ -630,6 +631,8 @@ async function runIntegrationFromFile(
630
631
  authProfileDomain: args.authProfileDomain,
631
632
  viewport: args.viewport,
632
633
  accessMode: args.accessMode,
634
+ cdpEndpoint: args.cdpEndpoint,
635
+ provider: args.provider,
633
636
  } satisfies RunIntegrationWorkerRequest);
634
637
  const worker = spawn(
635
638
  process.execPath,
@@ -803,6 +806,10 @@ export const runInput = SimpleCLI.input({
803
806
  viewport: SimpleCLI.option(z.string().optional(), {
804
807
  help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)",
805
808
  }),
809
+ provider: SimpleCLI.option(z.string().optional(), {
810
+ help: "Browser provider (local, kernel, browserbase)",
811
+ aliases: ["-p"],
812
+ }),
806
813
  },
807
814
  })
808
815
  .refine(
@@ -865,20 +872,53 @@ export const runCommand = SimpleCLI.command({
865
872
  ctx.logger,
866
873
  );
867
874
 
868
- await runIntegrationFromFile(
869
- {
870
- integrationPath: input.integrationFile!,
871
- session: ctx.session,
872
- params,
873
- tsconfigPath: input.tsconfig,
874
- headless: headlessMode ?? false,
875
- visualize,
876
- authProfileDomain: input.authProfile,
877
- viewport,
878
- accessMode: input.readOnly ? "read-only" : input.writeAccess ? "write-access" : (readLibrettoConfig().sessionMode ?? "write-access"),
879
- },
880
- ctx.logger,
881
- );
875
+ const providerName = resolveProviderName(input.provider);
876
+ let cdpEndpoint: string | undefined;
877
+ let providerInfo: { name: string; sessionId: string } | undefined;
878
+ let provider: ReturnType<typeof getCloudProviderApi> | undefined;
879
+ if (providerName !== "local") {
880
+ provider = getCloudProviderApi(providerName);
881
+ console.log(
882
+ `Creating ${providerName} browser session (session: ${ctx.session})...`,
883
+ );
884
+ const providerSession = await provider.createSession();
885
+ console.log(`Connecting to ${providerName} browser...`);
886
+ cdpEndpoint = providerSession.cdpEndpoint;
887
+ providerInfo = {
888
+ name: providerName,
889
+ sessionId: providerSession.sessionId,
890
+ };
891
+ }
892
+
893
+ try {
894
+ await runIntegrationFromFile(
895
+ {
896
+ integrationPath: input.integrationFile!,
897
+ session: ctx.session,
898
+ params,
899
+ tsconfigPath: input.tsconfig,
900
+ headless: cdpEndpoint ? true : (headlessMode ?? false),
901
+ visualize,
902
+ authProfileDomain: input.authProfile,
903
+ viewport,
904
+ accessMode: input.readOnly ? "read-only" : input.writeAccess ? "write-access" : (readLibrettoConfig().sessionMode ?? "write-access"),
905
+ cdpEndpoint,
906
+ provider: providerInfo,
907
+ },
908
+ ctx.logger,
909
+ );
910
+ } finally {
911
+ if (provider && providerInfo) {
912
+ try {
913
+ await provider.closeSession(providerInfo.sessionId);
914
+ } catch (cleanupErr) {
915
+ console.error(
916
+ `Failed to clean up ${providerInfo.name} session ${providerInfo.sessionId}:`,
917
+ cleanupErr instanceof Error ? cleanupErr.message : cleanupErr,
918
+ );
919
+ }
920
+ }
921
+ }
882
922
  });
883
923
 
884
924
  export const resumeInput = SimpleCLI.input({
@@ -77,6 +77,18 @@ function promptUser(
77
77
  });
78
78
  }
79
79
 
80
+ /** Map provider to a human-readable label for status messages. */
81
+ function providerLabel(provider: Provider): string {
82
+ const choice = PROVIDER_CHOICES.find((c) => c.provider === provider);
83
+ return choice?.label ?? provider;
84
+ }
85
+
86
+ /** Extract the env var name from source like "env:GOOGLE_CLOUD_PROJECT". */
87
+ function sourceEnvVar(source: string): string | null {
88
+ if (source.startsWith("env:")) return source.slice(4);
89
+ return null;
90
+ }
91
+
80
92
  /**
81
93
  * If the workspace has usable credentials but no pinned model in config,
82
94
  * write the resolved default model to `.libretto/config.json`.
@@ -92,18 +104,24 @@ function ensurePinnedDefaultModel(
92
104
  }
93
105
 
94
106
  function printHealthySummary(status: AiSetupStatus & { kind: "ready" }): void {
95
- console.log(` ✓ Model: ${status.model}`);
96
- console.log(` Config: ${LIBRETTO_CONFIG_PATH}`);
107
+ const envVar = sourceEnvVar(status.source);
108
+ if (envVar) {
109
+ console.log(
110
+ `✓ Detected ${envVar}. Using ${providerLabel(status.provider)}.`,
111
+ );
112
+ } else {
113
+ console.log(`✓ Using ${providerLabel(status.provider)} (${status.model}).`);
114
+ }
97
115
  console.log(
98
- " To change: npx libretto ai configure openai | anthropic | gemini | vertex",
116
+ "To change: npx libretto ai configure openai | anthropic | gemini | vertex",
99
117
  );
100
118
  }
101
119
 
102
120
  function printInvalidAiConfigWarning(status: AiSetupStatus): void {
103
121
  if (status.kind !== "invalid-config") return;
104
- console.log(" ! Existing AI config is invalid:");
122
+ console.log("! Existing AI config is invalid:");
105
123
  for (const line of status.message.split("\n")) {
106
- console.log(` ${line}`);
124
+ console.log(` ${line}`);
107
125
  }
108
126
  }
109
127
 
@@ -152,56 +170,54 @@ export function buildRepairPlan(status: AiSetupStatus): RepairPlan {
152
170
  export function formatMissingCredentialsMessage(
153
171
  plan: RepairPlan & { kind: "repair-missing-credentials" },
154
172
  ): string {
155
- return [
156
- ` ✗ ${plan.provider} is configured (model: ${plan.model}), but ${plan.envVar} is not set.`,
157
- ].join("\n");
173
+ return `✗ ${plan.provider} is configured (model: ${plan.model}), but ${plan.envVar} is not set.`;
158
174
  }
159
175
 
160
176
  function printSnapshotApiStatus(): boolean {
161
177
  const status = resolveAiSetupStatus();
162
- const envPath = join(REPO_ROOT, ".env");
163
178
 
164
- console.log("\nSnapshot analysis:");
165
179
  console.log(
166
- " Libretto uses direct API calls for snapshot analysis when supported credentials are available.",
180
+ "\nLibretto uses a sub-agent to analyze DOM snapshots. The model is determined by environment variables.",
167
181
  );
168
- console.log(` Credentials are loaded from process env and ${envPath}.`);
169
182
 
170
183
  if (status.kind === "ready") {
171
- const pinned = ensurePinnedDefaultModel(status);
172
- printHealthySummary(pinned);
184
+ console.log();
185
+ printHealthySummary(status);
186
+ ensurePinnedDefaultModel(status);
173
187
  return true;
174
188
  }
175
189
 
176
190
  // Provider-specific missing-credentials message
177
191
  const plan = buildRepairPlan(status);
178
192
  if (plan.kind === "repair-missing-credentials") {
193
+ console.log();
179
194
  console.log(formatMissingCredentialsMessage(plan));
180
195
  console.log(
181
- ` To fix: add ${plan.envVar} to .env, or run \`npx libretto setup\` interactively to repair.`,
196
+ ` To fix: add ${plan.envVar} to .env, or run \`npx libretto setup\` interactively to repair.`,
182
197
  );
183
198
  return false;
184
199
  }
185
200
 
186
201
  if (plan.kind === "repair-invalid-config") {
187
202
  printInvalidAiConfigWarning(status);
188
- console.log(" Run `npx libretto setup` interactively to reconfigure.");
203
+ console.log(" Run `npx libretto setup` interactively to reconfigure.");
189
204
  return false;
190
205
  }
191
206
 
192
- console.log(" ✗ No snapshot API credentials detected.");
193
- console.log(" Add one provider to .env:");
194
- console.log(" OPENAI_API_KEY=...");
195
- console.log(" ANTHROPIC_API_KEY=...");
196
- console.log(" GEMINI_API_KEY=... # or GOOGLE_GENERATIVE_AI_API_KEY");
207
+ console.log();
208
+ console.log(" No snapshot API credentials detected.");
209
+ console.log(" Add one provider to .env:");
210
+ console.log(" OPENAI_API_KEY=...");
211
+ console.log(" ANTHROPIC_API_KEY=...");
212
+ console.log(" GEMINI_API_KEY=... # or GOOGLE_GENERATIVE_AI_API_KEY");
197
213
  console.log(
198
- " GOOGLE_CLOUD_PROJECT=... # plus application default credentials for Vertex",
214
+ " GOOGLE_CLOUD_PROJECT=... # plus application default credentials for Vertex",
199
215
  );
200
216
  console.log(
201
- " Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model.",
217
+ " Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model.",
202
218
  );
203
219
  console.log(
204
- " Run `npx libretto setup` interactively to set up credentials.",
220
+ " Run `npx libretto setup` interactively to set up credentials.",
205
221
  );
206
222
  return false;
207
223
  }
@@ -222,11 +238,11 @@ function writeEnvVar(envVar: string, value: string, envPath: string): void {
222
238
  () => envLine,
223
239
  );
224
240
  writeFileSync(envPath, updated);
225
- console.log(`\n ✓ Updated ${envVar} in ${envPath}`);
241
+ console.log(`\n✓ Updated ${envVar} in ${envPath}`);
226
242
  } else {
227
243
  const separator = envContent && !envContent.endsWith("\n") ? "\n" : "";
228
244
  appendFileSync(envPath, `${separator}${envLine}\n`);
229
- console.log(`\n ✓ Added ${envVar} to ${envPath}`);
245
+ console.log(`\n✓ Added ${envVar} to ${envPath}`);
230
246
  }
231
247
 
232
248
  process.env[envVar] = value;
@@ -244,13 +260,13 @@ async function promptForCredential(
244
260
  envPath: string,
245
261
  modelOverride?: string,
246
262
  ): Promise<boolean> {
247
- console.log(`\n ${choice.label} selected.`);
248
- console.log(` ${choice.envHint}\n`);
263
+ console.log(`\n${choice.label} selected.`);
264
+ console.log(`${choice.envHint}\n`);
249
265
 
250
- const apiKeyValue = await promptUser(rl, ` Enter your ${choice.envVar}: `);
266
+ const apiKeyValue = await promptUser(rl, `Enter your ${choice.envVar}: `);
251
267
 
252
268
  if (!apiKeyValue) {
253
- console.log("\n No value entered. Skipping API key setup.");
269
+ console.log("\nNo value entered. Skipping API key setup.");
254
270
  return false;
255
271
  }
256
272
 
@@ -259,7 +275,10 @@ async function promptForCredential(
259
275
 
260
276
  const model = modelOverride ?? DEFAULT_SNAPSHOT_MODELS[choice.provider];
261
277
  writeAiConfig(model);
262
- console.log(` ✓ Snapshot API ready: ${model}`);
278
+ console.log(`✓ Snapshot API ready: ${model}`);
279
+ console.log(
280
+ "To change: npx libretto ai configure openai | anthropic | gemini | vertex",
281
+ );
263
282
  return true;
264
283
  }
265
284
 
@@ -272,14 +291,14 @@ async function promptProviderSelection(
272
291
  envPath: string,
273
292
  ): Promise<boolean> {
274
293
  console.log(
275
- " Which API provider would you like to use for snapshot analysis?\n",
294
+ "Which model provider would you like to use for snapshot analysis?\n",
276
295
  );
277
296
  for (const choice of PROVIDER_CHOICES) {
278
- console.log(` ${choice.key}) ${choice.label}`);
297
+ console.log(` ${choice.key}) ${choice.label}`);
279
298
  }
280
- console.log(" s) Skip for now\n");
299
+ console.log(" s) Skip for now\n");
281
300
 
282
- const answer = await promptUser(rl, " Choice: ");
301
+ const answer = await promptUser(rl, "Choice: ");
283
302
 
284
303
  if (answer.toLowerCase() === "s" || !answer) {
285
304
  printSkipMessage();
@@ -288,7 +307,7 @@ async function promptProviderSelection(
288
307
 
289
308
  const selected = PROVIDER_CHOICES.find((choice) => choice.key === answer);
290
309
  if (!selected) {
291
- console.log(`\n Unknown choice "${answer}". Skipping API setup.`);
310
+ console.log(`\nUnknown choice "${answer}". Skipping API setup.`);
292
311
  return false;
293
312
  }
294
313
 
@@ -297,14 +316,14 @@ async function promptProviderSelection(
297
316
 
298
317
  function printSkipMessage(): void {
299
318
  console.log(
300
- "\n Skipped. You can set up API credentials later by rerunning `npx libretto setup`.",
319
+ "\nSkipped. You can set up API credentials later by rerunning `npx libretto setup`.",
301
320
  );
302
- console.log(" Or add credentials directly to your .env file:");
303
- console.log(" OPENAI_API_KEY=...");
304
- console.log(" ANTHROPIC_API_KEY=...");
305
- console.log(" GEMINI_API_KEY=...");
321
+ console.log("Or add credentials directly to your .env file:");
322
+ console.log(" OPENAI_API_KEY=...");
323
+ console.log(" ANTHROPIC_API_KEY=...");
324
+ console.log(" GEMINI_API_KEY=...");
306
325
  console.log(
307
- " Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model.",
326
+ " Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model.",
308
327
  );
309
328
  }
310
329
 
@@ -312,13 +331,14 @@ async function runInteractiveApiSetup(): Promise<void> {
312
331
  const status = resolveAiSetupStatus();
313
332
  const envPath = join(REPO_ROOT, ".env");
314
333
 
315
- console.log("\nSnapshot analysis setup:");
316
- console.log(" Libretto uses direct API calls for snapshot analysis.");
317
- console.log(` Credentials are loaded from process env and ${envPath}.`);
334
+ console.log(
335
+ "\nLibretto uses a sub-agent to analyze DOM snapshots. The model is determined by environment variables.",
336
+ );
318
337
 
319
338
  if (status.kind === "ready") {
320
- const pinned = ensurePinnedDefaultModel(status);
321
- printHealthySummary(pinned);
339
+ console.log();
340
+ printHealthySummary(status);
341
+ ensurePinnedDefaultModel(status);
322
342
  return;
323
343
  }
324
344
 
@@ -334,12 +354,12 @@ async function runInteractiveApiSetup(): Promise<void> {
334
354
  if (plan.kind === "repair-missing-credentials") {
335
355
  console.log(formatMissingCredentialsMessage(plan));
336
356
  console.log("");
337
- console.log(" How would you like to fix this?\n");
338
- console.log(` 1) Enter ${plan.envVar}`);
339
- console.log(" 2) Switch to a different provider");
340
- console.log(" s) Skip for now\n");
357
+ console.log("How would you like to fix this?\n");
358
+ console.log(` 1) Enter ${plan.envVar}`);
359
+ console.log(" 2) Switch to a different provider");
360
+ console.log(" s) Skip for now\n");
341
361
 
342
- const answer = await promptUser(rl, " Choice: ");
362
+ const answer = await promptUser(rl, "Choice: ");
343
363
 
344
364
  if (answer === "1") {
345
365
  const matchingChoice = PROVIDER_CHOICES.find(
@@ -365,14 +385,14 @@ async function runInteractiveApiSetup(): Promise<void> {
365
385
  if (plan.kind === "repair-invalid-config") {
366
386
  printInvalidAiConfigWarning(status);
367
387
  console.log(
368
- "\n Would you like to reconfigure with a fresh provider selection?\n",
388
+ "\nWould you like to reconfigure with a fresh provider selection?\n",
369
389
  );
370
390
  await promptProviderSelection(rl, envPath);
371
391
  return;
372
392
  }
373
393
 
374
394
  // ── Unconfigured: standard first-run flow ──
375
- console.log(" ✗ No snapshot API credentials detected.\n");
395
+ console.log("✗ No snapshot API credentials detected.\n");
376
396
  await promptProviderSelection(rl, envPath);
377
397
  } finally {
378
398
  rl.close();
@@ -380,16 +400,16 @@ async function runInteractiveApiSetup(): Promise<void> {
380
400
  }
381
401
 
382
402
  function installBrowsers(): void {
383
- console.log("\nInstalling Playwright Chromium...");
403
+ console.log("Installing Playwright Chromium...");
384
404
  const result = spawnSync("npx", ["playwright", "install", "chromium"], {
385
405
  stdio: "inherit",
386
406
  shell: true,
387
407
  });
388
408
  if (result.status === 0) {
389
- console.log(" ✓ Playwright Chromium installed");
409
+ console.log("✓ Playwright Chromium installed");
390
410
  } else {
391
411
  console.error(
392
- " ✗ Failed to install Playwright Chromium. Run manually: npx playwright install chromium",
412
+ "✗ Failed to install Playwright Chromium. Run manually: npx playwright install chromium",
393
413
  );
394
414
  }
395
415
  }
@@ -421,9 +441,6 @@ function copySkills(): void {
421
441
  const agentDirs = detectAgentDirs(REPO_ROOT);
422
442
 
423
443
  if (agentDirs.length === 0) {
424
- console.log(
425
- "\nSkills: No .agents/ or .claude/ directory found in repo root — skipping.",
426
- );
427
444
  return;
428
445
  }
429
446
 
@@ -431,7 +448,7 @@ function copySkills(): void {
431
448
  try {
432
449
  skillsRoot = getPackageSkillsRoot();
433
450
  } catch (e) {
434
- console.error(` ✗ ${e instanceof Error ? e.message : String(e)}`);
451
+ console.error(`✗ ${e instanceof Error ? e.message : String(e)}`);
435
452
  return;
436
453
  }
437
454
 
@@ -452,7 +469,7 @@ function copySkills(): void {
452
469
  cpSync(sourceDir, skillDest, { recursive: true });
453
470
  const fileCount = readdirSync(skillDest).length;
454
471
  console.log(
455
- ` ✓ Copied ${fileCount} skill files to ${agentName}/skills/${skillName}/`,
472
+ `✓ Copied ${fileCount} skill files to ${agentName}/skills/${skillName}/`,
456
473
  );
457
474
  }
458
475
  }
@@ -473,13 +490,12 @@ export const setupCommand = SimpleCLI.command({
473
490
  })
474
491
  .input(setupInput)
475
492
  .handle(async ({ input }) => {
476
- console.log("Setting up libretto...\n");
477
493
  ensureLibrettoSetup();
478
494
 
479
495
  if (!input.skipBrowsers) {
480
496
  installBrowsers();
481
497
  } else {
482
- console.log("\nSkipping browser installation (--skip-browsers)");
498
+ console.log("Skipping browser installation (--skip-browsers)");
483
499
  }
484
500
 
485
501
  copySkills();
@@ -495,5 +511,6 @@ export const setupCommand = SimpleCLI.command({
495
511
  }
496
512
  }
497
513
 
514
+ console.log(`\nConfig set up at ${LIBRETTO_CONFIG_PATH}`);
498
515
  console.log("\n✓ libretto setup complete");
499
516
  });
@@ -57,7 +57,9 @@ function printOpenSessions(sessions: SessionState[]): void {
57
57
 
58
58
  for (const session of sessions) {
59
59
  const statusLabel = session.status ? ` [${session.status}]` : "";
60
- const endpoint = `http://127.0.0.1:${session.port}`;
60
+ const endpoint = session.provider
61
+ ? `${session.provider.name} (${session.cdpEndpoint})`
62
+ : `http://127.0.0.1:${session.port}`;
61
63
  console.log(` ${session.session}${statusLabel} — ${endpoint}`);
62
64
  }
63
65
  }