@lumerahq/cli 0.8.0 → 0.9.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.
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getBaseUrl
|
|
3
|
+
} from "./chunk-D2BLSEGR.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/skills.ts
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import pc from "picocolors";
|
|
10
|
+
function slugToFilename(slug) {
|
|
11
|
+
const normalizedSlug = slug.startsWith("lumera-") ? slug : `lumera-${slug}`;
|
|
12
|
+
return `${normalizedSlug.replace(/-/g, "_")}.md`;
|
|
13
|
+
}
|
|
14
|
+
function filenameToSlug(filename) {
|
|
15
|
+
return filename.replace(/\.md$/, "").replace(/_/g, "-");
|
|
16
|
+
}
|
|
17
|
+
function hashContent(content) {
|
|
18
|
+
return createHash("md5").update(content).digest("hex");
|
|
19
|
+
}
|
|
20
|
+
async function fetchSkillsList() {
|
|
21
|
+
const baseUrl = getBaseUrl();
|
|
22
|
+
const skillsApiUrl = `${baseUrl}/api/public/skills`;
|
|
23
|
+
const listRes = await fetch(skillsApiUrl);
|
|
24
|
+
if (!listRes.ok) {
|
|
25
|
+
throw new Error(`Failed to fetch skills list: ${listRes.status}`);
|
|
26
|
+
}
|
|
27
|
+
return listRes.json();
|
|
28
|
+
}
|
|
29
|
+
async function fetchSkillContent(slug) {
|
|
30
|
+
const baseUrl = getBaseUrl();
|
|
31
|
+
const skillsApiUrl = `${baseUrl}/api/public/skills`;
|
|
32
|
+
const mdRes = await fetch(`${skillsApiUrl}/${slug}.md`);
|
|
33
|
+
if (!mdRes.ok) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return mdRes.text();
|
|
37
|
+
}
|
|
38
|
+
function getLocalSkills(skillsDir) {
|
|
39
|
+
const localSkills = /* @__PURE__ */ new Map();
|
|
40
|
+
if (!existsSync(skillsDir)) {
|
|
41
|
+
return localSkills;
|
|
42
|
+
}
|
|
43
|
+
for (const file of readdirSync(skillsDir)) {
|
|
44
|
+
if (file.endsWith(".md")) {
|
|
45
|
+
const content = readFileSync(join(skillsDir, file), "utf-8");
|
|
46
|
+
const slug = filenameToSlug(file);
|
|
47
|
+
localSkills.set(slug, hashContent(content));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return localSkills;
|
|
51
|
+
}
|
|
52
|
+
function parseSkillSummary(content) {
|
|
53
|
+
const parts = content.split("\n---\n");
|
|
54
|
+
const header = parts[0];
|
|
55
|
+
const lines = header.split("\n");
|
|
56
|
+
let title = "";
|
|
57
|
+
const summaryLines = [];
|
|
58
|
+
let inSummary = false;
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
const trimmed = line.trim();
|
|
61
|
+
if (trimmed.startsWith("# ")) {
|
|
62
|
+
title = trimmed.slice(2).trim();
|
|
63
|
+
inSummary = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!inSummary || summaryLines.length === 0 && trimmed === "") continue;
|
|
67
|
+
if (trimmed === "" && summaryLines.length > 0) break;
|
|
68
|
+
summaryLines.push(trimmed);
|
|
69
|
+
}
|
|
70
|
+
return { title, summary: summaryLines.join(" ") };
|
|
71
|
+
}
|
|
72
|
+
async function installAllSkills(targetDir, options) {
|
|
73
|
+
const verbose = options?.verbose ?? false;
|
|
74
|
+
const skills = await fetchSkillsList();
|
|
75
|
+
const skillsDir = join(targetDir, ".claude", "skills");
|
|
76
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
77
|
+
const results = await Promise.allSettled(
|
|
78
|
+
skills.map(async (skill) => {
|
|
79
|
+
const content = await fetchSkillContent(skill.slug);
|
|
80
|
+
return { skill, content };
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
let installed = 0;
|
|
84
|
+
let failed = 0;
|
|
85
|
+
for (const result of results) {
|
|
86
|
+
if (result.status === "fulfilled" && result.value.content) {
|
|
87
|
+
const { skill, content } = result.value;
|
|
88
|
+
const filename = slugToFilename(skill.slug);
|
|
89
|
+
writeFileSync(join(skillsDir, filename), content);
|
|
90
|
+
if (verbose) {
|
|
91
|
+
console.log(pc.green(" \u2713"), pc.dim(filename));
|
|
92
|
+
}
|
|
93
|
+
installed++;
|
|
94
|
+
} else {
|
|
95
|
+
if (verbose) {
|
|
96
|
+
const slug = result.status === "fulfilled" ? result.value.skill.slug : "unknown";
|
|
97
|
+
console.log(pc.yellow(" \u26A0"), pc.dim(`Failed to fetch ${slug}`));
|
|
98
|
+
}
|
|
99
|
+
failed++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { installed, failed };
|
|
103
|
+
}
|
|
104
|
+
var SKILLS_START_MARKER = "<!-- LUMERA_SKILLS_START -->";
|
|
105
|
+
var SKILLS_END_MARKER = "<!-- LUMERA_SKILLS_END -->";
|
|
106
|
+
function syncClaudeMd(projectRoot) {
|
|
107
|
+
const claudeMdPath = join(projectRoot, "CLAUDE.md");
|
|
108
|
+
const skillsDir = join(projectRoot, ".claude", "skills");
|
|
109
|
+
if (!existsSync(claudeMdPath)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const claudeMd = readFileSync(claudeMdPath, "utf-8");
|
|
113
|
+
const startIdx = claudeMd.indexOf(SKILLS_START_MARKER);
|
|
114
|
+
const endIdx = claudeMd.indexOf(SKILLS_END_MARKER);
|
|
115
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
116
|
+
console.log(pc.dim(" Skipping CLAUDE.md sync (no skill markers found)"));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const skillEntries = [];
|
|
120
|
+
if (existsSync(skillsDir)) {
|
|
121
|
+
for (const file of readdirSync(skillsDir).sort()) {
|
|
122
|
+
if (!file.endsWith(".md")) continue;
|
|
123
|
+
const content = readFileSync(join(skillsDir, file), "utf-8");
|
|
124
|
+
const { title, summary } = parseSkillSummary(content);
|
|
125
|
+
const slug = filenameToSlug(file);
|
|
126
|
+
skillEntries.push({ slug, title, summary });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let generated;
|
|
130
|
+
if (skillEntries.length === 0) {
|
|
131
|
+
generated = "_No skills installed. Run `lumera skills install` to add skills._";
|
|
132
|
+
} else {
|
|
133
|
+
generated = skillEntries.map((s) => `**${s.slug}** \u2014 ${s.summary}`).join("\n\n");
|
|
134
|
+
}
|
|
135
|
+
const before = claudeMd.slice(0, startIdx + SKILLS_START_MARKER.length);
|
|
136
|
+
const after = claudeMd.slice(endIdx);
|
|
137
|
+
const updated = `${before}
|
|
138
|
+
${generated}
|
|
139
|
+
${after}`;
|
|
140
|
+
writeFileSync(claudeMdPath, updated);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
slugToFilename,
|
|
145
|
+
hashContent,
|
|
146
|
+
fetchSkillsList,
|
|
147
|
+
fetchSkillContent,
|
|
148
|
+
getLocalSkills,
|
|
149
|
+
installAllSkills,
|
|
150
|
+
syncClaudeMd
|
|
151
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -118,7 +118,7 @@ async function main() {
|
|
|
118
118
|
break;
|
|
119
119
|
// Project
|
|
120
120
|
case "init":
|
|
121
|
-
await import("./init-
|
|
121
|
+
await import("./init-WQ4DQWXY.js").then((m) => m.init(args.slice(1)));
|
|
122
122
|
break;
|
|
123
123
|
case "status":
|
|
124
124
|
await import("./status-E4IHEUKO.js").then((m) => m.status(args.slice(1)));
|
|
@@ -128,7 +128,7 @@ async function main() {
|
|
|
128
128
|
break;
|
|
129
129
|
// Skills
|
|
130
130
|
case "skills":
|
|
131
|
-
await import("./skills-
|
|
131
|
+
await import("./skills-MMDJDUGC.js").then((m) => m.skills(subcommand, args.slice(2)));
|
|
132
132
|
break;
|
|
133
133
|
// Auth
|
|
134
134
|
case "login":
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
installAllSkills,
|
|
3
|
+
syncClaudeMd
|
|
4
|
+
} from "./chunk-UP3GV4HN.js";
|
|
5
|
+
import "./chunk-D2BLSEGR.js";
|
|
4
6
|
|
|
5
7
|
// src/commands/init.ts
|
|
6
8
|
import pc from "picocolors";
|
|
@@ -113,28 +115,6 @@ function createPythonVenv(targetDir) {
|
|
|
113
115
|
return false;
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
|
-
async function installSkills(targetDir) {
|
|
117
|
-
const baseUrl = getBaseUrl();
|
|
118
|
-
const skillsApiUrl = `${baseUrl}/api/public/skills`;
|
|
119
|
-
const listRes = await fetch(skillsApiUrl);
|
|
120
|
-
if (!listRes.ok) {
|
|
121
|
-
throw new Error(`Failed to fetch skills list: ${listRes.status}`);
|
|
122
|
-
}
|
|
123
|
-
const skills = await listRes.json();
|
|
124
|
-
const skillsDir = join(targetDir, ".claude", "skills");
|
|
125
|
-
mkdirSync(skillsDir, { recursive: true });
|
|
126
|
-
for (const skill of skills) {
|
|
127
|
-
const mdRes = await fetch(`${skillsApiUrl}/${skill.slug}.md`);
|
|
128
|
-
if (!mdRes.ok) {
|
|
129
|
-
console.log(pc.yellow(" \u26A0"), pc.dim(`Failed to fetch skill ${skill.slug}`));
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
const content = await mdRes.text();
|
|
133
|
-
const slug = skill.slug.startsWith("lumera-") ? skill.slug : `lumera-${skill.slug}`;
|
|
134
|
-
const filename = `${slug.replace(/-/g, "_")}.md`;
|
|
135
|
-
writeFileSync(join(skillsDir, filename), content);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
118
|
function parseArgs(args) {
|
|
139
119
|
const result = {
|
|
140
120
|
projectName: void 0,
|
|
@@ -342,8 +322,13 @@ async function init(args) {
|
|
|
342
322
|
console.log();
|
|
343
323
|
console.log(pc.dim(" Installing Lumera skills for AI agents..."));
|
|
344
324
|
try {
|
|
345
|
-
await
|
|
346
|
-
|
|
325
|
+
const { installed, failed } = await installAllSkills(targetDir);
|
|
326
|
+
if (failed > 0) {
|
|
327
|
+
console.log(pc.yellow(" \u26A0"), pc.dim(`Installed ${installed} skills (${failed} failed)`));
|
|
328
|
+
} else {
|
|
329
|
+
console.log(pc.green(" \u2713"), pc.dim(`${installed} Lumera skills installed`));
|
|
330
|
+
}
|
|
331
|
+
syncClaudeMd(targetDir);
|
|
347
332
|
} catch (err) {
|
|
348
333
|
console.log(pc.yellow(" \u26A0"), pc.dim(`Failed to install skills: ${err}`));
|
|
349
334
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
fetchSkillContent,
|
|
3
|
+
fetchSkillsList,
|
|
4
|
+
getLocalSkills,
|
|
5
|
+
hashContent,
|
|
6
|
+
installAllSkills,
|
|
7
|
+
slugToFilename,
|
|
8
|
+
syncClaudeMd
|
|
9
|
+
} from "./chunk-UP3GV4HN.js";
|
|
10
|
+
import "./chunk-D2BLSEGR.js";
|
|
4
11
|
|
|
5
12
|
// src/commands/skills.ts
|
|
6
13
|
import pc from "picocolors";
|
|
7
|
-
import {
|
|
8
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
14
|
+
import { existsSync, readdirSync, rmSync, writeFileSync } from "fs";
|
|
9
15
|
import { join, resolve } from "path";
|
|
10
16
|
function findProjectRoot() {
|
|
11
17
|
let dir = process.cwd();
|
|
@@ -17,48 +23,6 @@ function findProjectRoot() {
|
|
|
17
23
|
}
|
|
18
24
|
return null;
|
|
19
25
|
}
|
|
20
|
-
function slugToFilename(slug) {
|
|
21
|
-
const normalizedSlug = slug.startsWith("lumera-") ? slug : `lumera-${slug}`;
|
|
22
|
-
return `${normalizedSlug.replace(/-/g, "_")}.md`;
|
|
23
|
-
}
|
|
24
|
-
function filenameToSlug(filename) {
|
|
25
|
-
return filename.replace(/\.md$/, "").replace(/_/g, "-");
|
|
26
|
-
}
|
|
27
|
-
function hashContent(content) {
|
|
28
|
-
return createHash("md5").update(content).digest("hex");
|
|
29
|
-
}
|
|
30
|
-
async function fetchSkillsList() {
|
|
31
|
-
const baseUrl = getBaseUrl();
|
|
32
|
-
const skillsApiUrl = `${baseUrl}/api/public/skills`;
|
|
33
|
-
const listRes = await fetch(skillsApiUrl);
|
|
34
|
-
if (!listRes.ok) {
|
|
35
|
-
throw new Error(`Failed to fetch skills list: ${listRes.status}`);
|
|
36
|
-
}
|
|
37
|
-
return listRes.json();
|
|
38
|
-
}
|
|
39
|
-
async function fetchSkillContent(slug) {
|
|
40
|
-
const baseUrl = getBaseUrl();
|
|
41
|
-
const skillsApiUrl = `${baseUrl}/api/public/skills`;
|
|
42
|
-
const mdRes = await fetch(`${skillsApiUrl}/${slug}.md`);
|
|
43
|
-
if (!mdRes.ok) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
return mdRes.text();
|
|
47
|
-
}
|
|
48
|
-
function getLocalSkills(skillsDir) {
|
|
49
|
-
const localSkills = /* @__PURE__ */ new Map();
|
|
50
|
-
if (!existsSync(skillsDir)) {
|
|
51
|
-
return localSkills;
|
|
52
|
-
}
|
|
53
|
-
for (const file of readdirSync(skillsDir)) {
|
|
54
|
-
if (file.endsWith(".md")) {
|
|
55
|
-
const content = readFileSync(join(skillsDir, file), "utf-8");
|
|
56
|
-
const slug = filenameToSlug(file);
|
|
57
|
-
localSkills.set(slug, hashContent(content));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return localSkills;
|
|
61
|
-
}
|
|
62
26
|
async function computeDiff(skillsDir, filterSlug) {
|
|
63
27
|
const skills2 = await fetchSkillsList();
|
|
64
28
|
const localSkills = getLocalSkills(skillsDir);
|
|
@@ -69,28 +33,31 @@ async function computeDiff(skillsDir, filterSlug) {
|
|
|
69
33
|
removed: [],
|
|
70
34
|
unchanged: []
|
|
71
35
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
36
|
+
const skillsToCheck = filterSlug ? skills2.filter((skill) => {
|
|
37
|
+
const normalizedFilter = filterSlug.startsWith("lumera-") ? filterSlug : `lumera-${filterSlug}`;
|
|
38
|
+
const normalizedSlug = skill.slug.startsWith("lumera-") ? skill.slug : `lumera-${skill.slug}`;
|
|
39
|
+
return normalizedSlug === normalizedFilter || skill.slug === filterSlug;
|
|
40
|
+
}) : 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;
|
|
80
50
|
const normalizedSlug = skill.slug.startsWith("lumera-") ? skill.slug : `lumera-${skill.slug}`;
|
|
81
51
|
remoteSkillSlugs.add(normalizedSlug);
|
|
82
52
|
const localHash = localSkills.get(normalizedSlug);
|
|
83
53
|
if (!localHash) {
|
|
84
54
|
diff.added.push(skill);
|
|
85
55
|
} else {
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
} else {
|
|
92
|
-
diff.unchanged.push(skill.slug);
|
|
93
|
-
}
|
|
56
|
+
const remoteHash = hashContent(content);
|
|
57
|
+
if (localHash !== remoteHash) {
|
|
58
|
+
diff.updated.push(skill);
|
|
59
|
+
} else {
|
|
60
|
+
diff.unchanged.push(skill.slug);
|
|
94
61
|
}
|
|
95
62
|
}
|
|
96
63
|
}
|
|
@@ -103,6 +70,43 @@ async function computeDiff(skillsDir, filterSlug) {
|
|
|
103
70
|
}
|
|
104
71
|
return diff;
|
|
105
72
|
}
|
|
73
|
+
function parseFlags(args) {
|
|
74
|
+
const result = {};
|
|
75
|
+
for (let i = 0; i < args.length; i++) {
|
|
76
|
+
const arg = args[i];
|
|
77
|
+
if (arg.startsWith("--")) {
|
|
78
|
+
const key = arg.slice(2);
|
|
79
|
+
const next = args[i + 1];
|
|
80
|
+
if (next && !next.startsWith("-")) {
|
|
81
|
+
result[key] = next;
|
|
82
|
+
i++;
|
|
83
|
+
} else {
|
|
84
|
+
result[key] = true;
|
|
85
|
+
}
|
|
86
|
+
} else if (arg.startsWith("-") && arg.length === 2) {
|
|
87
|
+
const key = arg.slice(1);
|
|
88
|
+
result[key] = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
function getPositionalArgs(args) {
|
|
94
|
+
const positional = [];
|
|
95
|
+
for (let i = 0; i < args.length; i++) {
|
|
96
|
+
const arg = args[i];
|
|
97
|
+
if (arg.startsWith("-")) {
|
|
98
|
+
if (arg.startsWith("--")) {
|
|
99
|
+
const next = args[i + 1];
|
|
100
|
+
if (next && !next.startsWith("-")) {
|
|
101
|
+
i++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
positional.push(arg);
|
|
107
|
+
}
|
|
108
|
+
return positional;
|
|
109
|
+
}
|
|
106
110
|
function showHelp() {
|
|
107
111
|
console.log(`
|
|
108
112
|
${pc.dim("Usage:")}
|
|
@@ -156,43 +160,6 @@ async function skills(subcommand, args) {
|
|
|
156
160
|
process.exit(1);
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
|
-
function parseFlags(args) {
|
|
160
|
-
const result = {};
|
|
161
|
-
for (let i = 0; i < args.length; i++) {
|
|
162
|
-
const arg = args[i];
|
|
163
|
-
if (arg.startsWith("--")) {
|
|
164
|
-
const key = arg.slice(2);
|
|
165
|
-
const next = args[i + 1];
|
|
166
|
-
if (next && !next.startsWith("-")) {
|
|
167
|
-
result[key] = next;
|
|
168
|
-
i++;
|
|
169
|
-
} else {
|
|
170
|
-
result[key] = true;
|
|
171
|
-
}
|
|
172
|
-
} else if (arg.startsWith("-") && arg.length === 2) {
|
|
173
|
-
const key = arg.slice(1);
|
|
174
|
-
result[key] = true;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return result;
|
|
178
|
-
}
|
|
179
|
-
function getPositionalArgs(args) {
|
|
180
|
-
const positional = [];
|
|
181
|
-
for (let i = 0; i < args.length; i++) {
|
|
182
|
-
const arg = args[i];
|
|
183
|
-
if (arg.startsWith("-")) {
|
|
184
|
-
if (arg.startsWith("--")) {
|
|
185
|
-
const next = args[i + 1];
|
|
186
|
-
if (next && !next.startsWith("-")) {
|
|
187
|
-
i++;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
positional.push(arg);
|
|
193
|
-
}
|
|
194
|
-
return positional;
|
|
195
|
-
}
|
|
196
163
|
async function list(flags) {
|
|
197
164
|
const verbose = Boolean(flags.verbose || flags.v);
|
|
198
165
|
console.log();
|
|
@@ -247,36 +214,17 @@ async function install(flags) {
|
|
|
247
214
|
}
|
|
248
215
|
if (verbose) {
|
|
249
216
|
console.log(pc.dim(` Project root: ${projectRoot}`));
|
|
250
|
-
console.log(pc.dim(` Fetching skills
|
|
217
|
+
console.log(pc.dim(` Fetching skills...`));
|
|
251
218
|
}
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
for (const file of readdirSync(skillsDir)) {
|
|
257
|
-
if (file.endsWith(".md")) {
|
|
258
|
-
rmSync(join(skillsDir, file));
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
let installed = 0;
|
|
263
|
-
let failed = 0;
|
|
264
|
-
for (const skill of skills2) {
|
|
265
|
-
const content = await fetchSkillContent(skill.slug);
|
|
266
|
-
if (!content) {
|
|
267
|
-
if (verbose) {
|
|
268
|
-
console.log(pc.yellow(" \u26A0"), pc.dim(`Failed to fetch ${skill.slug}`));
|
|
269
|
-
}
|
|
270
|
-
failed++;
|
|
271
|
-
continue;
|
|
219
|
+
if (force && existsSync(skillsDir)) {
|
|
220
|
+
for (const file of readdirSync(skillsDir)) {
|
|
221
|
+
if (file.endsWith(".md")) {
|
|
222
|
+
rmSync(join(skillsDir, file));
|
|
272
223
|
}
|
|
273
|
-
const filename = slugToFilename(skill.slug);
|
|
274
|
-
writeFileSync(join(skillsDir, filename), content);
|
|
275
|
-
if (verbose) {
|
|
276
|
-
console.log(pc.green(" \u2713"), pc.dim(filename));
|
|
277
|
-
}
|
|
278
|
-
installed++;
|
|
279
224
|
}
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const { installed, failed } = await installAllSkills(projectRoot, { verbose });
|
|
280
228
|
console.log();
|
|
281
229
|
if (failed > 0) {
|
|
282
230
|
console.log(pc.yellow(" \u26A0"), `Installed ${installed} skills (${failed} failed)`);
|
|
@@ -284,6 +232,7 @@ async function install(flags) {
|
|
|
284
232
|
console.log(pc.green(" \u2713"), `Installed ${installed} skills`);
|
|
285
233
|
}
|
|
286
234
|
console.log(pc.dim(` Location: .claude/skills/`));
|
|
235
|
+
syncClaudeMd(projectRoot);
|
|
287
236
|
console.log();
|
|
288
237
|
} catch (err) {
|
|
289
238
|
console.log(pc.red(" Error:"), String(err));
|
|
@@ -377,19 +326,22 @@ async function update(args, flags) {
|
|
|
377
326
|
}
|
|
378
327
|
console.log(pc.dim(" Applying changes..."));
|
|
379
328
|
console.log();
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const
|
|
384
|
-
|
|
329
|
+
const toFetch = [...diff.added, ...diff.updated];
|
|
330
|
+
const fetchResults = await Promise.allSettled(
|
|
331
|
+
toFetch.map(async (skill) => {
|
|
332
|
+
const content = await fetchSkillContent(skill.slug);
|
|
333
|
+
return { skill, content };
|
|
334
|
+
})
|
|
335
|
+
);
|
|
336
|
+
for (const result of fetchResults) {
|
|
337
|
+
if (result.status !== "fulfilled" || !result.value.content) continue;
|
|
338
|
+
const { skill, content } = result.value;
|
|
339
|
+
const filename = slugToFilename(skill.slug);
|
|
340
|
+
writeFileSync(join(skillsDir, filename), content);
|
|
341
|
+
const isNew = diff.added.includes(skill);
|
|
342
|
+
if (isNew) {
|
|
385
343
|
console.log(pc.green(" +"), `Added ${skill.name}`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
for (const skill of diff.updated) {
|
|
389
|
-
const content = await fetchSkillContent(skill.slug);
|
|
390
|
-
if (content) {
|
|
391
|
-
const filename = slugToFilename(skill.slug);
|
|
392
|
-
writeFileSync(join(skillsDir, filename), content);
|
|
344
|
+
} else {
|
|
393
345
|
console.log(pc.yellow(" ~"), `Updated ${skill.name}`);
|
|
394
346
|
}
|
|
395
347
|
}
|
|
@@ -403,6 +355,7 @@ async function update(args, flags) {
|
|
|
403
355
|
}
|
|
404
356
|
console.log();
|
|
405
357
|
console.log(pc.green(" \u2713"), `Update complete (${changes.join(", ")})`);
|
|
358
|
+
syncClaudeMd(projectRoot);
|
|
406
359
|
console.log();
|
|
407
360
|
} catch (err) {
|
|
408
361
|
console.log(pc.red(" Error:"), String(err));
|
package/package.json
CHANGED
|
@@ -6,26 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
## AI Agent Skills
|
|
8
8
|
|
|
9
|
-
This project includes Lumera skills for AI coding agents
|
|
9
|
+
This project includes Lumera skills for AI coding agents in `.claude/skills/`. Read the relevant skill file when you need detailed API docs and usage patterns for that capability.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- **lumera-webhooks** - Receiving external webhooks
|
|
15
|
-
- **write-hooks** - Server-side JavaScript hooks
|
|
16
|
-
- **lumera-sdk** - Python SDK reference
|
|
17
|
-
- **using-lumera** - Platform overview and patterns
|
|
11
|
+
<!-- LUMERA_SKILLS_START -->
|
|
12
|
+
_Run `lumera skills install` to populate skill descriptions._
|
|
13
|
+
<!-- LUMERA_SKILLS_END -->
|
|
18
14
|
|
|
19
|
-
###
|
|
20
|
-
|
|
21
|
-
Skills are auto-installed when creating the app. To manually install or update:
|
|
15
|
+
### Managing Skills
|
|
22
16
|
|
|
23
17
|
```bash
|
|
24
|
-
|
|
18
|
+
lumera skills update # Update all skills to latest
|
|
19
|
+
lumera skills install --force # Re-install from scratch
|
|
25
20
|
```
|
|
26
21
|
|
|
27
|
-
> **Note:** Requires SSH access to the lumerahq GitHub organization.
|
|
28
|
-
|
|
29
22
|
---
|
|
30
23
|
|
|
31
24
|
## Quick Reference
|