@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.
- package/dist/{chunk-VL5GDJKU.js → chunk-GFLMEXIK.js} +92 -27
- package/dist/index.js +9 -9
- package/dist/{init-B4DXTC7L.js → init-TRZMDKYB.js} +13 -1
- package/dist/{resources-XQHHIQYV.js → resources-R53XKJL2.js} +7 -5
- package/dist/{skills-PXZHERAS.js → skills-O3NZFM7V.js} +16 -31
- package/package.json +1 -1
|
@@ -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
|
@@ -227,25 +227,25 @@ async function main() {
|
|
|
227
227
|
switch (command) {
|
|
228
228
|
// Resource commands
|
|
229
229
|
case "plan":
|
|
230
|
-
await import("./resources-
|
|
230
|
+
await import("./resources-R53XKJL2.js").then((m) => m.plan(args.slice(1)));
|
|
231
231
|
break;
|
|
232
232
|
case "apply":
|
|
233
|
-
await import("./resources-
|
|
233
|
+
await import("./resources-R53XKJL2.js").then((m) => m.apply(args.slice(1)));
|
|
234
234
|
break;
|
|
235
235
|
case "pull":
|
|
236
|
-
await import("./resources-
|
|
236
|
+
await import("./resources-R53XKJL2.js").then((m) => m.pull(args.slice(1)));
|
|
237
237
|
break;
|
|
238
238
|
case "destroy":
|
|
239
|
-
await import("./resources-
|
|
239
|
+
await import("./resources-R53XKJL2.js").then((m) => m.destroy(args.slice(1)));
|
|
240
240
|
break;
|
|
241
241
|
case "list":
|
|
242
|
-
await import("./resources-
|
|
242
|
+
await import("./resources-R53XKJL2.js").then((m) => m.list(args.slice(1)));
|
|
243
243
|
break;
|
|
244
244
|
case "show":
|
|
245
|
-
await import("./resources-
|
|
245
|
+
await import("./resources-R53XKJL2.js").then((m) => m.show(args.slice(1)));
|
|
246
246
|
break;
|
|
247
247
|
case "diff":
|
|
248
|
-
await import("./resources-
|
|
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-
|
|
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
|
}
|
|
@@ -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(
|
|
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
|
-
|
|
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();
|