libretto 0.6.12 → 0.6.13
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 +3 -8
- package/README.template.md +3 -8
- package/dist/cli/cli.js +0 -23
- package/dist/cli/commands/browser.js +1 -7
- package/dist/cli/commands/setup.js +1 -294
- package/dist/cli/commands/snapshot.js +9 -99
- package/dist/cli/commands/status.js +1 -41
- package/dist/cli/core/browser.js +3 -3
- package/dist/cli/core/config.js +3 -6
- package/dist/cli/core/daemon/daemon.js +25 -64
- package/dist/cli/core/daemon/snapshot.js +2 -29
- package/dist/cli/core/experiments.js +1 -28
- package/dist/cli/index.js +0 -2
- package/dist/cli/router.js +0 -2
- package/dist/shared/instrumentation/instrument.js +4 -4
- package/docs/releasing.md +8 -6
- package/package.json +2 -1
- package/skills/libretto/SKILL.md +17 -19
- package/skills/libretto/references/configuration-file-reference.md +6 -12
- package/skills/libretto/references/pages-and-page-targeting.md +1 -1
- package/skills/libretto-readonly/SKILL.md +2 -9
- package/src/cli/cli.ts +0 -24
- package/src/cli/commands/browser.ts +1 -7
- package/src/cli/commands/setup.ts +1 -380
- package/src/cli/commands/snapshot.ts +8 -136
- package/src/cli/commands/status.ts +1 -49
- package/src/cli/core/browser.ts +3 -3
- package/src/cli/core/config.ts +3 -6
- package/src/cli/core/daemon/daemon.ts +25 -67
- package/src/cli/core/daemon/ipc.ts +5 -16
- package/src/cli/core/daemon/snapshot.ts +1 -43
- package/src/cli/core/experiments.ts +9 -38
- package/src/cli/core/resolve-model.ts +5 -0
- package/src/cli/core/workflow-runtime.ts +1 -0
- package/src/cli/index.ts +0 -1
- package/src/cli/router.ts +0 -2
- package/src/shared/instrumentation/instrument.ts +4 -4
- package/dist/cli/commands/ai.js +0 -110
- package/dist/cli/core/ai-model.js +0 -195
- package/dist/cli/core/api-snapshot-analyzer.js +0 -86
- package/dist/cli/core/snapshot-analyzer.js +0 -667
- package/scripts/summarize-evals.mjs +0 -135
- package/src/cli/commands/ai.ts +0 -144
- package/src/cli/core/ai-model.ts +0 -301
- package/src/cli/core/api-snapshot-analyzer.ts +0 -110
- package/src/cli/core/snapshot-analyzer.ts +0 -856
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
} from "../browser.js";
|
|
64
64
|
import { handlePages } from "./pages.js";
|
|
65
65
|
import { handleExec, handleReadonlyExec } from "./exec.js";
|
|
66
|
-
import { handleCompactSnapshot
|
|
66
|
+
import { handleCompactSnapshot } from "./snapshot.js";
|
|
67
67
|
import { librettoCommand } from "../../../shared/package-manager.js";
|
|
68
68
|
import type { Snapshot } from "../../../shared/snapshot/types.js";
|
|
69
69
|
import { snapshot } from "../../../shared/snapshot/capture-snapshot.js";
|
|
@@ -271,9 +271,7 @@ class BrowserDaemon {
|
|
|
271
271
|
});
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
|
|
275
|
-
await context.addInitScript(installPageStabilityWaiter);
|
|
276
|
-
}
|
|
274
|
+
await context.addInitScript(installPageStabilityWaiter);
|
|
277
275
|
|
|
278
276
|
// IPC server — typed handlers are attached per client connection so one
|
|
279
277
|
// daemon lifetime can serve multiple CLI invocations.
|
|
@@ -295,19 +293,15 @@ class BrowserDaemon {
|
|
|
295
293
|
wrapPageForActionLogging(p, session);
|
|
296
294
|
daemon.trackPage(p);
|
|
297
295
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
);
|
|
304
|
-
}
|
|
296
|
+
await Promise.all(
|
|
297
|
+
initialPages.map((initialPage) =>
|
|
298
|
+
daemon.installCompactSnapshotWaiter(initialPage),
|
|
299
|
+
),
|
|
300
|
+
);
|
|
305
301
|
context.on("page", (newPage) => {
|
|
306
302
|
wrapPageForActionLogging(newPage, session);
|
|
307
303
|
daemon.trackPage(newPage);
|
|
308
|
-
|
|
309
|
-
void daemon.installCompactSnapshotWaiter(newPage);
|
|
310
|
-
}
|
|
304
|
+
void daemon.installCompactSnapshotWaiter(newPage);
|
|
311
305
|
});
|
|
312
306
|
|
|
313
307
|
// Navigate after telemetry is installed (so we capture the initial
|
|
@@ -659,40 +653,23 @@ class BrowserDaemon {
|
|
|
659
653
|
private async runSnapshot(
|
|
660
654
|
args: Parameters<CliToDaemonApi["snapshot"]>[0],
|
|
661
655
|
): Promise<ReturnType<CliToDaemonApi["snapshot"]>> {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
`Close and reopen the session after running ${librettoCommand("experiments enable compact-snapshot-format")}.`,
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
const targetPage = this.resolveTargetPage(args.pageId);
|
|
670
|
-
const result = await this.withRequestTimeout(() =>
|
|
671
|
-
handleCompactSnapshot(
|
|
672
|
-
targetPage,
|
|
673
|
-
this.session,
|
|
674
|
-
this.logger,
|
|
675
|
-
{
|
|
676
|
-
pageId: args.pageId,
|
|
677
|
-
cachedSnapshot: this.latestCompactSnapshotByPage.get(targetPage),
|
|
678
|
-
useCachedSnapshot: args.useCachedSnapshot,
|
|
679
|
-
},
|
|
680
|
-
),
|
|
681
|
-
);
|
|
682
|
-
if (!args.useCachedSnapshot) {
|
|
683
|
-
this.latestCompactSnapshotByPage.set(targetPage, result.snapshot);
|
|
684
|
-
}
|
|
685
|
-
return result;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
return this.withRequestTimeout(() =>
|
|
689
|
-
handleSnapshot(
|
|
690
|
-
this.resolveTargetPage(args.pageId),
|
|
656
|
+
const targetPage = this.resolveTargetPage(args.pageId);
|
|
657
|
+
const result = await this.withRequestTimeout(() =>
|
|
658
|
+
handleCompactSnapshot(
|
|
659
|
+
targetPage,
|
|
691
660
|
this.session,
|
|
692
661
|
this.logger,
|
|
693
|
-
|
|
662
|
+
{
|
|
663
|
+
pageId: args.pageId,
|
|
664
|
+
cachedSnapshot: this.latestCompactSnapshotByPage.get(targetPage),
|
|
665
|
+
useCachedSnapshot: args.useCachedSnapshot,
|
|
666
|
+
},
|
|
694
667
|
),
|
|
695
668
|
);
|
|
669
|
+
if (!args.useCachedSnapshot) {
|
|
670
|
+
this.latestCompactSnapshotByPage.set(targetPage, result.snapshot);
|
|
671
|
+
}
|
|
672
|
+
return result;
|
|
696
673
|
}
|
|
697
674
|
|
|
698
675
|
private async withRequestTimeout<T>(
|
|
@@ -720,26 +697,7 @@ class BrowserDaemon {
|
|
|
720
697
|
private async runExec(
|
|
721
698
|
args: Parameters<CliToDaemonApi["exec"]>[0],
|
|
722
699
|
): Promise<DaemonExecResult> {
|
|
723
|
-
|
|
724
|
-
return this.runCompactExec(args);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
try {
|
|
728
|
-
const data = await this.withRequestTimeout(() =>
|
|
729
|
-
handleExec(
|
|
730
|
-
this.resolveTargetPage(args.pageId),
|
|
731
|
-
args.code,
|
|
732
|
-
this.context,
|
|
733
|
-
this.browser,
|
|
734
|
-
this.execState,
|
|
735
|
-
this.session,
|
|
736
|
-
args.visualize,
|
|
737
|
-
),
|
|
738
|
-
);
|
|
739
|
-
return { ok: true, data };
|
|
740
|
-
} catch (error) {
|
|
741
|
-
return this.createExecErrorResult(error);
|
|
742
|
-
}
|
|
700
|
+
return this.runCompactExec(args);
|
|
743
701
|
}
|
|
744
702
|
|
|
745
703
|
private async runCompactExec(
|
|
@@ -831,17 +789,17 @@ class BrowserDaemon {
|
|
|
831
789
|
context: this.context,
|
|
832
790
|
logger: this.logger,
|
|
833
791
|
onLog: (event) => {
|
|
834
|
-
this.broadcast("workflowOutput", event);
|
|
792
|
+
void this.broadcast("workflowOutput", event);
|
|
835
793
|
},
|
|
836
794
|
onOutcome: (outcome) => {
|
|
837
795
|
if (outcome.state === "paused") {
|
|
838
|
-
this.broadcast("workflowPaused", {
|
|
796
|
+
void this.broadcast("workflowPaused", {
|
|
839
797
|
pausedAt: outcome.pausedAt,
|
|
840
798
|
url: outcome.url,
|
|
841
799
|
});
|
|
842
800
|
return;
|
|
843
801
|
}
|
|
844
|
-
this.broadcast(
|
|
802
|
+
void this.broadcast(
|
|
845
803
|
"workflowFinished",
|
|
846
804
|
outcome.result === "completed"
|
|
847
805
|
? { result: "completed", completedAt: outcome.completedAt }
|
|
@@ -25,9 +25,10 @@ export type DaemonExecArgs = {
|
|
|
25
25
|
|
|
26
26
|
export type DaemonReadonlyExecArgs = { code: string; pageId?: string };
|
|
27
27
|
|
|
28
|
-
export type DaemonSnapshotArgs =
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
export type DaemonSnapshotArgs = {
|
|
29
|
+
pageId?: string;
|
|
30
|
+
useCachedSnapshot?: boolean;
|
|
31
|
+
};
|
|
31
32
|
|
|
32
33
|
export type DaemonExecSuccess = {
|
|
33
34
|
result: unknown;
|
|
@@ -35,24 +36,12 @@ export type DaemonExecSuccess = {
|
|
|
35
36
|
snapshotDiff?: SnapshotDiff;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
export type
|
|
39
|
-
pngPath: string;
|
|
40
|
-
htmlPath: string;
|
|
41
|
-
snapshotRunId: string;
|
|
42
|
-
pageUrl: string;
|
|
43
|
-
title: string;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export type DaemonCompactSnapshotResult = {
|
|
39
|
+
export type DaemonSnapshotResult = {
|
|
47
40
|
mode: "compact";
|
|
48
41
|
pngPath: string;
|
|
49
42
|
snapshot: Snapshot;
|
|
50
43
|
};
|
|
51
44
|
|
|
52
|
-
export type DaemonSnapshotResult =
|
|
53
|
-
| DaemonLegacySnapshotResult
|
|
54
|
-
| DaemonCompactSnapshotResult;
|
|
55
|
-
|
|
56
45
|
export type DaemonCloseResult = { replayUrl?: string };
|
|
57
46
|
|
|
58
47
|
export type DaemonCommandResult<T> =
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Page } from "playwright";
|
|
2
|
-
import { mkdirSync
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
3
|
import type { LoggerApi } from "../../../shared/logger/index.js";
|
|
4
4
|
import { getSessionSnapshotRunDir } from "../context.js";
|
|
5
5
|
import {
|
|
@@ -25,48 +25,6 @@ type SnapshotScreenshot = {
|
|
|
25
25
|
title: string;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
export async function handleSnapshot(
|
|
29
|
-
targetPage: Page,
|
|
30
|
-
session: string,
|
|
31
|
-
logger: LoggerApi,
|
|
32
|
-
pageId?: string,
|
|
33
|
-
): Promise<{
|
|
34
|
-
pngPath: string;
|
|
35
|
-
htmlPath: string;
|
|
36
|
-
snapshotRunId: string;
|
|
37
|
-
pageUrl: string;
|
|
38
|
-
title: string;
|
|
39
|
-
}> {
|
|
40
|
-
const screenshot = await captureSnapshotScreenshot(
|
|
41
|
-
targetPage,
|
|
42
|
-
session,
|
|
43
|
-
logger,
|
|
44
|
-
pageId,
|
|
45
|
-
);
|
|
46
|
-
const htmlPath = `${getSessionSnapshotRunDir(
|
|
47
|
-
session,
|
|
48
|
-
screenshot.snapshotRunId,
|
|
49
|
-
)}/page.html`;
|
|
50
|
-
|
|
51
|
-
// Capture HTML content.
|
|
52
|
-
const htmlContent = await targetPage.content();
|
|
53
|
-
writeFileSync(htmlPath, htmlContent);
|
|
54
|
-
|
|
55
|
-
logger.info("screenshot-success", {
|
|
56
|
-
session,
|
|
57
|
-
pageUrl: screenshot.pageUrl,
|
|
58
|
-
title: screenshot.title,
|
|
59
|
-
pngPath: screenshot.pngPath,
|
|
60
|
-
htmlPath,
|
|
61
|
-
snapshotRunId: screenshot.snapshotRunId,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
...screenshot,
|
|
66
|
-
htmlPath,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
28
|
export async function handleCompactSnapshot(
|
|
71
29
|
targetPage: Page,
|
|
72
30
|
session: string,
|
|
@@ -4,45 +4,16 @@ import {
|
|
|
4
4
|
writeLibrettoConfig,
|
|
5
5
|
} from "./config.js";
|
|
6
6
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"Compared with the skill's documented behavior:",
|
|
16
|
-
" - Run libretto snapshot --session <name> without --objective or --context.",
|
|
17
|
-
" - Snapshot output is a screenshot path plus a compact accessibility tree; it does not use the PNG + HTML + AI analysis path.",
|
|
18
|
-
" - Run libretto snapshot <ref> --session <name> to inspect a subtree from the latest full compact snapshot.",
|
|
19
|
-
" - Run libretto exec normally; after successful mutations, Libretto prints page-change diffs from compact snapshots without AI analysis.",
|
|
20
|
-
" - If a session was already open before enabling the experiment, close and reopen it before relying on this behavior.",
|
|
21
|
-
"",
|
|
22
|
-
"Full compact snapshot:",
|
|
23
|
-
" libretto snapshot --session <name>",
|
|
24
|
-
"",
|
|
25
|
-
"Cached subtree snapshot:",
|
|
26
|
-
" libretto snapshot <ref> --session <name>",
|
|
27
|
-
"",
|
|
28
|
-
"Run an unscoped snapshot before using refs. Subtree snapshots capture a fresh screenshot but reuse the latest cached tree.",
|
|
29
|
-
"",
|
|
30
|
-
"Notes:",
|
|
31
|
-
" - Use ref forms printed in the tree, such as l16. Numeric-suffix aliases such as e16 also match l16.",
|
|
32
|
-
].join("\n"),
|
|
33
|
-
defaultValue: false,
|
|
34
|
-
},
|
|
35
|
-
} as const satisfies Record<
|
|
36
|
-
string,
|
|
37
|
-
{
|
|
38
|
-
title: string;
|
|
39
|
-
oneSentenceDescription: string;
|
|
40
|
-
docs?: string;
|
|
41
|
-
defaultValue: boolean;
|
|
42
|
-
}
|
|
43
|
-
>;
|
|
7
|
+
export type ExperimentMetadata = {
|
|
8
|
+
title: string;
|
|
9
|
+
oneSentenceDescription: string;
|
|
10
|
+
docs?: string;
|
|
11
|
+
defaultValue: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const EXPERIMENTS: Readonly<Record<string, ExperimentMetadata>> = {};
|
|
44
15
|
|
|
45
|
-
export type ExperimentName =
|
|
16
|
+
export type ExperimentName = string;
|
|
46
17
|
export type Experiments = Record<ExperimentName, boolean>;
|
|
47
18
|
|
|
48
19
|
export function isExperimentName(name: string): name is ExperimentName {
|
|
@@ -105,6 +105,7 @@ async function getProviderModel(
|
|
|
105
105
|
if (!apiKey) {
|
|
106
106
|
throw new Error(missingProviderCredentialsMessage(provider));
|
|
107
107
|
}
|
|
108
|
+
// @lintc-ignore Human-approved: we don't want to import unless the user is using that subagent.
|
|
108
109
|
const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
|
|
109
110
|
const google = createGoogleGenerativeAI({ apiKey });
|
|
110
111
|
return google(modelId);
|
|
@@ -114,6 +115,7 @@ async function getProviderModel(
|
|
|
114
115
|
if (!project) {
|
|
115
116
|
throw new Error(missingProviderCredentialsMessage(provider));
|
|
116
117
|
}
|
|
118
|
+
// @lintc-ignore Human-approved: we don't want to import unless the user is using that subagent.
|
|
117
119
|
const { createVertex } = await import("@ai-sdk/google-vertex");
|
|
118
120
|
const vertex = createVertex({
|
|
119
121
|
project,
|
|
@@ -126,6 +128,7 @@ async function getProviderModel(
|
|
|
126
128
|
if (!apiKey) {
|
|
127
129
|
throw new Error(missingProviderCredentialsMessage(provider));
|
|
128
130
|
}
|
|
131
|
+
// @lintc-ignore Human-approved: we don't want to import unless the user is using that subagent.
|
|
129
132
|
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
130
133
|
const anthropic = createAnthropic({ apiKey });
|
|
131
134
|
return anthropic(modelId);
|
|
@@ -135,6 +138,7 @@ async function getProviderModel(
|
|
|
135
138
|
if (!apiKey) {
|
|
136
139
|
throw new Error(missingProviderCredentialsMessage(provider));
|
|
137
140
|
}
|
|
141
|
+
// @lintc-ignore Human-approved: we don't want to import unless the user is using that subagent.
|
|
138
142
|
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
139
143
|
const openai = createOpenAI({ apiKey });
|
|
140
144
|
return openai(modelId);
|
|
@@ -144,6 +148,7 @@ async function getProviderModel(
|
|
|
144
148
|
if (!apiKey) {
|
|
145
149
|
throw new Error(missingProviderCredentialsMessage(provider));
|
|
146
150
|
}
|
|
151
|
+
// @lintc-ignore Human-approved: we don't want to import unless the user is using that subagent.
|
|
147
152
|
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
148
153
|
const openrouter = createOpenAI({
|
|
149
154
|
apiKey,
|
|
@@ -37,6 +37,7 @@ export async function loadDefaultWorkflow(
|
|
|
37
37
|
): Promise<ExportedLibrettoWorkflow> {
|
|
38
38
|
let loadedModule: Record<string, unknown>;
|
|
39
39
|
try {
|
|
40
|
+
// @lintc-ignore Human-approved: user workflow files must be loaded dynamically from the CLI argument.
|
|
40
41
|
loadedModule = (await import(pathToFileURL(absolutePath).href)) as Record<
|
|
41
42
|
string,
|
|
42
43
|
unknown
|
package/src/cli/index.ts
CHANGED
package/src/cli/router.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { aiCommands } from "./commands/ai.js";
|
|
2
1
|
import { authCommands } from "./commands/auth.js";
|
|
3
2
|
import { billingCommands } from "./commands/billing.js";
|
|
4
3
|
import { browserCommands } from "./commands/browser.js";
|
|
@@ -16,7 +15,6 @@ export const cliRoutes = {
|
|
|
16
15
|
deploy: deployCommand,
|
|
17
16
|
experiments: experimentsCommand,
|
|
18
17
|
...executionCommands,
|
|
19
|
-
ai: aiCommands,
|
|
20
18
|
auth: authCommands,
|
|
21
19
|
billing: billingCommands,
|
|
22
20
|
setup: setupCommand,
|
|
@@ -131,12 +131,12 @@ function wrapLocatorActions(
|
|
|
131
131
|
try {
|
|
132
132
|
const result = await orig(...args);
|
|
133
133
|
if (opts.visualize) {
|
|
134
|
-
enqueue(page, () => visualizeAfterAction(page));
|
|
134
|
+
void enqueue(page, () => visualizeAfterAction(page));
|
|
135
135
|
}
|
|
136
136
|
return result;
|
|
137
137
|
} catch (err: any) {
|
|
138
138
|
if (opts.visualize) {
|
|
139
|
-
enqueue(page, () => visualizeAfterAction(page));
|
|
139
|
+
void enqueue(page, () => visualizeAfterAction(page));
|
|
140
140
|
}
|
|
141
141
|
// Enrich timeout errors for pointer actions
|
|
142
142
|
if (POINTER_ACTIONS.has(method) && isTimeoutError(err)) {
|
|
@@ -323,12 +323,12 @@ export async function installInstrumentation(
|
|
|
323
323
|
try {
|
|
324
324
|
const result = await orig(...args);
|
|
325
325
|
if (visualize) {
|
|
326
|
-
enqueue(page, () => visualizeAfterAction(page));
|
|
326
|
+
void enqueue(page, () => visualizeAfterAction(page));
|
|
327
327
|
}
|
|
328
328
|
return result;
|
|
329
329
|
} catch (err: any) {
|
|
330
330
|
if (visualize) {
|
|
331
|
-
enqueue(page, () => visualizeAfterAction(page));
|
|
331
|
+
void enqueue(page, () => visualizeAfterAction(page));
|
|
332
332
|
}
|
|
333
333
|
if (
|
|
334
334
|
POINTER_ACTIONS.has(method) &&
|
package/dist/cli/commands/ai.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import {
|
|
3
|
-
readSnapshotModel,
|
|
4
|
-
writeSnapshotModel,
|
|
5
|
-
clearSnapshotModel
|
|
6
|
-
} from "../core/config.js";
|
|
7
|
-
import { LIBRETTO_CONFIG_PATH } from "../core/context.js";
|
|
8
|
-
import { DEFAULT_SNAPSHOT_MODELS } from "../core/ai-model.js";
|
|
9
|
-
import { librettoCommand } from "../../shared/package-manager.js";
|
|
10
|
-
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
11
|
-
const PROVIDER_ALIASES = {
|
|
12
|
-
claude: DEFAULT_SNAPSHOT_MODELS.anthropic,
|
|
13
|
-
gemini: DEFAULT_SNAPSHOT_MODELS.google,
|
|
14
|
-
google: DEFAULT_SNAPSHOT_MODELS.google
|
|
15
|
-
};
|
|
16
|
-
const CONFIGURE_PROVIDERS = [
|
|
17
|
-
"openai",
|
|
18
|
-
"anthropic",
|
|
19
|
-
"gemini",
|
|
20
|
-
"vertex"
|
|
21
|
-
];
|
|
22
|
-
function formatConfigureProviders(separator = " | ") {
|
|
23
|
-
return CONFIGURE_PROVIDERS.join(separator);
|
|
24
|
-
}
|
|
25
|
-
function printSnapshotModelConfig(model, configPath) {
|
|
26
|
-
console.log(`Snapshot model: ${model}`);
|
|
27
|
-
console.log(`Config file: ${configPath}`);
|
|
28
|
-
}
|
|
29
|
-
function resolveModelFromInput(input) {
|
|
30
|
-
const trimmed = input.trim();
|
|
31
|
-
if (!trimmed) return null;
|
|
32
|
-
if (trimmed.includes("/")) return trimmed;
|
|
33
|
-
const normalized = trimmed.toLowerCase();
|
|
34
|
-
return DEFAULT_SNAPSHOT_MODELS[normalized] ?? PROVIDER_ALIASES[normalized] ?? null;
|
|
35
|
-
}
|
|
36
|
-
function runAiConfigure(input, options = {}) {
|
|
37
|
-
const configureCommandName = options.configureCommandName ?? librettoCommand("ai configure");
|
|
38
|
-
const configPath = options.configPath ?? LIBRETTO_CONFIG_PATH;
|
|
39
|
-
const presetArg = input.preset?.trim();
|
|
40
|
-
if (!presetArg && !input.clear) {
|
|
41
|
-
const model2 = readSnapshotModel(configPath);
|
|
42
|
-
if (!model2) {
|
|
43
|
-
console.log(
|
|
44
|
-
`No snapshot model set. Choose a default model: ${configureCommandName} ${formatConfigureProviders()}`
|
|
45
|
-
);
|
|
46
|
-
console.log(
|
|
47
|
-
"Provider credentials still come from your shell or .env file."
|
|
48
|
-
);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
printSnapshotModelConfig(model2, configPath);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (input.clear) {
|
|
55
|
-
const removed = clearSnapshotModel(configPath);
|
|
56
|
-
if (removed) {
|
|
57
|
-
console.log(`Cleared snapshot model config: ${configPath}`);
|
|
58
|
-
} else {
|
|
59
|
-
console.log("No snapshot model was set.");
|
|
60
|
-
}
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const model = resolveModelFromInput(presetArg);
|
|
64
|
-
if (!model) {
|
|
65
|
-
console.log(
|
|
66
|
-
`Usage: ${configureCommandName} <${CONFIGURE_PROVIDERS.join("|")}|provider/model-id>
|
|
67
|
-
${configureCommandName}
|
|
68
|
-
${configureCommandName} --clear`
|
|
69
|
-
);
|
|
70
|
-
throw new Error(
|
|
71
|
-
`Invalid provider or model. Use one of: ${formatConfigureProviders()}, or a full model string like "openai/gpt-4o".`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
writeSnapshotModel(model, configPath);
|
|
75
|
-
console.log("Snapshot model saved.");
|
|
76
|
-
printSnapshotModelConfig(model, configPath);
|
|
77
|
-
}
|
|
78
|
-
const aiConfigureInput = SimpleCLI.input({
|
|
79
|
-
positionals: [
|
|
80
|
-
SimpleCLI.positional("preset", z.string().optional(), {
|
|
81
|
-
help: "Provider shorthand or provider/model-id"
|
|
82
|
-
})
|
|
83
|
-
],
|
|
84
|
-
named: {
|
|
85
|
-
clear: SimpleCLI.flag({ help: "Clear existing AI config" })
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
const aiCommands = SimpleCLI.group({
|
|
89
|
-
description: "AI commands",
|
|
90
|
-
routes: {
|
|
91
|
-
configure: SimpleCLI.command({
|
|
92
|
-
description: "Configure AI runtime"
|
|
93
|
-
}).input(aiConfigureInput).handle(async ({ input }) => {
|
|
94
|
-
runAiConfigure(
|
|
95
|
-
{
|
|
96
|
-
clear: input.clear,
|
|
97
|
-
preset: input.preset
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
configureCommandName: librettoCommand("ai configure")
|
|
101
|
-
}
|
|
102
|
-
);
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
export {
|
|
107
|
-
aiCommands,
|
|
108
|
-
aiConfigureInput,
|
|
109
|
-
runAiConfigure
|
|
110
|
-
};
|