libretto 0.5.1 → 0.5.3-experimental.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.
- package/README.md +10 -5
- package/dist/cli/commands/execution.js +38 -12
- package/dist/cli/commands/init.js +4 -21
- package/dist/cli/core/ai-config.js +12 -2
- package/dist/cli/core/browser.js +75 -8
- package/dist/cli/core/session-telemetry.js +429 -172
- package/dist/cli/core/telemetry.js +10 -2
- package/dist/cli/framework/simple-cli.js +4 -0
- package/dist/cli/workers/run-integration-runtime.js +18 -41
- package/dist/cli/workers/run-integration-worker-protocol.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -0
- package/dist/shared/condense-dom/condense-dom.js +11 -56
- package/dist/shared/dom-semantics.d.ts +8 -0
- package/dist/shared/dom-semantics.js +69 -0
- package/dist/shared/run/browser.js +40 -1
- package/dist/shared/visualization/ghost-cursor.js +17 -4
- package/dist/shared/workflow/workflow.d.ts +14 -3
- package/dist/shared/workflow/workflow.js +50 -3
- package/package.json +7 -4
- package/scripts/check-skills-sync.mjs +1 -1
- package/scripts/generate-changelog.ts +132 -0
- package/scripts/skills-libretto.mjs +1 -1
- package/scripts/sync-skills.mjs +1 -1
- package/skills/libretto/SKILL.md +54 -38
- package/skills/libretto/references/action-logs.md +101 -0
- package/skills/libretto/references/auth-profiles.md +1 -2
- package/skills/libretto/references/code-generation-rules.md +10 -6
- package/skills/libretto/references/pages-and-page-targeting.md +1 -1
- package/src/cli/commands/execution.ts +39 -11
- package/src/cli/commands/init.ts +5 -24
- package/src/cli/core/ai-config.ts +12 -1
- package/src/cli/core/browser.ts +82 -8
- package/src/cli/core/session-telemetry.ts +431 -190
- package/src/cli/core/telemetry.ts +23 -1
- package/src/cli/framework/simple-cli.ts +5 -0
- package/src/cli/workers/run-integration-runtime.ts +24 -52
- package/src/cli/workers/run-integration-worker-protocol.ts +2 -1
- package/src/index.ts +4 -0
- package/src/shared/condense-dom/condense-dom.ts +12 -64
- package/src/shared/dom-semantics.ts +68 -0
- package/src/shared/run/browser.ts +53 -0
- package/src/shared/visualization/ghost-cursor.ts +22 -4
- package/src/shared/workflow/workflow.ts +88 -2
- package/scripts/prepare-release.sh +0 -97
package/src/cli/commands/init.ts
CHANGED
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
loadSnapshotEnv,
|
|
18
18
|
resolveSnapshotApiModel,
|
|
19
19
|
} from "../core/snapshot-api-config.js";
|
|
20
|
-
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
21
20
|
import { hasProviderCredentials } from "../../shared/llm/client.js";
|
|
21
|
+
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
22
22
|
|
|
23
23
|
type ProviderChoice = {
|
|
24
24
|
key: string;
|
|
@@ -66,16 +66,6 @@ function promptUser(
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function askYesNo(question: string): Promise<boolean> {
|
|
70
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
71
|
-
return new Promise((resolve) => {
|
|
72
|
-
rl.question(`${question} (y/N) `, (answer) => {
|
|
73
|
-
rl.close();
|
|
74
|
-
resolve(answer.trim().toLowerCase() === "y");
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
69
|
function safeReadAiConfig(): ReturnType<typeof readAiConfig> {
|
|
80
70
|
try {
|
|
81
71
|
return readAiConfig();
|
|
@@ -270,7 +260,7 @@ function detectAgentDirs(root: string): string[] {
|
|
|
270
260
|
return dirs;
|
|
271
261
|
}
|
|
272
262
|
|
|
273
|
-
|
|
263
|
+
function copySkills(): void {
|
|
274
264
|
const agentDirs = detectAgentDirs(REPO_ROOT);
|
|
275
265
|
|
|
276
266
|
if (agentDirs.length === 0) {
|
|
@@ -281,17 +271,6 @@ async function copySkills(): Promise<void> {
|
|
|
281
271
|
}
|
|
282
272
|
|
|
283
273
|
const destinations = agentDirs.map((d) => join(d, "skills", "libretto"));
|
|
284
|
-
const dirNames = agentDirs.map((d) => basename(d)).join(" and ");
|
|
285
|
-
// Say "Overwrite" if skills already exist in ANY target dir — skills must
|
|
286
|
-
// be identical across coding agents, so we always copy to all of them.
|
|
287
|
-
const existing = destinations.filter((d) => existsSync(d));
|
|
288
|
-
const verb = existing.length > 0 ? "Overwrite" : "Install";
|
|
289
|
-
|
|
290
|
-
const proceed = await askYesNo(`\n${verb} libretto skills in ${dirNames}?`);
|
|
291
|
-
if (!proceed) {
|
|
292
|
-
console.log(" Skipping skill copy.");
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
274
|
|
|
296
275
|
let sourceDir: string;
|
|
297
276
|
try {
|
|
@@ -339,10 +318,12 @@ export const initCommand = SimpleCLI.command({
|
|
|
339
318
|
console.log("\nSkipping browser installation (--skip-browsers)");
|
|
340
319
|
}
|
|
341
320
|
|
|
321
|
+
copySkills();
|
|
322
|
+
|
|
342
323
|
if (process.stdin.isTTY) {
|
|
343
|
-
await copySkills();
|
|
344
324
|
await runInteractiveApiSetup();
|
|
345
325
|
} else {
|
|
326
|
+
loadSnapshotEnv();
|
|
346
327
|
printSnapshotApiStatus();
|
|
347
328
|
}
|
|
348
329
|
|
|
@@ -29,11 +29,18 @@ export const ViewportConfigSchema = z.object({
|
|
|
29
29
|
});
|
|
30
30
|
export type ViewportConfig = z.infer<typeof ViewportConfigSchema>;
|
|
31
31
|
|
|
32
|
+
export const WindowPositionConfigSchema = z.object({
|
|
33
|
+
x: z.number().int(),
|
|
34
|
+
y: z.number().int(),
|
|
35
|
+
});
|
|
36
|
+
export type WindowPositionConfig = z.infer<typeof WindowPositionConfigSchema>;
|
|
37
|
+
|
|
32
38
|
export const LibrettoConfigSchema = z
|
|
33
39
|
.object({
|
|
34
40
|
version: z.literal(CURRENT_CONFIG_VERSION),
|
|
35
41
|
ai: AiConfigSchema.optional(),
|
|
36
42
|
viewport: ViewportConfigSchema.optional(),
|
|
43
|
+
windowPosition: WindowPositionConfigSchema.optional(),
|
|
37
44
|
})
|
|
38
45
|
.passthrough();
|
|
39
46
|
export type LibrettoConfig = z.infer<typeof LibrettoConfigSchema>;
|
|
@@ -80,6 +87,10 @@ function formatExpectedConfigExample(): string {
|
|
|
80
87
|
width: 1280,
|
|
81
88
|
height: 800,
|
|
82
89
|
},
|
|
90
|
+
windowPosition: {
|
|
91
|
+
x: 1600,
|
|
92
|
+
y: 120,
|
|
93
|
+
},
|
|
83
94
|
},
|
|
84
95
|
null,
|
|
85
96
|
2,
|
|
@@ -94,7 +105,7 @@ function invalidConfigError(configPath: string, detail?: string): Error {
|
|
|
94
105
|
"Expected config example:",
|
|
95
106
|
formatExpectedConfigExample(),
|
|
96
107
|
"Notes:",
|
|
97
|
-
' - "ai" and "
|
|
108
|
+
' - "ai", "viewport", and "windowPosition" are optional.',
|
|
98
109
|
' - "ai.model" must be a provider/model string like "openai/gpt-5.4" or "anthropic/claude-sonnet-4-6".',
|
|
99
110
|
"Fix the file to match this shape, or delete it and rerun:",
|
|
100
111
|
` npx libretto ai configure ${formatConfigureProviders()}`,
|
package/src/cli/core/browser.ts
CHANGED
|
@@ -12,6 +12,14 @@ import { createRequire } from "node:module";
|
|
|
12
12
|
import { createServer } from "node:net";
|
|
13
13
|
import { spawn } from "node:child_process";
|
|
14
14
|
import type { LoggerApi } from "../../shared/logger/index.js";
|
|
15
|
+
import {
|
|
16
|
+
filterSemanticClasses,
|
|
17
|
+
INTERACTIVE_ROLE_NAMES,
|
|
18
|
+
INTERACTIVE_TAG_NAMES,
|
|
19
|
+
isObfuscatedClass,
|
|
20
|
+
TEST_ATTRIBUTE_NAMES,
|
|
21
|
+
TRUSTED_ATTRIBUTE_NAMES,
|
|
22
|
+
} from "../../shared/dom-semantics.js";
|
|
15
23
|
import {
|
|
16
24
|
getSessionActionsLogPath,
|
|
17
25
|
getSessionNetworkLogPath,
|
|
@@ -330,6 +338,20 @@ export function resolveViewport(
|
|
|
330
338
|
return DEFAULT_VIEWPORT;
|
|
331
339
|
}
|
|
332
340
|
|
|
341
|
+
function resolveWindowPosition(
|
|
342
|
+
logger: LoggerApi,
|
|
343
|
+
): { x: number; y: number } | undefined {
|
|
344
|
+
const config = readLibrettoConfig();
|
|
345
|
+
if (config.windowPosition) {
|
|
346
|
+
logger.info("window-position-source", {
|
|
347
|
+
source: "config",
|
|
348
|
+
windowPosition: config.windowPosition,
|
|
349
|
+
});
|
|
350
|
+
return config.windowPosition;
|
|
351
|
+
}
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
|
|
333
355
|
export async function runOpen(
|
|
334
356
|
rawUrl: string,
|
|
335
357
|
headed: boolean,
|
|
@@ -339,7 +361,8 @@ export async function runOpen(
|
|
|
339
361
|
): Promise<void> {
|
|
340
362
|
const url = normalizeUrl(rawUrl);
|
|
341
363
|
const viewport = resolveViewport(options?.viewport, logger);
|
|
342
|
-
|
|
364
|
+
const windowPosition = headed ? resolveWindowPosition(logger) : undefined;
|
|
365
|
+
logger.info("open-start", { url, headed, session, viewport, windowPosition });
|
|
343
366
|
assertSessionAvailableForStart(session, logger);
|
|
344
367
|
|
|
345
368
|
const port = await pickFreePort();
|
|
@@ -382,6 +405,49 @@ export async function runOpen(
|
|
|
382
405
|
const escapedActionsLogPath = actionsLogPath
|
|
383
406
|
.replace(/\\/g, "\\\\")
|
|
384
407
|
.replace(/'/g, "\\'");
|
|
408
|
+
const windowPositionArg = windowPosition
|
|
409
|
+
? `, '--window-position=${windowPosition.x},${windowPosition.y}'`
|
|
410
|
+
: "";
|
|
411
|
+
const windowBoundsSetupCode = windowPosition
|
|
412
|
+
? `
|
|
413
|
+
const requestedWindowBounds = { left: ${windowPosition.x}, top: ${windowPosition.y}, windowState: 'normal' };
|
|
414
|
+
const pageCdp = await context.newCDPSession(page);
|
|
415
|
+
let browserCdp;
|
|
416
|
+
try {
|
|
417
|
+
const targetInfo = await pageCdp.send('Target.getTargetInfo');
|
|
418
|
+
const targetId = targetInfo?.targetInfo?.targetId;
|
|
419
|
+
browserCdp = await browser.newBrowserCDPSession();
|
|
420
|
+
const windowResult = await browserCdp.send(
|
|
421
|
+
'Browser.getWindowForTarget',
|
|
422
|
+
targetId ? { targetId } : {},
|
|
423
|
+
);
|
|
424
|
+
await browserCdp.send('Browser.setWindowBounds', {
|
|
425
|
+
windowId: windowResult.windowId,
|
|
426
|
+
bounds: requestedWindowBounds,
|
|
427
|
+
});
|
|
428
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
429
|
+
const actualWindow = await browserCdp.send('Browser.getWindowBounds', {
|
|
430
|
+
windowId: windowResult.windowId,
|
|
431
|
+
});
|
|
432
|
+
childLog('info', 'window-bounds-set', {
|
|
433
|
+
windowId: windowResult.windowId,
|
|
434
|
+
requestedBounds: requestedWindowBounds,
|
|
435
|
+
actualBounds: actualWindow.bounds,
|
|
436
|
+
});
|
|
437
|
+
} catch (error) {
|
|
438
|
+
childLog('warn', 'window-bounds-set-failed', {
|
|
439
|
+
requestedBounds: requestedWindowBounds,
|
|
440
|
+
message: error instanceof Error ? error.message : String(error),
|
|
441
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
442
|
+
});
|
|
443
|
+
} finally {
|
|
444
|
+
await pageCdp.detach().catch(() => {});
|
|
445
|
+
if (browserCdp) {
|
|
446
|
+
await browserCdp.detach().catch(() => {});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
`
|
|
450
|
+
: "";
|
|
385
451
|
|
|
386
452
|
const launcherCode = `
|
|
387
453
|
import { chromium } from 'playwright';
|
|
@@ -394,14 +460,21 @@ const ACTIONS_LOG = '${escapedActionsLogPath}';
|
|
|
394
460
|
mkdirSync(dirname(NETWORK_LOG), { recursive: true });
|
|
395
461
|
|
|
396
462
|
// tsx/esbuild may emit __name() wrappers in Function#toString output.
|
|
397
|
-
const __name = (target, value) =>
|
|
398
|
-
|
|
463
|
+
const __name = (target, value) =>
|
|
464
|
+
Object.defineProperty(target, 'name', { value, configurable: true });
|
|
399
465
|
|
|
400
|
-
${
|
|
466
|
+
const TEST_ATTRIBUTE_NAMES = ${JSON.stringify([...TEST_ATTRIBUTE_NAMES])};
|
|
467
|
+
const TRUSTED_ATTRIBUTE_NAMES = ${JSON.stringify([...TRUSTED_ATTRIBUTE_NAMES])};
|
|
468
|
+
const INTERACTIVE_TAG_NAMES = ${JSON.stringify([...INTERACTIVE_TAG_NAMES])};
|
|
469
|
+
const INTERACTIVE_ROLE_NAMES = ${JSON.stringify([...INTERACTIVE_ROLE_NAMES])};
|
|
470
|
+
const filterSemanticClasses = ${filterSemanticClasses.toString()};
|
|
471
|
+
const isObfuscatedClass = ${isObfuscatedClass.toString()};
|
|
401
472
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
473
|
+
${installSessionTelemetry.toString()}
|
|
474
|
+
|
|
475
|
+
function logAction(entry) {
|
|
476
|
+
appendFileSync(ACTIONS_LOG, JSON.stringify(entry) + '\\n');
|
|
477
|
+
}
|
|
405
478
|
|
|
406
479
|
function logNetwork(entry) {
|
|
407
480
|
appendFileSync(NETWORK_LOG, JSON.stringify(entry) + '\\n');
|
|
@@ -423,7 +496,7 @@ function childLog(level, event, data = {}) {
|
|
|
423
496
|
|
|
424
497
|
const browser = await chromium.launch({
|
|
425
498
|
headless: ${!headed},
|
|
426
|
-
args: ['--disable-blink-features=AutomationControlled', '--remote-debugging-port=${port}', '--remote-debugging-address=127.0.0.1', '--no-focus-on-check'],
|
|
499
|
+
args: ['--disable-blink-features=AutomationControlled', '--remote-debugging-port=${port}', '--remote-debugging-address=127.0.0.1', '--no-focus-on-check'${windowPositionArg}],
|
|
427
500
|
});
|
|
428
501
|
|
|
429
502
|
browser.on('disconnected', () => {
|
|
@@ -437,6 +510,7 @@ const context = await browser.newContext({
|
|
|
437
510
|
});
|
|
438
511
|
|
|
439
512
|
const page = await context.newPage();
|
|
513
|
+
${windowBoundsSetupCode}
|
|
440
514
|
page.setDefaultTimeout(30000);
|
|
441
515
|
page.setDefaultNavigationTimeout(45000);
|
|
442
516
|
|