yeknal 1.3.3 → 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 CHANGED
@@ -41,7 +41,7 @@ Behavior:
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
43
  - Installs each synced skill with a `yeknal-` folder prefix, for example `taste-skill` installs as `yeknal-taste-skill`.
44
- - Only the matching `yeknal-*` managed destination folders are overwritten during sync.
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.
45
45
  - Sync targets (if parent folder exists):
46
46
  - Gemini: `~/.gemini/antigravity` or `~/.antigravity`
47
47
  - Codex: `~/.codex`
package/bin/yeknal.js CHANGED
@@ -195,6 +195,26 @@ async function copyDirRecursive(sourceDir, targetDir) {
195
195
  }
196
196
  }
197
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
+
198
218
  function execCommand(command, options = {}) {
199
219
  return new Promise((resolve, reject) => {
200
220
  exec(command, options, (error, stdout, stderr) => {
@@ -408,6 +428,17 @@ async function runSkillsCommand() {
408
428
  try {
409
429
  await fsp.mkdir(target.skillsPath, { recursive: true });
410
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
+ }
411
442
 
412
443
  for (const folder of skillFolders) {
413
444
  const sourceFolder = path.join(tempRoot, folder);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yeknal",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "description": "CLI to fetch markdown templates and sync AI agent skills",
5
5
  "main": "bin/yeknal.js",
6
6
  "bin": {