yeknal 1.3.3 → 1.4.1
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 +1 -1
- package/bin/yeknal.js +63 -2
- package/package.json +1 -1
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
|
-
-
|
|
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);
|
|
@@ -446,9 +477,36 @@ const SCAN_IGNORE_DIRS = new Set([
|
|
|
446
477
|
"node_modules", ".git", ".next", "dist", "build", ".cache", ".output",
|
|
447
478
|
"coverage", ".nyc_output", "__pycache__", ".venv", "venv", "env",
|
|
448
479
|
".terraform", "vendor", "target", "bin", "obj", ".nuxt", ".svelte-kit",
|
|
449
|
-
".vercel", ".netlify", ".turbo", "out", ".parcel-cache", "tmp",
|
|
480
|
+
".astro", ".vercel", ".netlify", ".turbo", "out", ".parcel-cache", "tmp",
|
|
450
481
|
]);
|
|
451
482
|
|
|
483
|
+
// Dependency backups and similarly named generated/build directories are
|
|
484
|
+
// commonly left beside the working tree after reinstalls or builds.
|
|
485
|
+
const SCAN_IGNORE_DIR_PATTERNS = [
|
|
486
|
+
/^node_modules(?:\.|[-_])(?:bak|backup)(?:[-_.]|$)/i,
|
|
487
|
+
/^(?:vendor|bower_components|\.venv|venv|env)(?:\.|[-_])(?:bak|backup)(?:[-_.]|$)/i,
|
|
488
|
+
/^(?:build|dist|out|coverage|generated|__generated__)(?:\.|[-_])(?:bak|backup)(?:[-_.]|$)/i,
|
|
489
|
+
];
|
|
490
|
+
|
|
491
|
+
// Generated source/config files can contain copied secrets or noisy bundled
|
|
492
|
+
// content that is not the project's authored implementation.
|
|
493
|
+
const SCAN_IGNORE_FILE_PATTERNS = [
|
|
494
|
+
/\.min\.(?:js|css|mjs|cjs)$/i,
|
|
495
|
+
/\.(?:bundle|chunk|generated|gen)\.[^.]+$/i,
|
|
496
|
+
/(?:^|[._-])generated(?:[._-]|$)/i,
|
|
497
|
+
/\.d\.ts$/i,
|
|
498
|
+
/\.map$/i,
|
|
499
|
+
];
|
|
500
|
+
|
|
501
|
+
function isScanIgnoredDirectory(name) {
|
|
502
|
+
const nameLC = name.toLowerCase();
|
|
503
|
+
return SCAN_IGNORE_DIRS.has(nameLC) || SCAN_IGNORE_DIR_PATTERNS.some((pattern) => pattern.test(name));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function isScanIgnoredFile(name) {
|
|
507
|
+
return SCAN_IGNORE_FILE_PATTERNS.some((pattern) => pattern.test(name));
|
|
508
|
+
}
|
|
509
|
+
|
|
452
510
|
const SCAN_SOURCE_EXTENSIONS = new Set([
|
|
453
511
|
".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs",
|
|
454
512
|
".py", ".rb", ".php", ".go", ".rs", ".java", ".cs",
|
|
@@ -524,13 +582,16 @@ async function walkProjectFiles(rootDir) {
|
|
|
524
582
|
const fullPath = path.join(dir, entry.name);
|
|
525
583
|
|
|
526
584
|
if (entry.isDirectory()) {
|
|
527
|
-
if (!
|
|
585
|
+
if (!isScanIgnoredDirectory(entry.name)) {
|
|
528
586
|
await walk(fullPath, depth + 1);
|
|
529
587
|
}
|
|
530
588
|
continue;
|
|
531
589
|
}
|
|
532
590
|
|
|
533
591
|
if (entry.isFile()) {
|
|
592
|
+
if (isScanIgnoredFile(entry.name)) {
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
534
595
|
const ext = path.extname(entry.name).toLowerCase();
|
|
535
596
|
const nameLC = entry.name.toLowerCase();
|
|
536
597
|
if (SCAN_ALL_EXTENSIONS.has(ext) || nameLC.startsWith(".env")) {
|