@lumerahq/cli 0.24.5 → 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
@@ -227,25 +227,25 @@ async function main() {
227
227
  switch (command) {
228
228
  // Resource commands
229
229
  case "plan":
230
- await import("./resources-XQHHIQYV.js").then((m) => m.plan(args.slice(1)));
230
+ await import("./resources-R53XKJL2.js").then((m) => m.plan(args.slice(1)));
231
231
  break;
232
232
  case "apply":
233
- await import("./resources-XQHHIQYV.js").then((m) => m.apply(args.slice(1)));
233
+ await import("./resources-R53XKJL2.js").then((m) => m.apply(args.slice(1)));
234
234
  break;
235
235
  case "pull":
236
- await import("./resources-XQHHIQYV.js").then((m) => m.pull(args.slice(1)));
236
+ await import("./resources-R53XKJL2.js").then((m) => m.pull(args.slice(1)));
237
237
  break;
238
238
  case "destroy":
239
- await import("./resources-XQHHIQYV.js").then((m) => m.destroy(args.slice(1)));
239
+ await import("./resources-R53XKJL2.js").then((m) => m.destroy(args.slice(1)));
240
240
  break;
241
241
  case "list":
242
- await import("./resources-XQHHIQYV.js").then((m) => m.list(args.slice(1)));
242
+ await import("./resources-R53XKJL2.js").then((m) => m.list(args.slice(1)));
243
243
  break;
244
244
  case "show":
245
- await import("./resources-XQHHIQYV.js").then((m) => m.show(args.slice(1)));
245
+ await import("./resources-R53XKJL2.js").then((m) => m.show(args.slice(1)));
246
246
  break;
247
247
  case "diff":
248
- await import("./resources-XQHHIQYV.js").then((m) => m.diff(args.slice(1)));
248
+ await import("./resources-R53XKJL2.js").then((m) => m.diff(args.slice(1)));
249
249
  break;
250
250
  // Development
251
251
  case "dev":
@@ -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
  }
@@ -1667,10 +1667,12 @@ function normalizeCollectionSearch(search) {
1667
1667
  } : {}
1668
1668
  });
1669
1669
  }
1670
- async function planCollections(api, localCollections, appName) {
1670
+ async function planCollections(api, localCollections, appName, projectId) {
1671
1671
  const changes = [];
1672
1672
  const remoteCollections = await api.listCollections();
1673
- const remoteById = new Map(remoteCollections.map((c) => [c.id, c]));
1673
+ const remoteById = new Map(
1674
+ remoteCollections.filter((collection) => !projectId || !collection.project_id || collection.project_id === projectId).map((collection) => [collection.id, collection])
1675
+ );
1674
1676
  for (const local of localCollections) {
1675
1677
  let remote = remoteById.get(local.id);
1676
1678
  if (!remote) {
@@ -1684,7 +1686,7 @@ async function planCollections(api, localCollections, appName) {
1684
1686
  break;
1685
1687
  }
1686
1688
  } catch (error3) {
1687
- if (!isApiErrorStatus(error3, 404)) throw error3;
1689
+ if (!isApiErrorStatus(error3, 404) && !isApiErrorStatus(error3, 403)) throw error3;
1688
1690
  }
1689
1691
  }
1690
1692
  }
@@ -3281,7 +3283,7 @@ async function plan(args) {
3281
3283
  if (!type || type === "collections") {
3282
3284
  const localCollections = loadLocalCollections(platformDir, name || void 0);
3283
3285
  if (localCollections.length > 0) {
3284
- const changes = await planCollections(api, localCollections, appName);
3286
+ const changes = await planCollections(api, localCollections, appName, projectId);
3285
3287
  allChanges.push(...changes);
3286
3288
  }
3287
3289
  }
@@ -3428,7 +3430,7 @@ async function apply(args) {
3428
3430
  const localHooks = !type || type === "hooks" ? loadLocalHooks(platformDir, name || void 0, appName) : [];
3429
3431
  const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
3430
3432
  const localMailboxes = !type || type === "mailboxes" ? loadLocalMailboxes(platformDir, name || void 0) : [];
3431
- if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections, appName));
3433
+ if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections, appName, projectId));
3432
3434
  if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations, projectId));
3433
3435
  if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections, projectId));
3434
3436
  if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents, projectId));
@@ -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.5",
3
+ "version": "0.24.7-dev.0",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {