@lumerahq/cli 0.24.6 → 0.24.7-dev.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.
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
userContextHeaders
|
|
3
|
+
} from "./chunk-FHHWIV4C.js";
|
|
4
|
+
import {
|
|
5
|
+
getBaseUrl,
|
|
6
|
+
getProjectId,
|
|
7
|
+
getToken,
|
|
8
|
+
init_auth
|
|
3
9
|
} from "./chunk-JLVVHTBY.js";
|
|
4
10
|
import {
|
|
5
11
|
fetchWithRetry
|
|
6
12
|
} from "./chunk-FJFIWC7G.js";
|
|
7
13
|
|
|
8
14
|
// src/lib/skills.ts
|
|
15
|
+
init_auth();
|
|
9
16
|
import { createHash } from "crypto";
|
|
10
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, symlinkSync, lstatSync, statSync, writeFileSync } from "fs";
|
|
11
|
-
import { join, relative } from "path";
|
|
17
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, symlinkSync, lstatSync, statSync, writeFileSync } from "fs";
|
|
18
|
+
import { dirname, join, relative } from "path";
|
|
12
19
|
import pc from "picocolors";
|
|
13
20
|
function slugToDirName(slug) {
|
|
14
21
|
return slug.replace(/-/g, "_");
|
|
@@ -20,7 +27,19 @@ function slugToFilename(slug) {
|
|
|
20
27
|
return `${slug.replace(/-/g, "_")}.md`;
|
|
21
28
|
}
|
|
22
29
|
function hashContent(content) {
|
|
23
|
-
return createHash("
|
|
30
|
+
return createHash("sha256").update(content).digest("hex");
|
|
31
|
+
}
|
|
32
|
+
var SKILLS_HASH_MANIFEST_PATH = join(".lumera", "skills-hashes.json");
|
|
33
|
+
function writeSkillsHashManifest(projectRoot, skills) {
|
|
34
|
+
const hashes = {};
|
|
35
|
+
for (const skill of [...skills].sort((a, b) => a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0)) {
|
|
36
|
+
hashes[skill.slug] = hashContent(skill.content);
|
|
37
|
+
}
|
|
38
|
+
const manifestPath = join(projectRoot, SKILLS_HASH_MANIFEST_PATH);
|
|
39
|
+
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
40
|
+
writeFileSync(manifestPath, `${JSON.stringify({ version: 1, skills: hashes }, null, 2)}
|
|
41
|
+
`);
|
|
42
|
+
rmSync(join(projectRoot, ".lumera", "skills-content-hash"), { force: true });
|
|
24
43
|
}
|
|
25
44
|
async function fetchSkillsList() {
|
|
26
45
|
const baseUrl = getBaseUrl();
|
|
@@ -44,6 +63,58 @@ async function fetchSkillContent(slug) {
|
|
|
44
63
|
}
|
|
45
64
|
return mdRes.text();
|
|
46
65
|
}
|
|
66
|
+
async function fetchEffectiveSkills(projectRoot) {
|
|
67
|
+
const baseUrl = getBaseUrl();
|
|
68
|
+
const token = getToken(projectRoot);
|
|
69
|
+
const projectId = getProjectId(projectRoot)?.trim();
|
|
70
|
+
const url = new URL(`${baseUrl}/api/skills/effective`);
|
|
71
|
+
if (projectId) url.searchParams.set("project_id", projectId);
|
|
72
|
+
const response = await fetchWithRetry(url.toString(), {
|
|
73
|
+
headers: {
|
|
74
|
+
Authorization: `Bearer ${token}`,
|
|
75
|
+
"X-Lumera-Client": "lumera-cli",
|
|
76
|
+
...userContextHeaders()
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const detail = (await response.text()).trim();
|
|
81
|
+
throw new Error(`Failed to fetch effective skills: ${response.status}${detail ? ` ${detail}` : ""}`);
|
|
82
|
+
}
|
|
83
|
+
const payload = await response.json();
|
|
84
|
+
return (payload.skills ?? []).map((skill) => ({
|
|
85
|
+
...skill,
|
|
86
|
+
summary: skill.summary || skill.name,
|
|
87
|
+
content_hash: skill.content_hash || hashContent(skill.content)
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
async function fetchPublicSkillFiles() {
|
|
91
|
+
const skills = await fetchSkillsList();
|
|
92
|
+
const results = await Promise.allSettled(
|
|
93
|
+
skills.map(async (skill) => ({
|
|
94
|
+
...skill,
|
|
95
|
+
content: await fetchSkillContent(skill.slug)
|
|
96
|
+
}))
|
|
97
|
+
);
|
|
98
|
+
const out = [];
|
|
99
|
+
for (const result of results) {
|
|
100
|
+
if (result.status !== "fulfilled" || !result.value.content) continue;
|
|
101
|
+
out.push({
|
|
102
|
+
...result.value,
|
|
103
|
+
content: result.value.content,
|
|
104
|
+
content_hash: hashContent(result.value.content),
|
|
105
|
+
managed: true
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
async function fetchSkillsForProject(projectRoot) {
|
|
111
|
+
try {
|
|
112
|
+
getToken(projectRoot);
|
|
113
|
+
} catch {
|
|
114
|
+
return fetchPublicSkillFiles();
|
|
115
|
+
}
|
|
116
|
+
return fetchEffectiveSkills(projectRoot);
|
|
117
|
+
}
|
|
47
118
|
function getLocalSkills(skillsDir) {
|
|
48
119
|
const localSkills = /* @__PURE__ */ new Map();
|
|
49
120
|
if (!existsSync(skillsDir)) {
|
|
@@ -126,37 +197,30 @@ function ensureSkillSymlinks(projectRoot) {
|
|
|
126
197
|
}
|
|
127
198
|
async function installAllSkills(targetDir, options) {
|
|
128
199
|
const verbose = options?.verbose ?? false;
|
|
129
|
-
const skills = await
|
|
200
|
+
const skills = await fetchSkillsForProject(targetDir);
|
|
130
201
|
const skillsDir = join(targetDir, ".agents", "skills");
|
|
131
202
|
mkdirSync(skillsDir, { recursive: true });
|
|
132
|
-
const results = await Promise.allSettled(
|
|
133
|
-
skills.map(async (skill) => {
|
|
134
|
-
const content = await fetchSkillContent(skill.slug);
|
|
135
|
-
return { skill, content };
|
|
136
|
-
})
|
|
137
|
-
);
|
|
138
203
|
let installed = 0;
|
|
139
204
|
let failed = 0;
|
|
140
|
-
for (const
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
const dirName = slugToDirName(skill.slug);
|
|
144
|
-
const skillDir = join(skillsDir, dirName);
|
|
145
|
-
mkdirSync(skillDir, { recursive: true });
|
|
146
|
-
writeFileSync(join(skillDir, "SKILL.md"), content);
|
|
147
|
-
if (verbose) {
|
|
148
|
-
console.log(pc.green(" \u2713"), pc.dim(`${dirName}/SKILL.md`));
|
|
149
|
-
}
|
|
150
|
-
installed++;
|
|
151
|
-
} else {
|
|
205
|
+
for (const skill of skills) {
|
|
206
|
+
if (!skill.content) {
|
|
207
|
+
failed++;
|
|
152
208
|
if (verbose) {
|
|
153
|
-
|
|
154
|
-
console.log(pc.yellow(" \u26A0"), pc.dim(`Failed to fetch ${slug}`));
|
|
209
|
+
console.log(pc.yellow(" \u26A0"), pc.dim(`Missing content for ${skill.slug}`));
|
|
155
210
|
}
|
|
156
|
-
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const dirName = slugToDirName(skill.slug);
|
|
214
|
+
const skillDir = join(skillsDir, dirName);
|
|
215
|
+
mkdirSync(skillDir, { recursive: true });
|
|
216
|
+
writeFileSync(join(skillDir, "SKILL.md"), skill.content);
|
|
217
|
+
if (verbose) {
|
|
218
|
+
console.log(pc.green(" \u2713"), pc.dim(`${dirName}/SKILL.md`));
|
|
157
219
|
}
|
|
220
|
+
installed++;
|
|
158
221
|
}
|
|
159
222
|
ensureSkillSymlinks(targetDir);
|
|
223
|
+
if (failed === 0) writeSkillsHashManifest(targetDir, skills);
|
|
160
224
|
return { installed, failed };
|
|
161
225
|
}
|
|
162
226
|
var SKILLS_START_MARKER = "<!-- LUMERA_SKILLS_START -->";
|
|
@@ -217,8 +281,9 @@ export {
|
|
|
217
281
|
slugToDirName,
|
|
218
282
|
slugToFilename,
|
|
219
283
|
hashContent,
|
|
284
|
+
writeSkillsHashManifest,
|
|
220
285
|
fetchSkillsList,
|
|
221
|
-
|
|
286
|
+
fetchSkillsForProject,
|
|
222
287
|
getLocalSkills,
|
|
223
288
|
ensureSkillSymlinks,
|
|
224
289
|
installAllSkills,
|
package/dist/index.js
CHANGED
|
@@ -256,7 +256,7 @@ async function main() {
|
|
|
256
256
|
break;
|
|
257
257
|
// Project
|
|
258
258
|
case "init":
|
|
259
|
-
await import("./init-
|
|
259
|
+
await import("./init-TRZMDKYB.js").then((m) => m.init(args.slice(1)));
|
|
260
260
|
break;
|
|
261
261
|
case "register":
|
|
262
262
|
await import("./register-HRLBT4FI.js").then((m) => m.register(args.slice(1)));
|
|
@@ -272,7 +272,7 @@ async function main() {
|
|
|
272
272
|
break;
|
|
273
273
|
// Skills
|
|
274
274
|
case "skills":
|
|
275
|
-
await import("./skills-
|
|
275
|
+
await import("./skills-O3NZFM7V.js").then((m) => m.skills(subcommand, args.slice(2)));
|
|
276
276
|
break;
|
|
277
277
|
// Dependencies
|
|
278
278
|
case "deps":
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
installAllSkills,
|
|
3
3
|
syncClaudeMd
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GFLMEXIK.js";
|
|
5
5
|
import {
|
|
6
6
|
spinner
|
|
7
7
|
} from "./chunk-BHYDYR75.js";
|
|
@@ -456,6 +456,18 @@ async function init(args) {
|
|
|
456
456
|
setProjectId(targetDir, project.id);
|
|
457
457
|
stopRegister(pc.green("\u2713") + pc.dim(` Project registered (${project.id})`));
|
|
458
458
|
registered = true;
|
|
459
|
+
const stopProjectSkills = spinner("Syncing project skills...");
|
|
460
|
+
try {
|
|
461
|
+
const { installed, failed } = await installAllSkills(targetDir);
|
|
462
|
+
syncClaudeMd(targetDir);
|
|
463
|
+
if (failed > 0) {
|
|
464
|
+
stopProjectSkills(pc.yellow("\u26A0") + pc.dim(` Synced ${installed} project skills (${failed} failed)`));
|
|
465
|
+
} else {
|
|
466
|
+
stopProjectSkills(pc.green("\u2713") + pc.dim(` ${installed} project skills synced`));
|
|
467
|
+
}
|
|
468
|
+
} catch (skillError) {
|
|
469
|
+
stopProjectSkills(pc.yellow("\u26A0") + pc.dim(` Failed to sync project skills: ${skillError}`));
|
|
470
|
+
}
|
|
459
471
|
} catch (e) {
|
|
460
472
|
stopRegister(pc.yellow("\u26A0") + pc.dim(` Could not register project: ${e instanceof Error ? e.message : e}`));
|
|
461
473
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureSkillSymlinks,
|
|
3
|
-
|
|
3
|
+
fetchSkillsForProject,
|
|
4
4
|
fetchSkillsList,
|
|
5
5
|
getLocalSkills,
|
|
6
6
|
hashContent,
|
|
7
7
|
installAllSkills,
|
|
8
8
|
slugToDirName,
|
|
9
9
|
slugToFilename,
|
|
10
|
-
syncClaudeMd
|
|
11
|
-
|
|
10
|
+
syncClaudeMd,
|
|
11
|
+
writeSkillsHashManifest
|
|
12
|
+
} from "./chunk-GFLMEXIK.js";
|
|
13
|
+
import "./chunk-FHHWIV4C.js";
|
|
12
14
|
import "./chunk-JLVVHTBY.js";
|
|
13
15
|
import "./chunk-FJFIWC7G.js";
|
|
14
16
|
import "./chunk-PNKVD2UK.js";
|
|
@@ -27,32 +29,25 @@ function findProjectRoot() {
|
|
|
27
29
|
}
|
|
28
30
|
return null;
|
|
29
31
|
}
|
|
30
|
-
async function computeDiff(skillsDir, filterSlug) {
|
|
31
|
-
const skills2 = await
|
|
32
|
+
async function computeDiff(projectRoot, skillsDir, filterSlug) {
|
|
33
|
+
const skills2 = await fetchSkillsForProject(projectRoot);
|
|
32
34
|
const localSkills = getLocalSkills(skillsDir);
|
|
33
35
|
const remoteSkillSlugs = /* @__PURE__ */ new Set();
|
|
34
36
|
const diff = {
|
|
37
|
+
resolved: skills2,
|
|
35
38
|
added: [],
|
|
36
39
|
updated: [],
|
|
37
40
|
removed: [],
|
|
38
41
|
unchanged: []
|
|
39
42
|
};
|
|
40
43
|
const skillsToCheck = filterSlug ? skills2.filter((skill) => skill.slug === filterSlug) : skills2;
|
|
41
|
-
const
|
|
42
|
-
skillsToCheck.map(async (skill) => {
|
|
43
|
-
const content = await fetchSkillContent(skill.slug);
|
|
44
|
-
return { skill, content };
|
|
45
|
-
})
|
|
46
|
-
);
|
|
47
|
-
for (const result of remoteResults) {
|
|
48
|
-
if (result.status !== "fulfilled" || !result.value.content) continue;
|
|
49
|
-
const { skill, content } = result.value;
|
|
44
|
+
for (const skill of skillsToCheck) {
|
|
50
45
|
remoteSkillSlugs.add(skill.slug);
|
|
51
46
|
const localHash = localSkills.get(skill.slug);
|
|
52
47
|
if (!localHash) {
|
|
53
48
|
diff.added.push(skill);
|
|
54
49
|
} else {
|
|
55
|
-
const remoteHash = hashContent(content);
|
|
50
|
+
const remoteHash = hashContent(skill.content);
|
|
56
51
|
if (localHash !== remoteHash) {
|
|
57
52
|
diff.updated.push(skill);
|
|
58
53
|
} else {
|
|
@@ -261,10 +256,7 @@ async function update(args, flags) {
|
|
|
261
256
|
}
|
|
262
257
|
const skillsDir = join(projectRoot, ".agents", "skills");
|
|
263
258
|
if (!existsSync(skillsDir)) {
|
|
264
|
-
|
|
265
|
-
console.log(pc.dim(' Run "lumera skills install" first'));
|
|
266
|
-
console.log();
|
|
267
|
-
return;
|
|
259
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
268
260
|
}
|
|
269
261
|
if (verbose) {
|
|
270
262
|
console.log(pc.dim(` Project root: ${projectRoot}`));
|
|
@@ -275,13 +267,14 @@ async function update(args, flags) {
|
|
|
275
267
|
console.log();
|
|
276
268
|
}
|
|
277
269
|
try {
|
|
278
|
-
const diff = await computeDiff(skillsDir, filterSlug);
|
|
270
|
+
const diff = await computeDiff(projectRoot, skillsDir, filterSlug);
|
|
279
271
|
const hasChanges = diff.added.length > 0 || diff.updated.length > 0 || diff.removed.length > 0;
|
|
280
272
|
if (!hasChanges) {
|
|
281
273
|
console.log(pc.green(" \u2713"), "All skills are up to date");
|
|
282
274
|
if (verbose && diff.unchanged.length > 0) {
|
|
283
275
|
console.log(pc.dim(` ${diff.unchanged.length} skills unchanged`));
|
|
284
276
|
}
|
|
277
|
+
if (!filterSlug) writeSkillsHashManifest(projectRoot, diff.resolved);
|
|
285
278
|
syncClaudeMd(projectRoot);
|
|
286
279
|
console.log();
|
|
287
280
|
return;
|
|
@@ -329,20 +322,11 @@ async function update(args, flags) {
|
|
|
329
322
|
}
|
|
330
323
|
console.log(pc.dim(" Applying changes..."));
|
|
331
324
|
console.log();
|
|
332
|
-
const
|
|
333
|
-
const fetchResults = await Promise.allSettled(
|
|
334
|
-
toFetch.map(async (skill) => {
|
|
335
|
-
const content = await fetchSkillContent(skill.slug);
|
|
336
|
-
return { skill, content };
|
|
337
|
-
})
|
|
338
|
-
);
|
|
339
|
-
for (const result of fetchResults) {
|
|
340
|
-
if (result.status !== "fulfilled" || !result.value.content) continue;
|
|
341
|
-
const { skill, content } = result.value;
|
|
325
|
+
for (const skill of [...diff.added, ...diff.updated]) {
|
|
342
326
|
const dirName = slugToDirName(skill.slug);
|
|
343
327
|
const skillDir = join(skillsDir, dirName);
|
|
344
328
|
mkdirSync(skillDir, { recursive: true });
|
|
345
|
-
writeFileSync(join(skillDir, "SKILL.md"), content);
|
|
329
|
+
writeFileSync(join(skillDir, "SKILL.md"), skill.content);
|
|
346
330
|
const legacyFile = join(skillsDir, slugToFilename(skill.slug));
|
|
347
331
|
if (existsSync(legacyFile)) {
|
|
348
332
|
rmSync(legacyFile);
|
|
@@ -369,6 +353,7 @@ async function update(args, flags) {
|
|
|
369
353
|
}
|
|
370
354
|
console.log();
|
|
371
355
|
console.log(pc.green(" \u2713"), `Update complete (${changes.join(", ")})`);
|
|
356
|
+
if (!filterSlug) writeSkillsHashManifest(projectRoot, diff.resolved);
|
|
372
357
|
syncClaudeMd(projectRoot);
|
|
373
358
|
ensureSkillSymlinks(projectRoot);
|
|
374
359
|
console.log();
|