Skip to content

Commit 5437285

Browse files
committed
Merge remote-tracking branch 'origin/main' into refactor/skill-md-builder
# Conflicts: # src/commands/install.ts
2 parents 54ab54e + d7dfb1b commit 5437285

15 files changed

Lines changed: 260 additions & 203 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "skilld",
33
"type": "module",
44
"version": "1.7.0",
5-
"packageManager": "pnpm@10.33.1",
5+
"packageManager": "pnpm@10.33.2",
66
"description": "Generate AI agent skills from npm package documentation",
77
"author": {
88
"name": "Harlan Wilton",
@@ -86,6 +86,7 @@
8686
"sqlite-vec": "catalog:deps",
8787
"std-env": "catalog:",
8888
"tinyglobby": "catalog:deps",
89+
"typebox": "catalog:",
8990
"typescript": "catalog:",
9091
"unagent": "catalog:",
9192
"unist-util-visit": "catalog:deps"

pnpm-lock.yaml

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

pnpm-workspace.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ packages:
77
overrides:
88
global-agent: ^4.1.3
99
catalog:
10-
'@mariozechner/pi-ai': ^0.68.1
10+
'@mariozechner/pi-ai': ^0.70.6
1111
'@mdream/crawl': ^1.1.0
1212
'@types/semver': ^7.7.1
1313
bumpp: ^11.0.1
@@ -20,12 +20,13 @@ catalog:
2020
semver: ^7.7.4
2121
std-env: ^4.1.0
2222
tsx: ^4.21.0
23+
typebox: ^1.1.34
2324
typescript: 6.0.2
2425
unagent: ^0.0.8
2526
catalogs:
2627
deps:
2728
'@clack/prompts': ^1.2.0
28-
'@huggingface/transformers': ^4.1.0
29+
'@huggingface/transformers': ^4.2.0
2930
citty: ^0.2.2
3031
consola: ^3.4.2
3132
log-update: ^8.0.0
@@ -34,7 +35,7 @@ catalogs:
3435
mdast-util-to-string: ^4.0.0
3536
micromark-extension-frontmatter: ^2.0.0
3637
mlly: ^1.8.2
37-
oxc-parser: ^0.127.0
38+
oxc-parser: ^0.128.0
3839
p-limit: ^7.3.0
3940
sqlite-vec: ^0.1.9
4041
tinyglobby: ^0.2.16

src/agent/clis/pi-ai.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import { homedir } from 'node:os'
2121
import { resolve } from 'node:path'
2222
import { getEnvApiKey, getModel, getModels, getProviders, streamSimple } from '@mariozechner/pi-ai'
2323
import { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from '@mariozechner/pi-ai/oauth'
24-
import { Type } from '@sinclair/typebox'
2524
import { join } from 'pathe'
25+
import { Type } from 'typebox'
2626
import { sanitizeMarkdown } from '../../core/sanitize.ts'
2727

2828
export function isPiAiModel(model: string): boolean {

src/cache/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ export {
1515
getPkgKeyFiles,
1616
getShippedSkills,
1717
hasShippedDocs,
18+
inferDocsTypeFromCache,
1819
isCached,
20+
isReadmeOnlyCache,
1921
linkCachedDir,
2022
linkPkg,
2123
linkPkgNamed,

src/cache/storage.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ export function isCached(name: string, version: string): boolean {
3333
return existsSync(getCacheDir(name, version))
3434
}
3535

36+
/** Check if cache only has docs/README.md (pkg/ already has this) */
37+
export function isReadmeOnlyCache(cacheDir: string): boolean {
38+
const docsDir = join(cacheDir, 'docs')
39+
if (!existsSync(docsDir))
40+
return false
41+
const files = readdirSync(docsDir)
42+
return files.length === 1 && files[0] === 'README.md'
43+
}
44+
45+
export function inferDocsTypeFromCache(cacheDir: string, source?: string): 'llms.txt' | 'readme' | 'docs' {
46+
if (source?.includes('llms.txt') || existsSync(join(cacheDir, 'docs', 'llms.txt')))
47+
return 'llms.txt'
48+
return isReadmeOnlyCache(cacheDir) ? 'readme' : 'docs'
49+
}
50+
3651
/**
3752
* Ensure cache directories exist
3853
*/

src/commands/install.ts

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ import {
2727
getPkgKeyFiles,
2828
getRepoCacheDir,
2929
getShippedSkills,
30+
inferDocsTypeFromCache,
3031
isCached,
32+
isReadmeOnlyCache,
3133
linkPkgNamed,
3234
linkShippedSkill,
3335
listReferenceFiles,
@@ -38,7 +40,7 @@ import {
3840
import { promptForAgent, resolveAgent, sharedArgs } from '../cli-helpers.ts'
3941
import { defaultFeatures, readConfig } from '../core/config.ts'
4042
import { timedSpinner } from '../core/formatting.ts'
41-
import { mergeLocks, parsePackages, readLock, syncLockfilesToDirs, writeLock } from '../core/lockfile.ts'
43+
import { mergeLocks, parsePackageNames, parsePackages, readLock, syncLockfilesToDirs, writeLock } from '../core/lockfile.ts'
4244
import { readPackageJsonSafe } from '../core/package-json.ts'
4345
import { toStoragePackageName } from '../core/prefix.ts'
4446
import { sanitizeMarkdown } from '../core/sanitize.ts'
@@ -199,7 +201,7 @@ export async function installCommand(opts: InstallOptions): Promise<void> {
199201
for (const pkg of parsePackages(info.packages))
200202
linkPkgNamed(skillDir, pkg.name, cwd, pkg.version)
201203
// Only link external docs if package doesn't ship its own and has more than just README
202-
if (!pkgHasShippedDocs(pkgName, cwd, version) && !isReadmeOnly(globalCachePath)) {
204+
if (!pkgHasShippedDocs(pkgName, cwd, version) && !isReadmeOnlyCache(globalCachePath)) {
203205
const docsLink = join(referencesPath, 'docs')
204206
const cachedDocs = join(globalCachePath, 'docs')
205207
if (existsSync(docsLink))
@@ -368,7 +370,7 @@ export async function installCommand(opts: InstallOptions): Promise<void> {
368370
for (const pkg of parsePackages(info.packages))
369371
linkPkgNamed(skillDir, pkg.name, cwd, pkg.version)
370372
// Link fetched docs unless it's just a README (already in pkg/)
371-
if (!isReadmeOnly(globalCachePath)) {
373+
if (!isReadmeOnlyCache(globalCachePath)) {
372374
const docsLink = join(referencesPath, 'docs')
373375
const cachedDocsDir = join(globalCachePath, 'docs')
374376
if (existsSync(docsLink))
@@ -514,15 +516,6 @@ function linkPkgSymlink(referencesDir: string, name: string, cwd: string, versio
514516
symlinkSync(pkgPath, pkgLink, 'junction')
515517
}
516518

517-
/** Check if cache only has docs/README.md (pkg/ already has this) */
518-
function isReadmeOnly(cacheDir: string): boolean {
519-
const docsDir = join(cacheDir, 'docs')
520-
if (!existsSync(docsDir))
521-
return false
522-
const files = readdirSync(docsDir)
523-
return files.length === 1 && files[0] === 'README.md'
524-
}
525-
526519
/** Check if package ships its own docs folder */
527520
function pkgHasShippedDocs(name: string, cwd: string, version?: string): boolean {
528521
const pkgPath = resolvePkgDir(name, cwd, version)
@@ -587,16 +580,12 @@ async function enhanceRegenerated(
587580
}
588581
}
589582

590-
let docsType: 'llms.txt' | 'readme' | 'docs' = 'docs'
591-
if (existsSync(join(globalCachePath, 'docs', 'llms.txt')))
592-
docsType = 'llms.txt'
593-
else if (isReadmeOnly(globalCachePath))
594-
docsType = 'readme'
583+
const docsType = inferDocsTypeFromCache(globalCachePath)
595584

596585
// Derive dirName from the skill directory name
597586
const dirName = skillDir.split('/').pop()
598587

599-
const allPackages = parsePackages(packages).map(p => ({ name: p.name }))
588+
const allPackages = parsePackageNames(packages)
600589
writeGeneratedSkillMd(skillDir, {
601590
name: pkgName,
602591
version,
@@ -667,11 +656,7 @@ function regenerateBaseSkillMd(
667656

668657
// Infer docsType from source or cache
669658
const globalCachePath = getCacheDir(pkgName, version)
670-
let docsType: 'llms.txt' | 'readme' | 'docs' = 'docs'
671-
if (source?.includes('llms.txt') || existsSync(join(globalCachePath, 'docs', 'llms.txt')))
672-
docsType = 'llms.txt'
673-
else if (isReadmeOnly(globalCachePath))
674-
docsType = 'readme'
659+
const docsType = inferDocsTypeFromCache(globalCachePath, source)
675660

676661
// Check cache dirs for issues/discussions/releases (only if feature enabled)
677662
const feat = readConfig().features ?? defaultFeatures
@@ -686,7 +671,7 @@ function regenerateBaseSkillMd(
686671
const dirName = skillDir.split('/').pop()
687672

688673
// Build multi-package list from lockfile packages field
689-
const allPackages = parsePackages(packages).map(p => ({ name: p.name }))
674+
const allPackages = parsePackageNames(packages)
690675

691676
mkdirSync(skillDir, { recursive: true })
692677
writeGeneratedSkillMd(skillDir, {

src/commands/sync-parallel.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ import {
3333
} from '../cache/index.ts'
3434
import { defaultFeatures, readConfig, registerProject } from '../core/config.ts'
3535
import { formatDuration } from '../core/formatting.ts'
36-
import { parsePackages, readLock, writeLock } from '../core/lockfile.ts'
36+
import { parsePackageNames, parsePackages, readLock, writeLock } from '../core/lockfile.ts'
3737
import { parseFrontmatter } from '../core/markdown.ts'
3838
import { getSharedSkillsDir, semverDiff, SHARED_SKILLS_DIR } from '../core/shared.ts'
3939
import { shutdownWorker } from '../retriv/pool.ts'
4040
import {
4141
fetchPkgDist,
42+
parseGitHubRepoSlug,
4243
parsePackageSpec,
4344
readLocalDependencies,
4445
resolvePackageDocsWithAttempts,
@@ -555,7 +556,7 @@ async function syncBaseSkill(
555556
const pkgFiles = getPkgKeyFiles(packageName, cwd, version)
556557

557558
// Write base SKILL.md
558-
const repoSlug = resolved.repoUrl?.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?(?:[/#]|$)/)?.[1]
559+
const repoSlug = parseGitHubRepoSlug(resolved.repoUrl)
559560

560561
// Create named symlink for this package
561562
linkPkgNamed(skillDir, packageName, cwd, version)
@@ -571,7 +572,7 @@ async function syncBaseSkill(
571572

572573
// Read back merged packages from lockfile
573574
const updatedLock = readLock(baseDir)?.skills[skillDirName]
574-
const allPackages = parsePackages(updatedLock?.packages).map(p => ({ name: p.name }))
575+
const allPackages = parsePackageNames(updatedLock?.packages)
575576

576577
const skillMd = writeGeneratedSkillMd(skillDir, {
577578
name: packageName,

src/commands/sync.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
import { getInstalledGenerators, introLine, isInteractive, promptForAgent, resolveAgent, sharedArgs, suggestPrepareHook } from '../cli-helpers.ts'
3737
import { defaultFeatures, hasCompletedWizard, readConfig, registerProject } from '../core/config.ts'
3838
import { timedSpinner } from '../core/formatting.ts'
39-
import { parsePackages, readLock, removeLockEntry, writeLock } from '../core/lockfile.ts'
39+
import { parsePackageNames, parsePackages, readLock, removeLockEntry, writeLock } from '../core/lockfile.ts'
4040
import { parseFrontmatter } from '../core/markdown.ts'
4141
import { parseSkillInput, resolveSkillName, toStoragePackageName } from '../core/prefix.ts'
4242
import { getSharedSkillsDir, SHARED_SKILLS_DIR } from '../core/shared.ts'
@@ -45,6 +45,7 @@ import { shutdownWorker } from '../retriv/pool.ts'
4545
import {
4646
fetchPkgDist,
4747
isPrerelease,
48+
parseGitHubRepoSlug,
4849
parsePackageSpec,
4950
readLocalDependencies,
5051
resolveCrateDocsWithAttempts,
@@ -460,7 +461,7 @@ async function syncSinglePackage(packageSpec: string, config: SyncConfig): Promi
460461
linkPkgNamed(skillDir, storagePackageName, cwd, version)
461462

462463
// Merge into lockfile
463-
const repoSlug = resolved.repoUrl?.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?(?:[/#]|$)/)?.[1]
464+
const repoSlug = parseGitHubRepoSlug(resolved.repoUrl)
464465
writeLock(baseDir, skillDirName, {
465466
packageName: identityPackageName,
466467
version,
@@ -472,7 +473,7 @@ async function syncSinglePackage(packageSpec: string, config: SyncConfig): Promi
472473

473474
// Regenerate SKILL.md with all packages listed
474475
const updatedLock = readLock(baseDir)?.skills[skillDirName]
475-
const allPackages = parsePackages(updatedLock?.packages).map(p => ({ name: p.name }))
476+
const allPackages = parsePackageNames(updatedLock?.packages)
476477
const relatedSkills = await findRelatedSkills(storagePackageName, baseDir)
477478
const existingStorageName = toStoragePackageName(existingLock.packageName!)
478479
const pkgFiles = getPkgKeyFiles(existingStorageName, cwd, existingLock.version)
@@ -565,7 +566,7 @@ async function syncSinglePackage(packageSpec: string, config: SyncConfig): Promi
565566
const pkgFiles = getPkgKeyFiles(storagePackageName, cwd, version)
566567

567568
// Write base SKILL.md (no LLM needed)
568-
const repoSlug = resolved.repoUrl?.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?(?:[/#]|$)/)?.[1]
569+
const repoSlug = parseGitHubRepoSlug(resolved.repoUrl)
569570

570571
// Also create named symlink for this package (skip in eject mode)
571572
if (!config.eject)
@@ -600,7 +601,7 @@ async function syncSinglePackage(packageSpec: string, config: SyncConfig): Promi
600601

601602
// Read back merged packages from lockfile for SKILL.md generation
602603
const updatedLock = config.eject ? undefined : readLock(baseDir)?.skills[skillDirName]
603-
const allPackages = parsePackages(updatedLock?.packages).map(p => ({ name: p.name }))
604+
const allPackages = parsePackageNames(updatedLock?.packages)
604605

605606
const isEject = !!config.eject
606607
const baseSkillMd = writeGeneratedSkillMd(skillDir, {
@@ -1248,7 +1249,7 @@ export async function exportPortablePrompts(packageSpec: string, opts: {
12481249
})
12491250

12501251
// Write lockfile so skilld list/update/assemble can discover this skill
1251-
const repoSlug = resolved.repoUrl?.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?(?:[/#]|$)/)?.[1]
1252+
const repoSlug = parseGitHubRepoSlug(resolved.repoUrl)
12521253
writeLock(baseDir, skillDirName, {
12531254
packageName,
12541255
version,

src/core/lockfile.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export function parsePackages(packages?: string): Array<{ name: string, version:
3232
}).filter(p => p.name)
3333
}
3434

35+
export function parsePackageNames(packages?: string): Array<{ name: string }> {
36+
return parsePackages(packages).map(({ name }) => ({ name }))
37+
}
38+
3539
export function serializePackages(pkgs: Array<{ name: string, version: string }>): string {
3640
return pkgs.map(p => `${p.name}@${p.version}`).join(', ')
3741
}

0 commit comments

Comments
 (0)