@heylemon/lemonade 0.0.5 → 0.0.6
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/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
51eb12e5a38916af8a01151207c0974086f45b5a7c962e824bb8ebd5fe8887d1
|
|
@@ -92,6 +92,25 @@ export async function handleSkillsHttpRequest(req, res, opts) {
|
|
|
92
92
|
}
|
|
93
93
|
// ─── Helpers ───────────────────────────────────────────────────────────────────
|
|
94
94
|
const MANAGED_SKILLS_DIR = path.join(CONFIG_DIR, "skills");
|
|
95
|
+
function getBuiltInSkillNames() {
|
|
96
|
+
const bundledDir = resolveBundledSkillsDir();
|
|
97
|
+
if (!bundledDir || !fs.existsSync(bundledDir))
|
|
98
|
+
return new Set();
|
|
99
|
+
const loadSkills = (params) => {
|
|
100
|
+
const loaded = loadSkillsFromDir(params);
|
|
101
|
+
if (Array.isArray(loaded))
|
|
102
|
+
return loaded;
|
|
103
|
+
if (loaded &&
|
|
104
|
+
typeof loaded === "object" &&
|
|
105
|
+
"skills" in loaded &&
|
|
106
|
+
Array.isArray(loaded.skills)) {
|
|
107
|
+
return loaded.skills;
|
|
108
|
+
}
|
|
109
|
+
return [];
|
|
110
|
+
};
|
|
111
|
+
const skills = loadSkills({ dir: bundledDir, source: "lemonade-bundled" });
|
|
112
|
+
return new Set(skills.map((s) => s.name));
|
|
113
|
+
}
|
|
95
114
|
function loadAllSkillEntries() {
|
|
96
115
|
const config = loadConfig();
|
|
97
116
|
const loadSkills = (params) => {
|
|
@@ -175,43 +194,55 @@ async function handleCreateSkill(req, res) {
|
|
|
175
194
|
sendInvalidRequest(res, "name is invalid after sanitization");
|
|
176
195
|
return true;
|
|
177
196
|
}
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
197
|
+
const builtInNames = getBuiltInSkillNames();
|
|
198
|
+
let finalName = safeName;
|
|
199
|
+
if (builtInNames.has(safeName)) {
|
|
200
|
+
finalName = `${safeName}-custom`;
|
|
201
|
+
}
|
|
202
|
+
const customSkillDir = path.join(MANAGED_SKILLS_DIR, finalName);
|
|
203
|
+
const customSkillFile = path.join(customSkillDir, "SKILL.md");
|
|
204
|
+
if (fs.existsSync(customSkillFile)) {
|
|
182
205
|
sendJson(res, 409, {
|
|
183
|
-
error: {
|
|
206
|
+
error: {
|
|
207
|
+
message: `You already have a custom skill '${finalName}'. You can replace its content or rename your new skill.`,
|
|
208
|
+
type: "conflict",
|
|
209
|
+
},
|
|
184
210
|
});
|
|
185
211
|
return true;
|
|
186
212
|
}
|
|
187
213
|
// Build SKILL.md
|
|
188
214
|
let fileContent;
|
|
189
215
|
if (content.startsWith("---")) {
|
|
190
|
-
// Content already has frontmatter
|
|
191
216
|
fileContent = content;
|
|
192
217
|
}
|
|
193
218
|
else {
|
|
194
|
-
fileContent = `---\nname: ${
|
|
219
|
+
fileContent = `---\nname: ${finalName}\ndescription: "${description || "Custom skill"}"\n---\n\n${content}`;
|
|
195
220
|
}
|
|
196
|
-
await fsp.mkdir(
|
|
197
|
-
await fsp.writeFile(
|
|
198
|
-
// Enable in config
|
|
221
|
+
await fsp.mkdir(customSkillDir, { recursive: true });
|
|
222
|
+
await fsp.writeFile(customSkillFile, fileContent, "utf-8");
|
|
199
223
|
const config = loadConfig();
|
|
200
224
|
if (!config.skills)
|
|
201
225
|
config.skills = {};
|
|
202
226
|
if (!config.skills.entries)
|
|
203
227
|
config.skills.entries = {};
|
|
204
|
-
|
|
228
|
+
// If this shadows a built-in, disable the built-in and enable the custom one
|
|
229
|
+
if (finalName !== safeName && builtInNames.has(safeName)) {
|
|
230
|
+
config.skills.entries[safeName] = { ...config.skills.entries[safeName], enabled: false };
|
|
231
|
+
}
|
|
232
|
+
config.skills.entries[finalName] = { enabled: true };
|
|
205
233
|
await writeConfigFile(config);
|
|
206
234
|
sendJson(res, 201, {
|
|
207
235
|
ok: true,
|
|
208
236
|
skill: {
|
|
209
|
-
name:
|
|
237
|
+
name: finalName,
|
|
210
238
|
description: description || "Custom skill",
|
|
211
|
-
path:
|
|
239
|
+
path: customSkillFile,
|
|
212
240
|
isBuiltIn: false,
|
|
213
241
|
enabled: true,
|
|
214
242
|
},
|
|
243
|
+
...(finalName !== safeName
|
|
244
|
+
? { note: `Renamed to '${finalName}' because '${safeName}' is a built-in skill. The built-in version has been deactivated.` }
|
|
245
|
+
: {}),
|
|
215
246
|
});
|
|
216
247
|
return true;
|
|
217
248
|
}
|
|
@@ -219,9 +250,9 @@ async function handleUpdateSkill(req, res, skillName) {
|
|
|
219
250
|
const body = (await readJsonBodyOrError(req, res, 1_000_000));
|
|
220
251
|
if (!body)
|
|
221
252
|
return true;
|
|
253
|
+
const builtInNames = getBuiltInSkillNames();
|
|
222
254
|
const config = loadConfig();
|
|
223
255
|
const updated = {};
|
|
224
|
-
// Update enabled state in config
|
|
225
256
|
if (typeof body.enabled === "boolean") {
|
|
226
257
|
if (!config.skills)
|
|
227
258
|
config.skills = {};
|
|
@@ -234,12 +265,19 @@ async function handleUpdateSkill(req, res, skillName) {
|
|
|
234
265
|
await writeConfigFile(config);
|
|
235
266
|
updated.enabled = body.enabled;
|
|
236
267
|
}
|
|
237
|
-
// Update content on disk (only for user/managed skills)
|
|
238
268
|
if (typeof body.content === "string") {
|
|
269
|
+
if (builtInNames.has(skillName)) {
|
|
270
|
+
sendJson(res, 403, {
|
|
271
|
+
error: {
|
|
272
|
+
message: `'${skillName}' is a built-in skill and its content cannot be modified. You can only enable or disable it.`,
|
|
273
|
+
type: "forbidden",
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
239
278
|
const skillFile = path.join(MANAGED_SKILLS_DIR, skillName, "SKILL.md");
|
|
240
279
|
if (fs.existsSync(skillFile)) {
|
|
241
280
|
let newContent = body.content.trim();
|
|
242
|
-
// Ensure frontmatter exists
|
|
243
281
|
if (!newContent.startsWith("---")) {
|
|
244
282
|
const desc = typeof body.description === "string" ? body.description : "Custom skill";
|
|
245
283
|
newContent = `---\nname: ${skillName}\ndescription: "${desc}"\n---\n\n${newContent}`;
|
|
@@ -261,6 +299,16 @@ async function handleUpdateSkill(req, res, skillName) {
|
|
|
261
299
|
return true;
|
|
262
300
|
}
|
|
263
301
|
async function handleDeleteSkill(res, skillName) {
|
|
302
|
+
const builtInNames = getBuiltInSkillNames();
|
|
303
|
+
if (builtInNames.has(skillName)) {
|
|
304
|
+
sendJson(res, 403, {
|
|
305
|
+
error: {
|
|
306
|
+
message: `'${skillName}' is a built-in skill and cannot be deleted. You can only enable or disable it.`,
|
|
307
|
+
type: "forbidden",
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
264
312
|
const skillDir = path.join(MANAGED_SKILLS_DIR, skillName);
|
|
265
313
|
if (!fs.existsSync(skillDir)) {
|
|
266
314
|
sendJson(res, 404, {
|
|
@@ -268,14 +316,21 @@ async function handleDeleteSkill(res, skillName) {
|
|
|
268
316
|
});
|
|
269
317
|
return true;
|
|
270
318
|
}
|
|
271
|
-
// Remove directory
|
|
272
319
|
await fsp.rm(skillDir, { recursive: true, force: true });
|
|
273
|
-
// Remove from config
|
|
274
320
|
const config = loadConfig();
|
|
275
321
|
if (config.skills?.entries?.[skillName]) {
|
|
276
322
|
delete config.skills.entries[skillName];
|
|
277
|
-
await writeConfigFile(config);
|
|
278
323
|
}
|
|
324
|
+
// If deleting a custom override (e.g. "pdf-custom"), re-enable the built-in
|
|
325
|
+
const baseSkillName = skillName.replace(/-custom$/, "");
|
|
326
|
+
if (baseSkillName !== skillName && builtInNames.has(baseSkillName)) {
|
|
327
|
+
if (!config.skills)
|
|
328
|
+
config.skills = {};
|
|
329
|
+
if (!config.skills.entries)
|
|
330
|
+
config.skills.entries = {};
|
|
331
|
+
config.skills.entries[baseSkillName] = { ...config.skills.entries[baseSkillName], enabled: true };
|
|
332
|
+
}
|
|
333
|
+
await writeConfigFile(config);
|
|
279
334
|
sendJson(res, 200, { ok: true, deleted: skillName });
|
|
280
335
|
return true;
|
|
281
336
|
}
|