libretto 0.4.4 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -36
- package/dist/cli/cli.js +39 -113
- package/dist/cli/commands/ai.js +1 -1
- package/dist/cli/commands/browser.js +87 -60
- package/dist/cli/commands/execution.js +201 -88
- package/dist/cli/commands/init.js +30 -8
- package/dist/cli/commands/logs.js +5 -6
- package/dist/cli/commands/shared.js +30 -29
- package/dist/cli/commands/snapshot.js +26 -39
- package/dist/cli/core/ai-config.js +9 -2
- package/dist/cli/core/api-snapshot-analyzer.js +15 -5
- package/dist/cli/core/browser.js +141 -33
- package/dist/cli/core/context.js +7 -18
- package/dist/cli/core/session-telemetry.js +5 -2
- package/dist/cli/core/session.js +23 -10
- package/dist/cli/core/snapshot-analyzer.js +16 -33
- package/dist/cli/core/snapshot-api-config.js +2 -6
- package/dist/cli/core/telemetry.js +10 -2
- package/dist/cli/framework/simple-cli.js +45 -25
- package/dist/cli/router.js +14 -21
- package/dist/cli/workers/run-integration-runtime.js +26 -7
- package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
- package/dist/cli/workers/run-integration-worker.js +1 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.js +7 -10
- package/dist/runtime/download/download.js +5 -1
- package/dist/runtime/extract/extract.js +11 -2
- package/dist/runtime/network/network.js +8 -1
- package/dist/runtime/recovery/agent.js +6 -2
- package/dist/runtime/recovery/errors.js +3 -1
- package/dist/runtime/recovery/recovery.js +3 -1
- package/dist/shared/condense-dom/condense-dom.js +6 -13
- package/dist/shared/config/config.d.ts +1 -9
- package/dist/shared/config/config.js +0 -18
- package/dist/shared/config/index.d.ts +2 -1
- package/dist/shared/config/index.js +0 -10
- package/dist/shared/debug/pause.js +9 -3
- package/dist/shared/instrumentation/instrument.js +101 -5
- package/dist/shared/llm/ai-sdk-adapter.js +3 -1
- package/dist/shared/llm/client.js +3 -1
- package/dist/shared/logger/index.js +4 -1
- package/dist/shared/paths/paths.js +2 -1
- package/dist/shared/paths/repo-root.d.ts +3 -0
- package/dist/shared/paths/repo-root.js +24 -0
- package/dist/shared/run/api.js +3 -1
- package/dist/shared/run/browser.js +7 -2
- package/dist/shared/state/session-state.d.ts +2 -1
- package/dist/shared/state/session-state.js +5 -2
- package/dist/shared/visualization/ghost-cursor.js +19 -10
- package/dist/shared/visualization/highlight.js +9 -6
- package/dist/shared/workflow/workflow.d.ts +4 -5
- package/dist/shared/workflow/workflow.js +3 -5
- package/package.json +11 -8
- package/scripts/check-skills-sync.mjs +25 -0
- package/scripts/compare-eval-summary.mjs +47 -0
- package/scripts/postinstall.mjs +26 -17
- package/scripts/prepare-release.sh +97 -0
- package/scripts/skills-libretto.mjs +103 -0
- package/scripts/summarize-evals.mjs +135 -0
- package/scripts/sync-skills.mjs +12 -0
- package/skills/libretto/SKILL.md +130 -377
- package/skills/libretto/references/auth-profiles.md +30 -0
- package/skills/libretto/{code-generation-rules.md → references/code-generation-rules.md} +27 -42
- package/skills/libretto/references/configuration-file-reference.md +53 -0
- package/skills/libretto/references/pages-and-page-targeting.md +29 -0
- package/skills/libretto/references/site-security-review.md +143 -0
- package/src/cli/cli.ts +86 -0
- package/src/cli/commands/ai.ts +35 -0
- package/src/cli/commands/browser.ts +189 -0
- package/src/cli/commands/execution.ts +822 -0
- package/src/cli/commands/init.ts +350 -0
- package/src/cli/commands/logs.ts +128 -0
- package/src/cli/commands/shared.ts +69 -0
- package/src/cli/commands/snapshot.ts +312 -0
- package/src/cli/core/ai-config.ts +264 -0
- package/src/cli/core/api-snapshot-analyzer.ts +108 -0
- package/src/cli/core/browser.ts +976 -0
- package/src/cli/core/context.ts +127 -0
- package/src/cli/core/pause-signals.ts +35 -0
- package/src/cli/core/session-telemetry.ts +564 -0
- package/src/cli/core/session.ts +223 -0
- package/src/cli/core/snapshot-analyzer.ts +855 -0
- package/src/cli/core/snapshot-api-config.ts +231 -0
- package/src/cli/core/telemetry.ts +459 -0
- package/src/cli/framework/simple-cli.ts +1340 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/router.ts +20 -0
- package/src/cli/workers/run-integration-runtime.ts +338 -0
- package/src/cli/workers/run-integration-worker-protocol.ts +16 -0
- package/src/cli/workers/run-integration-worker.ts +72 -0
- package/src/index.ts +127 -0
- package/src/runtime/download/download.ts +104 -0
- package/src/runtime/download/index.ts +7 -0
- package/src/runtime/extract/extract.ts +102 -0
- package/src/runtime/extract/index.ts +1 -0
- package/src/runtime/network/index.ts +5 -0
- package/src/runtime/network/network.ts +119 -0
- package/{dist/runtime/recovery/agent.cjs → src/runtime/recovery/agent.ts} +114 -76
- package/src/runtime/recovery/errors.ts +155 -0
- package/src/runtime/recovery/index.ts +7 -0
- package/src/runtime/recovery/recovery.ts +53 -0
- package/{dist/shared/condense-dom/condense-dom.cjs → src/shared/condense-dom/condense-dom.ts} +249 -124
- package/src/shared/config/config.ts +3 -0
- package/src/shared/config/index.ts +0 -0
- package/src/shared/debug/index.ts +1 -0
- package/src/shared/debug/pause.ts +91 -0
- package/src/shared/instrumentation/errors.ts +84 -0
- package/src/shared/instrumentation/index.ts +9 -0
- package/src/shared/instrumentation/instrument.ts +406 -0
- package/src/shared/llm/ai-sdk-adapter.ts +81 -0
- package/{dist/shared/llm/client.cjs → src/shared/llm/client.ts} +86 -80
- package/src/shared/llm/index.ts +3 -0
- package/src/shared/llm/types.ts +63 -0
- package/src/shared/logger/index.ts +13 -0
- package/src/shared/logger/logger.ts +358 -0
- package/src/shared/logger/sinks.ts +148 -0
- package/src/shared/paths/paths.ts +110 -0
- package/src/shared/paths/repo-root.ts +27 -0
- package/src/shared/run/api.ts +6 -0
- package/src/shared/run/browser.ts +107 -0
- package/src/shared/state/index.ts +11 -0
- package/src/shared/state/session-state.ts +77 -0
- package/src/shared/visualization/ghost-cursor.ts +213 -0
- package/src/shared/visualization/highlight.ts +149 -0
- package/src/shared/visualization/index.ts +18 -0
- package/src/shared/workflow/workflow.ts +36 -0
- package/dist/index.cjs +0 -144
- package/dist/index.d.cts +0 -21
- package/dist/runtime/download/download.cjs +0 -70
- package/dist/runtime/download/download.d.cts +0 -35
- package/dist/runtime/download/index.cjs +0 -30
- package/dist/runtime/download/index.d.cts +0 -3
- package/dist/runtime/extract/extract.cjs +0 -88
- package/dist/runtime/extract/extract.d.cts +0 -23
- package/dist/runtime/extract/index.cjs +0 -28
- package/dist/runtime/extract/index.d.cts +0 -5
- package/dist/runtime/network/index.cjs +0 -28
- package/dist/runtime/network/index.d.cts +0 -4
- package/dist/runtime/network/network.cjs +0 -91
- package/dist/runtime/network/network.d.cts +0 -28
- package/dist/runtime/recovery/agent.d.cts +0 -13
- package/dist/runtime/recovery/errors.cjs +0 -124
- package/dist/runtime/recovery/errors.d.cts +0 -31
- package/dist/runtime/recovery/index.cjs +0 -34
- package/dist/runtime/recovery/index.d.cts +0 -7
- package/dist/runtime/recovery/recovery.cjs +0 -55
- package/dist/runtime/recovery/recovery.d.cts +0 -12
- package/dist/shared/condense-dom/condense-dom.d.cts +0 -34
- package/dist/shared/config/config.cjs +0 -44
- package/dist/shared/config/config.d.cts +0 -10
- package/dist/shared/config/index.cjs +0 -32
- package/dist/shared/config/index.d.cts +0 -1
- package/dist/shared/debug/index.cjs +0 -28
- package/dist/shared/debug/index.d.cts +0 -1
- package/dist/shared/debug/pause.cjs +0 -86
- package/dist/shared/debug/pause.d.cts +0 -12
- package/dist/shared/instrumentation/errors.cjs +0 -81
- package/dist/shared/instrumentation/errors.d.cts +0 -12
- package/dist/shared/instrumentation/index.cjs +0 -35
- package/dist/shared/instrumentation/index.d.cts +0 -6
- package/dist/shared/instrumentation/instrument.cjs +0 -206
- package/dist/shared/instrumentation/instrument.d.cts +0 -32
- package/dist/shared/llm/ai-sdk-adapter.cjs +0 -71
- package/dist/shared/llm/ai-sdk-adapter.d.cts +0 -22
- package/dist/shared/llm/client.d.cts +0 -13
- package/dist/shared/llm/index.cjs +0 -31
- package/dist/shared/llm/index.d.cts +0 -5
- package/dist/shared/llm/types.cjs +0 -16
- package/dist/shared/llm/types.d.cts +0 -67
- package/dist/shared/logger/index.cjs +0 -37
- package/dist/shared/logger/index.d.cts +0 -2
- package/dist/shared/logger/logger.cjs +0 -232
- package/dist/shared/logger/logger.d.cts +0 -86
- package/dist/shared/logger/sinks.cjs +0 -160
- package/dist/shared/logger/sinks.d.cts +0 -9
- package/dist/shared/paths/paths.cjs +0 -104
- package/dist/shared/paths/paths.d.cts +0 -10
- package/dist/shared/run/api.cjs +0 -28
- package/dist/shared/run/api.d.cts +0 -2
- package/dist/shared/run/browser.cjs +0 -98
- package/dist/shared/run/browser.d.cts +0 -22
- package/dist/shared/state/index.cjs +0 -38
- package/dist/shared/state/index.d.cts +0 -2
- package/dist/shared/state/session-state.cjs +0 -92
- package/dist/shared/state/session-state.d.cts +0 -40
- package/dist/shared/visualization/ghost-cursor.cjs +0 -174
- package/dist/shared/visualization/ghost-cursor.d.cts +0 -37
- package/dist/shared/visualization/highlight.cjs +0 -134
- package/dist/shared/visualization/highlight.d.cts +0 -22
- package/dist/shared/visualization/index.cjs +0 -45
- package/dist/shared/visualization/index.d.cts +0 -3
- package/dist/shared/workflow/workflow.cjs +0 -47
- package/dist/shared/workflow/workflow.d.cts +0 -21
- package/skills/libretto/integration-approach-selection.md +0 -174
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import {
|
|
3
|
+
appendFileSync,
|
|
4
|
+
cpSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { basename, dirname, join } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { readAiConfig } from "../core/ai-config.js";
|
|
15
|
+
import { REPO_ROOT } from "../core/context.js";
|
|
16
|
+
import {
|
|
17
|
+
loadSnapshotEnv,
|
|
18
|
+
resolveSnapshotApiModel,
|
|
19
|
+
} from "../core/snapshot-api-config.js";
|
|
20
|
+
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
21
|
+
import { hasProviderCredentials } from "../../shared/llm/client.js";
|
|
22
|
+
|
|
23
|
+
type ProviderChoice = {
|
|
24
|
+
key: string;
|
|
25
|
+
label: string;
|
|
26
|
+
envVar: string;
|
|
27
|
+
envHint: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const PROVIDER_CHOICES: ProviderChoice[] = [
|
|
31
|
+
{
|
|
32
|
+
key: "1",
|
|
33
|
+
label: "OpenAI",
|
|
34
|
+
envVar: "OPENAI_API_KEY",
|
|
35
|
+
envHint: "Get your key at https://platform.openai.com/api-keys",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: "2",
|
|
39
|
+
label: "Anthropic",
|
|
40
|
+
envVar: "ANTHROPIC_API_KEY",
|
|
41
|
+
envHint: "Get your key at https://console.anthropic.com/settings/keys",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: "3",
|
|
45
|
+
label: "Google Gemini",
|
|
46
|
+
envVar: "GEMINI_API_KEY",
|
|
47
|
+
envHint: "Get your key at https://aistudio.google.com/apikey",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: "4",
|
|
51
|
+
label: "Google Vertex AI",
|
|
52
|
+
envVar: "GOOGLE_CLOUD_PROJECT",
|
|
53
|
+
envHint:
|
|
54
|
+
"Requires gcloud auth application-default login and a GCP project ID",
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
function promptUser(
|
|
59
|
+
rl: ReturnType<typeof createInterface>,
|
|
60
|
+
question: string,
|
|
61
|
+
): Promise<string> {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
rl.question(question, (answer) => {
|
|
64
|
+
resolve(answer.trim());
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
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
|
+
function safeReadAiConfig(): ReturnType<typeof readAiConfig> {
|
|
80
|
+
try {
|
|
81
|
+
return readAiConfig();
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function printInvalidAiConfigWarning(): void {
|
|
88
|
+
try {
|
|
89
|
+
readAiConfig();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
console.log(" ! Existing AI config is invalid:");
|
|
93
|
+
for (const line of message.split("\n")) {
|
|
94
|
+
console.log(` ${line}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function printSnapshotApiStatus(): void {
|
|
100
|
+
const config = safeReadAiConfig();
|
|
101
|
+
const selection = resolveSnapshotApiModel(config);
|
|
102
|
+
const envPath = join(REPO_ROOT, ".env");
|
|
103
|
+
|
|
104
|
+
console.log("\nSnapshot analysis:");
|
|
105
|
+
console.log(
|
|
106
|
+
" Libretto uses direct API calls for snapshot analysis when supported credentials are available.",
|
|
107
|
+
);
|
|
108
|
+
console.log(` Credentials are loaded from process env and ${envPath}.`);
|
|
109
|
+
printInvalidAiConfigWarning();
|
|
110
|
+
|
|
111
|
+
if (selection && hasProviderCredentials(selection.provider)) {
|
|
112
|
+
console.log(` ✓ Ready: ${selection.model} (${selection.source})`);
|
|
113
|
+
console.log(
|
|
114
|
+
" Snapshot objectives will use the API analyzer by default.",
|
|
115
|
+
);
|
|
116
|
+
console.log(" No further action required.");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(" ✗ No snapshot API credentials detected.");
|
|
121
|
+
console.log(" Add one provider to .env:");
|
|
122
|
+
console.log(" OPENAI_API_KEY=...");
|
|
123
|
+
console.log(" ANTHROPIC_API_KEY=...");
|
|
124
|
+
console.log(" GEMINI_API_KEY=... # or GOOGLE_GENERATIVE_AI_API_KEY");
|
|
125
|
+
console.log(
|
|
126
|
+
" GOOGLE_CLOUD_PROJECT=... # plus application default credentials for Vertex",
|
|
127
|
+
);
|
|
128
|
+
console.log(
|
|
129
|
+
" Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model.",
|
|
130
|
+
);
|
|
131
|
+
console.log(
|
|
132
|
+
" Run `npx libretto init` interactively to set up credentials.",
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function runInteractiveApiSetup(): Promise<void> {
|
|
137
|
+
const config = safeReadAiConfig();
|
|
138
|
+
const selection = resolveSnapshotApiModel(config);
|
|
139
|
+
const envPath = join(REPO_ROOT, ".env");
|
|
140
|
+
|
|
141
|
+
console.log("\nSnapshot analysis setup:");
|
|
142
|
+
console.log(" Libretto uses direct API calls for snapshot analysis.");
|
|
143
|
+
console.log(` Credentials are loaded from process env and ${envPath}.`);
|
|
144
|
+
printInvalidAiConfigWarning();
|
|
145
|
+
|
|
146
|
+
if (selection && hasProviderCredentials(selection.provider)) {
|
|
147
|
+
console.log(` ✓ Ready: ${selection.model} (${selection.source})`);
|
|
148
|
+
console.log(
|
|
149
|
+
" Snapshot objectives will use the API analyzer by default.",
|
|
150
|
+
);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(" ✗ No snapshot API credentials detected.\n");
|
|
155
|
+
|
|
156
|
+
const rl = createInterface({
|
|
157
|
+
input: process.stdin,
|
|
158
|
+
output: process.stdout,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
console.log(
|
|
163
|
+
" Which API provider would you like to use for snapshot analysis?\n",
|
|
164
|
+
);
|
|
165
|
+
for (const choice of PROVIDER_CHOICES) {
|
|
166
|
+
console.log(` ${choice.key}) ${choice.label}`);
|
|
167
|
+
}
|
|
168
|
+
console.log(" s) Skip for now\n");
|
|
169
|
+
|
|
170
|
+
const answer = await promptUser(rl, " Choice: ");
|
|
171
|
+
|
|
172
|
+
if (answer.toLowerCase() === "s" || !answer) {
|
|
173
|
+
console.log(
|
|
174
|
+
"\n Skipped. You can set up API credentials later by rerunning `npx libretto init`.",
|
|
175
|
+
);
|
|
176
|
+
console.log(" Or add credentials directly to your .env file:");
|
|
177
|
+
console.log(" OPENAI_API_KEY=...");
|
|
178
|
+
console.log(" ANTHROPIC_API_KEY=...");
|
|
179
|
+
console.log(" GEMINI_API_KEY=...");
|
|
180
|
+
console.log(
|
|
181
|
+
" Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model.",
|
|
182
|
+
);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const selected = PROVIDER_CHOICES.find((choice) => choice.key === answer);
|
|
187
|
+
if (!selected) {
|
|
188
|
+
console.log(`\n Unknown choice "${answer}". Skipping API setup.`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log(`\n ${selected.label} selected.`);
|
|
193
|
+
console.log(` ${selected.envHint}\n`);
|
|
194
|
+
|
|
195
|
+
const apiKeyValue = await promptUser(
|
|
196
|
+
rl,
|
|
197
|
+
` Enter your ${selected.envVar}: `,
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (!apiKeyValue) {
|
|
201
|
+
console.log("\n No value entered. Skipping API key setup.");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let envContent = "";
|
|
206
|
+
if (existsSync(envPath)) {
|
|
207
|
+
envContent = readFileSync(envPath, "utf-8");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const envLine = `${selected.envVar}=${apiKeyValue}`;
|
|
211
|
+
if (envContent.includes(`${selected.envVar}=`)) {
|
|
212
|
+
const updated = envContent.replace(
|
|
213
|
+
new RegExp(`^${selected.envVar}=.*$`, "m"),
|
|
214
|
+
() => envLine,
|
|
215
|
+
);
|
|
216
|
+
writeFileSync(envPath, updated);
|
|
217
|
+
console.log(`\n ✓ Updated ${selected.envVar} in ${envPath}`);
|
|
218
|
+
} else {
|
|
219
|
+
const separator = envContent && !envContent.endsWith("\n") ? "\n" : "";
|
|
220
|
+
appendFileSync(envPath, `${separator}${envLine}\n`);
|
|
221
|
+
console.log(`\n ✓ Added ${selected.envVar} to ${envPath}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
loadSnapshotEnv();
|
|
225
|
+
process.env[selected.envVar] = apiKeyValue;
|
|
226
|
+
const newSelection = resolveSnapshotApiModel(safeReadAiConfig());
|
|
227
|
+
if (newSelection && hasProviderCredentials(newSelection.provider)) {
|
|
228
|
+
console.log(` ✓ Snapshot API ready: ${newSelection.model}`);
|
|
229
|
+
}
|
|
230
|
+
} finally {
|
|
231
|
+
rl.close();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function installBrowsers(): void {
|
|
236
|
+
console.log("\nInstalling Playwright Chromium...");
|
|
237
|
+
const result = spawnSync("npx", ["playwright", "install", "chromium"], {
|
|
238
|
+
stdio: "inherit",
|
|
239
|
+
shell: true,
|
|
240
|
+
});
|
|
241
|
+
if (result.status === 0) {
|
|
242
|
+
console.log(" ✓ Playwright Chromium installed");
|
|
243
|
+
} else {
|
|
244
|
+
console.error(
|
|
245
|
+
" ✗ Failed to install Playwright Chromium. Run manually: npx playwright install chromium",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getPackageSkillsDir(): string {
|
|
251
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
252
|
+
// Walk up from dist/cli/commands/ to package root
|
|
253
|
+
let dir = dirname(thisFile);
|
|
254
|
+
while (dir !== dirname(dir)) {
|
|
255
|
+
if (existsSync(join(dir, "skills", "libretto"))) {
|
|
256
|
+
return join(dir, "skills", "libretto");
|
|
257
|
+
}
|
|
258
|
+
dir = dirname(dir);
|
|
259
|
+
}
|
|
260
|
+
throw new Error("Could not locate libretto skill files in package");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Auto-detect .agents/ and .claude/ directories at a given root path.
|
|
265
|
+
*/
|
|
266
|
+
function detectAgentDirs(root: string): string[] {
|
|
267
|
+
const dirs: string[] = [];
|
|
268
|
+
if (existsSync(join(root, ".agents"))) dirs.push(join(root, ".agents"));
|
|
269
|
+
if (existsSync(join(root, ".claude"))) dirs.push(join(root, ".claude"));
|
|
270
|
+
return dirs;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function copySkills(): Promise<void> {
|
|
274
|
+
const agentDirs = detectAgentDirs(REPO_ROOT);
|
|
275
|
+
|
|
276
|
+
if (agentDirs.length === 0) {
|
|
277
|
+
console.log(
|
|
278
|
+
"\nSkills: No .agents/ or .claude/ directory found in repo root — skipping.",
|
|
279
|
+
);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
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
|
+
|
|
296
|
+
let sourceDir: string;
|
|
297
|
+
try {
|
|
298
|
+
sourceDir = getPackageSkillsDir();
|
|
299
|
+
} catch (e) {
|
|
300
|
+
console.error(` ✗ ${e instanceof Error ? e.message : String(e)}`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
for (let i = 0; i < agentDirs.length; i++) {
|
|
305
|
+
const skillDest = destinations[i];
|
|
306
|
+
const name = basename(agentDirs[i]);
|
|
307
|
+
// Remove existing dir first so stale files from prior versions don't persist
|
|
308
|
+
if (existsSync(skillDest)) {
|
|
309
|
+
rmSync(skillDest, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
cpSync(sourceDir, skillDest, { recursive: true });
|
|
312
|
+
const fileCount = readdirSync(skillDest).length;
|
|
313
|
+
console.log(
|
|
314
|
+
` ✓ Copied ${fileCount} skill files to ${name}/skills/libretto/`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export const initInput = SimpleCLI.input({
|
|
320
|
+
positionals: [],
|
|
321
|
+
named: {
|
|
322
|
+
skipBrowsers: SimpleCLI.flag({
|
|
323
|
+
name: "skip-browsers",
|
|
324
|
+
help: "Skip Playwright Chromium installation",
|
|
325
|
+
}),
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
export const initCommand = SimpleCLI.command({
|
|
330
|
+
description: "Initialize libretto in the current project",
|
|
331
|
+
})
|
|
332
|
+
.input(initInput)
|
|
333
|
+
.handle(async ({ input }) => {
|
|
334
|
+
console.log("Initializing libretto...\n");
|
|
335
|
+
|
|
336
|
+
if (!input.skipBrowsers) {
|
|
337
|
+
installBrowsers();
|
|
338
|
+
} else {
|
|
339
|
+
console.log("\nSkipping browser installation (--skip-browsers)");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (process.stdin.isTTY) {
|
|
343
|
+
await copySkills();
|
|
344
|
+
await runInteractiveApiSetup();
|
|
345
|
+
} else {
|
|
346
|
+
printSnapshotApiStatus();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
console.log("\n✓ libretto init complete");
|
|
350
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listOpenPages } from "../core/browser.js";
|
|
3
|
+
import { withSessionLogger } from "../core/context.js";
|
|
4
|
+
import {
|
|
5
|
+
clearActionLog,
|
|
6
|
+
clearNetworkLog,
|
|
7
|
+
formatActionEntry,
|
|
8
|
+
formatNetworkEntry,
|
|
9
|
+
readActionLog,
|
|
10
|
+
readNetworkLog,
|
|
11
|
+
} from "../core/telemetry.js";
|
|
12
|
+
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
13
|
+
import {
|
|
14
|
+
integerOption,
|
|
15
|
+
pageOption,
|
|
16
|
+
sessionOption,
|
|
17
|
+
withRequiredSession,
|
|
18
|
+
} from "./shared.js";
|
|
19
|
+
|
|
20
|
+
async function resolvePageId(
|
|
21
|
+
session: string,
|
|
22
|
+
pageId?: string,
|
|
23
|
+
): Promise<string | undefined> {
|
|
24
|
+
if (!pageId) return undefined;
|
|
25
|
+
const pages = await withSessionLogger(session, async (logger) =>
|
|
26
|
+
listOpenPages(session, logger),
|
|
27
|
+
);
|
|
28
|
+
const foundPage = pages.find((page) => page.id === pageId);
|
|
29
|
+
if (!foundPage) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Page "${pageId}" was not found in session "${session}". Run "libretto pages --session ${session}" to list ids.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return pageId;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const networkInput = SimpleCLI.input({
|
|
38
|
+
positionals: [],
|
|
39
|
+
named: {
|
|
40
|
+
session: sessionOption(),
|
|
41
|
+
last: integerOption(),
|
|
42
|
+
filter: SimpleCLI.option(z.string().optional()),
|
|
43
|
+
method: SimpleCLI.option(z.string().optional()),
|
|
44
|
+
page: pageOption(),
|
|
45
|
+
clear: SimpleCLI.flag(),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const networkCommand = SimpleCLI.command({
|
|
50
|
+
description: "View captured network requests",
|
|
51
|
+
})
|
|
52
|
+
.input(networkInput)
|
|
53
|
+
.use(withRequiredSession())
|
|
54
|
+
.handle(async ({ input, ctx }) => {
|
|
55
|
+
if (input.clear) {
|
|
56
|
+
clearNetworkLog(ctx.session);
|
|
57
|
+
console.log("Network log cleared.");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const pageId = await resolvePageId(ctx.session, input.page);
|
|
62
|
+
const entries = readNetworkLog(ctx.session, {
|
|
63
|
+
last: input.last,
|
|
64
|
+
filter: input.filter,
|
|
65
|
+
method: input.method,
|
|
66
|
+
pageId,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (entries.length === 0) {
|
|
70
|
+
console.log("No network requests captured.");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
console.log(formatNetworkEntry(entry));
|
|
76
|
+
}
|
|
77
|
+
console.log(`\n${entries.length} request(s) shown.`);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const actionsInput = SimpleCLI.input({
|
|
81
|
+
positionals: [],
|
|
82
|
+
named: {
|
|
83
|
+
session: sessionOption(),
|
|
84
|
+
last: integerOption(),
|
|
85
|
+
filter: SimpleCLI.option(z.string().optional()),
|
|
86
|
+
action: SimpleCLI.option(z.string().optional()),
|
|
87
|
+
source: SimpleCLI.option(z.string().optional()),
|
|
88
|
+
page: pageOption(),
|
|
89
|
+
clear: SimpleCLI.flag(),
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export const actionsCommand = SimpleCLI.command({
|
|
94
|
+
description: "View captured actions",
|
|
95
|
+
})
|
|
96
|
+
.input(actionsInput)
|
|
97
|
+
.use(withRequiredSession())
|
|
98
|
+
.handle(async ({ input, ctx }) => {
|
|
99
|
+
if (input.clear) {
|
|
100
|
+
clearActionLog(ctx.session);
|
|
101
|
+
console.log("Action log cleared.");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const pageId = await resolvePageId(ctx.session, input.page);
|
|
106
|
+
const entries = readActionLog(ctx.session, {
|
|
107
|
+
last: input.last,
|
|
108
|
+
filter: input.filter,
|
|
109
|
+
action: input.action,
|
|
110
|
+
source: input.source,
|
|
111
|
+
pageId,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (entries.length === 0) {
|
|
115
|
+
console.log("No actions captured.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
console.log(formatActionEntry(entry));
|
|
121
|
+
}
|
|
122
|
+
console.log(`\n${entries.length} action(s) shown.`);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export const logCommands = {
|
|
126
|
+
network: networkCommand,
|
|
127
|
+
actions: actionsCommand,
|
|
128
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { LoggerApi } from "../../shared/logger/index.js";
|
|
3
|
+
import { createLoggerForSession } from "../core/context.js";
|
|
4
|
+
import {
|
|
5
|
+
generateSessionName,
|
|
6
|
+
readSessionStateOrThrow,
|
|
7
|
+
type SessionState,
|
|
8
|
+
validateSessionName,
|
|
9
|
+
} from "../core/session.js";
|
|
10
|
+
import {
|
|
11
|
+
SimpleCLI,
|
|
12
|
+
type SimpleCLIMiddleware,
|
|
13
|
+
} from "../framework/simple-cli.js";
|
|
14
|
+
|
|
15
|
+
export function sessionOption(help = "Session name") {
|
|
16
|
+
return SimpleCLI.option(z.string().optional(), { help });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function pageOption(help = "Target a specific page id") {
|
|
20
|
+
return SimpleCLI.option(z.string().optional(), { help });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function integerOption(help?: string) {
|
|
24
|
+
return SimpleCLI.option(z.coerce.number().int().optional(), { help });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type SessionContext = {
|
|
28
|
+
session: string;
|
|
29
|
+
logger: LoggerApi;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type SessionStateContext = SessionContext & {
|
|
33
|
+
sessionState: SessionState;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function withRequiredSession(): SimpleCLIMiddleware<
|
|
37
|
+
{ session?: string },
|
|
38
|
+
{},
|
|
39
|
+
SessionStateContext
|
|
40
|
+
> {
|
|
41
|
+
return async ({ input, ctx }) => {
|
|
42
|
+
if (!input.session) {
|
|
43
|
+
throw new Error("Missing required option --session.");
|
|
44
|
+
}
|
|
45
|
+
validateSessionName(input.session);
|
|
46
|
+
const logger = createLoggerForSession(input.session);
|
|
47
|
+
return {
|
|
48
|
+
...ctx,
|
|
49
|
+
session: input.session,
|
|
50
|
+
logger,
|
|
51
|
+
sessionState: readSessionStateOrThrow(input.session),
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function withAutoSession(): SimpleCLIMiddleware<
|
|
57
|
+
{ session?: string },
|
|
58
|
+
{},
|
|
59
|
+
SessionContext
|
|
60
|
+
> {
|
|
61
|
+
return async ({ input, ctx }) => {
|
|
62
|
+
const session = input.session ?? generateSessionName();
|
|
63
|
+
if (input.session) {
|
|
64
|
+
validateSessionName(input.session);
|
|
65
|
+
}
|
|
66
|
+
const logger = createLoggerForSession(session);
|
|
67
|
+
return { ...ctx, session, logger };
|
|
68
|
+
};
|
|
69
|
+
}
|