Skip to content

Commit 46698ba

Browse files
committed
fix: scan nested skill directories up to 3 levels deep
scanDirectoryWithSkillMd only checked one level, missing grouped structures like ~/.agents/skills/<group>/skills/<name>/SKILL.md Now recurses into subdirs that don't contain SKILL.md, up to depth 3. Also includes skillkit binary validation (isCrafterSkillkit). Closes #16
1 parent feab7dc commit 46698ba

5 files changed

Lines changed: 34 additions & 14 deletions

File tree

main.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "agentfiles",
33
"name": "Agentfiles",
4-
"version": "0.4.1",
4+
"version": "0.4.2",
55
"minAppVersion": "1.4.11",
66
"description": "Discover, organize, and edit AI agent skills, commands, and agents across Claude Code, Cursor, Codex, Windsurf, and more.",
77
"author": "Railly Hugo",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "agentfiles",
3-
"version": "0.4.1",
3+
"version": "0.4.2",
44
"description": "AI Skills Manager for Obsidian",
55
"main": "main.js",
66
"scripts": {

src/scanner.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,20 +74,23 @@ function scanDirectoryWithSkillMd(
7474
baseDir: string,
7575
type: SkillType,
7676
toolId: string,
77-
namingMode: NamingMode = "auto"
77+
namingMode: NamingMode = "auto",
78+
depth: number = 0
7879
): SkillItem[] {
79-
if (!existsSync(baseDir)) return [];
80+
if (!existsSync(baseDir) || depth > 3) return [];
8081
const items: SkillItem[] = [];
8182

8283
for (const entry of readdirSync(baseDir, { withFileTypes: true })) {
8384
const fullPath = join(baseDir, entry.name);
8485
const isDir = entry.isDirectory() || (entry.isSymbolicLink() && statSync(fullPath, { throwIfNoEntry: false })?.isDirectory());
8586
if (!isDir) continue;
8687
const skillFile = join(fullPath, "SKILL.md");
87-
if (!existsSync(skillFile)) continue;
88-
89-
const item = parseSkillFile(skillFile, type, toolId, "directory-with-skillmd", namingMode);
90-
if (item) items.push(item);
88+
if (existsSync(skillFile)) {
89+
const item = parseSkillFile(skillFile, type, toolId, "directory-with-skillmd", namingMode);
90+
if (item) items.push(item);
91+
} else {
92+
items.push(...scanDirectoryWithSkillMd(fullPath, type, toolId, namingMode, depth + 1));
93+
}
9194
}
9295
return items;
9396
}

src/skillkit.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,21 @@ function buildPath(): string {
4646
return [...extra, process.env.PATH || ""].join(delimiter);
4747
}
4848

49+
function isCrafterSkillkit(binPath: string): boolean {
50+
try {
51+
const out = execSync(`"${binPath}" help`, {
52+
encoding: "utf-8",
53+
timeout: 5000,
54+
env: { ...process.env, NO_COLOR: "1", PATH: buildPath() },
55+
stdio: ["pipe", "pipe", "pipe"],
56+
shell: IS_WIN ? "cmd.exe" : undefined,
57+
});
58+
return out.includes("Analytics for AI agent skills");
59+
} catch { return false; }
60+
}
61+
4962
function findSkillkitBin(): string | null {
63+
const candidates: string[] = [];
5064
const searchDirs: string[] = [];
5165
if (IS_WIN) {
5266
const appData = process.env.APPDATA || join(HOME, "AppData", "Roaming");
@@ -67,7 +81,7 @@ function findSkillkitBin(): string | null {
6781
for (const dir of searchDirs) {
6882
for (const bin of BIN_NAMES) {
6983
const p = join(dir, bin);
70-
if (existsSync(p)) return p;
84+
if (existsSync(p)) candidates.push(p);
7185
}
7286
}
7387
const nvmDir = IS_WIN
@@ -78,7 +92,7 @@ function findSkillkitBin(): string | null {
7892
const binDir = IS_WIN ? join(nvmDir, d) : join(nvmDir, d, "bin");
7993
for (const bin of BIN_NAMES) {
8094
const p = join(binDir, bin);
81-
if (existsSync(p)) return p;
95+
if (existsSync(p)) candidates.push(p);
8296
}
8397
}
8498
} catch { /* empty */ }
@@ -88,11 +102,14 @@ function findSkillkitBin(): string | null {
88102
try {
89103
for (const d of readdirSync(join(miseDir, runtime))) {
90104
const p = join(miseDir, runtime, d, "bin", "skillkit");
91-
if (existsSync(p)) return p;
105+
if (existsSync(p)) candidates.push(p);
92106
}
93107
} catch { /* empty */ }
94108
}
95109
}
110+
for (const c of candidates) {
111+
if (isCrafterSkillkit(c)) return c;
112+
}
96113
return null;
97114
}
98115

0 commit comments

Comments
 (0)