@lythos/skill-deck 0.1.2 → 0.1.3
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/package.json +1 -1
- package/src/cli.ts +4 -2
- package/src/link.ts +34 -10
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -3,13 +3,15 @@ import { linkDeck } from './link.js'
|
|
|
3
3
|
|
|
4
4
|
const command = process.argv[2]
|
|
5
5
|
const deckFlagIdx = process.argv.indexOf('--deck')
|
|
6
|
+
const workdirFlagIdx = process.argv.indexOf('--workdir')
|
|
6
7
|
const deckPath = deckFlagIdx >= 0 ? process.argv[deckFlagIdx + 1] : undefined
|
|
8
|
+
const workdir = workdirFlagIdx >= 0 ? process.argv[workdirFlagIdx + 1] : undefined
|
|
7
9
|
|
|
8
10
|
switch (command) {
|
|
9
11
|
case 'link':
|
|
10
|
-
linkDeck(deckPath)
|
|
12
|
+
linkDeck(deckPath, workdir)
|
|
11
13
|
break
|
|
12
14
|
default:
|
|
13
|
-
console.error('Usage: lythoskill-deck link [--deck <path>]')
|
|
15
|
+
console.error('Usage: lythoskill-deck link [--deck <path>] [--workdir <dir>]')
|
|
14
16
|
process.exit(1)
|
|
15
17
|
}
|
package/src/link.ts
CHANGED
|
@@ -83,6 +83,26 @@ function extractArrayField(fm: string, field: string): string[] {
|
|
|
83
83
|
// ── 冷池查找 ────────────────────────────────────────────────
|
|
84
84
|
|
|
85
85
|
function findSource(name: string, coldPool: string, projectDir: string): string | null {
|
|
86
|
+
// 0. Fully-qualified path: host.tld/owner/repo/skill
|
|
87
|
+
// → cold_pool/host.tld/owner/repo/skills/skill
|
|
88
|
+
// Also handles host.tld/owner/repo (standalone skill without skills/ subdir)
|
|
89
|
+
const fqMatch = name.match(/^[a-z0-9-]+\.[a-z0-9-]+\//);
|
|
90
|
+
if (fqMatch) {
|
|
91
|
+
const parts = name.split("/");
|
|
92
|
+
const host = parts[0]; // github.com
|
|
93
|
+
const owner = parts[1]; // lythos-labs
|
|
94
|
+
const repo = parts[2]; // lythoskill
|
|
95
|
+
const skill = parts.slice(3).join("/"); // lythoskill-deck
|
|
96
|
+
|
|
97
|
+
if (skill) {
|
|
98
|
+
const fqPath = join(coldPool, host, owner, repo, "skills", skill);
|
|
99
|
+
if (existsSync(join(fqPath, "SKILL.md"))) return fqPath;
|
|
100
|
+
}
|
|
101
|
+
// fallback: standalone skill at repo root
|
|
102
|
+
const directPath = join(coldPool, host, owner, repo);
|
|
103
|
+
if (existsSync(join(directPath, "SKILL.md"))) return directPath;
|
|
104
|
+
}
|
|
105
|
+
|
|
86
106
|
// 1. 直接路径
|
|
87
107
|
const direct = resolve(coldPool, name);
|
|
88
108
|
if (existsSync(join(direct, "SKILL.md"))) return direct;
|
|
@@ -94,10 +114,17 @@ function findSource(name: string, coldPool: string, projectDir: string): string
|
|
|
94
114
|
if (existsSync(join(mono, "SKILL.md"))) return mono;
|
|
95
115
|
}
|
|
96
116
|
|
|
97
|
-
// 3.
|
|
117
|
+
// 3. 项目本地: <project>/skills/<name>(build 输出目录,优先级高于扁平扫描)
|
|
118
|
+
const local = resolve(projectDir, "skills", name);
|
|
119
|
+
if (existsSync(join(local, "SKILL.md"))) return local;
|
|
120
|
+
|
|
121
|
+
// 4. 扁平扫描: cold_pool/<any-repo>/<name> 或 <any-repo>/skills/<name>
|
|
122
|
+
// 跳过 agent/working-set 相关目录,避免把 symlink 误判为有效源
|
|
123
|
+
const skipDirs = new Set(['.claude', '.kimi', '.git', 'node_modules', '.lythos-curator']);
|
|
98
124
|
try {
|
|
99
125
|
for (const entry of readdirSync(coldPool, { withFileTypes: true })) {
|
|
100
126
|
if (!entry.isDirectory()) continue;
|
|
127
|
+
if (skipDirs.has(entry.name)) continue;
|
|
101
128
|
const base = join(coldPool, entry.name);
|
|
102
129
|
for (const sub of [join(base, name), join(base, "skills", name)]) {
|
|
103
130
|
if (existsSync(join(sub, "SKILL.md"))) return sub;
|
|
@@ -105,16 +132,12 @@ function findSource(name: string, coldPool: string, projectDir: string): string
|
|
|
105
132
|
}
|
|
106
133
|
} catch {}
|
|
107
134
|
|
|
108
|
-
// 4. 项目本地: <project>/skills/<name>
|
|
109
|
-
const local = resolve(projectDir, "skills", name);
|
|
110
|
-
if (existsSync(join(local, "SKILL.md"))) return local;
|
|
111
|
-
|
|
112
135
|
return null;
|
|
113
136
|
}
|
|
114
137
|
|
|
115
138
|
// ── 主流程 ──────────────────────────────────────────────────
|
|
116
139
|
|
|
117
|
-
export function linkDeck(cliDeckPath?: string): void {
|
|
140
|
+
export function linkDeck(cliDeckPath?: string, cliWorkdir?: string): void {
|
|
118
141
|
const cliDeck = cliDeckPath || process.argv.find((_, i, a) => a[i - 1] === "--deck");
|
|
119
142
|
const DECK_PATH = cliDeck
|
|
120
143
|
? resolve(cliDeck)
|
|
@@ -133,7 +156,7 @@ if (!existsSync(DECK_PATH)) {
|
|
|
133
156
|
process.exit(1);
|
|
134
157
|
}
|
|
135
158
|
|
|
136
|
-
const PROJECT_DIR = dirname(DECK_PATH);
|
|
159
|
+
const PROJECT_DIR = cliWorkdir ? resolve(cliWorkdir) : dirname(DECK_PATH);
|
|
137
160
|
const deckRaw = readFileSync(DECK_PATH, "utf-8");
|
|
138
161
|
const deckHash = hashContent(deckRaw);
|
|
139
162
|
const deck = parseToml(deckRaw) as any;
|
|
@@ -213,10 +236,11 @@ const linkedSkills: LinkedSkill[] = [];
|
|
|
213
236
|
for (const item of declared) {
|
|
214
237
|
const dest = join(WORKING_SET, item.name);
|
|
215
238
|
|
|
216
|
-
//
|
|
217
|
-
|
|
239
|
+
// 幂等:已存在则删除重建(lstat 不跟随 symlink,能处理断链/自引用 symlink)
|
|
240
|
+
try {
|
|
241
|
+
lstatSync(dest);
|
|
218
242
|
rmSync(dest, { recursive: true, force: true });
|
|
219
|
-
}
|
|
243
|
+
} catch {}
|
|
220
244
|
|
|
221
245
|
try {
|
|
222
246
|
mkdirSync(dirname(dest), { recursive: true });
|