|
11 | 11 |
|
12 | 12 | import type { AgentType, CustomPrompt, SkillSection } from '../agent' |
13 | 13 | import type { SkillInfo } from '../core/lockfile' |
14 | | -import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs' |
| 14 | +import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs' |
| 15 | +import { homedir } from 'node:os' |
15 | 16 | import * as p from '@clack/prompts' |
16 | 17 | import { join } from 'pathe' |
17 | 18 | import { agents, getModelLabel, optimizeDocs } from '../agent' |
@@ -57,8 +58,7 @@ export async function installCommand(opts: InstallOptions): Promise<void> { |
57 | 58 | const cwd = process.cwd() |
58 | 59 | const agent = agents[opts.agent] |
59 | 60 | const skillsDir = opts.global |
60 | | - // eslint-disable-next-line ts/no-require-imports |
61 | | - ? join(require('node:os').homedir(), '.skilld', 'skills') |
| 61 | + ? join(homedir(), '.skilld', 'skills') |
62 | 62 | : join(cwd, agent.skillsDir) |
63 | 63 |
|
64 | 64 | // Collect lockfiles from all agent skill dirs and merge |
@@ -186,8 +186,10 @@ export async function installCommand(opts: InstallOptions): Promise<void> { |
186 | 186 | unlinkSync(sectionsLink) |
187 | 187 | if (existsSync(cachedSections)) |
188 | 188 | symlinkSync(cachedSections, sectionsLink, 'junction') |
189 | | - if (regenerateBaseSkillMd(skillDir, pkgName, version, cwd, allSkillNames, info.source, info.packages)) |
190 | | - regenerated.push({ name, pkgName, version, skillDir, packages: info.packages }) |
| 189 | + if (!copyFromExistingAgent(skillDir, name, allSkillsDirs)) { |
| 190 | + if (regenerateBaseSkillMd(skillDir, pkgName, version, cwd, allSkillNames, info.source, info.packages)) |
| 191 | + regenerated.push({ name, pkgName, version, skillDir, packages: info.packages }) |
| 192 | + } |
191 | 193 | spin.stop(`Linked ${name}`) |
192 | 194 | continue |
193 | 195 | } |
@@ -316,8 +318,10 @@ export async function installCommand(opts: InstallOptions): Promise<void> { |
316 | 318 | })), { dbPath: getPackageDbPath(pkgName, version) }) |
317 | 319 | } |
318 | 320 |
|
319 | | - if (regenerateBaseSkillMd(skillDir, pkgName, version, cwd, allSkillNames, info.source, info.packages)) |
320 | | - regenerated.push({ name, pkgName, version, skillDir, packages: info.packages }) |
| 321 | + if (!copyFromExistingAgent(skillDir, name, allSkillsDirs)) { |
| 322 | + if (regenerateBaseSkillMd(skillDir, pkgName, version, cwd, allSkillNames, info.source, info.packages)) |
| 323 | + regenerated.push({ name, pkgName, version, skillDir, packages: info.packages }) |
| 324 | + } |
321 | 325 | spin.stop(`Downloaded and linked ${name}`) |
322 | 326 | } |
323 | 327 | else { |
@@ -348,6 +352,24 @@ export async function installCommand(opts: InstallOptions): Promise<void> { |
348 | 352 | p.outro('Install complete') |
349 | 353 | } |
350 | 354 |
|
| 355 | +/** Copy SKILL.md from another agent's skill dir if one exists */ |
| 356 | +function copyFromExistingAgent(skillDir: string, name: string, allSkillsDirs: string[]): boolean { |
| 357 | + const targetMd = join(skillDir, 'SKILL.md') |
| 358 | + if (existsSync(targetMd)) |
| 359 | + return false |
| 360 | + for (const dir of allSkillsDirs) { |
| 361 | + if (dir === skillDir) |
| 362 | + continue |
| 363 | + const candidateMd = join(dir, name, 'SKILL.md') |
| 364 | + if (existsSync(candidateMd) && !lstatSync(candidateMd).isSymbolicLink()) { |
| 365 | + mkdirSync(skillDir, { recursive: true }) |
| 366 | + copyFileSync(candidateMd, targetMd) |
| 367 | + return true |
| 368 | + } |
| 369 | + } |
| 370 | + return false |
| 371 | +} |
| 372 | + |
351 | 373 | /** Try to recover original package name from sanitized name + source */ |
352 | 374 | function unsanitizeName(sanitized: string, source?: string): string { |
353 | 375 | if (source?.includes('ungh://')) { |
|
0 commit comments