skilld 1.7.1 → 1.7.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/dist/_chunks/agent.mjs.map +1 -1
- package/dist/_chunks/assemble.mjs.map +1 -1
- package/dist/_chunks/author-group.mjs.map +1 -1
- package/dist/_chunks/author.mjs.map +1 -1
- package/dist/_chunks/cache.mjs.map +1 -1
- package/dist/_chunks/cache2.mjs.map +1 -1
- package/dist/_chunks/cli-helpers.mjs.map +1 -1
- package/dist/_chunks/config.mjs.map +1 -1
- package/dist/_chunks/detect.mjs.map +1 -1
- package/dist/_chunks/embedding-cache2.mjs.map +1 -1
- package/dist/_chunks/index3.d.mts.map +1 -1
- package/dist/_chunks/install.mjs.map +1 -1
- package/dist/_chunks/list.mjs.map +1 -1
- package/dist/_chunks/lockfile.mjs.map +1 -1
- package/dist/_chunks/markdown.mjs.map +1 -1
- package/dist/_chunks/package-json.mjs.map +1 -1
- package/dist/_chunks/pool2.mjs +6 -1
- package/dist/_chunks/pool2.mjs.map +1 -1
- package/dist/_chunks/prefix.mjs +5 -5
- package/dist/_chunks/prefix.mjs.map +1 -1
- package/dist/_chunks/prepare.mjs.map +1 -1
- package/dist/_chunks/prepare2.mjs.map +1 -1
- package/dist/_chunks/prompts.mjs.map +1 -1
- package/dist/_chunks/retriv.mjs.map +1 -1
- package/dist/_chunks/sanitize.mjs.map +1 -1
- package/dist/_chunks/search-helpers.mjs.map +1 -1
- package/dist/_chunks/search-interactive.mjs.map +1 -1
- package/dist/_chunks/search.mjs.map +1 -1
- package/dist/_chunks/setup.mjs.map +1 -1
- package/dist/_chunks/shared.mjs.map +1 -1
- package/dist/_chunks/skill.mjs.map +1 -1
- package/dist/_chunks/skills.mjs.map +1 -1
- package/dist/_chunks/sources.mjs +21 -9
- package/dist/_chunks/sources.mjs.map +1 -1
- package/dist/_chunks/sync-registry.mjs.map +1 -1
- package/dist/_chunks/sync-shared2.mjs.map +1 -1
- package/dist/_chunks/sync.mjs.map +1 -1
- package/dist/_chunks/uninstall.mjs.map +1 -1
- package/dist/_chunks/upload.mjs.map +1 -1
- package/dist/_chunks/validate.mjs.map +1 -1
- package/dist/_chunks/version.mjs.map +1 -1
- package/dist/_chunks/wizard.mjs.map +1 -1
- package/dist/_chunks/yaml.mjs.map +1 -1
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/prepare.mjs.map +1 -1
- package/dist/retriv/worker.d.mts +1 -0
- package/dist/retriv/worker.d.mts.map +1 -1
- package/dist/retriv/worker.mjs +2 -1
- package/dist/retriv/worker.mjs.map +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"author.mjs","names":[],"sources":["../../src/commands/author.ts"],"sourcesContent":["import type { OptimizeModel } from '../agent/index.ts'\nimport type { FeaturesConfig } from '../core/config.ts'\nimport type { LlmConfig } from './sync-shared.ts'\nimport { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs'\nimport * as p from '@clack/prompts'\nimport { defineCommand } from 'citty'\nimport { join, relative, resolve } from 'pathe'\nimport {\n computeSkillDirName,\n getModelLabel,\n writeGeneratedSkillMd,\n} from '../agent/index.ts'\nimport {\n ensureCacheDir,\n getCacheDir,\n writeToCache,\n} from '../cache/index.ts'\nimport { guard } from '../cli-helpers.ts'\nimport { defaultFeatures, readConfig } from '../core/config.ts'\nimport { timedSpinner } from '../core/formatting.ts'\nimport { appendToJsonArray, patchPackageJson, readPackageJsonSafe } from '../core/package-json.ts'\nimport { sanitizeMarkdown } from '../core/sanitize.ts'\nimport {\n fetchGitHubDiscussions,\n fetchGitHubIssues,\n formatDiscussionAsMarkdown,\n formatIssueAsMarkdown,\n generateDiscussionIndex,\n generateIssueIndex,\n isGhAvailable,\n parseGitHubUrl,\n readLocalPackageInfo,\n} from '../sources/index.ts'\nimport {\n detectChangelog,\n ejectReferences,\n enhanceSkillWithLLM,\n forceClearCache,\n linkAllReferences,\n selectLlmConfig,\n writePromptFiles,\n} from './sync-shared.ts'\n\nconst QUOTE_PREFIX_RE = /^['\"]/\nconst QUOTE_SUFFIX_RE = /['\"]$/\n\n// ── Monorepo detection ──\n\nexport interface MonorepoPackage {\n name: string\n version: string\n description?: string\n repoUrl?: string\n dir: string\n}\n\nexport function detectMonorepoPackages(cwd: string): MonorepoPackage[] | null {\n const rootResult = readPackageJsonSafe(join(cwd, 'package.json'))\n if (!rootResult)\n return null\n\n const pkg = rootResult.parsed as Record<string, any>\n\n // Must be private (monorepo root) with workspaces or pnpm-workspace.yaml\n if (!pkg.private)\n return null\n\n let patterns: string[] = []\n\n if (Array.isArray(pkg.workspaces)) {\n patterns = pkg.workspaces\n }\n else if (pkg.workspaces?.packages) {\n patterns = pkg.workspaces.packages\n }\n\n // Check pnpm-workspace.yaml\n if (patterns.length === 0) {\n const pnpmWs = join(cwd, 'pnpm-workspace.yaml')\n if (existsSync(pnpmWs)) {\n const lines = readFileSync(pnpmWs, 'utf-8').split('\\n')\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('-'))\n continue\n const value = trimmed.slice(1).trim().replace(QUOTE_PREFIX_RE, '').replace(QUOTE_SUFFIX_RE, '')\n if (value)\n patterns.push(value)\n }\n }\n }\n\n if (patterns.length === 0)\n return null\n\n const packages: MonorepoPackage[] = []\n\n for (const pattern of patterns) {\n // Expand simple glob: \"packages/*\" → scan packages/*/package.json\n const base = pattern.replace(/\\/?\\*+$/, '')\n const scanDir = resolve(cwd, base)\n if (!existsSync(scanDir))\n continue\n\n const directResult = readPackageJsonSafe(join(scanDir, 'package.json'))\n if (directResult) {\n const directPkg = directResult.parsed as Record<string, any>\n if (!directPkg.private && directPkg.name) {\n const repoUrl = typeof directPkg.repository === 'string'\n ? directPkg.repository\n : directPkg.repository?.url?.replace(/^git\\+/, '').replace(/\\.git$/, '')\n\n packages.push({\n name: directPkg.name,\n version: directPkg.version || '0.0.0',\n description: directPkg.description,\n repoUrl,\n dir: scanDir,\n })\n continue\n }\n }\n\n for (const entry of readdirSync(scanDir, { withFileTypes: true })) {\n if (!entry.isDirectory())\n continue\n const childResult = readPackageJsonSafe(join(scanDir, entry.name, 'package.json'))\n if (!childResult)\n continue\n\n const childPkg = childResult.parsed as Record<string, any>\n if (childPkg.private)\n continue\n if (!childPkg.name)\n continue\n\n const repoUrl = typeof childPkg.repository === 'string'\n ? childPkg.repository\n : childPkg.repository?.url?.replace(/^git\\+/, '').replace(/\\.git$/, '')\n\n packages.push({\n name: childPkg.name,\n version: childPkg.version || '0.0.0',\n description: childPkg.description,\n repoUrl,\n dir: join(scanDir, entry.name),\n })\n }\n }\n\n return packages.length > 0 ? packages : null\n}\n\n// ── Docs resolution ──\n\nfunction walkMarkdownFiles(dir: string, base = ''): Array<{ path: string, content: string }> {\n const results: Array<{ path: string, content: string }> = []\n if (!existsSync(dir))\n return results\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const rel = base ? `${base}/${entry.name}` : entry.name\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n results.push(...walkMarkdownFiles(full, rel))\n }\n else if (/\\.mdx?$/.test(entry.name)) {\n results.push({ path: rel, content: readFileSync(full, 'utf-8') })\n }\n }\n return results\n}\n\n/**\n * Resolve docs from local filesystem. Cascade:\n * 1. Package-level docs/ directory\n * 2. Monorepo-root docs/ directory (if monorepoRoot provided)\n * 3. Monorepo-root docs/content/ (Nuxt Content convention)\n * 4. llms.txt in package dir\n * 5. README.md in package dir\n */\nfunction resolveLocalDocs(\n packageDir: string,\n packageName: string,\n version: string,\n monorepoRoot?: string,\n): { docsType: 'docs' | 'llms.txt' | 'readme', docSource: string } {\n const cachedDocs: Array<{ path: string, content: string }> = []\n\n const cacheChangelog = () => cacheLocalChangelog(packageDir, packageName, version, monorepoRoot)\n\n // 1. Package-level docs/\n const docsDir = join(packageDir, 'docs')\n if (existsSync(docsDir)) {\n const mdFiles = walkMarkdownFiles(docsDir)\n if (mdFiles.length > 0) {\n for (const f of mdFiles)\n cachedDocs.push({ path: `docs/${f.path}`, content: sanitizeMarkdown(f.content) })\n writeToCache(packageName, version, cachedDocs)\n cacheChangelog()\n return { docsType: 'docs', docSource: `local docs/ (${mdFiles.length} files)` }\n }\n }\n\n // 2. Monorepo-root docs/ or docs/content/\n if (monorepoRoot) {\n for (const candidate of ['docs/content', 'docs']) {\n const rootDocsDir = join(monorepoRoot, candidate)\n if (existsSync(rootDocsDir)) {\n const mdFiles = walkMarkdownFiles(rootDocsDir)\n if (mdFiles.length > 0) {\n for (const f of mdFiles)\n cachedDocs.push({ path: `docs/${f.path}`, content: sanitizeMarkdown(f.content) })\n writeToCache(packageName, version, cachedDocs)\n cacheChangelog()\n return { docsType: 'docs', docSource: `monorepo ${candidate}/ (${mdFiles.length} files)` }\n }\n }\n }\n }\n\n // 3. llms.txt (package dir, then monorepo root)\n for (const dir of [packageDir, monorepoRoot].filter(Boolean) as string[]) {\n const llmsPath = join(dir, 'llms.txt')\n if (existsSync(llmsPath)) {\n cachedDocs.push({ path: 'llms.txt', content: sanitizeMarkdown(readFileSync(llmsPath, 'utf-8')) })\n writeToCache(packageName, version, cachedDocs)\n cacheChangelog()\n const source = dir === packageDir ? 'local llms.txt' : 'monorepo llms.txt'\n return { docsType: 'llms.txt', docSource: source }\n }\n }\n\n // 4. README.md (package dir, then monorepo root)\n for (const dir of [packageDir, monorepoRoot].filter(Boolean) as string[]) {\n const readmeFile = readdirSync(dir).find(f => /^readme\\.md$/i.test(f))\n if (readmeFile) {\n cachedDocs.push({ path: 'docs/README.md', content: sanitizeMarkdown(readFileSync(join(dir, readmeFile), 'utf-8')) })\n writeToCache(packageName, version, cachedDocs)\n cacheChangelog()\n const source = dir === packageDir ? 'local README.md' : 'monorepo README.md'\n return { docsType: 'readme', docSource: source }\n }\n }\n\n cacheChangelog()\n return { docsType: 'readme', docSource: 'none' }\n}\n\nfunction cacheLocalChangelog(dir: string, packageName: string, version: string, monorepoRoot?: string): void {\n const candidates = ['CHANGELOG.md', 'changelog.md']\n const changelogFile = candidates.find(f => existsSync(join(dir, f)))\n || (monorepoRoot ? candidates.find(f => existsSync(join(monorepoRoot, f))) : undefined)\n const changelogDir = changelogFile && existsSync(join(dir, changelogFile)) ? dir : monorepoRoot\n if (changelogFile && changelogDir) {\n writeToCache(packageName, version, [{\n path: `releases/${changelogFile}`,\n content: sanitizeMarkdown(readFileSync(join(changelogDir, changelogFile), 'utf-8')),\n }])\n }\n}\n\n// ── Remote supplements ──\n\nasync function fetchRemoteSupplements(opts: {\n packageName: string\n version: string\n repoUrl?: string\n features: FeaturesConfig\n onProgress: (msg: string) => void\n}): Promise<{ hasIssues: boolean, hasDiscussions: boolean }> {\n const { packageName, version, repoUrl, features, onProgress } = opts\n\n if (!repoUrl || !isGhAvailable())\n return { hasIssues: false, hasDiscussions: false }\n\n const gh = parseGitHubUrl(repoUrl)\n if (!gh)\n return { hasIssues: false, hasDiscussions: false }\n\n const cacheDir = getCacheDir(packageName, version)\n\n let hasIssues = false\n const issuesDir = join(cacheDir, 'issues')\n if (features.issues && !existsSync(issuesDir)) {\n onProgress('Fetching issues via GitHub API')\n const issues = await fetchGitHubIssues(gh.owner, gh.repo, 30).catch(() => [])\n if (issues.length > 0) {\n onProgress(`Caching ${issues.length} issues`)\n writeToCache(packageName, version, issues.map(issue => ({\n path: `issues/issue-${issue.number}.md`,\n content: formatIssueAsMarkdown(issue),\n })))\n writeToCache(packageName, version, [{\n path: 'issues/_INDEX.md',\n content: generateIssueIndex(issues),\n }])\n hasIssues = true\n }\n }\n else {\n hasIssues = features.issues && existsSync(issuesDir)\n }\n\n let hasDiscussions = false\n const discussionsDir = join(cacheDir, 'discussions')\n if (features.discussions && !existsSync(discussionsDir)) {\n onProgress('Fetching discussions via GitHub API')\n const discussions = await fetchGitHubDiscussions(gh.owner, gh.repo, 20).catch(() => [])\n if (discussions.length > 0) {\n onProgress(`Caching ${discussions.length} discussions`)\n writeToCache(packageName, version, discussions.map(d => ({\n path: `discussions/discussion-${d.number}.md`,\n content: formatDiscussionAsMarkdown(d),\n })))\n writeToCache(packageName, version, [{\n path: 'discussions/_INDEX.md',\n content: generateDiscussionIndex(discussions),\n }])\n hasDiscussions = true\n }\n }\n else {\n hasDiscussions = features.discussions && existsSync(discussionsDir)\n }\n\n return { hasIssues, hasDiscussions }\n}\n\n// ── package.json patching ──\n\nexport function patchPackageJsonFiles(packageDir: string): void {\n const pkgPath = join(packageDir, 'package.json')\n if (!existsSync(pkgPath))\n return\n\n const wrote = patchPackageJson(pkgPath, (raw, pkg) => {\n if (!Array.isArray(pkg.files)) {\n p.log.warn('No `files` array in package.json. Add `\"skills\"` to your files array manually.')\n return null\n }\n\n if ((pkg.files as string[]).some((f: string) => f === 'skills' || f === 'skills/' || f === 'skills/**'))\n return null\n\n return appendToJsonArray(raw, ['files'], 'skills')\n })\n\n if (wrote)\n p.log.success('Added `\"skills\"` to package.json files array')\n}\n\n// ── Core author flow for a single package ──\n\nasync function authorSinglePackage(opts: {\n packageDir: string\n packageName: string\n version: string\n description?: string\n repoUrl?: string\n monorepoRoot?: string\n out?: string\n llmConfig?: LlmConfig | null\n force?: boolean\n debug?: boolean\n}): Promise<string | null> {\n const { packageDir, packageName, version } = opts\n const spin = timedSpinner()\n\n const sanitizedName = computeSkillDirName(packageName)\n const outDir = opts.out ? resolve(packageDir, opts.out) : join(packageDir, 'skills', sanitizedName)\n\n // Validate --out doesn't point at the package root or a parent\n if (opts.out) {\n const rel = relative(packageDir, outDir)\n if (!rel || rel === '.' || rel.startsWith('..')) {\n p.log.error('--out must point to a child directory, not the package root or a parent')\n return null\n }\n }\n\n if (existsSync(outDir))\n rmSync(outDir, { recursive: true, force: true })\n mkdirSync(outDir, { recursive: true })\n\n if (opts.force) {\n forceClearCache(packageName, version)\n }\n\n ensureCacheDir()\n const features = readConfig().features ?? defaultFeatures\n\n // Resolve local docs\n spin.start('Resolving local docs')\n const { docsType, docSource } = resolveLocalDocs(packageDir, packageName, version, opts.monorepoRoot)\n spin.stop(`Resolved docs: ${docSource}`)\n\n // Fetch remote supplements (issues/discussions)\n const supSpin = timedSpinner()\n supSpin.start('Checking remote supplements')\n const { hasIssues, hasDiscussions } = await fetchRemoteSupplements({\n packageName,\n version,\n repoUrl: opts.repoUrl,\n features,\n onProgress: msg => supSpin.message(msg),\n })\n const supParts: string[] = []\n if (hasIssues)\n supParts.push('issues')\n if (hasDiscussions)\n supParts.push('discussions')\n supSpin.stop(supParts.length > 0 ? `Fetched ${supParts.join(', ')}` : 'No remote supplements')\n\n // Create temporary .skilld/ symlinks (LLM needs these to read docs)\n linkAllReferences(outDir, packageName, packageDir, version, docsType, undefined, features)\n\n // Detect changelog + releases\n const cacheDir = getCacheDir(packageName, version)\n const hasChangelog = detectChangelog(packageDir, cacheDir)\n const hasReleases = existsSync(join(cacheDir, 'releases'))\n\n // Generate base SKILL.md\n writeGeneratedSkillMd(outDir, {\n name: packageName,\n version,\n description: opts.description,\n relatedSkills: [],\n hasIssues,\n hasDiscussions,\n hasReleases,\n hasChangelog,\n docsType,\n hasShippedDocs: false,\n pkgFiles: [],\n dirName: sanitizedName,\n repoUrl: opts.repoUrl,\n features,\n eject: true,\n })\n p.log.success(`Created base skill: ${relative(packageDir, outDir)}`)\n\n // LLM enhancement (config resolved by caller)\n const skilldDir = join(outDir, '.skilld')\n try {\n const llmConfig = opts.llmConfig\n if (llmConfig?.promptOnly) {\n writePromptFiles({\n packageName,\n skillDir: outDir,\n version,\n hasIssues,\n hasDiscussions,\n hasReleases,\n hasChangelog,\n docsType,\n hasShippedDocs: false,\n pkgFiles: [],\n sections: llmConfig.sections,\n customPrompt: llmConfig.customPrompt,\n features,\n })\n }\n else if (llmConfig) {\n p.log.step(getModelLabel(llmConfig.model))\n await enhanceSkillWithLLM({\n packageName,\n version,\n skillDir: outDir,\n dirName: sanitizedName,\n model: llmConfig.model,\n resolved: { repoUrl: opts.repoUrl },\n relatedSkills: [],\n hasIssues,\n hasDiscussions,\n hasReleases,\n hasChangelog,\n docsType,\n hasShippedDocs: false,\n pkgFiles: [],\n force: opts.force,\n debug: opts.debug,\n sections: llmConfig.sections,\n customPrompt: llmConfig.customPrompt,\n features,\n eject: true,\n })\n }\n\n ejectReferences(outDir, packageName, packageDir, version, docsType, features)\n }\n finally {\n // Always clean up .skilld/ symlinks, even if LLM enhancement fails\n if (existsSync(skilldDir))\n rmSync(skilldDir, { recursive: true, force: true })\n }\n\n // Only patch package.json when output is under skills/\n const relOut = relative(packageDir, outDir)\n if (relOut === 'skills' || relOut.startsWith('skills/'))\n patchPackageJsonFiles(packageDir)\n else if (opts.out)\n p.log.info('Output is outside skills/, skipping package.json patch. Add the path to \"files\" manually if publishing.')\n\n return outDir\n}\n\n// ── Main command ──\n\nasync function resolveLlmConfig(model?: OptimizeModel, yes?: boolean): Promise<LlmConfig | null | undefined> {\n const globalConfig = readConfig()\n if (globalConfig.skipLlm || (yes && !model))\n return undefined\n return selectLlmConfig(model, 'Generate skill sections')\n}\n\nasync function authorCommand(opts: {\n out?: string\n model?: OptimizeModel\n yes?: boolean\n force?: boolean\n debug?: boolean\n}): Promise<void> {\n const cwd = process.cwd()\n\n // Check for monorepo\n const monoPackages = detectMonorepoPackages(cwd)\n\n if (monoPackages && monoPackages.length > 0) {\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m author \\x1B[90m(monorepo: ${monoPackages.length} packages)\\x1B[0m`)\n\n if (opts.out) {\n p.log.error('--out is not supported in monorepo mode (each package gets its own skills/ directory)')\n return\n }\n\n const selected = guard(await p.multiselect({\n message: 'Which packages should ship skills?',\n options: monoPackages.map(pkg => ({\n label: pkg.name,\n value: pkg,\n hint: pkg.description,\n })),\n }))\n\n if (selected.length === 0)\n return\n\n // Resolve LLM config once for all packages\n const llmConfig = await resolveLlmConfig(opts.model, opts.yes)\n if (llmConfig === null) {\n p.cancel('Cancelled')\n return\n }\n\n // Resolve monorepo-level repoUrl for packages that lack their own\n const rootPkgResult = readPackageJsonSafe(join(cwd, 'package.json'))\n const rootPkg = rootPkgResult?.parsed as Record<string, any> | undefined\n const rootRepoUrl = typeof rootPkg?.repository === 'string'\n ? rootPkg.repository\n : rootPkg?.repository?.url?.replace(/^git\\+/, '').replace(/\\.git$/, '')\n\n const results: Array<{ name: string, outDir: string }> = []\n\n for (const pkg of selected) {\n p.log.step(`\\x1B[36m${pkg.name}\\x1B[0m@${pkg.version}`)\n const outDir = await authorSinglePackage({\n packageDir: pkg.dir,\n packageName: pkg.name,\n version: pkg.version,\n description: pkg.description,\n repoUrl: pkg.repoUrl || rootRepoUrl,\n monorepoRoot: cwd,\n llmConfig,\n force: opts.force,\n debug: opts.debug,\n })\n if (outDir)\n results.push({ name: pkg.name, outDir })\n }\n\n if (results.length > 0) {\n p.log.message('')\n for (const { name, outDir } of results)\n p.log.success(`${name} → ${relative(cwd, outDir)}`)\n\n printConsumerGuidance(results.map(r => r.name))\n }\n\n p.outro('Done')\n return\n }\n\n // Single package mode\n const pkgInfo = readLocalPackageInfo(cwd)\n if (!pkgInfo) {\n p.log.error('No package.json found in current directory')\n return\n }\n\n const { name: packageName, version, repoUrl } = pkgInfo\n\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m author \\x1B[36m${packageName}\\x1B[0m@${version}`)\n\n const llmConfig = await resolveLlmConfig(opts.model, opts.yes)\n if (llmConfig === null) {\n p.cancel('Cancelled')\n return\n }\n\n const outDir = await authorSinglePackage({\n packageDir: cwd,\n packageName,\n version,\n description: pkgInfo.description,\n repoUrl,\n out: opts.out,\n llmConfig,\n force: opts.force,\n debug: opts.debug,\n })\n\n if (outDir) {\n printConsumerGuidance([packageName])\n p.outro(`Authored skill to ${relative(cwd, outDir)}`)\n }\n}\n\nfunction printConsumerGuidance(packageNames: string[]): void {\n const names = packageNames.join(', ')\n p.log.info(\n `\\x1B[90mConsumers get ${packageNames.length > 1 ? 'these skills' : 'this skill'} automatically:\\x1B[0m\\n`\n + ` \\x1B[90m1. Install ${names} as a dependency\\x1B[0m\\n`\n + ` \\x1B[90m2. Run \\x1B[36mskilld prepare\\x1B[90m (or add to package.json: \\x1B[36m\"prepare\": \"skilld prepare\"\\x1B[90m)\\x1B[0m`,\n )\n}\n\nexport const authorCommandDef = defineCommand({\n meta: { name: 'package', description: 'Generate a package skill from documentation' },\n args: {\n out: {\n type: 'string',\n alias: 'o',\n description: 'Output directory (default: ./skills/<name>/)',\n },\n model: {\n type: 'string',\n alias: 'm',\n description: 'Enhancement model for SKILL.md generation',\n valueHint: 'id',\n },\n yes: {\n type: 'boolean',\n alias: 'y',\n description: 'Skip prompts, use defaults',\n default: false,\n },\n force: {\n type: 'boolean',\n alias: 'f',\n description: 'Clear cache and regenerate',\n default: false,\n },\n debug: {\n type: 'boolean',\n description: 'Save raw enhancement output to logs/',\n default: false,\n },\n },\n async run({ args }) {\n await authorCommand({\n out: args.out,\n model: args.model as OptimizeModel | undefined,\n yes: args.yes,\n force: args.force,\n debug: args.debug,\n })\n },\n})\n"],"mappings":";;;;;;;;;;;;;;AA2CA,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AAYxB,SAAgB,uBAAuB,KAAuC;CAC5E,MAAM,aAAa,oBAAoB,KAAK,KAAK,eAAe,CAAC;AACjE,KAAI,CAAC,WACH,QAAO;CAET,MAAM,MAAM,WAAW;AAGvB,KAAI,CAAC,IAAI,QACP,QAAO;CAET,IAAI,WAAqB,EAAE;AAE3B,KAAI,MAAM,QAAQ,IAAI,WAAW,CAC/B,YAAW,IAAI;UAER,IAAI,YAAY,SACvB,YAAW,IAAI,WAAW;AAI5B,KAAI,SAAS,WAAW,GAAG;EACzB,MAAM,SAAS,KAAK,KAAK,sBAAsB;AAC/C,MAAI,WAAW,OAAO,EAAE;GACtB,MAAM,QAAQ,aAAa,QAAQ,QAAQ,CAAC,MAAM,KAAK;AACvD,QAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,UAAU,KAAK,MAAM;AAC3B,QAAI,CAAC,QAAQ,WAAW,IAAI,CAC1B;IACF,MAAM,QAAQ,QAAQ,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,iBAAiB,GAAG,CAAC,QAAQ,iBAAiB,GAAG;AAC/F,QAAI,MACF,UAAS,KAAK,MAAM;;;;AAK5B,KAAI,SAAS,WAAW,EACtB,QAAO;CAET,MAAM,WAA8B,EAAE;AAEtC,MAAK,MAAM,WAAW,UAAU;EAG9B,MAAM,UAAU,QAAQ,KADX,QAAQ,QAAQ,WAAW,GAAG,CACT;AAClC,MAAI,CAAC,WAAW,QAAQ,CACtB;EAEF,MAAM,eAAe,oBAAoB,KAAK,SAAS,eAAe,CAAC;AACvE,MAAI,cAAc;GAChB,MAAM,YAAY,aAAa;AAC/B,OAAI,CAAC,UAAU,WAAW,UAAU,MAAM;IACxC,MAAM,UAAU,OAAO,UAAU,eAAe,WAC5C,UAAU,aACV,UAAU,YAAY,KAAK,QAAQ,UAAU,GAAG,CAAC,QAAQ,UAAU,GAAG;AAE1E,aAAS,KAAK;KACZ,MAAM,UAAU;KAChB,SAAS,UAAU,WAAW;KAC9B,aAAa,UAAU;KACvB;KACA,KAAK;KACN,CAAC;AACF;;;AAIJ,OAAK,MAAM,SAAS,YAAY,SAAS,EAAE,eAAe,MAAM,CAAC,EAAE;AACjE,OAAI,CAAC,MAAM,aAAa,CACtB;GACF,MAAM,cAAc,oBAAoB,KAAK,SAAS,MAAM,MAAM,eAAe,CAAC;AAClF,OAAI,CAAC,YACH;GAEF,MAAM,WAAW,YAAY;AAC7B,OAAI,SAAS,QACX;AACF,OAAI,CAAC,SAAS,KACZ;GAEF,MAAM,UAAU,OAAO,SAAS,eAAe,WAC3C,SAAS,aACT,SAAS,YAAY,KAAK,QAAQ,UAAU,GAAG,CAAC,QAAQ,UAAU,GAAG;AAEzE,YAAS,KAAK;IACZ,MAAM,SAAS;IACf,SAAS,SAAS,WAAW;IAC7B,aAAa,SAAS;IACtB;IACA,KAAK,KAAK,SAAS,MAAM,KAAA;IAC1B,CAAC;;;AAIN,QAAO,SAAS,SAAS,IAAI,WAAW;;AAK1C,SAAS,kBAAkB,KAAa,OAAO,IAA8C;CAC3F,MAAM,UAAoD,EAAE;AAC5D,KAAI,CAAC,WAAW,IAAI,CAClB,QAAO;AAET,MAAK,MAAM,SAAS,YAAY,KAAK,EAAE,eAAe,MAAM,CAAC,EAAE;EAC7D,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,SAAS,MAAM;EACnD,MAAM,OAAO,KAAK,KAAK,MAAM,KAAK;AAClC,MAAI,MAAM,aAAa,CACrB,SAAQ,KAAK,GAAG,kBAAkB,MAAM,IAAI,CAAC;WAEtC,UAAU,KAAK,MAAM,KAAK,CACjC,SAAQ,KAAK;GAAE,MAAM;GAAK,SAAS,aAAa,MAAM,QAAA;GAAU,CAAC;;AAGrE,QAAO;;;;;;;;;AAWT,QAAA,MAAS,KAAA,QACP,YACA,KAAA;IAIA,MAAM,QAAA,EAAuD;IAE7D,SAAM,iBAAuB,EAAA,QAAA;IAG7B,CAAA;AACA,gBAAe,aAAU,SAAA,WAAA;AACvB,mBAAgB;AAChB,UAAI;IACF,UAAW;IACS,WAAM,gBAAU,QAAA,OAAA;IAAQ;;;AAE5C,KAAA,aAAgB,MAAA,MAAA,aAAA,CAAA,gBAAA,OAAA,EAAA;EAChB,MAAA,cAAO,KAAA,cAAA,UAAA;MAAE,WAAU,YAAA,EAAA;SAAQ,UAAW,kBAAgB,YAAe;OAAU,QAAA,SAAA,GAAA;;;KAK/E,SAAA,iBACS,EAAA,QAAA;KACT,CAAA;AACA,iBAAe,aAAY,SAAE,WAAA;AAC3B,oBAAgB;AAChB,WAAI;KACF,UAAW;KACS,WAAM,YAAU,UAAA,KAAA,QAAA,OAAA;KAAQ;;;;AAG5C,MAAA,MAAO,OAAA,CAAA,YAAA,aAAA,CAAA,OAAA,QAAA,EAAA;QAAE,WAAU,KAAA,KAAA,WAAA;MAAQ,WAAW,SAAA,EAAY;cAAwC,KAAA;;;;AAOlG,gBAAW,aAAQ,SAAY,WAAc;AAC3C,mBAAiB;AACjB,UAAI;IACF,UAAW;IAAO,WAAM,QAAA,aAAA,mBAAA;IAAY;;;AAEpC,MAAA,MAAA,OAAgB,CAAA,YAAA,aAAA,CAAA,OAAA,QAAA,EAAA;EAEhB,MAAA,aAAO,YAAA,IAAA,CAAA,MAAA,MAAA,gBAAA,KAAA,EAAA,CAAA;MAAE,YAAU;cAAY,KADhB;IACmC,MAAA;;;AAKtD,gBAAW,aAAQ,SAAY,WAAc;AAC3C,mBAAM;AACN,UAAI;IACF,UAAW;IAAO,WAAM,QAAA,aAAA,oBAAA;IAAkB;;;AAE1C,iBAAA;AAEA,QAAA;YAAS;aAAoB;;;;CAIjC,MAAA,aAAgB,CAAA,gBAAA,eAAA;CAChB,MAAA,gBAAO,WAAA,MAAA,MAAA,WAAA,KAAA,KAAA,EAAA,CAAA,CAAA,KAAA,eAAA,WAAA,MAAA,MAAA,WAAA,KAAA,cAAA,EAAA,CAAA,CAAA,GAAA,KAAA;OAAE,eAAU,iBAAA,WAAA,KAAA,KAAA,cAAA,CAAA,GAAA,MAAA;KAAU,iBAAW,aAAA,cAAA,aAAA,SAAA,CAAA;EAAQ,MAAA,YAAA;;EAGlD,CAAA,CAAA;;eAEQ,uBAA2B,MAAK;CAEtC,MAAM,EAAA,aAAe,SAAA,SAAiB,UAAW,eAAU;AAC3D,KAAI,CAAA,WAAA,CAAA,eAAiB,CACnB,QAAA;EACE,WAAM;EACN,gBAAS;EACV;;AAML,KAAA,CAAA,GAAA,QAAe;EAOb,WAAQ;EAER,gBAAiB;EACN;OAAkB,WAAgB,YAAA,aAAA,QAAA;KAAO,YAAA;CAEpD,MAAM,YAAK,KAAA,UAAe,SAAQ;AAClC,KAAI,SACF,UAAO,CAAA,WAAA,UAAA,EAAA;AAAE,aAAW,iCAAA;EAAO,MAAA,SAAA,MAAgB,kBAAA,GAAA,OAAA,GAAA,MAAA,GAAA,CAAA,YAAA,EAAA,CAAA;AAAO,MAAA,OAAA,SAAA,GAAA;AAEpD,cAAM,WAAW,OAAY,OAAA,SAAa;AAE1C,gBAAI,aAAY,SAAA,OAAA,KAAA,WAAA;IAChB,MAAM,gBAAiB,MAAA,OAAU;IACjC,SAAI,sBAAoB,MAAW;IACjC,EAAA,CAAA;AACA,gBAAM,aAAe,SAAA,CAAA;IACrB,MAAI;IACF,SAAA,mBAA6B,OAAO;IACpC,CAAA,CAAA;eACQ;;OAEJ,aAAA,SAAA,UAAA,WAAA,UAAA;CACJ,IAAA,iBAAa;OACX,iBAAM,KAAA,UAAA,cAAA;KACN,SAAS,eAAA,CAAA,WAAmB,eAAA,EAAA;aAC3B,sCAAA;EACH,MAAA,cAAY,MAAA,uBAAA,GAAA,OAAA,GAAA,MAAA,GAAA,CAAA,YAAA,EAAA,CAAA;;cAId,WAAY,YAAS,OAAU,cAAW;AAG5C,gBAAI,aAAiB,SAAA,YAAA,KAAA,OAAA;IACrB,MAAM,0BAAsB,EAAA,OAAU;IACtC,SAAI,2BAAyB,EAAW;IACtC,EAAA,CAAA;AACA,gBAAM,aAAoB,SAAA,CAAA;IAC1B,MAAI;IACF,SAAA,wBAAkC,YAAO;IACzC,CAAA,CAAA;oBACQ;;OAEJ,kBAAA,SAAA,eAAA,WAAA,eAAA;AACJ,QAAA;;;;;;OAQF,UAAA,KAAA,YAA0B,eAAe;AAG3C,KAAA,CAAA,WAAO,QAAA,CAAA;KAAE,iBAAA,UAAA,KAAA,QAAA;AAAW,MAAA,CAAA,MAAA,QAAA,IAAA,MAAA,EAAA;AAAgB,KAAA,IAAA,KAAA,mFAAA;;;AAMpC,MAAA,IAAM,MAAA,MAAe,MAAA,MAAY,YAAA,MAAe,aAAA,MAAA,YAAA,CAAA,QAAA;AAChD,SAAK,kBACH,KAAA,CAAA,QAAA,EAAA,SAAA;GAcF,CAZc,GAAA,IAAA,QAAA,iDAAwC;;AAElD,eAAW,oBAAA,MAAA;CACX,MAAA,EAAA,YAAO,aAAA,YAAA;;CAGT,MAAK,gBAA6B,oBAAoB,YAAkB;CAGxE,MAAA,SAAO,KAAA,MAAkB,QAAM,YAAU,KAAS,IAAA,GAAA,KAAA,YAAA,UAAA,cAAA;KAIlD,KAAE,KAAI;;AAKV,MAAA,CAAA,OAAA,QAAe,OAAA,IAAA,WAWY,KAAA,EAAA;AACzB,KAAA,IAAQ,MAAA,0EAAqC;AAC7C,UAAM;;;AAMN,KAAI,WAAU,OAAA,CAAA,QAAA,QAAA;EACZ,WAAM;EACN,OAAK;EACH,CAAA;AACA,WAAO,QAAA,EAAA,WAAA,MAAA,CAAA;;;CAIX,MAAI,WAAW,YACb,CAAA,YAAe;MAAE,MAAA,uBAAW;OAAM,EAAO,UAAA,cAAA,iBAAA,YAAA,aAAA,SAAA,KAAA,aAAA;MAAO,KAAA,kBAAA,YAAA;CAClD,MAAA,UAAU,cAAU;AAEpB,SAAI,MAAK,8BACS;CAGlB,MAAA,EAAA,WAAgB,mBAAA,MAAA,uBAAA;EAChB;EAGA;EACA,SAAQ,KAAA;EACR;EAGA,aAAM,QAAU,QAAc,QAAA,IAAA;EAC9B,CAAA;CACA,MAAM,WAAE,EAAW;KACjB,UAAA,UAAA,KAAA,SAAA;KACA,eAAA,UAAA,KAAA,cAAA;SACA,KAAS,SAAK,SAAA,IAAA,WAAA,SAAA,KAAA,KAAA,KAAA,wBAAA;mBACd,QAAA,aAAA,YAAA,SAAA,UAAA,KAAA,GAAA,SAAA;OACA,WAAY,YAAO,aAAgB,QAAA;OACnC,eAAA,gBAAA,YAAA,SAAA;CACF,MAAM,cAAuB,WAAA,KAAA,UAAA,WAAA,CAAA;AAC7B,uBACE,QAAc;EAChB,MAAI;EAEJ;EAGA,aAAA,KAAkB;EAGlB,eAAM,EAAW;EACjB;EACA;EAGA;EACE;EACA;EACA,gBAAa;EACb,UAAA,EAAA;EACA,SAAA;EACA,SAAA,KAAA;EACA;EACA,OAAA;EACA,CAAA;GACA,IAAA,QAAA,uBAAgB,SAAA,YAAA,OAAA,GAAA;OAChB,YAAY,KAAA,QAAA,UAAA;KACZ;EACA,MAAA,YAAc,KAAA;AACd,MAAA,WAAA,WAAA,kBAAA;GACA;GACA,UAAA;GACA;GAGF;GACA;GACE;GACA;GAEI;GACA,gBAAU;GACV,UAAA,EAAA;GACA,UAAA,UAAA;GACA,cAAA,UAAA;GACA;GACA,CAAA;WACA,WAAA;AACA,KAAA,IAAA,KAAA,cAAgB,UAAA,MAAA,CAAA;AAChB,SAAA,oBAAY;IACZ;IACA;IACA,UAAA;IACA,SAAA;WAEK,UAAA;IACP,UAAW,EAAA,SAAA,KAAc,SAAU;IACnC,eAAM,EAAA;IACJ;IACA;IACA;IACA;IACA;IACA,gBAAY;IACZ,UAAA,EAAA;IACA,OAAA,KAAA;IACA,OAAA,KAAA;IACA,UAAA,UAAA;IACA,cAAA,UAAA;IACA;IACA,OAAA;IACA,CAAA;;kBAEY,QAAA,aAAA,YAAA,SAAA,UAAA,SAAA;WACZ;MACA,WAAA,UAAc,CAAU,QAAA,WAAA;cACxB;UACA;IACD;;CAGH,MAAA,SAAA,SAAgB,YAAQ,OAAa;gBAE/B,YAAA,OAAA,WAAA,UAAA,CAAA,uBAAA,WAAA;UAEF,KAAA,IAAW,GAAA,IAAA,KACb,4GAAkB;QAAE;;eAA+B,iBAAA,OAAA,KAAA;;AAIvD,QAAM,gBAAS,OAAS,0BAAmB;;eAG7B,cACN,MAAK;CAEb,MAAA,MAAO,QAAA,KAAA;;AAKT,KAAA,gBAAe,aAAiB,SAAuB,GAAsD;AAE3G,IAAA,MADqB,0DAEZ,aAAA,OAAA,mBAAA;AACT,MAAA,KAAO,KAAA;;AAGT;;EAUE,MAAM,WAAA,MAAe,MAAA,EAAA,YAAuB;GAE5C,SAAI;GACF,SAAQ,aAAA,KAAA,SAAA;IAER,OAAS,IAAA;IACP,OAAM;IACN,MAAA,IAAA;;GAGF,CAAA,CAAA;MACE,SAAS,WAAA,EAAA;QACT,YAAS,MAAa,iBAAY,KAAA,OAAA,KAAA,IAAA;MAChC,cAAW,MAAA;KACX,OAAO,YAAA;;;QAGR,UAAA,oBAAA,KAAA,KAAA,eAAA,CAAA,EAAA;EAEH,MAAI,cAAS,OACX,SAAA,eAAA,WAAA,QAAA,aAAA,SAAA,YAAA,KAAA,QAAA,UAAA,GAAA,CAAA,QAAA,UAAA,GAAA;EAGF,MAAM,UAAA,EAAY;AAClB,OAAI,MAAA,OAAc,UAAM;AACtB,KAAE,IAAA,KAAO,WAAY,IAAA,KAAA,UAAA,IAAA,UAAA;GACrB,MAAA,SAAA,MAAA,oBAAA;;IAKF,aAAM,IADgB;IAEtB,SAAM,IAAA;IAIN,aAAM,IAAqD;IAE3D,SAAW,IAAA,WAAO;IAChB,cAAW;IACX;IACE,OAAA,KAAY;IACZ,OAAA,KAAA;IACA,CAAA;OACA,OAAA,SAAiB,KAAA;IACjB,MAAA,IAAS;IACT;IACA,CAAA;;MAEA,QAAO,SAAK,GAAA;KACZ,IAAA,QAAA,GAAA;AACF,QAAI,MACF,EAAA,MAAA,YAAa,QAAA,GAAA,IAAA,QAAA,GAAA,KAAA,KAAA,SAAA,KAAA,OAAA,GAAA;yBAAY,QAAA,KAAA,MAAA,EAAA,KAAA,CAAA;;IAAc,MAAC,OAAA;;;CAI1C,MAAE,UAAY,qBAAG,IAAA;AACjB,KAAA,CAAA,SAAW;AAGX,IAAA,IAAA,MAAA,6CAA+C;;;CAIjD,MAAA,EAAA,MAAA,aAAA,SAAA,YAAA;;CAIF,MAAM,YAAU,MAAA,iBAAyB,KAAA,OAAA,KAAA,IAAA;AACzC,KAAI,cAAU,MAAA;AACZ,IAAE,OAAI,YAAM;AACZ;;CAGF,MAAM,SAAQ,MAAA,oBAAsB;EAEpC,YAAQ;EAER;EACA;EACE,aAAS,QAAY;EACrB;;EAGF;EACE,OAAA,KAAY;EACZ,OAAA,KAAA;EACA,CAAA;KACA,QAAA;AACA,wBAAA,CAAA,YAAA,CAAA;AACA,IAAA,MAAK,qBAAK,SAAA,KAAA,OAAA,GAAA;;;SAGH,sBAAK,cAAA;OACZ,QAAA,aAAA,KAAA,KAAA;AAEF,GAAA,IAAI,KAAA,yBAAQ,aAAA,SAAA,IAAA,iBAAA,aAAA,+CAAA,MAAA,uJAAA;;AAEV,MAAE,mBAAM,cAAqB;;;EAIjC,aAAS;EACP;CACA,MAAM;;GAOR,MAAa;GACX,OAAM;GAAE,aAAM;GAAW;EAA4D,OAAA;GACrF,MAAM;GACJ,OAAK;GACH,aAAM;GACN,WAAO;GACP;OACD;GACD,MAAO;GACL,OAAM;GACN,aAAO;GACP,SAAA;GACA;SACD;GACD,MAAK;GACH,OAAM;GACN,aAAO;GACP,SAAA;GACA;SACD;GACD,MAAO;GACL,aAAM;GACN,SAAO;GACP;;OAED,IAAA,EAAA,QAAA;AACD,QAAA,cAAO;GACL,KAAA,KAAM;GACN,OAAA,KAAA;GACA,KAAA,KAAS;;GAEZ,OAAA,KAAA;GACD,CAAA;;;SAIS"}
|
|
1
|
+
{"version":3,"file":"author.mjs","names":[],"sources":["../../src/commands/author.ts"],"sourcesContent":["import type { OptimizeModel } from '../agent/index.ts'\nimport type { FeaturesConfig } from '../core/config.ts'\nimport type { LlmConfig } from './sync-shared.ts'\nimport { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs'\nimport * as p from '@clack/prompts'\nimport { defineCommand } from 'citty'\nimport { join, relative, resolve } from 'pathe'\nimport {\n computeSkillDirName,\n getModelLabel,\n writeGeneratedSkillMd,\n} from '../agent/index.ts'\nimport {\n ensureCacheDir,\n getCacheDir,\n writeToCache,\n} from '../cache/index.ts'\nimport { guard } from '../cli-helpers.ts'\nimport { defaultFeatures, readConfig } from '../core/config.ts'\nimport { timedSpinner } from '../core/formatting.ts'\nimport { appendToJsonArray, patchPackageJson, readPackageJsonSafe } from '../core/package-json.ts'\nimport { sanitizeMarkdown } from '../core/sanitize.ts'\nimport {\n fetchGitHubDiscussions,\n fetchGitHubIssues,\n formatDiscussionAsMarkdown,\n formatIssueAsMarkdown,\n generateDiscussionIndex,\n generateIssueIndex,\n isGhAvailable,\n parseGitHubUrl,\n readLocalPackageInfo,\n} from '../sources/index.ts'\nimport {\n detectChangelog,\n ejectReferences,\n enhanceSkillWithLLM,\n forceClearCache,\n linkAllReferences,\n selectLlmConfig,\n writePromptFiles,\n} from './sync-shared.ts'\n\nconst QUOTE_PREFIX_RE = /^['\"]/\nconst QUOTE_SUFFIX_RE = /['\"]$/\n\n// ── Monorepo detection ──\n\nexport interface MonorepoPackage {\n name: string\n version: string\n description?: string\n repoUrl?: string\n dir: string\n}\n\nexport function detectMonorepoPackages(cwd: string): MonorepoPackage[] | null {\n const rootResult = readPackageJsonSafe(join(cwd, 'package.json'))\n if (!rootResult)\n return null\n\n const pkg = rootResult.parsed as Record<string, any>\n\n // Must be private (monorepo root) with workspaces or pnpm-workspace.yaml\n if (!pkg.private)\n return null\n\n let patterns: string[] = []\n\n if (Array.isArray(pkg.workspaces)) {\n patterns = pkg.workspaces\n }\n else if (pkg.workspaces?.packages) {\n patterns = pkg.workspaces.packages\n }\n\n // Check pnpm-workspace.yaml\n if (patterns.length === 0) {\n const pnpmWs = join(cwd, 'pnpm-workspace.yaml')\n if (existsSync(pnpmWs)) {\n const lines = readFileSync(pnpmWs, 'utf-8').split('\\n')\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('-'))\n continue\n const value = trimmed.slice(1).trim().replace(QUOTE_PREFIX_RE, '').replace(QUOTE_SUFFIX_RE, '')\n if (value)\n patterns.push(value)\n }\n }\n }\n\n if (patterns.length === 0)\n return null\n\n const packages: MonorepoPackage[] = []\n\n for (const pattern of patterns) {\n // Expand simple glob: \"packages/*\" → scan packages/*/package.json\n const base = pattern.replace(/\\/?\\*+$/, '')\n const scanDir = resolve(cwd, base)\n if (!existsSync(scanDir))\n continue\n\n const directResult = readPackageJsonSafe(join(scanDir, 'package.json'))\n if (directResult) {\n const directPkg = directResult.parsed as Record<string, any>\n if (!directPkg.private && directPkg.name) {\n const repoUrl = typeof directPkg.repository === 'string'\n ? directPkg.repository\n : directPkg.repository?.url?.replace(/^git\\+/, '').replace(/\\.git$/, '')\n\n packages.push({\n name: directPkg.name,\n version: directPkg.version || '0.0.0',\n description: directPkg.description,\n repoUrl,\n dir: scanDir,\n })\n continue\n }\n }\n\n for (const entry of readdirSync(scanDir, { withFileTypes: true })) {\n if (!entry.isDirectory())\n continue\n const childResult = readPackageJsonSafe(join(scanDir, entry.name, 'package.json'))\n if (!childResult)\n continue\n\n const childPkg = childResult.parsed as Record<string, any>\n if (childPkg.private)\n continue\n if (!childPkg.name)\n continue\n\n const repoUrl = typeof childPkg.repository === 'string'\n ? childPkg.repository\n : childPkg.repository?.url?.replace(/^git\\+/, '').replace(/\\.git$/, '')\n\n packages.push({\n name: childPkg.name,\n version: childPkg.version || '0.0.0',\n description: childPkg.description,\n repoUrl,\n dir: join(scanDir, entry.name),\n })\n }\n }\n\n return packages.length > 0 ? packages : null\n}\n\n// ── Docs resolution ──\n\nfunction walkMarkdownFiles(dir: string, base = ''): Array<{ path: string, content: string }> {\n const results: Array<{ path: string, content: string }> = []\n if (!existsSync(dir))\n return results\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const rel = base ? `${base}/${entry.name}` : entry.name\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n results.push(...walkMarkdownFiles(full, rel))\n }\n else if (/\\.mdx?$/.test(entry.name)) {\n results.push({ path: rel, content: readFileSync(full, 'utf-8') })\n }\n }\n return results\n}\n\n/**\n * Resolve docs from local filesystem. Cascade:\n * 1. Package-level docs/ directory\n * 2. Monorepo-root docs/ directory (if monorepoRoot provided)\n * 3. Monorepo-root docs/content/ (Nuxt Content convention)\n * 4. llms.txt in package dir\n * 5. README.md in package dir\n */\nfunction resolveLocalDocs(\n packageDir: string,\n packageName: string,\n version: string,\n monorepoRoot?: string,\n): { docsType: 'docs' | 'llms.txt' | 'readme', docSource: string } {\n const cachedDocs: Array<{ path: string, content: string }> = []\n\n const cacheChangelog = () => cacheLocalChangelog(packageDir, packageName, version, monorepoRoot)\n\n // 1. Package-level docs/\n const docsDir = join(packageDir, 'docs')\n if (existsSync(docsDir)) {\n const mdFiles = walkMarkdownFiles(docsDir)\n if (mdFiles.length > 0) {\n for (const f of mdFiles)\n cachedDocs.push({ path: `docs/${f.path}`, content: sanitizeMarkdown(f.content) })\n writeToCache(packageName, version, cachedDocs)\n cacheChangelog()\n return { docsType: 'docs', docSource: `local docs/ (${mdFiles.length} files)` }\n }\n }\n\n // 2. Monorepo-root docs/ or docs/content/\n if (monorepoRoot) {\n for (const candidate of ['docs/content', 'docs']) {\n const rootDocsDir = join(monorepoRoot, candidate)\n if (existsSync(rootDocsDir)) {\n const mdFiles = walkMarkdownFiles(rootDocsDir)\n if (mdFiles.length > 0) {\n for (const f of mdFiles)\n cachedDocs.push({ path: `docs/${f.path}`, content: sanitizeMarkdown(f.content) })\n writeToCache(packageName, version, cachedDocs)\n cacheChangelog()\n return { docsType: 'docs', docSource: `monorepo ${candidate}/ (${mdFiles.length} files)` }\n }\n }\n }\n }\n\n // 3. llms.txt (package dir, then monorepo root)\n for (const dir of [packageDir, monorepoRoot].filter(Boolean) as string[]) {\n const llmsPath = join(dir, 'llms.txt')\n if (existsSync(llmsPath)) {\n cachedDocs.push({ path: 'llms.txt', content: sanitizeMarkdown(readFileSync(llmsPath, 'utf-8')) })\n writeToCache(packageName, version, cachedDocs)\n cacheChangelog()\n const source = dir === packageDir ? 'local llms.txt' : 'monorepo llms.txt'\n return { docsType: 'llms.txt', docSource: source }\n }\n }\n\n // 4. README.md (package dir, then monorepo root)\n for (const dir of [packageDir, monorepoRoot].filter(Boolean) as string[]) {\n const readmeFile = readdirSync(dir).find(f => /^readme\\.md$/i.test(f))\n if (readmeFile) {\n cachedDocs.push({ path: 'docs/README.md', content: sanitizeMarkdown(readFileSync(join(dir, readmeFile), 'utf-8')) })\n writeToCache(packageName, version, cachedDocs)\n cacheChangelog()\n const source = dir === packageDir ? 'local README.md' : 'monorepo README.md'\n return { docsType: 'readme', docSource: source }\n }\n }\n\n cacheChangelog()\n return { docsType: 'readme', docSource: 'none' }\n}\n\nfunction cacheLocalChangelog(dir: string, packageName: string, version: string, monorepoRoot?: string): void {\n const candidates = ['CHANGELOG.md', 'changelog.md']\n const changelogFile = candidates.find(f => existsSync(join(dir, f)))\n || (monorepoRoot ? candidates.find(f => existsSync(join(monorepoRoot, f))) : undefined)\n const changelogDir = changelogFile && existsSync(join(dir, changelogFile)) ? dir : monorepoRoot\n if (changelogFile && changelogDir) {\n writeToCache(packageName, version, [{\n path: `releases/${changelogFile}`,\n content: sanitizeMarkdown(readFileSync(join(changelogDir, changelogFile), 'utf-8')),\n }])\n }\n}\n\n// ── Remote supplements ──\n\nasync function fetchRemoteSupplements(opts: {\n packageName: string\n version: string\n repoUrl?: string\n features: FeaturesConfig\n onProgress: (msg: string) => void\n}): Promise<{ hasIssues: boolean, hasDiscussions: boolean }> {\n const { packageName, version, repoUrl, features, onProgress } = opts\n\n if (!repoUrl || !isGhAvailable())\n return { hasIssues: false, hasDiscussions: false }\n\n const gh = parseGitHubUrl(repoUrl)\n if (!gh)\n return { hasIssues: false, hasDiscussions: false }\n\n const cacheDir = getCacheDir(packageName, version)\n\n let hasIssues = false\n const issuesDir = join(cacheDir, 'issues')\n if (features.issues && !existsSync(issuesDir)) {\n onProgress('Fetching issues via GitHub API')\n const issues = await fetchGitHubIssues(gh.owner, gh.repo, 30).catch(() => [])\n if (issues.length > 0) {\n onProgress(`Caching ${issues.length} issues`)\n writeToCache(packageName, version, issues.map(issue => ({\n path: `issues/issue-${issue.number}.md`,\n content: formatIssueAsMarkdown(issue),\n })))\n writeToCache(packageName, version, [{\n path: 'issues/_INDEX.md',\n content: generateIssueIndex(issues),\n }])\n hasIssues = true\n }\n }\n else {\n hasIssues = features.issues && existsSync(issuesDir)\n }\n\n let hasDiscussions = false\n const discussionsDir = join(cacheDir, 'discussions')\n if (features.discussions && !existsSync(discussionsDir)) {\n onProgress('Fetching discussions via GitHub API')\n const discussions = await fetchGitHubDiscussions(gh.owner, gh.repo, 20).catch(() => [])\n if (discussions.length > 0) {\n onProgress(`Caching ${discussions.length} discussions`)\n writeToCache(packageName, version, discussions.map(d => ({\n path: `discussions/discussion-${d.number}.md`,\n content: formatDiscussionAsMarkdown(d),\n })))\n writeToCache(packageName, version, [{\n path: 'discussions/_INDEX.md',\n content: generateDiscussionIndex(discussions),\n }])\n hasDiscussions = true\n }\n }\n else {\n hasDiscussions = features.discussions && existsSync(discussionsDir)\n }\n\n return { hasIssues, hasDiscussions }\n}\n\n// ── package.json patching ──\n\nexport function patchPackageJsonFiles(packageDir: string): void {\n const pkgPath = join(packageDir, 'package.json')\n if (!existsSync(pkgPath))\n return\n\n const wrote = patchPackageJson(pkgPath, (raw, pkg) => {\n if (!Array.isArray(pkg.files)) {\n p.log.warn('No `files` array in package.json. Add `\"skills\"` to your files array manually.')\n return null\n }\n\n if ((pkg.files as string[]).some((f: string) => f === 'skills' || f === 'skills/' || f === 'skills/**'))\n return null\n\n return appendToJsonArray(raw, ['files'], 'skills')\n })\n\n if (wrote)\n p.log.success('Added `\"skills\"` to package.json files array')\n}\n\n// ── Core author flow for a single package ──\n\nasync function authorSinglePackage(opts: {\n packageDir: string\n packageName: string\n version: string\n description?: string\n repoUrl?: string\n monorepoRoot?: string\n out?: string\n llmConfig?: LlmConfig | null\n force?: boolean\n debug?: boolean\n}): Promise<string | null> {\n const { packageDir, packageName, version } = opts\n const spin = timedSpinner()\n\n const sanitizedName = computeSkillDirName(packageName)\n const outDir = opts.out ? resolve(packageDir, opts.out) : join(packageDir, 'skills', sanitizedName)\n\n // Validate --out doesn't point at the package root or a parent\n if (opts.out) {\n const rel = relative(packageDir, outDir)\n if (!rel || rel === '.' || rel.startsWith('..')) {\n p.log.error('--out must point to a child directory, not the package root or a parent')\n return null\n }\n }\n\n if (existsSync(outDir))\n rmSync(outDir, { recursive: true, force: true })\n mkdirSync(outDir, { recursive: true })\n\n if (opts.force) {\n forceClearCache(packageName, version)\n }\n\n ensureCacheDir()\n const features = readConfig().features ?? defaultFeatures\n\n // Resolve local docs\n spin.start('Resolving local docs')\n const { docsType, docSource } = resolveLocalDocs(packageDir, packageName, version, opts.monorepoRoot)\n spin.stop(`Resolved docs: ${docSource}`)\n\n // Fetch remote supplements (issues/discussions)\n const supSpin = timedSpinner()\n supSpin.start('Checking remote supplements')\n const { hasIssues, hasDiscussions } = await fetchRemoteSupplements({\n packageName,\n version,\n repoUrl: opts.repoUrl,\n features,\n onProgress: msg => supSpin.message(msg),\n })\n const supParts: string[] = []\n if (hasIssues)\n supParts.push('issues')\n if (hasDiscussions)\n supParts.push('discussions')\n supSpin.stop(supParts.length > 0 ? `Fetched ${supParts.join(', ')}` : 'No remote supplements')\n\n // Create temporary .skilld/ symlinks (LLM needs these to read docs)\n linkAllReferences(outDir, packageName, packageDir, version, docsType, undefined, features)\n\n // Detect changelog + releases\n const cacheDir = getCacheDir(packageName, version)\n const hasChangelog = detectChangelog(packageDir, cacheDir)\n const hasReleases = existsSync(join(cacheDir, 'releases'))\n\n // Generate base SKILL.md\n writeGeneratedSkillMd(outDir, {\n name: packageName,\n version,\n description: opts.description,\n relatedSkills: [],\n hasIssues,\n hasDiscussions,\n hasReleases,\n hasChangelog,\n docsType,\n hasShippedDocs: false,\n pkgFiles: [],\n dirName: sanitizedName,\n repoUrl: opts.repoUrl,\n features,\n eject: true,\n })\n p.log.success(`Created base skill: ${relative(packageDir, outDir)}`)\n\n // LLM enhancement (config resolved by caller)\n const skilldDir = join(outDir, '.skilld')\n try {\n const llmConfig = opts.llmConfig\n if (llmConfig?.promptOnly) {\n writePromptFiles({\n packageName,\n skillDir: outDir,\n version,\n hasIssues,\n hasDiscussions,\n hasReleases,\n hasChangelog,\n docsType,\n hasShippedDocs: false,\n pkgFiles: [],\n sections: llmConfig.sections,\n customPrompt: llmConfig.customPrompt,\n features,\n })\n }\n else if (llmConfig) {\n p.log.step(getModelLabel(llmConfig.model))\n await enhanceSkillWithLLM({\n packageName,\n version,\n skillDir: outDir,\n dirName: sanitizedName,\n model: llmConfig.model,\n resolved: { repoUrl: opts.repoUrl },\n relatedSkills: [],\n hasIssues,\n hasDiscussions,\n hasReleases,\n hasChangelog,\n docsType,\n hasShippedDocs: false,\n pkgFiles: [],\n force: opts.force,\n debug: opts.debug,\n sections: llmConfig.sections,\n customPrompt: llmConfig.customPrompt,\n features,\n eject: true,\n })\n }\n\n ejectReferences(outDir, packageName, packageDir, version, docsType, features)\n }\n finally {\n // Always clean up .skilld/ symlinks, even if LLM enhancement fails\n if (existsSync(skilldDir))\n rmSync(skilldDir, { recursive: true, force: true })\n }\n\n // Only patch package.json when output is under skills/\n const relOut = relative(packageDir, outDir)\n if (relOut === 'skills' || relOut.startsWith('skills/'))\n patchPackageJsonFiles(packageDir)\n else if (opts.out)\n p.log.info('Output is outside skills/, skipping package.json patch. Add the path to \"files\" manually if publishing.')\n\n return outDir\n}\n\n// ── Main command ──\n\nasync function resolveLlmConfig(model?: OptimizeModel, yes?: boolean): Promise<LlmConfig | null | undefined> {\n const globalConfig = readConfig()\n if (globalConfig.skipLlm || (yes && !model))\n return undefined\n return selectLlmConfig(model, 'Generate skill sections')\n}\n\nasync function authorCommand(opts: {\n out?: string\n model?: OptimizeModel\n yes?: boolean\n force?: boolean\n debug?: boolean\n}): Promise<void> {\n const cwd = process.cwd()\n\n // Check for monorepo\n const monoPackages = detectMonorepoPackages(cwd)\n\n if (monoPackages && monoPackages.length > 0) {\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m author \\x1B[90m(monorepo: ${monoPackages.length} packages)\\x1B[0m`)\n\n if (opts.out) {\n p.log.error('--out is not supported in monorepo mode (each package gets its own skills/ directory)')\n return\n }\n\n const selected = guard(await p.multiselect({\n message: 'Which packages should ship skills?',\n options: monoPackages.map(pkg => ({\n label: pkg.name,\n value: pkg,\n hint: pkg.description,\n })),\n }))\n\n if (selected.length === 0)\n return\n\n // Resolve LLM config once for all packages\n const llmConfig = await resolveLlmConfig(opts.model, opts.yes)\n if (llmConfig === null) {\n p.cancel('Cancelled')\n return\n }\n\n // Resolve monorepo-level repoUrl for packages that lack their own\n const rootPkgResult = readPackageJsonSafe(join(cwd, 'package.json'))\n const rootPkg = rootPkgResult?.parsed as Record<string, any> | undefined\n const rootRepoUrl = typeof rootPkg?.repository === 'string'\n ? rootPkg.repository\n : rootPkg?.repository?.url?.replace(/^git\\+/, '').replace(/\\.git$/, '')\n\n const results: Array<{ name: string, outDir: string }> = []\n\n for (const pkg of selected) {\n p.log.step(`\\x1B[36m${pkg.name}\\x1B[0m@${pkg.version}`)\n const outDir = await authorSinglePackage({\n packageDir: pkg.dir,\n packageName: pkg.name,\n version: pkg.version,\n description: pkg.description,\n repoUrl: pkg.repoUrl || rootRepoUrl,\n monorepoRoot: cwd,\n llmConfig,\n force: opts.force,\n debug: opts.debug,\n })\n if (outDir)\n results.push({ name: pkg.name, outDir })\n }\n\n if (results.length > 0) {\n p.log.message('')\n for (const { name, outDir } of results)\n p.log.success(`${name} → ${relative(cwd, outDir)}`)\n\n printConsumerGuidance(results.map(r => r.name))\n }\n\n p.outro('Done')\n return\n }\n\n // Single package mode\n const pkgInfo = readLocalPackageInfo(cwd)\n if (!pkgInfo) {\n p.log.error('No package.json found in current directory')\n return\n }\n\n const { name: packageName, version, repoUrl } = pkgInfo\n\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m author \\x1B[36m${packageName}\\x1B[0m@${version}`)\n\n const llmConfig = await resolveLlmConfig(opts.model, opts.yes)\n if (llmConfig === null) {\n p.cancel('Cancelled')\n return\n }\n\n const outDir = await authorSinglePackage({\n packageDir: cwd,\n packageName,\n version,\n description: pkgInfo.description,\n repoUrl,\n out: opts.out,\n llmConfig,\n force: opts.force,\n debug: opts.debug,\n })\n\n if (outDir) {\n printConsumerGuidance([packageName])\n p.outro(`Authored skill to ${relative(cwd, outDir)}`)\n }\n}\n\nfunction printConsumerGuidance(packageNames: string[]): void {\n const names = packageNames.join(', ')\n p.log.info(\n `\\x1B[90mConsumers get ${packageNames.length > 1 ? 'these skills' : 'this skill'} automatically:\\x1B[0m\\n`\n + ` \\x1B[90m1. Install ${names} as a dependency\\x1B[0m\\n`\n + ` \\x1B[90m2. Run \\x1B[36mskilld prepare\\x1B[90m (or add to package.json: \\x1B[36m\"prepare\": \"skilld prepare\"\\x1B[90m)\\x1B[0m`,\n )\n}\n\nexport const authorCommandDef = defineCommand({\n meta: { name: 'package', description: 'Generate a package skill from documentation' },\n args: {\n out: {\n type: 'string',\n alias: 'o',\n description: 'Output directory (default: ./skills/<name>/)',\n },\n model: {\n type: 'string',\n alias: 'm',\n description: 'Enhancement model for SKILL.md generation',\n valueHint: 'id',\n },\n yes: {\n type: 'boolean',\n alias: 'y',\n description: 'Skip prompts, use defaults',\n default: false,\n },\n force: {\n type: 'boolean',\n alias: 'f',\n description: 'Clear cache and regenerate',\n default: false,\n },\n debug: {\n type: 'boolean',\n description: 'Save raw enhancement output to logs/',\n default: false,\n },\n },\n async run({ args }) {\n await authorCommand({\n out: args.out,\n model: args.model as OptimizeModel | undefined,\n yes: args.yes,\n force: args.force,\n debug: args.debug,\n })\n },\n})\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.mjs","names":[],"sources":["../../src/cache/storage.ts"],"sourcesContent":["/**\n * Cache storage operations\n */\n\nimport type { CachedDoc, CachedPackage } from './types.ts'\nimport { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { basename, join, resolve } from 'pathe'\nimport { readPackageJsonSafe } from '../core/package-json.ts'\nimport { resolvePkgDir } from '../core/prepare.ts'\nimport { sanitizeMarkdown } from '../core/sanitize.ts'\nimport { getRepoCacheDir, REFERENCES_DIR, REPOS_DIR } from './config.ts'\nimport { getCacheDir } from './version.ts'\n\n/** Safely create a symlink, validating target is under REFERENCES_DIR or REPOS_DIR */\nfunction safeSymlink(target: string, linkPath: string): void {\n const resolved = resolve(target)\n if (!resolved.startsWith(REFERENCES_DIR) && !resolved.startsWith(REPOS_DIR))\n throw new Error(`Symlink target outside allowed dirs: ${resolved}`)\n // Remove pre-existing symlink (check with lstat to detect symlinks)\n try {\n const stat = lstatSync(linkPath)\n if (stat.isSymbolicLink() || stat.isFile())\n unlinkSync(linkPath)\n }\n catch {}\n symlinkSync(target, linkPath, 'junction')\n}\n\n/**\n * Check if package is cached at given version\n */\nexport function isCached(name: string, version: string): boolean {\n return existsSync(getCacheDir(name, version))\n}\n\n/** Check if cache only has docs/README.md (pkg/ already has this) */\nexport function isReadmeOnlyCache(cacheDir: string): boolean {\n const docsDir = join(cacheDir, 'docs')\n if (!existsSync(docsDir))\n return false\n const files = readdirSync(docsDir)\n return files.length === 1 && files[0] === 'README.md'\n}\n\nexport function inferDocsTypeFromCache(cacheDir: string, source?: string): 'llms.txt' | 'readme' | 'docs' {\n if (source?.includes('llms.txt') || existsSync(join(cacheDir, 'docs', 'llms.txt')))\n return 'llms.txt'\n return isReadmeOnlyCache(cacheDir) ? 'readme' : 'docs'\n}\n\n/**\n * Ensure cache directories exist\n */\nexport function ensureCacheDir(): void {\n mkdirSync(REFERENCES_DIR, { recursive: true, mode: 0o700 })\n mkdirSync(REPOS_DIR, { recursive: true, mode: 0o700 })\n}\n\n/**\n * Write docs to cache\n */\nexport function writeToCache(\n name: string,\n version: string,\n docs: CachedDoc[],\n): string {\n const cacheDir = getCacheDir(name, version)\n mkdirSync(cacheDir, { recursive: true, mode: 0o700 })\n\n for (const doc of docs) {\n const filePath = join(cacheDir, doc.path)\n mkdirSync(join(filePath, '..'), { recursive: true, mode: 0o700 })\n writeFileSync(filePath, sanitizeMarkdown(doc.content), { mode: 0o600 })\n }\n\n return cacheDir\n}\n\n/**\n * Write docs to repo-level cache (~/.skilld/repos/<owner>/<repo>/)\n */\nexport function writeToRepoCache(\n owner: string,\n repo: string,\n docs: CachedDoc[],\n): string {\n const repoDir = getRepoCacheDir(owner, repo)\n mkdirSync(repoDir, { recursive: true, mode: 0o700 })\n\n for (const doc of docs) {\n const filePath = join(repoDir, doc.path)\n mkdirSync(join(filePath, '..'), { recursive: true, mode: 0o700 })\n writeFileSync(filePath, sanitizeMarkdown(doc.content), { mode: 0o600 })\n }\n\n return repoDir\n}\n\n/**\n * Create symlink from .skilld dir to a repo-level cached subdirectory.\n * .claude/skills/<skill>/.skilld/<subdir> -> ~/.skilld/repos/<owner>/<repo>/<subdir>\n */\nexport function linkRepoCachedDir(skillDir: string, owner: string, repo: string, subdir: string): void {\n const repoDir = getRepoCacheDir(owner, repo)\n const referencesDir = join(skillDir, '.skilld')\n const linkPath = join(referencesDir, subdir)\n const cachedPath = join(repoDir, subdir)\n\n mkdirSync(referencesDir, { recursive: true })\n\n if (existsSync(cachedPath)) {\n safeSymlink(cachedPath, linkPath)\n }\n}\n\n/**\n * Create symlink from .skilld dir to a cached subdirectory.\n * Unified handler for docs, issues, discussions, sections, releases.\n *\n * Structure:\n * .claude/skills/<skill>/.skilld/<subdir> -> ~/.skilld/references/<pkg>@<version>/<subdir>\n *\n * The .skilld/ dirs are gitignored. After clone, `skilld install` recreates from lockfile.\n */\nexport function linkCachedDir(skillDir: string, name: string, version: string, subdir: string): void {\n const cacheDir = getCacheDir(name, version)\n const referencesDir = join(skillDir, '.skilld')\n const linkPath = join(referencesDir, subdir)\n const cachedPath = join(cacheDir, subdir)\n\n mkdirSync(referencesDir, { recursive: true })\n\n if (existsSync(cachedPath)) {\n safeSymlink(cachedPath, linkPath)\n }\n}\n\n/**\n * Resolve the package directory: node_modules first, then cached dist fallback.\n * Returns the path if found, null otherwise.\n */\nexport { resolvePkgDir } from '../core/prepare.ts'\nexport { getShippedSkills, linkShippedSkill } from '../core/prepare.ts'\nexport type { ShippedSkill } from '../core/prepare.ts'\n\n/**\n * Create symlink from .skilld dir to package directory\n *\n * Structure:\n * .claude/skills/<skill>/.skilld/pkg -> node_modules/<pkg> OR ~/.skilld/references/<pkg>@<version>/pkg\n *\n * This gives access to package.json, README.md, dist/, and any shipped docs/\n */\nexport function linkPkg(skillDir: string, name: string, cwd: string, version?: string): void {\n const pkgPath = resolvePkgDir(name, cwd, version)\n if (!pkgPath)\n return\n\n const referencesDir = join(skillDir, '.skilld')\n mkdirSync(referencesDir, { recursive: true })\n\n const pkgLinkPath = join(referencesDir, 'pkg')\n try {\n lstatSync(pkgLinkPath)\n unlinkSync(pkgLinkPath)\n }\n catch {}\n symlinkSync(pkgPath, pkgLinkPath, 'junction')\n}\n\n/**\n * Create named symlink from .skilld dir to package directory.\n * Short name = last segment of package name (e.g., @vue/reactivity → pkg-reactivity)\n *\n * Structure:\n * .claude/skills/<skill>/.skilld/pkg-<short> -> node_modules/<pkg>\n */\nexport function linkPkgNamed(skillDir: string, name: string, cwd: string, version?: string): void {\n const pkgPath = resolvePkgDir(name, cwd, version)\n if (!pkgPath)\n return\n\n const shortName = name.split('/').pop()!.toLowerCase()\n const referencesDir = join(skillDir, '.skilld')\n mkdirSync(referencesDir, { recursive: true })\n\n const linkPath = join(referencesDir, `pkg-${shortName}`)\n try {\n lstatSync(linkPath)\n unlinkSync(linkPath)\n }\n catch {}\n symlinkSync(pkgPath, linkPath, 'junction')\n}\n\n/**\n * Get key files from a package directory for display\n * Returns entry points + docs files\n */\nexport function getPkgKeyFiles(name: string, cwd: string, version?: string): string[] {\n const pkgPath = resolvePkgDir(name, cwd, version)\n if (!pkgPath)\n return []\n\n const files: string[] = []\n const pkgJsonPath = join(pkgPath, 'package.json')\n\n const pkgJsonResult = readPackageJsonSafe(pkgJsonPath)\n if (pkgJsonResult) {\n const pkg = pkgJsonResult.parsed as Record<string, any>\n\n // Entry points\n if (pkg.main)\n files.push(basename(pkg.main))\n if (pkg.module && pkg.module !== pkg.main)\n files.push(basename(pkg.module))\n\n // Type definitions (relative path preserved for LLM tool hints)\n const typesPath = pkg.types || pkg.typings\n if (typesPath && existsSync(join(pkgPath, typesPath)))\n files.push(typesPath)\n }\n\n // Check for common doc files (case-insensitive readme match)\n const entries = readdirSync(pkgPath).filter(f =>\n /^readme\\.md$/i.test(f) || /^changelog\\.md$/i.test(f),\n )\n files.push(...entries)\n\n return [...new Set(files)]\n}\n\n/**\n * Write LLM-generated section outputs to global cache for cross-project reuse\n *\n * Structure:\n * ~/.skilld/references/<pkg>@<version>/sections/_BEST_PRACTICES.md\n */\nexport function writeSections(name: string, version: string, sections: Array<{ file: string, content: string }>): void {\n const cacheDir = getCacheDir(name, version)\n const sectionsDir = join(cacheDir, 'sections')\n mkdirSync(sectionsDir, { recursive: true, mode: 0o700 })\n for (const { file, content } of sections) {\n writeFileSync(join(sectionsDir, file), content, { mode: 0o600 })\n }\n}\n\n/**\n * Read a cached section from the global references dir\n */\nexport function readCachedSection(name: string, version: string, file: string): string | null {\n const path = join(getCacheDir(name, version), 'sections', file)\n if (!existsSync(path))\n return null\n return readFileSync(path, 'utf-8')\n}\n\nexport function hasShippedDocs(name: string, cwd: string, version?: string): boolean {\n const pkgPath = resolvePkgDir(name, cwd, version)\n if (!pkgPath)\n return false\n\n const docsCandidates = ['docs', 'documentation', 'doc']\n for (const candidate of docsCandidates) {\n const docsPath = join(pkgPath, candidate)\n if (existsSync(docsPath))\n return true\n }\n return false\n}\n\n/**\n * List all cached packages\n */\nexport function listCached(): CachedPackage[] {\n if (!existsSync(REFERENCES_DIR))\n return []\n\n return readdirSync(REFERENCES_DIR)\n .filter(name => name.includes('@'))\n .map((dir) => {\n const atIdx = dir.lastIndexOf('@')\n return { name: dir.slice(0, atIdx), version: dir.slice(atIdx + 1), dir: join(REFERENCES_DIR, dir) }\n })\n}\n\n/**\n * Read cached docs for a package\n */\nexport function readCachedDocs(name: string, version: string): CachedDoc[] {\n const cacheDir = getCacheDir(name, version)\n if (!existsSync(cacheDir))\n return []\n\n const docs: CachedDoc[] = []\n\n function walk(dir: string, prefix = '') {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const entryPath = join(dir, entry.name)\n const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name\n\n if (entry.isDirectory()) {\n walk(entryPath, relativePath)\n }\n else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdx')) {\n docs.push({\n path: relativePath,\n content: readFileSync(entryPath, 'utf-8'),\n })\n }\n }\n }\n\n walk(cacheDir)\n return docs\n}\n\n/**\n * Clear cache for a specific package\n */\nexport function clearCache(name: string, version: string): boolean {\n const cacheDir = getCacheDir(name, version)\n if (!existsSync(cacheDir))\n return false\n\n rmSync(cacheDir, { recursive: true })\n return true\n}\n\n/**\n * Clear all cache\n */\nexport function clearAllCache(): number {\n const packages = listCached()\n for (const pkg of packages) {\n clearCache(pkg.name, pkg.version)\n }\n // Also clear repo-level cache\n if (existsSync(REPOS_DIR))\n rmSync(REPOS_DIR, { recursive: true })\n return packages.length\n}\n\n/**\n * List files in .skilld directory (pkg + docs) as relative paths for prompt context\n * Returns paths like ./.skilld/pkg/README.md, ./.skilld/docs/api.md\n */\nexport function listReferenceFiles(skillDir: string, maxDepth = 3): string[] {\n const referencesDir = join(skillDir, '.skilld')\n if (!existsSync(referencesDir))\n return []\n\n const files: string[] = []\n\n function walk(dir: string, depth: number) {\n if (depth > maxDepth)\n return\n try {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name)\n if (entry.isDirectory() || entry.isSymbolicLink()) {\n try {\n const stat = statSync(full)\n if (stat.isDirectory()) {\n walk(full, depth + 1)\n continue\n }\n }\n catch { continue }\n }\n if (entry.name.endsWith('.md')) {\n files.push(full)\n }\n }\n }\n catch {\n // Broken symlink or permission error\n }\n }\n\n walk(referencesDir, 0)\n return files\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cache.mjs","names":[],"sources":["../../src/cache/storage.ts"],"sourcesContent":["/**\n * Cache storage operations\n */\n\nimport type { CachedDoc, CachedPackage } from './types.ts'\nimport { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { basename, join, resolve } from 'pathe'\nimport { readPackageJsonSafe } from '../core/package-json.ts'\nimport { resolvePkgDir } from '../core/prepare.ts'\nimport { sanitizeMarkdown } from '../core/sanitize.ts'\nimport { getRepoCacheDir, REFERENCES_DIR, REPOS_DIR } from './config.ts'\nimport { getCacheDir } from './version.ts'\n\n/** Safely create a symlink, validating target is under REFERENCES_DIR or REPOS_DIR */\nfunction safeSymlink(target: string, linkPath: string): void {\n const resolved = resolve(target)\n if (!resolved.startsWith(REFERENCES_DIR) && !resolved.startsWith(REPOS_DIR))\n throw new Error(`Symlink target outside allowed dirs: ${resolved}`)\n // Remove pre-existing symlink (check with lstat to detect symlinks)\n try {\n const stat = lstatSync(linkPath)\n if (stat.isSymbolicLink() || stat.isFile())\n unlinkSync(linkPath)\n }\n catch {}\n symlinkSync(target, linkPath, 'junction')\n}\n\n/**\n * Check if package is cached at given version\n */\nexport function isCached(name: string, version: string): boolean {\n return existsSync(getCacheDir(name, version))\n}\n\n/** Check if cache only has docs/README.md (pkg/ already has this) */\nexport function isReadmeOnlyCache(cacheDir: string): boolean {\n const docsDir = join(cacheDir, 'docs')\n if (!existsSync(docsDir))\n return false\n const files = readdirSync(docsDir)\n return files.length === 1 && files[0] === 'README.md'\n}\n\nexport function inferDocsTypeFromCache(cacheDir: string, source?: string): 'llms.txt' | 'readme' | 'docs' {\n if (source?.includes('llms.txt') || existsSync(join(cacheDir, 'docs', 'llms.txt')))\n return 'llms.txt'\n return isReadmeOnlyCache(cacheDir) ? 'readme' : 'docs'\n}\n\n/**\n * Ensure cache directories exist\n */\nexport function ensureCacheDir(): void {\n mkdirSync(REFERENCES_DIR, { recursive: true, mode: 0o700 })\n mkdirSync(REPOS_DIR, { recursive: true, mode: 0o700 })\n}\n\n/**\n * Write docs to cache\n */\nexport function writeToCache(\n name: string,\n version: string,\n docs: CachedDoc[],\n): string {\n const cacheDir = getCacheDir(name, version)\n mkdirSync(cacheDir, { recursive: true, mode: 0o700 })\n\n for (const doc of docs) {\n const filePath = join(cacheDir, doc.path)\n mkdirSync(join(filePath, '..'), { recursive: true, mode: 0o700 })\n writeFileSync(filePath, sanitizeMarkdown(doc.content), { mode: 0o600 })\n }\n\n return cacheDir\n}\n\n/**\n * Write docs to repo-level cache (~/.skilld/repos/<owner>/<repo>/)\n */\nexport function writeToRepoCache(\n owner: string,\n repo: string,\n docs: CachedDoc[],\n): string {\n const repoDir = getRepoCacheDir(owner, repo)\n mkdirSync(repoDir, { recursive: true, mode: 0o700 })\n\n for (const doc of docs) {\n const filePath = join(repoDir, doc.path)\n mkdirSync(join(filePath, '..'), { recursive: true, mode: 0o700 })\n writeFileSync(filePath, sanitizeMarkdown(doc.content), { mode: 0o600 })\n }\n\n return repoDir\n}\n\n/**\n * Create symlink from .skilld dir to a repo-level cached subdirectory.\n * .claude/skills/<skill>/.skilld/<subdir> -> ~/.skilld/repos/<owner>/<repo>/<subdir>\n */\nexport function linkRepoCachedDir(skillDir: string, owner: string, repo: string, subdir: string): void {\n const repoDir = getRepoCacheDir(owner, repo)\n const referencesDir = join(skillDir, '.skilld')\n const linkPath = join(referencesDir, subdir)\n const cachedPath = join(repoDir, subdir)\n\n mkdirSync(referencesDir, { recursive: true })\n\n if (existsSync(cachedPath)) {\n safeSymlink(cachedPath, linkPath)\n }\n}\n\n/**\n * Create symlink from .skilld dir to a cached subdirectory.\n * Unified handler for docs, issues, discussions, sections, releases.\n *\n * Structure:\n * .claude/skills/<skill>/.skilld/<subdir> -> ~/.skilld/references/<pkg>@<version>/<subdir>\n *\n * The .skilld/ dirs are gitignored. After clone, `skilld install` recreates from lockfile.\n */\nexport function linkCachedDir(skillDir: string, name: string, version: string, subdir: string): void {\n const cacheDir = getCacheDir(name, version)\n const referencesDir = join(skillDir, '.skilld')\n const linkPath = join(referencesDir, subdir)\n const cachedPath = join(cacheDir, subdir)\n\n mkdirSync(referencesDir, { recursive: true })\n\n if (existsSync(cachedPath)) {\n safeSymlink(cachedPath, linkPath)\n }\n}\n\n/**\n * Resolve the package directory: node_modules first, then cached dist fallback.\n * Returns the path if found, null otherwise.\n */\nexport { resolvePkgDir } from '../core/prepare.ts'\nexport { getShippedSkills, linkShippedSkill } from '../core/prepare.ts'\nexport type { ShippedSkill } from '../core/prepare.ts'\n\n/**\n * Create symlink from .skilld dir to package directory\n *\n * Structure:\n * .claude/skills/<skill>/.skilld/pkg -> node_modules/<pkg> OR ~/.skilld/references/<pkg>@<version>/pkg\n *\n * This gives access to package.json, README.md, dist/, and any shipped docs/\n */\nexport function linkPkg(skillDir: string, name: string, cwd: string, version?: string): void {\n const pkgPath = resolvePkgDir(name, cwd, version)\n if (!pkgPath)\n return\n\n const referencesDir = join(skillDir, '.skilld')\n mkdirSync(referencesDir, { recursive: true })\n\n const pkgLinkPath = join(referencesDir, 'pkg')\n try {\n lstatSync(pkgLinkPath)\n unlinkSync(pkgLinkPath)\n }\n catch {}\n symlinkSync(pkgPath, pkgLinkPath, 'junction')\n}\n\n/**\n * Create named symlink from .skilld dir to package directory.\n * Short name = last segment of package name (e.g., @vue/reactivity → pkg-reactivity)\n *\n * Structure:\n * .claude/skills/<skill>/.skilld/pkg-<short> -> node_modules/<pkg>\n */\nexport function linkPkgNamed(skillDir: string, name: string, cwd: string, version?: string): void {\n const pkgPath = resolvePkgDir(name, cwd, version)\n if (!pkgPath)\n return\n\n const shortName = name.split('/').pop()!.toLowerCase()\n const referencesDir = join(skillDir, '.skilld')\n mkdirSync(referencesDir, { recursive: true })\n\n const linkPath = join(referencesDir, `pkg-${shortName}`)\n try {\n lstatSync(linkPath)\n unlinkSync(linkPath)\n }\n catch {}\n symlinkSync(pkgPath, linkPath, 'junction')\n}\n\n/**\n * Get key files from a package directory for display\n * Returns entry points + docs files\n */\nexport function getPkgKeyFiles(name: string, cwd: string, version?: string): string[] {\n const pkgPath = resolvePkgDir(name, cwd, version)\n if (!pkgPath)\n return []\n\n const files: string[] = []\n const pkgJsonPath = join(pkgPath, 'package.json')\n\n const pkgJsonResult = readPackageJsonSafe(pkgJsonPath)\n if (pkgJsonResult) {\n const pkg = pkgJsonResult.parsed as Record<string, any>\n\n // Entry points\n if (pkg.main)\n files.push(basename(pkg.main))\n if (pkg.module && pkg.module !== pkg.main)\n files.push(basename(pkg.module))\n\n // Type definitions (relative path preserved for LLM tool hints)\n const typesPath = pkg.types || pkg.typings\n if (typesPath && existsSync(join(pkgPath, typesPath)))\n files.push(typesPath)\n }\n\n // Check for common doc files (case-insensitive readme match)\n const entries = readdirSync(pkgPath).filter(f =>\n /^readme\\.md$/i.test(f) || /^changelog\\.md$/i.test(f),\n )\n files.push(...entries)\n\n return [...new Set(files)]\n}\n\n/**\n * Write LLM-generated section outputs to global cache for cross-project reuse\n *\n * Structure:\n * ~/.skilld/references/<pkg>@<version>/sections/_BEST_PRACTICES.md\n */\nexport function writeSections(name: string, version: string, sections: Array<{ file: string, content: string }>): void {\n const cacheDir = getCacheDir(name, version)\n const sectionsDir = join(cacheDir, 'sections')\n mkdirSync(sectionsDir, { recursive: true, mode: 0o700 })\n for (const { file, content } of sections) {\n writeFileSync(join(sectionsDir, file), content, { mode: 0o600 })\n }\n}\n\n/**\n * Read a cached section from the global references dir\n */\nexport function readCachedSection(name: string, version: string, file: string): string | null {\n const path = join(getCacheDir(name, version), 'sections', file)\n if (!existsSync(path))\n return null\n return readFileSync(path, 'utf-8')\n}\n\nexport function hasShippedDocs(name: string, cwd: string, version?: string): boolean {\n const pkgPath = resolvePkgDir(name, cwd, version)\n if (!pkgPath)\n return false\n\n const docsCandidates = ['docs', 'documentation', 'doc']\n for (const candidate of docsCandidates) {\n const docsPath = join(pkgPath, candidate)\n if (existsSync(docsPath))\n return true\n }\n return false\n}\n\n/**\n * List all cached packages\n */\nexport function listCached(): CachedPackage[] {\n if (!existsSync(REFERENCES_DIR))\n return []\n\n return readdirSync(REFERENCES_DIR)\n .filter(name => name.includes('@'))\n .map((dir) => {\n const atIdx = dir.lastIndexOf('@')\n return { name: dir.slice(0, atIdx), version: dir.slice(atIdx + 1), dir: join(REFERENCES_DIR, dir) }\n })\n}\n\n/**\n * Read cached docs for a package\n */\nexport function readCachedDocs(name: string, version: string): CachedDoc[] {\n const cacheDir = getCacheDir(name, version)\n if (!existsSync(cacheDir))\n return []\n\n const docs: CachedDoc[] = []\n\n function walk(dir: string, prefix = '') {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const entryPath = join(dir, entry.name)\n const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name\n\n if (entry.isDirectory()) {\n walk(entryPath, relativePath)\n }\n else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdx')) {\n docs.push({\n path: relativePath,\n content: readFileSync(entryPath, 'utf-8'),\n })\n }\n }\n }\n\n walk(cacheDir)\n return docs\n}\n\n/**\n * Clear cache for a specific package\n */\nexport function clearCache(name: string, version: string): boolean {\n const cacheDir = getCacheDir(name, version)\n if (!existsSync(cacheDir))\n return false\n\n rmSync(cacheDir, { recursive: true })\n return true\n}\n\n/**\n * Clear all cache\n */\nexport function clearAllCache(): number {\n const packages = listCached()\n for (const pkg of packages) {\n clearCache(pkg.name, pkg.version)\n }\n // Also clear repo-level cache\n if (existsSync(REPOS_DIR))\n rmSync(REPOS_DIR, { recursive: true })\n return packages.length\n}\n\n/**\n * List files in .skilld directory (pkg + docs) as relative paths for prompt context\n * Returns paths like ./.skilld/pkg/README.md, ./.skilld/docs/api.md\n */\nexport function listReferenceFiles(skillDir: string, maxDepth = 3): string[] {\n const referencesDir = join(skillDir, '.skilld')\n if (!existsSync(referencesDir))\n return []\n\n const files: string[] = []\n\n function walk(dir: string, depth: number) {\n if (depth > maxDepth)\n return\n try {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name)\n if (entry.isDirectory() || entry.isSymbolicLink()) {\n try {\n const stat = statSync(full)\n if (stat.isDirectory()) {\n walk(full, depth + 1)\n continue\n }\n }\n catch { continue }\n }\n if (entry.name.endsWith('.md')) {\n files.push(full)\n }\n }\n }\n catch {\n // Broken symlink or permission error\n }\n }\n\n walk(referencesDir, 0)\n return files\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache2.mjs","names":[],"sources":["../../src/commands/cache.ts"],"sourcesContent":["/**\n * Cache management commands\n */\n\nimport type { Dirent } from 'node:fs'\nimport { existsSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs'\nimport * as p from '@clack/prompts'\nimport { defineCommand } from 'citty'\nimport { join } from 'pathe'\nimport { CACHE_DIR, REFERENCES_DIR, REPOS_DIR } from '../cache/index.ts'\nimport { clearEmbeddingCache } from '../retriv/embedding-cache.ts'\n\nconst LLM_CACHE_DIR = join(CACHE_DIR, 'llm-cache')\nconst LLM_CACHE_MAX_AGE = 7 * 24 * 60 * 60 * 1000\n\nfunction safeRemove(path: string): number {\n try {\n const size = statSync(path).size\n rmSync(path)\n return size\n }\n catch {\n try {\n rmSync(path)\n }\n catch {}\n return 0\n }\n}\n\nexport async function cacheCleanCommand(): Promise<void> {\n let expiredLlm = 0\n let freedBytes = 0\n\n // Clean expired LLM cache entries\n if (existsSync(LLM_CACHE_DIR)) {\n const now = Date.now()\n for (const entry of readdirSync(LLM_CACHE_DIR)) {\n const path = join(LLM_CACHE_DIR, entry)\n try {\n const { timestamp } = JSON.parse(readFileSync(path, 'utf-8'))\n if (now - timestamp > LLM_CACHE_MAX_AGE) {\n freedBytes += safeRemove(path)\n expiredLlm++\n }\n }\n catch {\n // Corrupt cache entry — remove it\n freedBytes += safeRemove(path)\n expiredLlm++\n }\n }\n }\n\n // Clear embedding cache\n const embeddingDbPath = join(CACHE_DIR, 'embeddings.db')\n let embeddingCleared = false\n if (existsSync(embeddingDbPath)) {\n const size = statSync(embeddingDbPath).size\n clearEmbeddingCache()\n freedBytes += size\n embeddingCleared = true\n }\n\n const freedKB = Math.round(freedBytes / 1024)\n if (expiredLlm > 0 || embeddingCleared) {\n const parts: string[] = []\n if (expiredLlm > 0)\n parts.push(`${expiredLlm} expired enhancement cache entries`)\n if (embeddingCleared)\n parts.push('embedding cache')\n p.log.success(`Removed ${parts.join(' + ')} (${freedKB}KB freed)`)\n }\n else {\n p.log.info('Cache is clean — no expired entries')\n }\n}\n\nfunction dirEntries(dir: string): Dirent[] {\n if (!existsSync(dir))\n return []\n return readdirSync(dir, { withFileTypes: true, recursive: true })\n}\n\nfunction sumFileBytes(entries: Dirent[]): number {\n return entries\n .filter(e => e.isFile())\n .reduce((sum, e) => {\n try {\n return sum + statSync(join(e.parentPath, e.name)).size\n }\n catch { return sum }\n }, 0)\n}\n\nfunction fmtBytes(n: number): string {\n const units = ['B', 'KB', 'MB', 'GB'] as const\n let i = 0\n while (n >= 1024 && i < units.length - 1) {\n n /= 1024\n i++\n }\n return i === 0 ? `${n}${units[i]}` : `${n.toFixed(1)}${units[i]}`\n}\n\nexport function cacheStatsCommand(): void {\n const dim = (s: string) => `\\x1B[90m${s}\\x1B[0m`\n\n const refs = dirEntries(REFERENCES_DIR)\n const repos = dirEntries(REPOS_DIR)\n const llm = dirEntries(LLM_CACHE_DIR)\n const embPath = join(CACHE_DIR, 'embeddings.db')\n const embSize = existsSync(embPath) ? statSync(embPath).size : 0\n\n // Count packages: top-level non-scoped dirs + dirs inside @scope/ dirs\n const packages = refs.filter(e =>\n e.isDirectory()\n && (e.parentPath === REFERENCES_DIR\n ? !e.name.startsWith('@')\n : e.parentPath.startsWith(REFERENCES_DIR)),\n ).length\n\n const llmFiles = llm.filter(e => e.isFile())\n const sizes = { refs: sumFileBytes(refs), repos: sumFileBytes(repos), llm: sumFileBytes(llmFiles), emb: embSize }\n const total = sizes.refs + sizes.repos + sizes.llm + sizes.emb\n\n const lines = [\n `References ${fmtBytes(sizes.refs)} ${dim(`${packages} packages`)}`,\n ...(sizes.repos > 0 ? [`Repos ${fmtBytes(sizes.repos)}`] : []),\n `LLM cache ${fmtBytes(sizes.llm)} ${dim(`${llmFiles.length} entries`)}`,\n ...(sizes.emb > 0 ? [`Embeddings ${fmtBytes(sizes.emb)}`] : []),\n '',\n `Total ${fmtBytes(total)} ${dim(CACHE_DIR)}`,\n ]\n p.log.message(lines.join('\\n'))\n}\n\nexport const cacheCommandDef = defineCommand({\n meta: { name: 'cache', description: 'Cache management', hidden: true },\n args: {\n clean: {\n type: 'boolean',\n alias: 'c',\n description: 'Remove expired enhancement cache entries',\n default: false,\n },\n stats: {\n type: 'boolean',\n alias: 's',\n description: 'Show cache disk usage',\n default: false,\n },\n },\n async run({ args }) {\n if (args.stats) {\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m cache stats`)\n cacheStatsCommand()\n return\n }\n if (args.clean) {\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m cache clean`)\n await cacheCleanCommand()\n return\n }\n // No flag: show usage\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m cache`)\n p.log.message('Usage:\\n skilld cache --clean Remove expired cache entries\\n skilld cache --stats Show cache disk usage')\n },\n})\n"],"mappings":";;;;;;;AAYA,MAAM,gBAAgB,KAAK,WAAW,YAAY;AAClD,MAAM,oBAAoB,QAAc,KAAK;AAE7C,SAAS,WAAW,MAAsB;
|
|
1
|
+
{"version":3,"file":"cache2.mjs","names":[],"sources":["../../src/commands/cache.ts"],"sourcesContent":["/**\n * Cache management commands\n */\n\nimport type { Dirent } from 'node:fs'\nimport { existsSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs'\nimport * as p from '@clack/prompts'\nimport { defineCommand } from 'citty'\nimport { join } from 'pathe'\nimport { CACHE_DIR, REFERENCES_DIR, REPOS_DIR } from '../cache/index.ts'\nimport { clearEmbeddingCache } from '../retriv/embedding-cache.ts'\n\nconst LLM_CACHE_DIR = join(CACHE_DIR, 'llm-cache')\nconst LLM_CACHE_MAX_AGE = 7 * 24 * 60 * 60 * 1000\n\nfunction safeRemove(path: string): number {\n try {\n const size = statSync(path).size\n rmSync(path)\n return size\n }\n catch {\n try {\n rmSync(path)\n }\n catch {}\n return 0\n }\n}\n\nexport async function cacheCleanCommand(): Promise<void> {\n let expiredLlm = 0\n let freedBytes = 0\n\n // Clean expired LLM cache entries\n if (existsSync(LLM_CACHE_DIR)) {\n const now = Date.now()\n for (const entry of readdirSync(LLM_CACHE_DIR)) {\n const path = join(LLM_CACHE_DIR, entry)\n try {\n const { timestamp } = JSON.parse(readFileSync(path, 'utf-8'))\n if (now - timestamp > LLM_CACHE_MAX_AGE) {\n freedBytes += safeRemove(path)\n expiredLlm++\n }\n }\n catch {\n // Corrupt cache entry — remove it\n freedBytes += safeRemove(path)\n expiredLlm++\n }\n }\n }\n\n // Clear embedding cache\n const embeddingDbPath = join(CACHE_DIR, 'embeddings.db')\n let embeddingCleared = false\n if (existsSync(embeddingDbPath)) {\n const size = statSync(embeddingDbPath).size\n clearEmbeddingCache()\n freedBytes += size\n embeddingCleared = true\n }\n\n const freedKB = Math.round(freedBytes / 1024)\n if (expiredLlm > 0 || embeddingCleared) {\n const parts: string[] = []\n if (expiredLlm > 0)\n parts.push(`${expiredLlm} expired enhancement cache entries`)\n if (embeddingCleared)\n parts.push('embedding cache')\n p.log.success(`Removed ${parts.join(' + ')} (${freedKB}KB freed)`)\n }\n else {\n p.log.info('Cache is clean — no expired entries')\n }\n}\n\nfunction dirEntries(dir: string): Dirent[] {\n if (!existsSync(dir))\n return []\n return readdirSync(dir, { withFileTypes: true, recursive: true })\n}\n\nfunction sumFileBytes(entries: Dirent[]): number {\n return entries\n .filter(e => e.isFile())\n .reduce((sum, e) => {\n try {\n return sum + statSync(join(e.parentPath, e.name)).size\n }\n catch { return sum }\n }, 0)\n}\n\nfunction fmtBytes(n: number): string {\n const units = ['B', 'KB', 'MB', 'GB'] as const\n let i = 0\n while (n >= 1024 && i < units.length - 1) {\n n /= 1024\n i++\n }\n return i === 0 ? `${n}${units[i]}` : `${n.toFixed(1)}${units[i]}`\n}\n\nexport function cacheStatsCommand(): void {\n const dim = (s: string) => `\\x1B[90m${s}\\x1B[0m`\n\n const refs = dirEntries(REFERENCES_DIR)\n const repos = dirEntries(REPOS_DIR)\n const llm = dirEntries(LLM_CACHE_DIR)\n const embPath = join(CACHE_DIR, 'embeddings.db')\n const embSize = existsSync(embPath) ? statSync(embPath).size : 0\n\n // Count packages: top-level non-scoped dirs + dirs inside @scope/ dirs\n const packages = refs.filter(e =>\n e.isDirectory()\n && (e.parentPath === REFERENCES_DIR\n ? !e.name.startsWith('@')\n : e.parentPath.startsWith(REFERENCES_DIR)),\n ).length\n\n const llmFiles = llm.filter(e => e.isFile())\n const sizes = { refs: sumFileBytes(refs), repos: sumFileBytes(repos), llm: sumFileBytes(llmFiles), emb: embSize }\n const total = sizes.refs + sizes.repos + sizes.llm + sizes.emb\n\n const lines = [\n `References ${fmtBytes(sizes.refs)} ${dim(`${packages} packages`)}`,\n ...(sizes.repos > 0 ? [`Repos ${fmtBytes(sizes.repos)}`] : []),\n `LLM cache ${fmtBytes(sizes.llm)} ${dim(`${llmFiles.length} entries`)}`,\n ...(sizes.emb > 0 ? [`Embeddings ${fmtBytes(sizes.emb)}`] : []),\n '',\n `Total ${fmtBytes(total)} ${dim(CACHE_DIR)}`,\n ]\n p.log.message(lines.join('\\n'))\n}\n\nexport const cacheCommandDef = defineCommand({\n meta: { name: 'cache', description: 'Cache management', hidden: true },\n args: {\n clean: {\n type: 'boolean',\n alias: 'c',\n description: 'Remove expired enhancement cache entries',\n default: false,\n },\n stats: {\n type: 'boolean',\n alias: 's',\n description: 'Show cache disk usage',\n default: false,\n },\n },\n async run({ args }) {\n if (args.stats) {\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m cache stats`)\n cacheStatsCommand()\n return\n }\n if (args.clean) {\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m cache clean`)\n await cacheCleanCommand()\n return\n }\n // No flag: show usage\n p.intro(`\\x1B[1m\\x1B[35mskilld\\x1B[0m cache`)\n p.log.message('Usage:\\n skilld cache --clean Remove expired cache entries\\n skilld cache --stats Show cache disk usage')\n },\n})\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-helpers.mjs","names":["dirname","resolve","agents"],"sources":["../../src/version.ts","../../src/cli-helpers.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n// Walk up from current file to find package.json (works in both src/ and dist/_chunks/)\nfunction findPackageJson(): string {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let i = 0; i < 5; i++) {\n const candidate = resolve(dir, 'package.json')\n try {\n const content = readFileSync(candidate, 'utf8')\n if (content)\n return content\n }\n catch {}\n dir = resolve(dir, '..')\n }\n return '{\"version\":\"0.0.0\"}'\n}\n\nexport const version: string = JSON.parse(findPackageJson()).version\n","/**\n * Shared CLI helpers used by subcommand definitions and the main CLI entry.\n * Extracted to avoid circular deps between cli.ts and commands/*.ts.\n */\n\nimport type { AgentType, OptimizeModel } from './agent/index.ts'\nimport type { ProjectState } from './core/skills.ts'\nimport * as p from '@clack/prompts'\nimport { parseTree } from 'jsonc-parser'\nimport { join } from 'pathe'\nimport { detectCurrentAgent } from 'unagent/env'\nimport { agents, detectInstalledAgents, detectProjectAgents, detectTargetAgent, getAgentVersion, getModelName } from './agent/index.ts'\nimport { readConfig, updateConfig } from './core/config.ts'\nimport { editJsonProperty, patchPackageJson, readPackageJsonSafe } from './core/package-json.ts'\nimport { version } from './version.ts'\n\nexport type { AgentType, OptimizeModel }\n\nexport interface IntroOptions {\n state: ProjectState\n /** Installed CLIs that can serve as enhancement models */\n generators?: Array<{ name: string, version: string }>\n /** Configured enhancement model ID */\n modelId?: string\n /** Resolved target agent ID */\n agentId?: string\n}\n\nexport const sharedArgs = {\n global: {\n type: 'boolean' as const,\n alias: 'g',\n description: 'Install globally to ~/<agent>/skills',\n default: false,\n },\n agent: {\n type: 'enum' as const,\n options: Object.keys(agents),\n alias: 'a',\n description: 'Target agent — where skills are installed',\n },\n model: {\n type: 'string' as const,\n alias: 'm',\n description: 'Enhancement model for SKILL.md generation',\n valueHint: 'id',\n },\n yes: {\n type: 'boolean' as const,\n alias: 'y',\n description: 'Skip prompts, use defaults',\n default: false,\n },\n force: {\n type: 'boolean' as const,\n alias: 'f',\n description: 'Ignore all caches, re-fetch docs and regenerate',\n default: false,\n },\n debug: {\n type: 'boolean' as const,\n description: 'Save raw enhancement output to logs/ for each section',\n default: false,\n },\n}\n\n// ── Menu loop utility ─────────────────────────────────────────────────\n\n/** Thrown when a clack prompt is cancelled inside a menuLoop handler */\nexport class MenuCancel extends Error { override name = 'MenuCancel' }\n\n/** Assert a clack prompt result is not cancelled. Throws MenuCancel if cancelled. */\nexport function guard<T>(value: T | symbol): T {\n if (p.isCancel(value))\n throw new MenuCancel()\n return value as T\n}\n\nexport interface MenuOption {\n label: string\n value: string\n hint?: string\n}\n\n/**\n * Run a select menu in a loop with automatic back-navigation.\n *\n * - Cancel (Escape) at the menu itself → exits (returns)\n * - Cancel inside a handler (via guard()) → caught, loops back to menu\n * - Handler returns truthy → exits (returns)\n * - Handler returns void/false → loops back to menu\n *\n * Options are rebuilt each iteration so hints stay fresh after changes.\n */\nexport async function menuLoop(opts: {\n message: string\n options: () => MenuOption[] | Promise<MenuOption[]>\n onSelect: (value: string) => Promise<boolean | void>\n initialValue?: string | (() => string | undefined)\n /** Use fuzzy-searchable autocomplete instead of static select */\n searchable?: boolean\n}): Promise<void> {\n while (true) {\n const options = await opts.options()\n const initial = typeof opts.initialValue === 'function' ? opts.initialValue() : opts.initialValue\n const choice = opts.searchable\n ? await p.autocomplete({ message: opts.message, options, ...(initial != null ? { initialValue: initial } : {}) })\n : await p.select({ message: opts.message, options, ...(initial != null ? { initialValue: initial } : {}) })\n if (p.isCancel(choice))\n return\n try {\n if (await opts.onSelect(choice as string))\n return\n }\n catch (err) {\n if (err instanceof MenuCancel)\n continue\n throw err\n }\n }\n}\n\n/** Check if we're running inside an AI coding agent */\nexport function isRunningInsideAgent(): boolean {\n return !!detectCurrentAgent()\n}\n\n/** Check if the current environment supports interactive prompts */\nexport function isInteractive(): boolean {\n if (isRunningInsideAgent())\n return false\n if (process.env.CI)\n return false\n if (!process.stdout.isTTY)\n return false\n return true\n}\n\n/** Exit with error if interactive terminal is required but unavailable */\nexport function requireInteractive(command: string): void {\n if (!isInteractive()) {\n console.error(`Error: \\`skilld ${command}\\` requires an interactive terminal`)\n process.exit(1)\n }\n}\n\n/** Resolve agent from flags/cwd/config. cwd is source of truth over config. */\nexport function resolveAgent(agentFlag?: string): AgentType | 'none' | null {\n if (process.env.SKILLD_NO_AGENT)\n return null\n return (agentFlag as AgentType | undefined)\n ?? detectTargetAgent()\n ?? (readConfig().agent as AgentType | undefined)\n ?? null\n}\n\nlet _warnedNoAgent = false\nfunction warnNoAgent(): void {\n if (_warnedNoAgent)\n return\n _warnedNoAgent = true\n p.log.warn('No target agent detected — falling back to prompt-only mode.\\n Use --agent <name> to specify, or run `skilld config` to set a default.')\n}\n\n/** Prompt user to pick an agent when auto-detection fails */\nexport async function promptForAgent(): Promise<AgentType | 'none' | null> {\n const noAgent = !!process.env.SKILLD_NO_AGENT\n const installed = noAgent ? [] : detectInstalledAgents()\n const projectMatches = noAgent ? [] : detectProjectAgents()\n\n // Non-interactive: auto-select sole installed agent or fall back to prompt-only\n if (!isInteractive()) {\n if (installed.length === 1) {\n updateConfig({ agent: installed[0] })\n return installed[0]!\n }\n warnNoAgent()\n return 'none'\n }\n\n // Brief context before asking about agents\n p.log.info(\n `Skilld generates reference cards from package docs so your AI agent\\n`\n + ` always has accurate APIs for your exact dependency versions.`,\n )\n\n // Build options: prefer project-matched agents, then installed, then all\n const candidateIds = projectMatches.length > 0\n ? projectMatches\n : installed.length > 0\n ? installed\n : Object.keys(agents) as AgentType[]\n\n // Agents that also read .claude/skills/\n const sharedAgents = new Set(\n Object.entries(agents)\n .filter(([, a]) => a.additionalSkillsDirs.some(d => d.includes('.claude/skills')))\n .map(([id]) => id),\n )\n\n // Group: agents that share skills vs agents with their own directory\n const sharedIds = candidateIds.filter(id => id === 'claude-code' || sharedAgents.has(id))\n const isolatedIds = candidateIds.filter(id => id !== 'claude-code' && !sharedAgents.has(id))\n\n const options: Array<{ label: string, value: AgentType | 'none', hint?: string }> = []\n\n // Show shared-compatible agents first\n if (sharedIds.length > 0 && isolatedIds.length > 0) {\n for (const id of sharedIds) {\n const a = agents[id]\n const hint = id === 'claude-code'\n ? `skills shared with ${sharedIds.length - 1} other agents`\n : `skills shared with Claude Code and others`\n options.push({ label: a.displayName, value: id as AgentType, hint })\n }\n }\n\n // Agents with isolated skill dirs\n const isolatedAgentIds = new Set(\n Object.entries(agents)\n .filter(([, a]) => a.additionalSkillsDirs.length === 0)\n .map(([id]) => id),\n )\n\n for (const id of (sharedIds.length > 0 && isolatedIds.length > 0 ? isolatedIds : candidateIds)) {\n if (options.some(o => o.value === id))\n continue\n const a = agents[id]\n const hint = sharedAgents.has(id) && id !== 'claude-code'\n ? 'skills shared with Claude Code and others'\n : isolatedAgentIds.has(id)\n ? 'skills only visible to this agent'\n : undefined\n options.push({ label: a.displayName, value: id as AgentType, hint })\n }\n\n options.push({ label: 'No agent', value: 'none', hint: 'export as standalone files for any AI' })\n\n if (!_warnedNoAgent) {\n _warnedNoAgent = true\n const hint = projectMatches.length > 1\n ? `Multiple agent directories found: ${projectMatches.map(t => agents[t].displayName).join(', ')}`\n : installed.length > 0\n ? `Found ${installed.map(t => agents[t].displayName).join(', ')} but couldn't determine which to use`\n : 'No agents auto-detected'\n const crossNote = sharedIds.length > 1\n ? `\\n \\x1B[90mTip: Picking Claude Code shares skills with ${sharedIds.filter(id => id !== 'claude-code').map(id => agents[id].displayName).join(', ')} automatically.\\x1B[0m`\n : ''\n p.log.warn(`${hint}\\n Pick the agent you actively code with.${crossNote}`)\n }\n\n const choice = await p.select({\n message: 'Which AI coding agent do you use?',\n options,\n })\n\n if (p.isCancel(choice))\n return null\n\n if (choice === 'none')\n return 'none'\n\n // Save as default so they don't get asked again\n updateConfig({ agent: choice })\n p.log.success(`Target agent set to ${agents[choice].displayName}`)\n return choice\n}\n\n/** Get installed LLM generators with working CLIs (verified via --version) */\nexport function getInstalledGenerators(): Array<{ name: string, version: string }> {\n const installed = detectInstalledAgents()\n return installed\n .filter(id => agents[id].cli)\n .map((id) => {\n const ver = getAgentVersion(id)\n return ver ? { name: agents[id].displayName, version: ver } : null\n })\n .filter((a): a is { name: string, version: string } => a !== null)\n}\n\nexport function relativeTime(date: Date): string {\n const now = Date.now()\n const diff = now - date.getTime()\n const mins = Math.floor(diff / 60000)\n const hours = Math.floor(diff / 3600000)\n const days = Math.floor(diff / 86400000)\n if (mins < 1)\n return 'just now'\n if (mins < 60)\n return `${mins}m ago`\n if (hours < 24)\n return `${hours}h ago`\n return `${days}d ago`\n}\n\nexport function getLastSynced(state: ProjectState): string | null {\n let latest: Date | null = null\n for (const skill of state.skills) {\n if (skill.info?.syncedAt) {\n const d = new Date(skill.info.syncedAt)\n if (!latest || d > latest)\n latest = d\n }\n }\n return latest ? relativeTime(latest) : null\n}\n\nexport function introLine({ state, generators, modelId, agentId }: IntroOptions): string {\n const name = '\\x1B[1m\\x1B[35mskilld\\x1B[0m'\n const ver = `\\x1B[90mv${version}\\x1B[0m`\n const lastSynced = getLastSynced(state)\n const synced = lastSynced ? ` · \\x1B[90msynced ${lastSynced}\\x1B[0m` : ''\n\n // Status line: enhancement model → target agent\n const parts: string[] = []\n if (modelId)\n parts.push(getModelName(modelId as any))\n else if (generators?.length)\n parts.push(generators.map(g => `${g.name} v${g.version}`).join(', '))\n if (agentId && agents[agentId as AgentType])\n parts.push(agents[agentId as AgentType].displayName)\n const statusLine = parts.length > 0\n ? `\\n\\x1B[90m↳ ${parts.join(' → ')}\\x1B[0m`\n : ''\n\n return `${name} ${ver}${synced}${statusLine}`\n}\n\nexport function formatStatus(synced: number, outdated: number): string {\n const parts: string[] = []\n if (synced > 0)\n parts.push(`\\x1B[32m${synced} synced\\x1B[0m`)\n if (outdated > 0)\n parts.push(`\\x1B[33m${outdated} outdated\\x1B[0m`)\n return `Skills: ${parts.join(' · ')}`\n}\n\n// ── Shared UI constants ───────────────────────────────────────────────\n\nexport const OAUTH_NOTE\n = '\\x1B[33m⚠\\x1B[0m OAuth providers are disabled.\\n'\n + '\\n'\n + 'Consumer subscription OAuth impersonates official CLI clients and\\n'\n + 'violates provider Terms of Service, risking account bans.\\n'\n + '\\n'\n + 'Use API keys or native CLI tools instead:\\n'\n + ' \\x1B[36mANTHROPIC_API_KEY\\x1B[0m / \\x1B[36mclaude\\x1B[0m CLI\\n'\n + ' \\x1B[36mOPENAI_API_KEY\\x1B[0m / \\x1B[36mcodex\\x1B[0m CLI\\n'\n + ' \\x1B[36mGEMINI_API_KEY\\x1B[0m / \\x1B[36mgemini\\x1B[0m CLI'\n\nexport const NO_MODELS_MESSAGE = 'No enhancement models detected.\\n'\n + ' \\x1B[90mSkills work fine without this, you get raw docs, issues, and types.\\n'\n + ' Enhancement compresses them into a concise cheat sheet with gotchas.\\x1B[0m\\n'\n + '\\n'\n + ' To connect a model (optional):\\n'\n + ' 1. Set an env var: ANTHROPIC_API_KEY, GEMINI_API_KEY, or OPENAI_API_KEY\\n'\n + ' 2. Install a CLI tool: \\x1B[36mclaude\\x1B[0m, \\x1B[36mgemini\\x1B[0m, or \\x1B[36mcodex\\x1B[0m (restart wizard after)'\n\n/** Group models by vendor for provider→model selection. Uses vendorGroup to merge CLI and API entries under one heading. */\nexport function groupModelsByProvider<T extends { provider: string, providerName: string, vendorGroup?: string }>(models: T[]): Map<string, { name: string, models: T[] }> {\n const byVendor = new Map<string, { name: string, models: T[] }>()\n for (const m of models) {\n const key = m.vendorGroup ?? m.provider\n if (!byVendor.has(key))\n byVendor.set(key, { name: key, models: [] })\n byVendor.get(key)!.models.push(m)\n }\n return byVendor\n}\n\nexport interface ModelPickerOptions {\n /** Extra options prepended (e.g. Auto, Connect OAuth) */\n before?: Array<{ label: string, value: string, hint?: string }>\n /** Extra options appended (e.g. Skip) */\n after?: Array<{ label: string, value: string, hint?: string }>\n}\n\n/**\n * Smart provider→model picker. Skips the provider step when there's only 1 provider.\n * Returns the selected model value, or a sentinel string from before/after options.\n */\nexport async function pickModel<T extends { provider: string, providerName: string, name: string, id: string, hint: string, recommended?: boolean }>(\n models: T[],\n opts: ModelPickerOptions = {},\n): Promise<string | null> {\n const byProvider = groupModelsByProvider(models)\n const before = opts.before ?? []\n const after = opts.after ?? []\n\n // Single provider → skip provider step, show models directly\n if (byProvider.size === 1 && before.length === 0) {\n const [, group] = [...byProvider.entries()][0]!\n const choice = await p.select({\n message: `${group.name}`,\n options: [\n ...group.models.map(m => ({\n label: m.recommended ? `${m.name} (recommended - fast and cheap)` : m.name,\n value: m.id,\n hint: m.hint,\n })),\n ...after,\n ],\n })\n return p.isCancel(choice) ? null : choice as string\n }\n\n // Multiple providers or has before options - two-step\n const providerChoice = await p.select({\n message: 'Select provider',\n options: [\n ...before,\n ...Array.from(byProvider.entries(), ([key, { name, models: ms }]) => ({\n label: name,\n value: key,\n hint: `${ms.length} models`,\n })),\n ...after,\n ],\n })\n\n if (p.isCancel(providerChoice))\n return null\n\n // Check if it's a sentinel from before/after\n const providerStr = providerChoice as string\n if (before.some(o => o.value === providerStr) || after.some(o => o.value === providerStr))\n return providerStr\n\n // Drill into provider's models\n const group = byProvider.get(providerStr)!\n const modelChoice = await p.select({\n message: `Select model (${group.name})`,\n options: group.models.map(m => ({\n label: m.recommended ? `${m.name} (recommended - fast and cheap)` : m.name,\n value: m.id,\n hint: m.hint,\n })),\n })\n\n return p.isCancel(modelChoice) ? null : modelChoice as string\n}\n\n/**\n * Check if the prepare hook is already installed in package.json.\n */\nexport function hasPrepareHook(cwd: string = process.cwd()): boolean {\n const pkg = readPackageJsonSafe(join(cwd, 'package.json'))\n if (!pkg)\n return true // no package.json means nothing to suggest\n const existing = (pkg.parsed.scripts as Record<string, unknown> | undefined)?.prepare\n return typeof existing === 'string' && existing.includes('skilld')\n}\n\n/**\n * Prompt to add `skilld prepare` to package.json \"prepare\" script.\n * In non-interactive environments, falls back to an info log.\n * Returns true if the hook was added or already present.\n */\nexport async function suggestPrepareHook(cwd: string = process.cwd()): Promise<boolean> {\n const pkgJsonPath = join(cwd, 'package.json')\n const pkg = readPackageJsonSafe(pkgJsonPath)\n if (!pkg)\n return false\n\n const rawExisting = (pkg.parsed.scripts as Record<string, unknown> | undefined)?.prepare\n const existing: string | undefined = typeof rawExisting === 'string' ? rawExisting : undefined\n\n if (existing?.includes('skilld'))\n return true\n\n const prepareCmd = buildPrepareScript(existing, cwd)\n\n if (!isInteractive()) {\n p.log.info(\n `\\x1B[90mAdd to package.json scripts:\\n`\n + ` \\x1B[36m\"prepare\": \"${prepareCmd}\"\\x1B[0m\\n`\n + ` \\x1B[90mRestores references and shipped skills on install.\\x1B[0m`,\n )\n return false\n }\n\n const confirmed = await p.confirm({\n message: `Add \\x1B[36m\"prepare\": \"${prepareCmd}\"\\x1B[0m to package.json?`,\n initialValue: true,\n })\n if (p.isCancel(confirmed) || !confirmed)\n return false\n\n patchPackageJson(pkgJsonPath, (content) => {\n const tree = parseTree(content)\n const hasScripts = tree?.children?.some(c =>\n c.type === 'property' && c.children?.[0]?.value === 'scripts',\n )\n\n let patched = content\n if (!hasScripts)\n patched = editJsonProperty(patched, ['scripts'], {})\n\n return editJsonProperty(patched, ['scripts', 'prepare'], prepareCmd)\n })\n p.log.success('Added \\x1B[36mskilld prepare\\x1B[0m to package.json')\n return true\n}\n\n/**\n * Build the full prepare script value, safely appending to any existing command.\n */\nexport function buildPrepareScript(existing: string | undefined, cwd: string = process.cwd()): string {\n const bin = isNpxExecution() && !isSkilldDep(cwd) ? 'npx skilld' : 'skilld'\n const cmd = `${bin} prepare || true`\n if (!existing || !existing.trim())\n return cmd\n\n const trimmed = existing.trim()\n\n // Strip trailing && or ; that would leave a dangling operator\n const cleaned = trimmed.replace(/[&|;]+\\s*$/, '').trim()\n if (!cleaned)\n return cmd\n\n return `${cleaned} && (${cmd})`\n}\n\n/**\n * Detect if the current process was launched via npx, pnpm dlx, or similar one-shot runners.\n */\nfunction isNpxExecution(): boolean {\n // npm/pnpm set npm_command=exec when running via npx/dlx\n if (process.env.npm_command === 'exec')\n return true\n // Fallback: check if the resolved binary path contains npx or dlx cache dirs\n const execPath = process.env._ || ''\n return /npx|\\.store|dlx/.test(execPath)\n}\n\n/**\n * Check if skilld is listed as a dependency (dev or regular) in the project's package.json.\n */\nfunction isSkilldDep(cwd: string): boolean {\n const pkg = readPackageJsonSafe(join(cwd, 'package.json'))\n if (!pkg)\n return false\n const deps = pkg.parsed as Record<string, any>\n return !!(deps.dependencies?.skilld || deps.devDependencies?.skilld)\n}\n\nexport function getRepoHint(name: string, cwd: string): string | undefined {\n const result = readPackageJsonSafe(join(cwd, 'node_modules', name, 'package.json'))\n if (!result)\n return undefined\n const pkg = result.parsed as Record<string, any>\n const url = typeof pkg.repository === 'string'\n ? pkg.repository\n : pkg.repository?.url\n if (!url)\n return undefined\n return url\n .replace(/^git\\+/, '')\n .replace(/\\.git$/, '')\n .replace(/^git:\\/\\//, 'https://')\n .replace(/^ssh:\\/\\/git@github\\.com/, 'https://github.com')\n .replace(/^https?:\\/\\/(www\\.)?github\\.com\\//, '')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli-helpers.mjs","names":["dirname","resolve","agents"],"sources":["../../src/version.ts","../../src/cli-helpers.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n// Walk up from current file to find package.json (works in both src/ and dist/_chunks/)\nfunction findPackageJson(): string {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let i = 0; i < 5; i++) {\n const candidate = resolve(dir, 'package.json')\n try {\n const content = readFileSync(candidate, 'utf8')\n if (content)\n return content\n }\n catch {}\n dir = resolve(dir, '..')\n }\n return '{\"version\":\"0.0.0\"}'\n}\n\nexport const version: string = JSON.parse(findPackageJson()).version\n","/**\n * Shared CLI helpers used by subcommand definitions and the main CLI entry.\n * Extracted to avoid circular deps between cli.ts and commands/*.ts.\n */\n\nimport type { AgentType, OptimizeModel } from './agent/index.ts'\nimport type { ProjectState } from './core/skills.ts'\nimport * as p from '@clack/prompts'\nimport { parseTree } from 'jsonc-parser'\nimport { join } from 'pathe'\nimport { detectCurrentAgent } from 'unagent/env'\nimport { agents, detectInstalledAgents, detectProjectAgents, detectTargetAgent, getAgentVersion, getModelName } from './agent/index.ts'\nimport { readConfig, updateConfig } from './core/config.ts'\nimport { editJsonProperty, patchPackageJson, readPackageJsonSafe } from './core/package-json.ts'\nimport { version } from './version.ts'\n\nexport type { AgentType, OptimizeModel }\n\nexport interface IntroOptions {\n state: ProjectState\n /** Installed CLIs that can serve as enhancement models */\n generators?: Array<{ name: string, version: string }>\n /** Configured enhancement model ID */\n modelId?: string\n /** Resolved target agent ID */\n agentId?: string\n}\n\nexport const sharedArgs = {\n global: {\n type: 'boolean' as const,\n alias: 'g',\n description: 'Install globally to ~/<agent>/skills',\n default: false,\n },\n agent: {\n type: 'enum' as const,\n options: Object.keys(agents),\n alias: 'a',\n description: 'Target agent — where skills are installed',\n },\n model: {\n type: 'string' as const,\n alias: 'm',\n description: 'Enhancement model for SKILL.md generation',\n valueHint: 'id',\n },\n yes: {\n type: 'boolean' as const,\n alias: 'y',\n description: 'Skip prompts, use defaults',\n default: false,\n },\n force: {\n type: 'boolean' as const,\n alias: 'f',\n description: 'Ignore all caches, re-fetch docs and regenerate',\n default: false,\n },\n debug: {\n type: 'boolean' as const,\n description: 'Save raw enhancement output to logs/ for each section',\n default: false,\n },\n}\n\n// ── Menu loop utility ─────────────────────────────────────────────────\n\n/** Thrown when a clack prompt is cancelled inside a menuLoop handler */\nexport class MenuCancel extends Error { override name = 'MenuCancel' }\n\n/** Assert a clack prompt result is not cancelled. Throws MenuCancel if cancelled. */\nexport function guard<T>(value: T | symbol): T {\n if (p.isCancel(value))\n throw new MenuCancel()\n return value as T\n}\n\nexport interface MenuOption {\n label: string\n value: string\n hint?: string\n}\n\n/**\n * Run a select menu in a loop with automatic back-navigation.\n *\n * - Cancel (Escape) at the menu itself → exits (returns)\n * - Cancel inside a handler (via guard()) → caught, loops back to menu\n * - Handler returns truthy → exits (returns)\n * - Handler returns void/false → loops back to menu\n *\n * Options are rebuilt each iteration so hints stay fresh after changes.\n */\nexport async function menuLoop(opts: {\n message: string\n options: () => MenuOption[] | Promise<MenuOption[]>\n onSelect: (value: string) => Promise<boolean | void>\n initialValue?: string | (() => string | undefined)\n /** Use fuzzy-searchable autocomplete instead of static select */\n searchable?: boolean\n}): Promise<void> {\n while (true) {\n const options = await opts.options()\n const initial = typeof opts.initialValue === 'function' ? opts.initialValue() : opts.initialValue\n const choice = opts.searchable\n ? await p.autocomplete({ message: opts.message, options, ...(initial != null ? { initialValue: initial } : {}) })\n : await p.select({ message: opts.message, options, ...(initial != null ? { initialValue: initial } : {}) })\n if (p.isCancel(choice))\n return\n try {\n if (await opts.onSelect(choice as string))\n return\n }\n catch (err) {\n if (err instanceof MenuCancel)\n continue\n throw err\n }\n }\n}\n\n/** Check if we're running inside an AI coding agent */\nexport function isRunningInsideAgent(): boolean {\n return !!detectCurrentAgent()\n}\n\n/** Check if the current environment supports interactive prompts */\nexport function isInteractive(): boolean {\n if (isRunningInsideAgent())\n return false\n if (process.env.CI)\n return false\n if (!process.stdout.isTTY)\n return false\n return true\n}\n\n/** Exit with error if interactive terminal is required but unavailable */\nexport function requireInteractive(command: string): void {\n if (!isInteractive()) {\n console.error(`Error: \\`skilld ${command}\\` requires an interactive terminal`)\n process.exit(1)\n }\n}\n\n/** Resolve agent from flags/cwd/config. cwd is source of truth over config. */\nexport function resolveAgent(agentFlag?: string): AgentType | 'none' | null {\n if (process.env.SKILLD_NO_AGENT)\n return null\n return (agentFlag as AgentType | undefined)\n ?? detectTargetAgent()\n ?? (readConfig().agent as AgentType | undefined)\n ?? null\n}\n\nlet _warnedNoAgent = false\nfunction warnNoAgent(): void {\n if (_warnedNoAgent)\n return\n _warnedNoAgent = true\n p.log.warn('No target agent detected — falling back to prompt-only mode.\\n Use --agent <name> to specify, or run `skilld config` to set a default.')\n}\n\n/** Prompt user to pick an agent when auto-detection fails */\nexport async function promptForAgent(): Promise<AgentType | 'none' | null> {\n const noAgent = !!process.env.SKILLD_NO_AGENT\n const installed = noAgent ? [] : detectInstalledAgents()\n const projectMatches = noAgent ? [] : detectProjectAgents()\n\n // Non-interactive: auto-select sole installed agent or fall back to prompt-only\n if (!isInteractive()) {\n if (installed.length === 1) {\n updateConfig({ agent: installed[0] })\n return installed[0]!\n }\n warnNoAgent()\n return 'none'\n }\n\n // Brief context before asking about agents\n p.log.info(\n `Skilld generates reference cards from package docs so your AI agent\\n`\n + ` always has accurate APIs for your exact dependency versions.`,\n )\n\n // Build options: prefer project-matched agents, then installed, then all\n const candidateIds = projectMatches.length > 0\n ? projectMatches\n : installed.length > 0\n ? installed\n : Object.keys(agents) as AgentType[]\n\n // Agents that also read .claude/skills/\n const sharedAgents = new Set(\n Object.entries(agents)\n .filter(([, a]) => a.additionalSkillsDirs.some(d => d.includes('.claude/skills')))\n .map(([id]) => id),\n )\n\n // Group: agents that share skills vs agents with their own directory\n const sharedIds = candidateIds.filter(id => id === 'claude-code' || sharedAgents.has(id))\n const isolatedIds = candidateIds.filter(id => id !== 'claude-code' && !sharedAgents.has(id))\n\n const options: Array<{ label: string, value: AgentType | 'none', hint?: string }> = []\n\n // Show shared-compatible agents first\n if (sharedIds.length > 0 && isolatedIds.length > 0) {\n for (const id of sharedIds) {\n const a = agents[id]\n const hint = id === 'claude-code'\n ? `skills shared with ${sharedIds.length - 1} other agents`\n : `skills shared with Claude Code and others`\n options.push({ label: a.displayName, value: id as AgentType, hint })\n }\n }\n\n // Agents with isolated skill dirs\n const isolatedAgentIds = new Set(\n Object.entries(agents)\n .filter(([, a]) => a.additionalSkillsDirs.length === 0)\n .map(([id]) => id),\n )\n\n for (const id of (sharedIds.length > 0 && isolatedIds.length > 0 ? isolatedIds : candidateIds)) {\n if (options.some(o => o.value === id))\n continue\n const a = agents[id]\n const hint = sharedAgents.has(id) && id !== 'claude-code'\n ? 'skills shared with Claude Code and others'\n : isolatedAgentIds.has(id)\n ? 'skills only visible to this agent'\n : undefined\n options.push({ label: a.displayName, value: id as AgentType, hint })\n }\n\n options.push({ label: 'No agent', value: 'none', hint: 'export as standalone files for any AI' })\n\n if (!_warnedNoAgent) {\n _warnedNoAgent = true\n const hint = projectMatches.length > 1\n ? `Multiple agent directories found: ${projectMatches.map(t => agents[t].displayName).join(', ')}`\n : installed.length > 0\n ? `Found ${installed.map(t => agents[t].displayName).join(', ')} but couldn't determine which to use`\n : 'No agents auto-detected'\n const crossNote = sharedIds.length > 1\n ? `\\n \\x1B[90mTip: Picking Claude Code shares skills with ${sharedIds.filter(id => id !== 'claude-code').map(id => agents[id].displayName).join(', ')} automatically.\\x1B[0m`\n : ''\n p.log.warn(`${hint}\\n Pick the agent you actively code with.${crossNote}`)\n }\n\n const choice = await p.select({\n message: 'Which AI coding agent do you use?',\n options,\n })\n\n if (p.isCancel(choice))\n return null\n\n if (choice === 'none')\n return 'none'\n\n // Save as default so they don't get asked again\n updateConfig({ agent: choice })\n p.log.success(`Target agent set to ${agents[choice].displayName}`)\n return choice\n}\n\n/** Get installed LLM generators with working CLIs (verified via --version) */\nexport function getInstalledGenerators(): Array<{ name: string, version: string }> {\n const installed = detectInstalledAgents()\n return installed\n .filter(id => agents[id].cli)\n .map((id) => {\n const ver = getAgentVersion(id)\n return ver ? { name: agents[id].displayName, version: ver } : null\n })\n .filter((a): a is { name: string, version: string } => a !== null)\n}\n\nexport function relativeTime(date: Date): string {\n const now = Date.now()\n const diff = now - date.getTime()\n const mins = Math.floor(diff / 60000)\n const hours = Math.floor(diff / 3600000)\n const days = Math.floor(diff / 86400000)\n if (mins < 1)\n return 'just now'\n if (mins < 60)\n return `${mins}m ago`\n if (hours < 24)\n return `${hours}h ago`\n return `${days}d ago`\n}\n\nexport function getLastSynced(state: ProjectState): string | null {\n let latest: Date | null = null\n for (const skill of state.skills) {\n if (skill.info?.syncedAt) {\n const d = new Date(skill.info.syncedAt)\n if (!latest || d > latest)\n latest = d\n }\n }\n return latest ? relativeTime(latest) : null\n}\n\nexport function introLine({ state, generators, modelId, agentId }: IntroOptions): string {\n const name = '\\x1B[1m\\x1B[35mskilld\\x1B[0m'\n const ver = `\\x1B[90mv${version}\\x1B[0m`\n const lastSynced = getLastSynced(state)\n const synced = lastSynced ? ` · \\x1B[90msynced ${lastSynced}\\x1B[0m` : ''\n\n // Status line: enhancement model → target agent\n const parts: string[] = []\n if (modelId)\n parts.push(getModelName(modelId as any))\n else if (generators?.length)\n parts.push(generators.map(g => `${g.name} v${g.version}`).join(', '))\n if (agentId && agents[agentId as AgentType])\n parts.push(agents[agentId as AgentType].displayName)\n const statusLine = parts.length > 0\n ? `\\n\\x1B[90m↳ ${parts.join(' → ')}\\x1B[0m`\n : ''\n\n return `${name} ${ver}${synced}${statusLine}`\n}\n\nexport function formatStatus(synced: number, outdated: number): string {\n const parts: string[] = []\n if (synced > 0)\n parts.push(`\\x1B[32m${synced} synced\\x1B[0m`)\n if (outdated > 0)\n parts.push(`\\x1B[33m${outdated} outdated\\x1B[0m`)\n return `Skills: ${parts.join(' · ')}`\n}\n\n// ── Shared UI constants ───────────────────────────────────────────────\n\nexport const OAUTH_NOTE\n = '\\x1B[33m⚠\\x1B[0m OAuth providers are disabled.\\n'\n + '\\n'\n + 'Consumer subscription OAuth impersonates official CLI clients and\\n'\n + 'violates provider Terms of Service, risking account bans.\\n'\n + '\\n'\n + 'Use API keys or native CLI tools instead:\\n'\n + ' \\x1B[36mANTHROPIC_API_KEY\\x1B[0m / \\x1B[36mclaude\\x1B[0m CLI\\n'\n + ' \\x1B[36mOPENAI_API_KEY\\x1B[0m / \\x1B[36mcodex\\x1B[0m CLI\\n'\n + ' \\x1B[36mGEMINI_API_KEY\\x1B[0m / \\x1B[36mgemini\\x1B[0m CLI'\n\nexport const NO_MODELS_MESSAGE = 'No enhancement models detected.\\n'\n + ' \\x1B[90mSkills work fine without this, you get raw docs, issues, and types.\\n'\n + ' Enhancement compresses them into a concise cheat sheet with gotchas.\\x1B[0m\\n'\n + '\\n'\n + ' To connect a model (optional):\\n'\n + ' 1. Set an env var: ANTHROPIC_API_KEY, GEMINI_API_KEY, or OPENAI_API_KEY\\n'\n + ' 2. Install a CLI tool: \\x1B[36mclaude\\x1B[0m, \\x1B[36mgemini\\x1B[0m, or \\x1B[36mcodex\\x1B[0m (restart wizard after)'\n\n/** Group models by vendor for provider→model selection. Uses vendorGroup to merge CLI and API entries under one heading. */\nexport function groupModelsByProvider<T extends { provider: string, providerName: string, vendorGroup?: string }>(models: T[]): Map<string, { name: string, models: T[] }> {\n const byVendor = new Map<string, { name: string, models: T[] }>()\n for (const m of models) {\n const key = m.vendorGroup ?? m.provider\n if (!byVendor.has(key))\n byVendor.set(key, { name: key, models: [] })\n byVendor.get(key)!.models.push(m)\n }\n return byVendor\n}\n\nexport interface ModelPickerOptions {\n /** Extra options prepended (e.g. Auto, Connect OAuth) */\n before?: Array<{ label: string, value: string, hint?: string }>\n /** Extra options appended (e.g. Skip) */\n after?: Array<{ label: string, value: string, hint?: string }>\n}\n\n/**\n * Smart provider→model picker. Skips the provider step when there's only 1 provider.\n * Returns the selected model value, or a sentinel string from before/after options.\n */\nexport async function pickModel<T extends { provider: string, providerName: string, name: string, id: string, hint: string, recommended?: boolean }>(\n models: T[],\n opts: ModelPickerOptions = {},\n): Promise<string | null> {\n const byProvider = groupModelsByProvider(models)\n const before = opts.before ?? []\n const after = opts.after ?? []\n\n // Single provider → skip provider step, show models directly\n if (byProvider.size === 1 && before.length === 0) {\n const [, group] = [...byProvider.entries()][0]!\n const choice = await p.select({\n message: `${group.name}`,\n options: [\n ...group.models.map(m => ({\n label: m.recommended ? `${m.name} (recommended - fast and cheap)` : m.name,\n value: m.id,\n hint: m.hint,\n })),\n ...after,\n ],\n })\n return p.isCancel(choice) ? null : choice as string\n }\n\n // Multiple providers or has before options - two-step\n const providerChoice = await p.select({\n message: 'Select provider',\n options: [\n ...before,\n ...Array.from(byProvider.entries(), ([key, { name, models: ms }]) => ({\n label: name,\n value: key,\n hint: `${ms.length} models`,\n })),\n ...after,\n ],\n })\n\n if (p.isCancel(providerChoice))\n return null\n\n // Check if it's a sentinel from before/after\n const providerStr = providerChoice as string\n if (before.some(o => o.value === providerStr) || after.some(o => o.value === providerStr))\n return providerStr\n\n // Drill into provider's models\n const group = byProvider.get(providerStr)!\n const modelChoice = await p.select({\n message: `Select model (${group.name})`,\n options: group.models.map(m => ({\n label: m.recommended ? `${m.name} (recommended - fast and cheap)` : m.name,\n value: m.id,\n hint: m.hint,\n })),\n })\n\n return p.isCancel(modelChoice) ? null : modelChoice as string\n}\n\n/**\n * Check if the prepare hook is already installed in package.json.\n */\nexport function hasPrepareHook(cwd: string = process.cwd()): boolean {\n const pkg = readPackageJsonSafe(join(cwd, 'package.json'))\n if (!pkg)\n return true // no package.json means nothing to suggest\n const existing = (pkg.parsed.scripts as Record<string, unknown> | undefined)?.prepare\n return typeof existing === 'string' && existing.includes('skilld')\n}\n\n/**\n * Prompt to add `skilld prepare` to package.json \"prepare\" script.\n * In non-interactive environments, falls back to an info log.\n * Returns true if the hook was added or already present.\n */\nexport async function suggestPrepareHook(cwd: string = process.cwd()): Promise<boolean> {\n const pkgJsonPath = join(cwd, 'package.json')\n const pkg = readPackageJsonSafe(pkgJsonPath)\n if (!pkg)\n return false\n\n const rawExisting = (pkg.parsed.scripts as Record<string, unknown> | undefined)?.prepare\n const existing: string | undefined = typeof rawExisting === 'string' ? rawExisting : undefined\n\n if (existing?.includes('skilld'))\n return true\n\n const prepareCmd = buildPrepareScript(existing, cwd)\n\n if (!isInteractive()) {\n p.log.info(\n `\\x1B[90mAdd to package.json scripts:\\n`\n + ` \\x1B[36m\"prepare\": \"${prepareCmd}\"\\x1B[0m\\n`\n + ` \\x1B[90mRestores references and shipped skills on install.\\x1B[0m`,\n )\n return false\n }\n\n const confirmed = await p.confirm({\n message: `Add \\x1B[36m\"prepare\": \"${prepareCmd}\"\\x1B[0m to package.json?`,\n initialValue: true,\n })\n if (p.isCancel(confirmed) || !confirmed)\n return false\n\n patchPackageJson(pkgJsonPath, (content) => {\n const tree = parseTree(content)\n const hasScripts = tree?.children?.some(c =>\n c.type === 'property' && c.children?.[0]?.value === 'scripts',\n )\n\n let patched = content\n if (!hasScripts)\n patched = editJsonProperty(patched, ['scripts'], {})\n\n return editJsonProperty(patched, ['scripts', 'prepare'], prepareCmd)\n })\n p.log.success('Added \\x1B[36mskilld prepare\\x1B[0m to package.json')\n return true\n}\n\n/**\n * Build the full prepare script value, safely appending to any existing command.\n */\nexport function buildPrepareScript(existing: string | undefined, cwd: string = process.cwd()): string {\n const bin = isNpxExecution() && !isSkilldDep(cwd) ? 'npx skilld' : 'skilld'\n const cmd = `${bin} prepare || true`\n if (!existing || !existing.trim())\n return cmd\n\n const trimmed = existing.trim()\n\n // Strip trailing && or ; that would leave a dangling operator\n const cleaned = trimmed.replace(/[&|;]+\\s*$/, '').trim()\n if (!cleaned)\n return cmd\n\n return `${cleaned} && (${cmd})`\n}\n\n/**\n * Detect if the current process was launched via npx, pnpm dlx, or similar one-shot runners.\n */\nfunction isNpxExecution(): boolean {\n // npm/pnpm set npm_command=exec when running via npx/dlx\n if (process.env.npm_command === 'exec')\n return true\n // Fallback: check if the resolved binary path contains npx or dlx cache dirs\n const execPath = process.env._ || ''\n return /npx|\\.store|dlx/.test(execPath)\n}\n\n/**\n * Check if skilld is listed as a dependency (dev or regular) in the project's package.json.\n */\nfunction isSkilldDep(cwd: string): boolean {\n const pkg = readPackageJsonSafe(join(cwd, 'package.json'))\n if (!pkg)\n return false\n const deps = pkg.parsed as Record<string, any>\n return !!(deps.dependencies?.skilld || deps.devDependencies?.skilld)\n}\n\nexport function getRepoHint(name: string, cwd: string): string | undefined {\n const result = readPackageJsonSafe(join(cwd, 'node_modules', name, 'package.json'))\n if (!result)\n return undefined\n const pkg = result.parsed as Record<string, any>\n const url = typeof pkg.repository === 'string'\n ? pkg.repository\n : pkg.repository?.url\n if (!url)\n return undefined\n return url\n .replace(/^git\\+/, '')\n .replace(/\\.git$/, '')\n .replace(/^git:\\/\\//, 'https://')\n .replace(/^ssh:\\/\\/git@github\\.com/, 'https://github.com')\n .replace(/^https?:\\/\\/(www\\.)?github\\.com\\//, '')\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/core/config.ts"],"sourcesContent":["import type { OptimizeModel } from '../agent/index.ts'\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'pathe'\nimport { yamlEscape, yamlParseKV, yamlUnescape } from './yaml.ts'\n\nexport interface FeaturesConfig {\n search: boolean\n issues: boolean\n discussions: boolean\n releases: boolean\n}\n\nexport const defaultFeatures: FeaturesConfig = {\n search: true,\n issues: true,\n discussions: true,\n releases: true,\n}\n\nexport interface SkilldConfig {\n model?: OptimizeModel\n agent?: string\n features?: FeaturesConfig\n projects?: string[]\n skipLlm?: boolean\n}\n\nconst CONFIG_DIR = join(homedir(), '.skilld')\nconst CONFIG_PATH = join(CONFIG_DIR, 'config.yaml')\n\nlet configCache: SkilldConfig | undefined\n\nexport function hasConfig(): boolean {\n return existsSync(CONFIG_PATH)\n}\n\n/** Whether the first-run wizard has been completed (not just agent selection) */\nexport function hasCompletedWizard(): boolean {\n if (!existsSync(CONFIG_PATH))\n return false\n const config = readConfig()\n return config.features !== undefined || config.model !== undefined || config.skipLlm !== undefined\n}\n\nexport function readConfig(): SkilldConfig {\n if (configCache) {\n return {\n ...configCache,\n features: configCache.features ? { ...configCache.features } : undefined,\n projects: configCache.projects ? [...configCache.projects] : undefined,\n }\n }\n if (!existsSync(CONFIG_PATH))\n return {}\n\n const content = readFileSync(CONFIG_PATH, 'utf-8')\n const config: SkilldConfig = {}\n let inBlock: 'projects' | 'features' | null = null\n const projects: string[] = []\n const features: Partial<FeaturesConfig> = {}\n\n for (const line of content.split('\\n')) {\n if (line.startsWith('projects:')) {\n inBlock = 'projects'\n continue\n }\n if (line.startsWith('features:')) {\n inBlock = 'features'\n continue\n }\n if (inBlock === 'projects') {\n if (line.startsWith(' - ')) {\n projects.push(yamlUnescape(line.slice(4)))\n continue\n }\n inBlock = null\n }\n if (inBlock === 'features') {\n const m = line.match(/^ {2}(\\w+):\\s*(.+)/)\n if (m) {\n const key = m[1] as keyof FeaturesConfig\n if (key in defaultFeatures)\n features[key] = m[2] === 'true'\n continue\n }\n inBlock = null\n }\n const kv = yamlParseKV(line)\n if (!kv)\n continue\n const [key, value] = kv\n if (key === 'model' && value)\n config.model = value as OptimizeModel\n if (key === 'agent' && value)\n config.agent = value\n if (key === 'skipLlm')\n config.skipLlm = value === 'true'\n }\n\n if (projects.length > 0)\n config.projects = projects\n if (Object.keys(features).length > 0)\n config.features = { ...defaultFeatures, ...features }\n configCache = config\n return config\n}\n\nexport function writeConfig(config: SkilldConfig): void {\n mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })\n\n let yaml = ''\n if (config.model)\n yaml += `model: ${config.model}\\n`\n if (config.agent)\n yaml += `agent: ${config.agent}\\n`\n if (config.skipLlm)\n yaml += `skipLlm: true\\n`\n if (config.features) {\n yaml += 'features:\\n'\n for (const [k, v] of Object.entries(config.features)) {\n yaml += ` ${k}: ${v}\\n`\n }\n }\n if (config.projects?.length) {\n yaml += 'projects:\\n'\n for (const p of config.projects) {\n yaml += ` - ${yamlEscape(p)}\\n`\n }\n }\n\n writeFileSync(CONFIG_PATH, yaml, { mode: 0o600 })\n configCache = undefined\n}\n\nexport function updateConfig(updates: Partial<SkilldConfig>): void {\n const config = readConfig()\n writeConfig({ ...config, ...updates })\n}\n\nexport function registerProject(projectPath: string): void {\n const config = readConfig()\n const projects = new Set(config.projects || [])\n projects.add(projectPath)\n writeConfig({ ...config, projects: [...projects] })\n}\n\nexport function unregisterProject(projectPath: string): void {\n const config = readConfig()\n const projects = (config.projects || []).filter(p => p !== projectPath)\n writeConfig({ ...config, projects })\n}\n\nexport function getRegisteredProjects(): string[] {\n return readConfig().projects || []\n}\n"],"mappings":";;;;AAaA,MAAa,kBAAkC;CAC7C,QAAQ;CACR,QAAQ;CACR,aAAa;CACb,UAAU;CACX;AAUD,MAAM,aAAa,KAAK,SAAS,EAAE,UAAU;AAC7C,MAAM,cAAc,KAAK,YAAY,cAAc;AAEnD,IAAI;AAEJ,SAAgB,YAAqB;
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/core/config.ts"],"sourcesContent":["import type { OptimizeModel } from '../agent/index.ts'\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'pathe'\nimport { yamlEscape, yamlParseKV, yamlUnescape } from './yaml.ts'\n\nexport interface FeaturesConfig {\n search: boolean\n issues: boolean\n discussions: boolean\n releases: boolean\n}\n\nexport const defaultFeatures: FeaturesConfig = {\n search: true,\n issues: true,\n discussions: true,\n releases: true,\n}\n\nexport interface SkilldConfig {\n model?: OptimizeModel\n agent?: string\n features?: FeaturesConfig\n projects?: string[]\n skipLlm?: boolean\n}\n\nconst CONFIG_DIR = join(homedir(), '.skilld')\nconst CONFIG_PATH = join(CONFIG_DIR, 'config.yaml')\n\nlet configCache: SkilldConfig | undefined\n\nexport function hasConfig(): boolean {\n return existsSync(CONFIG_PATH)\n}\n\n/** Whether the first-run wizard has been completed (not just agent selection) */\nexport function hasCompletedWizard(): boolean {\n if (!existsSync(CONFIG_PATH))\n return false\n const config = readConfig()\n return config.features !== undefined || config.model !== undefined || config.skipLlm !== undefined\n}\n\nexport function readConfig(): SkilldConfig {\n if (configCache) {\n return {\n ...configCache,\n features: configCache.features ? { ...configCache.features } : undefined,\n projects: configCache.projects ? [...configCache.projects] : undefined,\n }\n }\n if (!existsSync(CONFIG_PATH))\n return {}\n\n const content = readFileSync(CONFIG_PATH, 'utf-8')\n const config: SkilldConfig = {}\n let inBlock: 'projects' | 'features' | null = null\n const projects: string[] = []\n const features: Partial<FeaturesConfig> = {}\n\n for (const line of content.split('\\n')) {\n if (line.startsWith('projects:')) {\n inBlock = 'projects'\n continue\n }\n if (line.startsWith('features:')) {\n inBlock = 'features'\n continue\n }\n if (inBlock === 'projects') {\n if (line.startsWith(' - ')) {\n projects.push(yamlUnescape(line.slice(4)))\n continue\n }\n inBlock = null\n }\n if (inBlock === 'features') {\n const m = line.match(/^ {2}(\\w+):\\s*(.+)/)\n if (m) {\n const key = m[1] as keyof FeaturesConfig\n if (key in defaultFeatures)\n features[key] = m[2] === 'true'\n continue\n }\n inBlock = null\n }\n const kv = yamlParseKV(line)\n if (!kv)\n continue\n const [key, value] = kv\n if (key === 'model' && value)\n config.model = value as OptimizeModel\n if (key === 'agent' && value)\n config.agent = value\n if (key === 'skipLlm')\n config.skipLlm = value === 'true'\n }\n\n if (projects.length > 0)\n config.projects = projects\n if (Object.keys(features).length > 0)\n config.features = { ...defaultFeatures, ...features }\n configCache = config\n return config\n}\n\nexport function writeConfig(config: SkilldConfig): void {\n mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })\n\n let yaml = ''\n if (config.model)\n yaml += `model: ${config.model}\\n`\n if (config.agent)\n yaml += `agent: ${config.agent}\\n`\n if (config.skipLlm)\n yaml += `skipLlm: true\\n`\n if (config.features) {\n yaml += 'features:\\n'\n for (const [k, v] of Object.entries(config.features)) {\n yaml += ` ${k}: ${v}\\n`\n }\n }\n if (config.projects?.length) {\n yaml += 'projects:\\n'\n for (const p of config.projects) {\n yaml += ` - ${yamlEscape(p)}\\n`\n }\n }\n\n writeFileSync(CONFIG_PATH, yaml, { mode: 0o600 })\n configCache = undefined\n}\n\nexport function updateConfig(updates: Partial<SkilldConfig>): void {\n const config = readConfig()\n writeConfig({ ...config, ...updates })\n}\n\nexport function registerProject(projectPath: string): void {\n const config = readConfig()\n const projects = new Set(config.projects || [])\n projects.add(projectPath)\n writeConfig({ ...config, projects: [...projects] })\n}\n\nexport function unregisterProject(projectPath: string): void {\n const config = readConfig()\n const projects = (config.projects || []).filter(p => p !== projectPath)\n writeConfig({ ...config, projects })\n}\n\nexport function getRegisteredProjects(): string[] {\n return readConfig().projects || []\n}\n"],"mappings":"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"}
|