yeknal 1.3.2 → 1.4.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/README.md +3 -1
- package/bin/yeknal.js +41 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,12 +40,14 @@ Behavior:
|
|
|
40
40
|
- Uses GitHub API + raw file download by default.
|
|
41
41
|
- If GitHub API rate limit is hit, it automatically falls back to `git clone` (Git must be installed).
|
|
42
42
|
- Top-level folders are included only if they contain `SKILL.md`.
|
|
43
|
+
- Installs each synced skill with a `yeknal-` folder prefix, for example `taste-skill` installs as `yeknal-taste-skill`.
|
|
44
|
+
- Matching `yeknal-*` managed destination folders are overwritten during sync, and stale `yeknal-*` folders removed from an earlier repository version are cleaned up. Unprefixed personal skill folders are never touched.
|
|
43
45
|
- Sync targets (if parent folder exists):
|
|
44
46
|
- Gemini: `~/.gemini/antigravity` or `~/.antigravity`
|
|
45
47
|
- Codex: `~/.codex`
|
|
46
48
|
- Claude: `~/.claude`
|
|
47
49
|
- For each detected target, creates `<parent>/skills` if missing.
|
|
48
|
-
-
|
|
50
|
+
- Personal/private skill folders without the `yeknal-` prefix are left untouched.
|
|
49
51
|
|
|
50
52
|
Optional environment variable overrides:
|
|
51
53
|
- `YEKNAL_GEMINI_PARENT`
|
package/bin/yeknal.js
CHANGED
|
@@ -27,6 +27,7 @@ const API_BASE = `https://api.github.com/repos/${GITHUB_USERNAME}/${GITHUB_REPO}
|
|
|
27
27
|
const GITHUB_TOKEN = process.env.YEKNAL_GITHUB_TOKEN || process.env.GITHUB_TOKEN || "";
|
|
28
28
|
|
|
29
29
|
const EXCLUDED_SKILL_FOLDERS = new Set(["Design", "Security", "Security_Raw", "SEO"]);
|
|
30
|
+
const MANAGED_SKILL_FOLDER_PREFIX = "yeknal-";
|
|
30
31
|
|
|
31
32
|
const SECURITY_REPO_FOLDERS = ["Security", "Security_Raw"];
|
|
32
33
|
|
|
@@ -163,6 +164,13 @@ function listFilesForFolder(repoTree, folderName) {
|
|
|
163
164
|
.sort((a, b) => a.localeCompare(b));
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
function getManagedSkillFolderName(folderName) {
|
|
168
|
+
if (folderName.startsWith(MANAGED_SKILL_FOLDER_PREFIX)) {
|
|
169
|
+
return folderName;
|
|
170
|
+
}
|
|
171
|
+
return `${MANAGED_SKILL_FOLDER_PREFIX}${folderName}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
166
174
|
function buildRawFileUrl(repoFilePath) {
|
|
167
175
|
const encodedPath = repoFilePath.split("/").map((part) => encodeURIComponent(part)).join("/");
|
|
168
176
|
return `${RAW_BASE_URL}/${encodedPath}`;
|
|
@@ -187,6 +195,26 @@ async function copyDirRecursive(sourceDir, targetDir) {
|
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
197
|
|
|
198
|
+
async function removeStaleManagedSkillFolders(skillsPath, expectedFolders) {
|
|
199
|
+
const entries = await fsp.readdir(skillsPath, { withFileTypes: true });
|
|
200
|
+
const removed = [];
|
|
201
|
+
|
|
202
|
+
for (const entry of entries) {
|
|
203
|
+
if (!entry.isDirectory() || !entry.name.startsWith(MANAGED_SKILL_FOLDER_PREFIX)) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (expectedFolders.has(entry.name)) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
await fsp.rm(path.join(skillsPath, entry.name), { recursive: true, force: true });
|
|
212
|
+
removed.push(entry.name);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return removed;
|
|
216
|
+
}
|
|
217
|
+
|
|
190
218
|
function execCommand(command, options = {}) {
|
|
191
219
|
return new Promise((resolve, reject) => {
|
|
192
220
|
exec(command, options, (error, stdout, stderr) => {
|
|
@@ -393,16 +421,28 @@ async function runSkillsCommand() {
|
|
|
393
421
|
|
|
394
422
|
console.log(`\nSkill folders to sync (${skillFolders.length}) via ${sourceLabel}:`);
|
|
395
423
|
console.log(` ${skillFolders.join(", ")}`);
|
|
424
|
+
console.log(`\nInstalled folders use the managed "${MANAGED_SKILL_FOLDER_PREFIX}" prefix.`);
|
|
396
425
|
|
|
397
426
|
let hadFailure = false;
|
|
398
427
|
for (const target of targets) {
|
|
399
428
|
try {
|
|
400
429
|
await fsp.mkdir(target.skillsPath, { recursive: true });
|
|
401
430
|
let copiedCount = 0;
|
|
431
|
+
const expectedFolders = new Set(
|
|
432
|
+
skillFolders.map((folder) => getManagedSkillFolderName(folder)),
|
|
433
|
+
);
|
|
434
|
+
const removedFolders = await removeStaleManagedSkillFolders(
|
|
435
|
+
target.skillsPath,
|
|
436
|
+
expectedFolders,
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
for (const removedFolder of removedFolders) {
|
|
440
|
+
console.log(` [removed] ${target.label}: stale managed skill ${removedFolder}`);
|
|
441
|
+
}
|
|
402
442
|
|
|
403
443
|
for (const folder of skillFolders) {
|
|
404
444
|
const sourceFolder = path.join(tempRoot, folder);
|
|
405
|
-
const destinationFolder = path.join(target.skillsPath, folder);
|
|
445
|
+
const destinationFolder = path.join(target.skillsPath, getManagedSkillFolderName(folder));
|
|
406
446
|
await fsp.rm(destinationFolder, { recursive: true, force: true });
|
|
407
447
|
await copyDirRecursive(sourceFolder, destinationFolder);
|
|
408
448
|
copiedCount += 1;
|