@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
- getBaseUrl
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("md5").update(content).digest("hex");
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 fetchSkillsList();
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 result of results) {
141
- if (result.status === "fulfilled" && result.value.content) {
142
- const { skill, content } = result.value;
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
- const slug = result.status === "fulfilled" ? result.value.skill.slug : "unknown";
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
- failed++;
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
- fetchSkillContent,
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-B4DXTC7L.js").then((m) => m.init(args.slice(1)));
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-PXZHERAS.js").then((m) => m.skills(subcommand, args.slice(2)));
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-VL5GDJKU.js";
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
- fetchSkillContent,
3
+ fetchSkillsForProject,
4
4
  fetchSkillsList,
5
5
  getLocalSkills,
6
6
  hashContent,
7
7
  installAllSkills,
8
8
  slugToDirName,
9
9
  slugToFilename,
10
- syncClaudeMd
11
- } from "./chunk-VL5GDJKU.js";
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 fetchSkillsList();
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 remoteResults = await Promise.allSettled(
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
- console.log(pc.yellow(" \u26A0"), "No skills installed yet");
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 toFetch = [...diff.added, ...diff.updated];
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.24.6",
3
+ "version": "0.24.7-dev.0",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {