libretto 0.6.17 → 0.6.19
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/dist/cli/cli.js +2 -2
- package/dist/cli/commands/browser.js +3 -3
- package/dist/cli/commands/execution.js +2 -2
- package/dist/cli/core/skill-version.js +136 -15
- package/docs/releasing.md +6 -4
- package/package.json +18 -19
- package/skills/libretto/SKILL.md +1 -1
- package/skills/libretto-readonly/SKILL.md +1 -1
- package/src/cli/cli.ts +2 -2
- package/src/cli/commands/browser.ts +3 -3
- package/src/cli/commands/execution.ts +2 -2
- package/src/cli/core/skill-version.ts +188 -17
package/dist/cli/cli.js
CHANGED
|
@@ -2,14 +2,14 @@ import { ensureLibrettoSetup } from "./core/context.js";
|
|
|
2
2
|
import { createCLIApp } from "./router.js";
|
|
3
3
|
import {
|
|
4
4
|
readCurrentCliVersion,
|
|
5
|
-
|
|
5
|
+
warnIfLibrettoVersionsDiffer
|
|
6
6
|
} from "./core/skill-version.js";
|
|
7
7
|
import { loadEnv } from "../shared/env/load-env.js";
|
|
8
8
|
function renderVersion() {
|
|
9
9
|
return readCurrentCliVersion();
|
|
10
10
|
}
|
|
11
11
|
function printSetupAudit() {
|
|
12
|
-
|
|
12
|
+
warnIfLibrettoVersionsDiffer();
|
|
13
13
|
}
|
|
14
14
|
function isPackageManagerExec(env = process.env) {
|
|
15
15
|
return env.npm_command === "exec";
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
setSessionMode,
|
|
18
18
|
validateSessionName
|
|
19
19
|
} from "../core/session.js";
|
|
20
|
-
import {
|
|
20
|
+
import { warnIfLibrettoVersionsDiffer } from "../core/skill-version.js";
|
|
21
21
|
import { SimpleCLI } from "affordance";
|
|
22
22
|
import {
|
|
23
23
|
sessionOption,
|
|
@@ -88,7 +88,7 @@ const openInput = SimpleCLI.input({
|
|
|
88
88
|
const openCommand = SimpleCLI.command({
|
|
89
89
|
description: "Launch browser and open URL"
|
|
90
90
|
}).input(openInput).use(withAutoSession()).use(withExperiments()).handle(async ({ input, ctx }) => {
|
|
91
|
-
|
|
91
|
+
warnIfLibrettoVersionsDiffer();
|
|
92
92
|
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
93
93
|
const providerName = resolveProviderName(input.provider);
|
|
94
94
|
if (providerName === "local") {
|
|
@@ -141,7 +141,7 @@ const connectInput = SimpleCLI.input({
|
|
|
141
141
|
const connectCommand = SimpleCLI.command({
|
|
142
142
|
description: "Connect to an existing Chrome DevTools Protocol (CDP) endpoint"
|
|
143
143
|
}).input(connectInput).use(withAutoSession()).use(withExperiments()).handle(async ({ input, ctx }) => {
|
|
144
|
-
|
|
144
|
+
warnIfLibrettoVersionsDiffer();
|
|
145
145
|
await runConnectWithLogger(
|
|
146
146
|
input.cdpUrl,
|
|
147
147
|
ctx.session,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
setSessionStatus,
|
|
24
24
|
writeSessionState
|
|
25
25
|
} from "../core/session.js";
|
|
26
|
-
import {
|
|
26
|
+
import { warnIfLibrettoVersionsDiffer } from "../core/skill-version.js";
|
|
27
27
|
import { readLibrettoConfig } from "../core/config.js";
|
|
28
28
|
import { renderSnapshotDiff } from "../../shared/snapshot/diff-snapshots.js";
|
|
29
29
|
import {
|
|
@@ -682,7 +682,7 @@ function resolveRunParams(rawInlineParams, paramsFile) {
|
|
|
682
682
|
const runCommand = SimpleCLI.command({
|
|
683
683
|
description: "Run the default-exported Libretto workflow from a file"
|
|
684
684
|
}).input(runInput).use(withAutoSession()).use(withExperiments()).handle(async ({ input, ctx }) => {
|
|
685
|
-
|
|
685
|
+
warnIfLibrettoVersionsDiffer();
|
|
686
686
|
await stopExistingFailedRunSession(ctx.session, ctx.logger);
|
|
687
687
|
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
688
688
|
const params = resolveRunParams(input.params, input.paramsFile);
|
|
@@ -7,6 +7,15 @@ const INSTALLED_SKILL_PATHS = [
|
|
|
7
7
|
[".claude", "skills", "libretto", "SKILL.md"]
|
|
8
8
|
];
|
|
9
9
|
let cachedCliVersion = null;
|
|
10
|
+
function readPackageVersion(packageJsonPath) {
|
|
11
|
+
if (!existsSync(packageJsonPath)) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const manifest = JSON.parse(
|
|
15
|
+
readFileSync(packageJsonPath, "utf8")
|
|
16
|
+
);
|
|
17
|
+
return manifest.version?.trim() || null;
|
|
18
|
+
}
|
|
10
19
|
function readCurrentCliVersion() {
|
|
11
20
|
if (cachedCliVersion) {
|
|
12
21
|
return cachedCliVersion;
|
|
@@ -14,17 +23,20 @@ function readCurrentCliVersion() {
|
|
|
14
23
|
const packageJsonPath = fileURLToPath(
|
|
15
24
|
new URL("../../../package.json", import.meta.url)
|
|
16
25
|
);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
);
|
|
20
|
-
if (!manifest.version) {
|
|
26
|
+
const version = readPackageVersion(packageJsonPath);
|
|
27
|
+
if (!version) {
|
|
21
28
|
throw new Error(
|
|
22
29
|
`Unable to determine current libretto version from ${packageJsonPath}.`
|
|
23
30
|
);
|
|
24
31
|
}
|
|
25
|
-
cachedCliVersion =
|
|
32
|
+
cachedCliVersion = version;
|
|
26
33
|
return cachedCliVersion;
|
|
27
34
|
}
|
|
35
|
+
function readLocalPackageVersion() {
|
|
36
|
+
return readPackageVersion(
|
|
37
|
+
join(REPO_ROOT, "node_modules", "libretto", "package.json")
|
|
38
|
+
);
|
|
39
|
+
}
|
|
28
40
|
function readInstalledSkillVersion(skillPath) {
|
|
29
41
|
if (!existsSync(skillPath)) {
|
|
30
42
|
return null;
|
|
@@ -45,30 +57,139 @@ function readInstalledSkillVersion(skillPath) {
|
|
|
45
57
|
);
|
|
46
58
|
return versionMatch?.[1]?.trim() ?? null;
|
|
47
59
|
}
|
|
48
|
-
function
|
|
49
|
-
const
|
|
60
|
+
function readInstalledSkillVersions() {
|
|
61
|
+
const versions = /* @__PURE__ */ new Set();
|
|
50
62
|
for (const relativePathParts of INSTALLED_SKILL_PATHS) {
|
|
51
63
|
const skillPath = join(REPO_ROOT, ...relativePathParts);
|
|
52
64
|
const installedVersion = readInstalledSkillVersion(skillPath);
|
|
53
|
-
if (installedVersion
|
|
54
|
-
|
|
65
|
+
if (installedVersion) {
|
|
66
|
+
versions.add(installedVersion);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return [...versions];
|
|
70
|
+
}
|
|
71
|
+
function parseVersion(version) {
|
|
72
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/);
|
|
73
|
+
if (!match) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
major: Number(match[1]),
|
|
78
|
+
minor: Number(match[2]),
|
|
79
|
+
patch: Number(match[3]),
|
|
80
|
+
prerelease: match[4] ?? null
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function compareVersions(left, right) {
|
|
84
|
+
const parsedLeft = parseVersion(left);
|
|
85
|
+
const parsedRight = parseVersion(right);
|
|
86
|
+
if (!parsedLeft || !parsedRight) {
|
|
87
|
+
return left.localeCompare(right);
|
|
88
|
+
}
|
|
89
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
90
|
+
const diff = parsedLeft[key] - parsedRight[key];
|
|
91
|
+
if (diff !== 0) {
|
|
92
|
+
return diff;
|
|
55
93
|
}
|
|
56
94
|
}
|
|
57
|
-
|
|
95
|
+
if (parsedLeft.prerelease === parsedRight.prerelease) {
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
if (parsedLeft.prerelease === null) {
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
if (parsedRight.prerelease === null) {
|
|
102
|
+
return -1;
|
|
103
|
+
}
|
|
104
|
+
return parsedLeft.prerelease.localeCompare(parsedRight.prerelease);
|
|
105
|
+
}
|
|
106
|
+
function selectTargetVersion(versions) {
|
|
107
|
+
const counts = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const version of versions) {
|
|
109
|
+
counts.set(version, (counts.get(version) ?? 0) + 1);
|
|
110
|
+
}
|
|
111
|
+
const byCountThenVersion = [...counts.entries()].sort(
|
|
112
|
+
([leftVersion, leftCount], [rightVersion, rightCount]) => rightCount - leftCount || compareVersions(rightVersion, leftVersion)
|
|
113
|
+
);
|
|
114
|
+
return byCountThenVersion[0]?.[0] ?? versions[0] ?? "latest";
|
|
115
|
+
}
|
|
116
|
+
function formatVersion(version, targetVersion) {
|
|
117
|
+
return version === targetVersion ? version : `${version} (out of date)`;
|
|
118
|
+
}
|
|
119
|
+
function formatSkillVersions(versions, targetVersion) {
|
|
120
|
+
if (versions.length === 0) {
|
|
121
|
+
return "not installed";
|
|
122
|
+
}
|
|
123
|
+
return versions.map((version) => formatVersion(version, targetVersion)).join(", ");
|
|
124
|
+
}
|
|
125
|
+
function formatUpdateInstructions(components) {
|
|
126
|
+
const instructions = [];
|
|
127
|
+
if (components.cliVersion !== components.targetVersion) {
|
|
128
|
+
instructions.push(
|
|
129
|
+
` global CLI: curl -fsSL https://libretto.sh/install.sh | LIBRETTO_VERSION=${components.targetVersion} bash`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (components.localPackageVersion && components.localPackageVersion !== components.targetVersion) {
|
|
133
|
+
instructions.push(
|
|
134
|
+
` local package: npm install libretto@${components.targetVersion}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (components.skillVersions.length > 0 && components.skillVersions.some(
|
|
138
|
+
(skillVersion) => skillVersion !== components.targetVersion
|
|
139
|
+
)) {
|
|
140
|
+
instructions.push(" agent skill: libretto setup");
|
|
141
|
+
}
|
|
142
|
+
return instructions;
|
|
143
|
+
}
|
|
144
|
+
function formatVersionWarning(components) {
|
|
145
|
+
const targetVersion = selectTargetVersion([
|
|
146
|
+
components.cliVersion,
|
|
147
|
+
...components.localPackageVersion ? [components.localPackageVersion] : [],
|
|
148
|
+
...components.skillVersions
|
|
149
|
+
]);
|
|
150
|
+
const skillLabel = components.skillVersions.length > 1 ? "agent skills" : "agent skill";
|
|
151
|
+
const updateInstructions = formatUpdateInstructions({
|
|
152
|
+
...components,
|
|
153
|
+
targetVersion
|
|
154
|
+
});
|
|
155
|
+
return [
|
|
156
|
+
"WARNING: Libretto version mismatch detected.",
|
|
157
|
+
"",
|
|
158
|
+
` global CLI: ${formatVersion(components.cliVersion, targetVersion)}`,
|
|
159
|
+
` local package: ${components.localPackageVersion ? formatVersion(components.localPackageVersion, targetVersion) : "not installed"}`,
|
|
160
|
+
` ${skillLabel}: ${formatSkillVersions(
|
|
161
|
+
components.skillVersions,
|
|
162
|
+
targetVersion
|
|
163
|
+
)}`,
|
|
164
|
+
"",
|
|
165
|
+
"How to update:",
|
|
166
|
+
...updateInstructions
|
|
167
|
+
].join("\n");
|
|
58
168
|
}
|
|
59
|
-
function
|
|
169
|
+
function warnIfLibrettoVersionsDiffer() {
|
|
60
170
|
try {
|
|
61
|
-
const
|
|
62
|
-
|
|
171
|
+
const cliVersion = readCurrentCliVersion();
|
|
172
|
+
const localPackageVersion = readLocalPackageVersion();
|
|
173
|
+
const skillVersions = readInstalledSkillVersions();
|
|
174
|
+
const observedVersions = /* @__PURE__ */ new Set([
|
|
175
|
+
cliVersion,
|
|
176
|
+
...localPackageVersion ? [localPackageVersion] : [],
|
|
177
|
+
...skillVersions
|
|
178
|
+
]);
|
|
179
|
+
if (observedVersions.size <= 1) {
|
|
63
180
|
return;
|
|
64
181
|
}
|
|
65
182
|
console.error(
|
|
66
|
-
|
|
183
|
+
formatVersionWarning({
|
|
184
|
+
cliVersion,
|
|
185
|
+
localPackageVersion,
|
|
186
|
+
skillVersions
|
|
187
|
+
})
|
|
67
188
|
);
|
|
68
189
|
} catch {
|
|
69
190
|
}
|
|
70
191
|
}
|
|
71
192
|
export {
|
|
72
193
|
readCurrentCliVersion,
|
|
73
|
-
|
|
194
|
+
warnIfLibrettoVersionsDiffer
|
|
74
195
|
};
|
package/docs/releasing.md
CHANGED
|
@@ -28,17 +28,19 @@ GitHub Actions needs these repository secrets:
|
|
|
28
28
|
|
|
29
29
|
The release workflow uses a GitHub Actions environment named `release`. Create that environment in the repository settings (no required reviewers — access is controlled by branch protection on `main` instead).
|
|
30
30
|
|
|
31
|
-
On npm, configure
|
|
31
|
+
On npm, configure each published package to trust this repository and workflow for publishing:
|
|
32
32
|
|
|
33
33
|
- Organization or user: `saffron-health`
|
|
34
34
|
- Repository: `libretto`
|
|
35
35
|
- Workflow filename: `release.yml`
|
|
36
36
|
- Environment name: `release`
|
|
37
37
|
|
|
38
|
-
If you prefer the CLI,
|
|
38
|
+
If you prefer the CLI, run one setup command per package with npm 11.10.0 or newer:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
npm trust github libretto --repo saffron-health/libretto --file release.yml --env release
|
|
41
|
+
npx --yes npm@11.13.0 trust github libretto --repo saffron-health/libretto --file release.yml --env release --yes
|
|
42
|
+
npx --yes npm@11.13.0 trust github affordance --repo saffron-health/libretto --file release.yml --env release --yes
|
|
43
|
+
npx --yes npm@11.13.0 trust github create-libretto --repo saffron-health/libretto --file release.yml --env release --yes
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
Trusted publishing only works on supported cloud-hosted runners. This workflow uses `ubuntu-latest`, which satisfies that requirement. npm also requires a recent toolchain for trusted publishing, so the publish job runs on Node 24.
|
|
@@ -80,7 +82,7 @@ The workflow:
|
|
|
80
82
|
1. Reads the version from `packages/libretto/package.json`.
|
|
81
83
|
2. Checks whether that version already exists on npm and in GitHub Releases.
|
|
82
84
|
3. Runs install, type-check, and tests for the `libretto` package in a verification job.
|
|
83
|
-
4. Publishes `libretto@X.Y.Z
|
|
85
|
+
4. Publishes `affordance` first, then `libretto@X.Y.Z`, then `create-libretto@X.Y.Z` with trusted publishing.
|
|
84
86
|
5. Creates GitHub release `vX.Y.Z` with generated release notes if it does not already exist.
|
|
85
87
|
|
|
86
88
|
This makes the workflow safe to re-run after partial failures. For example, if npm publish succeeds but GitHub release creation fails, a re-run will skip npm and only create the missing release.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libretto",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.19",
|
|
4
4
|
"description": "AI-powered browser automation library and CLI built on Playwright",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://libretto.sh",
|
|
@@ -30,21 +30,6 @@
|
|
|
30
30
|
"default": "./dist/index.js"
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
|
-
"scripts": {
|
|
34
|
-
"sync:mirrors": "node ../dev-tools/scripts/sync-mirrors.mjs",
|
|
35
|
-
"check:mirrors": "node ../dev-tools/scripts/check-mirrors-sync.mjs",
|
|
36
|
-
"sync-skills": "pnpm run sync:mirrors",
|
|
37
|
-
"check:skills": "pnpm run check:mirrors",
|
|
38
|
-
"build": "tsup --config tsup.config.ts",
|
|
39
|
-
"lint": "lintcn lint --tsconfig tsconfig.json",
|
|
40
|
-
"type-check": "tsc --noEmit",
|
|
41
|
-
"test": "turbo run test:vitest --filter=libretto --log-order=grouped",
|
|
42
|
-
"test:vitest": "vitest run",
|
|
43
|
-
"test:watch": "vitest",
|
|
44
|
-
"cli": "node dist/index.js",
|
|
45
|
-
"generate-changelog": "tsx scripts/generate-changelog.ts",
|
|
46
|
-
"prepack": "pnpm run build"
|
|
47
|
-
},
|
|
48
33
|
"peerDependencies": {
|
|
49
34
|
"@ai-sdk/anthropic": "^3.0.58",
|
|
50
35
|
"@ai-sdk/google": "^3.0.51",
|
|
@@ -85,11 +70,25 @@
|
|
|
85
70
|
"vitest": "^4.1.5"
|
|
86
71
|
},
|
|
87
72
|
"dependencies": {
|
|
88
|
-
"affordance": "workspace:^",
|
|
89
73
|
"ai": "^6.0.116",
|
|
90
74
|
"esbuild": "^0.27.0",
|
|
91
75
|
"playwright": "^1.58.2",
|
|
92
76
|
"tsx": "^4.21.0",
|
|
93
|
-
"zod": "^4.3.6"
|
|
77
|
+
"zod": "^4.3.6",
|
|
78
|
+
"affordance": "^0.2.0"
|
|
79
|
+
},
|
|
80
|
+
"scripts": {
|
|
81
|
+
"sync:mirrors": "node ../dev-tools/scripts/sync-mirrors.mjs",
|
|
82
|
+
"check:mirrors": "node ../dev-tools/scripts/check-mirrors-sync.mjs",
|
|
83
|
+
"sync-skills": "pnpm run sync:mirrors",
|
|
84
|
+
"check:skills": "pnpm run check:mirrors",
|
|
85
|
+
"build": "tsup --config tsup.config.ts",
|
|
86
|
+
"lint": "lintcn lint --tsconfig tsconfig.json",
|
|
87
|
+
"type-check": "tsc --noEmit",
|
|
88
|
+
"test": "turbo run test:vitest --filter=libretto --log-order=grouped",
|
|
89
|
+
"test:vitest": "vitest run",
|
|
90
|
+
"test:watch": "vitest",
|
|
91
|
+
"cli": "node dist/index.js",
|
|
92
|
+
"generate-changelog": "tsx scripts/generate-changelog.ts"
|
|
94
93
|
}
|
|
95
|
-
}
|
|
94
|
+
}
|
package/skills/libretto/SKILL.md
CHANGED
package/src/cli/cli.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { ensureLibrettoSetup } from "./core/context.js";
|
|
|
2
2
|
import { createCLIApp } from "./router.js";
|
|
3
3
|
import {
|
|
4
4
|
readCurrentCliVersion,
|
|
5
|
-
|
|
5
|
+
warnIfLibrettoVersionsDiffer,
|
|
6
6
|
} from "./core/skill-version.js";
|
|
7
7
|
import { loadEnv } from "../shared/env/load-env.js";
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@ function renderVersion(): string {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function printSetupAudit(): void {
|
|
14
|
-
|
|
14
|
+
warnIfLibrettoVersionsDiffer();
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function isPackageManagerExec(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
setSessionMode,
|
|
19
19
|
validateSessionName,
|
|
20
20
|
} from "../core/session.js";
|
|
21
|
-
import {
|
|
21
|
+
import { warnIfLibrettoVersionsDiffer } from "../core/skill-version.js";
|
|
22
22
|
import { SimpleCLI } from "affordance";
|
|
23
23
|
import {
|
|
24
24
|
sessionOption,
|
|
@@ -107,7 +107,7 @@ export const openCommand = SimpleCLI.command({
|
|
|
107
107
|
.use(withAutoSession())
|
|
108
108
|
.use(withExperiments())
|
|
109
109
|
.handle(async ({ input, ctx }) => {
|
|
110
|
-
|
|
110
|
+
warnIfLibrettoVersionsDiffer();
|
|
111
111
|
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
112
112
|
const providerName = resolveProviderName(input.provider);
|
|
113
113
|
if (providerName === "local") {
|
|
@@ -168,7 +168,7 @@ export const connectCommand = SimpleCLI.command({
|
|
|
168
168
|
.use(withAutoSession())
|
|
169
169
|
.use(withExperiments())
|
|
170
170
|
.handle(async ({ input, ctx }) => {
|
|
171
|
-
|
|
171
|
+
warnIfLibrettoVersionsDiffer();
|
|
172
172
|
await runConnectWithLogger(
|
|
173
173
|
input.cdpUrl!,
|
|
174
174
|
ctx.session,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
writeSessionState,
|
|
26
26
|
type SessionState,
|
|
27
27
|
} from "../core/session.js";
|
|
28
|
-
import {
|
|
28
|
+
import { warnIfLibrettoVersionsDiffer } from "../core/skill-version.js";
|
|
29
29
|
import { readLibrettoConfig } from "../core/config.js";
|
|
30
30
|
import { renderSnapshotDiff } from "../../shared/snapshot/diff-snapshots.js";
|
|
31
31
|
import {
|
|
@@ -860,7 +860,7 @@ export const runCommand = SimpleCLI.command({
|
|
|
860
860
|
.use(withAutoSession())
|
|
861
861
|
.use(withExperiments())
|
|
862
862
|
.handle(async ({ input, ctx }) => {
|
|
863
|
-
|
|
863
|
+
warnIfLibrettoVersionsDiffer();
|
|
864
864
|
await stopExistingFailedRunSession(ctx.session, ctx.logger);
|
|
865
865
|
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
866
866
|
|
|
@@ -14,6 +14,17 @@ const INSTALLED_SKILL_PATHS = [
|
|
|
14
14
|
|
|
15
15
|
let cachedCliVersion: string | null = null;
|
|
16
16
|
|
|
17
|
+
function readPackageVersion(packageJsonPath: string): string | null {
|
|
18
|
+
if (!existsSync(packageJsonPath)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const manifest = JSON.parse(
|
|
23
|
+
readFileSync(packageJsonPath, "utf8"),
|
|
24
|
+
) as PackageManifest;
|
|
25
|
+
return manifest.version?.trim() || null;
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
export function readCurrentCliVersion(): string {
|
|
18
29
|
if (cachedCliVersion) {
|
|
19
30
|
return cachedCliVersion;
|
|
@@ -22,20 +33,24 @@ export function readCurrentCliVersion(): string {
|
|
|
22
33
|
const packageJsonPath = fileURLToPath(
|
|
23
34
|
new URL("../../../package.json", import.meta.url),
|
|
24
35
|
);
|
|
25
|
-
const
|
|
26
|
-
readFileSync(packageJsonPath, "utf8"),
|
|
27
|
-
) as PackageManifest;
|
|
36
|
+
const version = readPackageVersion(packageJsonPath);
|
|
28
37
|
|
|
29
|
-
if (!
|
|
38
|
+
if (!version) {
|
|
30
39
|
throw new Error(
|
|
31
40
|
`Unable to determine current libretto version from ${packageJsonPath}.`,
|
|
32
41
|
);
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
cachedCliVersion =
|
|
44
|
+
cachedCliVersion = version;
|
|
36
45
|
return cachedCliVersion;
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
function readLocalPackageVersion(): string | null {
|
|
49
|
+
return readPackageVersion(
|
|
50
|
+
join(REPO_ROOT, "node_modules", "libretto", "package.json"),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
function readInstalledSkillVersion(skillPath: string): string | null {
|
|
40
55
|
if (!existsSync(skillPath)) {
|
|
41
56
|
return null;
|
|
@@ -60,32 +75,188 @@ function readInstalledSkillVersion(skillPath: string): string | null {
|
|
|
60
75
|
return versionMatch?.[1]?.trim() ?? null;
|
|
61
76
|
}
|
|
62
77
|
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
cliVersion: string;
|
|
66
|
-
} | null {
|
|
67
|
-
const cliVersion = readCurrentCliVersion();
|
|
78
|
+
function readInstalledSkillVersions(): string[] {
|
|
79
|
+
const versions = new Set<string>();
|
|
68
80
|
|
|
69
81
|
for (const relativePathParts of INSTALLED_SKILL_PATHS) {
|
|
70
82
|
const skillPath = join(REPO_ROOT, ...relativePathParts);
|
|
71
83
|
const installedVersion = readInstalledSkillVersion(skillPath);
|
|
72
|
-
if (installedVersion
|
|
73
|
-
|
|
84
|
+
if (installedVersion) {
|
|
85
|
+
versions.add(installedVersion);
|
|
74
86
|
}
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
return
|
|
89
|
+
return [...versions];
|
|
78
90
|
}
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
function parseVersion(version: string): {
|
|
93
|
+
major: number;
|
|
94
|
+
minor: number;
|
|
95
|
+
patch: number;
|
|
96
|
+
prerelease: string | null;
|
|
97
|
+
} | null {
|
|
98
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/);
|
|
99
|
+
if (!match) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
major: Number(match[1]),
|
|
105
|
+
minor: Number(match[2]),
|
|
106
|
+
patch: Number(match[3]),
|
|
107
|
+
prerelease: match[4] ?? null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function compareVersions(left: string, right: string): number {
|
|
112
|
+
const parsedLeft = parseVersion(left);
|
|
113
|
+
const parsedRight = parseVersion(right);
|
|
114
|
+
if (!parsedLeft || !parsedRight) {
|
|
115
|
+
return left.localeCompare(right);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const key of ["major", "minor", "patch"] as const) {
|
|
119
|
+
const diff = parsedLeft[key] - parsedRight[key];
|
|
120
|
+
if (diff !== 0) {
|
|
121
|
+
return diff;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (parsedLeft.prerelease === parsedRight.prerelease) {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
if (parsedLeft.prerelease === null) {
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
if (parsedRight.prerelease === null) {
|
|
132
|
+
return -1;
|
|
133
|
+
}
|
|
134
|
+
return parsedLeft.prerelease.localeCompare(parsedRight.prerelease);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function selectTargetVersion(versions: string[]): string {
|
|
138
|
+
const counts = new Map<string, number>();
|
|
139
|
+
for (const version of versions) {
|
|
140
|
+
counts.set(version, (counts.get(version) ?? 0) + 1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const byCountThenVersion = [...counts.entries()].sort(
|
|
144
|
+
([leftVersion, leftCount], [rightVersion, rightCount]) =>
|
|
145
|
+
rightCount - leftCount || compareVersions(rightVersion, leftVersion),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return byCountThenVersion[0]?.[0] ?? versions[0] ?? "latest";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function formatVersion(version: string, targetVersion: string): string {
|
|
152
|
+
return version === targetVersion ? version : `${version} (out of date)`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function formatSkillVersions(
|
|
156
|
+
versions: string[],
|
|
157
|
+
targetVersion: string,
|
|
158
|
+
): string {
|
|
159
|
+
if (versions.length === 0) {
|
|
160
|
+
return "not installed";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return versions
|
|
164
|
+
.map((version) => formatVersion(version, targetVersion))
|
|
165
|
+
.join(", ");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function formatUpdateInstructions(components: {
|
|
169
|
+
cliVersion: string;
|
|
170
|
+
localPackageVersion: string | null;
|
|
171
|
+
skillVersions: string[];
|
|
172
|
+
targetVersion: string;
|
|
173
|
+
}): string[] {
|
|
174
|
+
const instructions: string[] = [];
|
|
175
|
+
|
|
176
|
+
if (components.cliVersion !== components.targetVersion) {
|
|
177
|
+
instructions.push(
|
|
178
|
+
` global CLI: curl -fsSL https://libretto.sh/install.sh | LIBRETTO_VERSION=${components.targetVersion} bash`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (
|
|
183
|
+
components.localPackageVersion &&
|
|
184
|
+
components.localPackageVersion !== components.targetVersion
|
|
185
|
+
) {
|
|
186
|
+
instructions.push(
|
|
187
|
+
` local package: npm install libretto@${components.targetVersion}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
components.skillVersions.length > 0 &&
|
|
193
|
+
components.skillVersions.some(
|
|
194
|
+
(skillVersion) => skillVersion !== components.targetVersion,
|
|
195
|
+
)
|
|
196
|
+
) {
|
|
197
|
+
instructions.push(" agent skill: libretto setup");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return instructions;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function formatVersionWarning(components: {
|
|
204
|
+
cliVersion: string;
|
|
205
|
+
localPackageVersion: string | null;
|
|
206
|
+
skillVersions: string[];
|
|
207
|
+
}): string {
|
|
208
|
+
const targetVersion = selectTargetVersion([
|
|
209
|
+
components.cliVersion,
|
|
210
|
+
...(components.localPackageVersion ? [components.localPackageVersion] : []),
|
|
211
|
+
...components.skillVersions,
|
|
212
|
+
]);
|
|
213
|
+
const skillLabel =
|
|
214
|
+
components.skillVersions.length > 1 ? "agent skills" : "agent skill";
|
|
215
|
+
const updateInstructions = formatUpdateInstructions({
|
|
216
|
+
...components,
|
|
217
|
+
targetVersion,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return [
|
|
221
|
+
"WARNING: Libretto version mismatch detected.",
|
|
222
|
+
"",
|
|
223
|
+
` global CLI: ${formatVersion(components.cliVersion, targetVersion)}`,
|
|
224
|
+
` local package: ${
|
|
225
|
+
components.localPackageVersion
|
|
226
|
+
? formatVersion(components.localPackageVersion, targetVersion)
|
|
227
|
+
: "not installed"
|
|
228
|
+
}`,
|
|
229
|
+
` ${skillLabel}: ${formatSkillVersions(
|
|
230
|
+
components.skillVersions,
|
|
231
|
+
targetVersion,
|
|
232
|
+
)}`,
|
|
233
|
+
"",
|
|
234
|
+
"How to update:",
|
|
235
|
+
...updateInstructions,
|
|
236
|
+
].join("\n");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function warnIfLibrettoVersionsDiffer(): void {
|
|
81
240
|
try {
|
|
82
|
-
const
|
|
83
|
-
|
|
241
|
+
const cliVersion = readCurrentCliVersion();
|
|
242
|
+
const localPackageVersion = readLocalPackageVersion();
|
|
243
|
+
const skillVersions = readInstalledSkillVersions();
|
|
244
|
+
const observedVersions = new Set([
|
|
245
|
+
cliVersion,
|
|
246
|
+
...(localPackageVersion ? [localPackageVersion] : []),
|
|
247
|
+
...skillVersions,
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
if (observedVersions.size <= 1) {
|
|
84
251
|
return;
|
|
85
252
|
}
|
|
86
253
|
|
|
87
254
|
console.error(
|
|
88
|
-
|
|
255
|
+
formatVersionWarning({
|
|
256
|
+
cliVersion,
|
|
257
|
+
localPackageVersion,
|
|
258
|
+
skillVersions,
|
|
259
|
+
}),
|
|
89
260
|
);
|
|
90
261
|
} catch {
|
|
91
262
|
// Never block command execution on a best-effort skill version check.
|