skilld 1.5.4 → 1.6.2
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/README.md +3 -3
- package/dist/_chunks/agent.mjs +10 -78
- package/dist/_chunks/agent.mjs.map +1 -1
- package/dist/_chunks/assemble.mjs +0 -17
- package/dist/_chunks/assemble.mjs.map +1 -1
- package/dist/_chunks/author.mjs +0 -18
- package/dist/_chunks/author.mjs.map +1 -1
- package/dist/_chunks/cache.mjs +0 -72
- package/dist/_chunks/cache.mjs.map +1 -1
- package/dist/_chunks/cache2.mjs +84 -17
- package/dist/_chunks/cache2.mjs.map +1 -1
- package/dist/_chunks/cli-helpers.mjs +2 -49
- package/dist/_chunks/cli-helpers.mjs.map +1 -1
- package/dist/_chunks/cli-helpers2.mjs +0 -11
- package/dist/_chunks/config.mjs +0 -27
- package/dist/_chunks/config.mjs.map +1 -1
- package/dist/_chunks/core.mjs +9 -0
- package/dist/_chunks/detect.mjs +29 -226
- package/dist/_chunks/detect.mjs.map +1 -1
- package/dist/_chunks/embedding-cache.mjs +0 -5
- package/dist/_chunks/embedding-cache2.mjs +1 -2
- package/dist/_chunks/formatting.mjs +0 -6
- package/dist/_chunks/formatting.mjs.map +1 -1
- package/dist/_chunks/index.d.mts +0 -10
- package/dist/_chunks/index.d.mts.map +1 -1
- package/dist/_chunks/index2.d.mts +3 -6
- package/dist/_chunks/index2.d.mts.map +1 -1
- package/dist/_chunks/index3.d.mts +2 -31
- package/dist/_chunks/index3.d.mts.map +1 -1
- package/dist/_chunks/install.mjs +0 -15
- package/dist/_chunks/install.mjs.map +1 -1
- package/dist/_chunks/libs/@sinclair/typebox.mjs +0 -444
- package/dist/_chunks/libs/@sinclair/typebox.mjs.map +1 -1
- package/dist/_chunks/list.mjs +0 -16
- package/dist/_chunks/list.mjs.map +1 -1
- package/dist/_chunks/lockfile.mjs +1 -10
- package/dist/_chunks/lockfile.mjs.map +1 -1
- package/dist/_chunks/markdown.mjs +0 -9
- package/dist/_chunks/markdown.mjs.map +1 -1
- package/dist/_chunks/package-json.mjs +0 -25
- package/dist/_chunks/package-json.mjs.map +1 -1
- package/dist/_chunks/pool2.mjs +0 -2
- package/dist/_chunks/pool2.mjs.map +1 -1
- package/dist/_chunks/prepare.mjs +8 -7
- package/dist/_chunks/prepare.mjs.map +1 -1
- package/dist/_chunks/prepare2.mjs +0 -18
- package/dist/_chunks/prepare2.mjs.map +1 -1
- package/dist/_chunks/prompts.mjs +1 -102
- package/dist/_chunks/prompts.mjs.map +1 -1
- package/dist/_chunks/retriv.mjs +23 -24
- package/dist/_chunks/retriv.mjs.map +1 -1
- package/dist/_chunks/rolldown-runtime.mjs +0 -2
- package/dist/_chunks/sanitize.mjs +0 -78
- package/dist/_chunks/sanitize.mjs.map +1 -1
- package/dist/_chunks/search-interactive.mjs +0 -17
- package/dist/_chunks/search-interactive.mjs.map +1 -1
- package/dist/_chunks/search.mjs +0 -19
- package/dist/_chunks/search2.mjs +3 -12
- package/dist/_chunks/search2.mjs.map +1 -1
- package/dist/_chunks/setup.mjs +0 -13
- package/dist/_chunks/setup.mjs.map +1 -1
- package/dist/_chunks/shared.mjs +0 -10
- package/dist/_chunks/shared.mjs.map +1 -1
- package/dist/_chunks/skills.mjs +2 -2
- package/dist/_chunks/skills.mjs.map +1 -1
- package/dist/_chunks/sources.mjs +3 -453
- package/dist/_chunks/sources.mjs.map +1 -1
- package/dist/_chunks/sync-shared.mjs +0 -16
- package/dist/_chunks/sync-shared2.mjs +0 -42
- package/dist/_chunks/sync-shared2.mjs.map +1 -1
- package/dist/_chunks/sync.mjs +1 -21
- package/dist/_chunks/sync.mjs.map +1 -1
- package/dist/_chunks/sync2.mjs +0 -20
- package/dist/_chunks/types.d.mts +0 -2
- package/dist/_chunks/types.d.mts.map +1 -1
- package/dist/_chunks/uninstall.mjs +0 -25
- package/dist/_chunks/uninstall.mjs.map +1 -1
- package/dist/_chunks/validate.mjs +0 -7
- package/dist/_chunks/validate.mjs.map +1 -1
- package/dist/_chunks/wizard.mjs +15 -12
- package/dist/_chunks/wizard.mjs.map +1 -1
- package/dist/_chunks/yaml.mjs +0 -21
- package/dist/_chunks/yaml.mjs.map +1 -1
- package/dist/agent/index.d.mts +0 -24
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +0 -8
- package/dist/cache/index.mjs +0 -2
- package/dist/cli-entry.mjs +0 -6
- package/dist/cli-entry.mjs.map +1 -1
- package/dist/cli.mjs +37 -44
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +0 -6
- package/dist/prepare.mjs +0 -12
- package/dist/prepare.mjs.map +1 -1
- package/dist/retriv/index.mjs +0 -2
- package/dist/retriv/worker.d.mts +0 -3
- package/dist/retriv/worker.d.mts.map +1 -1
- package/dist/retriv/worker.mjs +0 -2
- package/dist/retriv/worker.mjs.map +1 -1
- package/dist/sources/index.mjs +0 -4
- package/package.json +17 -17
package/dist/_chunks/list.mjs
CHANGED
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
import "./config.mjs";
|
|
2
|
-
import "./package-json.mjs";
|
|
3
|
-
import "./prepare.mjs";
|
|
4
|
-
import "./sanitize.mjs";
|
|
5
|
-
import "./cache.mjs";
|
|
6
|
-
import "./yaml.mjs";
|
|
7
|
-
import "./markdown.mjs";
|
|
8
|
-
import "./shared.mjs";
|
|
9
|
-
import "./sources.mjs";
|
|
10
|
-
import "./detect.mjs";
|
|
11
|
-
import "./prompts.mjs";
|
|
12
|
-
import "./agent.mjs";
|
|
13
|
-
import "./libs/@sinclair/typebox.mjs";
|
|
14
1
|
import { x as sharedArgs } from "./cli-helpers.mjs";
|
|
15
|
-
import "./lockfile.mjs";
|
|
16
2
|
import { i as iterateSkills, t as getProjectState } from "./skills.mjs";
|
|
17
3
|
import { c as timeAgo, i as formatSource } from "./formatting.mjs";
|
|
18
4
|
import { defineCommand } from "citty";
|
|
19
|
-
//#region src/commands/list.ts
|
|
20
5
|
async function listCommand(opts = {}) {
|
|
21
6
|
if (opts.outdated) {
|
|
22
7
|
const entries = (await getProjectState()).outdated.map((skill) => ({
|
|
@@ -111,7 +96,6 @@ const listCommandDef = defineCommand({
|
|
|
111
96
|
});
|
|
112
97
|
}
|
|
113
98
|
});
|
|
114
|
-
//#endregion
|
|
115
99
|
export { listCommandDef };
|
|
116
100
|
|
|
117
101
|
//# sourceMappingURL=list.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.mjs","names":[],"sources":["../../src/commands/list.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { sharedArgs } from '../cli-helpers.ts'\nimport { formatSource, timeAgo } from '../core/formatting.ts'\nimport { getProjectState, iterateSkills } from '../core/skills.ts'\n\nexport interface ListOptions {\n global?: boolean\n json?: boolean\n outdated?: boolean\n}\n\ninterface ListEntry {\n name: string\n version: string\n source: string\n synced: string\n latest?: string\n}\n\nexport async function listCommand(opts: ListOptions = {}): Promise<void> {\n if (opts.outdated) {\n const state = await getProjectState()\n const entries: ListEntry[] = state.outdated.map(skill => ({\n name: skill.name,\n version: skill.info?.version || '',\n latest: skill.latestVersion || '',\n source: formatSource(skill.info?.source),\n synced: timeAgo(skill.info?.syncedAt),\n }))\n\n if (opts.json) {\n process.stdout.write(`${JSON.stringify(entries)}\\n`)\n return\n }\n\n if (entries.length === 0) {\n process.stdout.write('All skills are up to date\\n')\n return\n }\n\n const nameW = Math.max(...entries.map(e => e.name.length))\n const verW = Math.max(...entries.map(e => e.version.length))\n const latW = Math.max(...entries.map(e => (e.latest || '').length))\n const srcW = Math.max(...entries.map(e => e.source.length))\n\n for (const e of entries) {\n const line = [\n e.name.padEnd(nameW),\n `${e.version.padEnd(verW)} → ${(e.latest || '').padEnd(latW)}`,\n e.source.padEnd(srcW),\n e.synced,\n ].join(' ')\n process.stdout.write(`${line}\\n`)\n }\n return\n }\n\n const scope = opts.global ? 'global' : 'all'\n const skills = [...iterateSkills({ scope })]\n\n // Deduplicate by package identity\n const seen = new Set<string>()\n const entries: ListEntry[] = []\n\n for (const skill of skills) {\n const key = skill.info?.packageName || skill.name\n if (seen.has(key))\n continue\n seen.add(key)\n entries.push({\n name: skill.name,\n version: skill.info?.version || '',\n source: formatSource(skill.info?.source),\n synced: timeAgo(skill.info?.syncedAt),\n })\n }\n\n if (opts.json) {\n process.stdout.write(`${JSON.stringify(entries)}\\n`)\n return\n }\n\n if (entries.length === 0) {\n process.stdout.write('No skills installed\\n')\n return\n }\n\n // Column widths\n const nameW = Math.max(...entries.map(e => e.name.length))\n const verW = Math.max(...entries.map(e => e.version.length))\n const srcW = Math.max(...entries.map(e => e.source.length))\n\n for (const e of entries) {\n const line = [\n e.name.padEnd(nameW),\n e.version.padEnd(verW),\n e.source.padEnd(srcW),\n e.synced,\n ].join(' ')\n process.stdout.write(`${line}\\n`)\n }\n}\n\nexport const listCommandDef = defineCommand({\n meta: { name: 'list', description: 'List installed skills' },\n args: {\n global: sharedArgs.global,\n json: {\n type: 'boolean' as const,\n description: 'Output as JSON',\n default: false,\n },\n outdated: {\n type: 'boolean' as const,\n alias: 'o',\n description: 'Show only outdated skills',\n default: false,\n },\n },\n run({ args }) {\n return listCommand({ global: args.global, json: args.json, outdated: args.outdated })\n },\n})\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"list.mjs","names":[],"sources":["../../src/commands/list.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { sharedArgs } from '../cli-helpers.ts'\nimport { formatSource, timeAgo } from '../core/formatting.ts'\nimport { getProjectState, iterateSkills } from '../core/skills.ts'\n\nexport interface ListOptions {\n global?: boolean\n json?: boolean\n outdated?: boolean\n}\n\ninterface ListEntry {\n name: string\n version: string\n source: string\n synced: string\n latest?: string\n}\n\nexport async function listCommand(opts: ListOptions = {}): Promise<void> {\n if (opts.outdated) {\n const state = await getProjectState()\n const entries: ListEntry[] = state.outdated.map(skill => ({\n name: skill.name,\n version: skill.info?.version || '',\n latest: skill.latestVersion || '',\n source: formatSource(skill.info?.source),\n synced: timeAgo(skill.info?.syncedAt),\n }))\n\n if (opts.json) {\n process.stdout.write(`${JSON.stringify(entries)}\\n`)\n return\n }\n\n if (entries.length === 0) {\n process.stdout.write('All skills are up to date\\n')\n return\n }\n\n const nameW = Math.max(...entries.map(e => e.name.length))\n const verW = Math.max(...entries.map(e => e.version.length))\n const latW = Math.max(...entries.map(e => (e.latest || '').length))\n const srcW = Math.max(...entries.map(e => e.source.length))\n\n for (const e of entries) {\n const line = [\n e.name.padEnd(nameW),\n `${e.version.padEnd(verW)} → ${(e.latest || '').padEnd(latW)}`,\n e.source.padEnd(srcW),\n e.synced,\n ].join(' ')\n process.stdout.write(`${line}\\n`)\n }\n return\n }\n\n const scope = opts.global ? 'global' : 'all'\n const skills = [...iterateSkills({ scope })]\n\n // Deduplicate by package identity\n const seen = new Set<string>()\n const entries: ListEntry[] = []\n\n for (const skill of skills) {\n const key = skill.info?.packageName || skill.name\n if (seen.has(key))\n continue\n seen.add(key)\n entries.push({\n name: skill.name,\n version: skill.info?.version || '',\n source: formatSource(skill.info?.source),\n synced: timeAgo(skill.info?.syncedAt),\n })\n }\n\n if (opts.json) {\n process.stdout.write(`${JSON.stringify(entries)}\\n`)\n return\n }\n\n if (entries.length === 0) {\n process.stdout.write('No skills installed\\n')\n return\n }\n\n // Column widths\n const nameW = Math.max(...entries.map(e => e.name.length))\n const verW = Math.max(...entries.map(e => e.version.length))\n const srcW = Math.max(...entries.map(e => e.source.length))\n\n for (const e of entries) {\n const line = [\n e.name.padEnd(nameW),\n e.version.padEnd(verW),\n e.source.padEnd(srcW),\n e.synced,\n ].join(' ')\n process.stdout.write(`${line}\\n`)\n }\n}\n\nexport const listCommandDef = defineCommand({\n meta: { name: 'list', description: 'List installed skills' },\n args: {\n global: sharedArgs.global,\n json: {\n type: 'boolean' as const,\n description: 'Output as JSON',\n default: false,\n },\n outdated: {\n type: 'boolean' as const,\n alias: 'o',\n description: 'Show only outdated skills',\n default: false,\n },\n },\n run({ args }) {\n return listCommand({ global: args.global, json: args.json, outdated: args.outdated })\n },\n})\n"],"mappings":"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"}
|
|
@@ -2,7 +2,6 @@ import { n as yamlParseKV, t as yamlEscape } from "./yaml.mjs";
|
|
|
2
2
|
import { i as parseFrontmatter } from "./markdown.mjs";
|
|
3
3
|
import { join } from "pathe";
|
|
4
4
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
-
//#region src/core/lockfile.ts
|
|
6
5
|
function parsePackages(packages) {
|
|
7
6
|
if (!packages) return [];
|
|
8
7
|
return packages.split(",").map((s) => {
|
|
@@ -51,7 +50,7 @@ function invalidateLockCache(skillsDir) {
|
|
|
51
50
|
}
|
|
52
51
|
function readLock(skillsDir) {
|
|
53
52
|
const cached = lockCache.get(skillsDir);
|
|
54
|
-
if (cached) return cached;
|
|
53
|
+
if (cached) return { skills: { ...cached.skills } };
|
|
55
54
|
const lockPath = join(skillsDir, "skilld-lock.yaml");
|
|
56
55
|
if (!existsSync(lockPath)) return null;
|
|
57
56
|
const content = readFileSync(lockPath, "utf-8");
|
|
@@ -109,9 +108,6 @@ function writeLock(skillsDir, skillName, info) {
|
|
|
109
108
|
writeFileSync(lockPath, serializeLock(lock));
|
|
110
109
|
invalidateLockCache(skillsDir);
|
|
111
110
|
}
|
|
112
|
-
/**
|
|
113
|
-
* Merge multiple lockfiles, preferring the most recently synced entry per skill.
|
|
114
|
-
*/
|
|
115
111
|
function mergeLocks(locks) {
|
|
116
112
|
const merged = {};
|
|
117
113
|
for (const lock of locks) for (const [name, info] of Object.entries(lock.skills)) {
|
|
@@ -120,10 +116,6 @@ function mergeLocks(locks) {
|
|
|
120
116
|
}
|
|
121
117
|
return { skills: merged };
|
|
122
118
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Sync a lockfile to all other dirs that already have a skilld-lock.yaml.
|
|
125
|
-
* Only updates existing lockfiles — does not create new ones.
|
|
126
|
-
*/
|
|
127
119
|
function syncLockfilesToDirs(sourceLock, dirs) {
|
|
128
120
|
for (const dir of dirs) {
|
|
129
121
|
const lockPath = join(dir, "skilld-lock.yaml");
|
|
@@ -147,7 +139,6 @@ function removeLockEntry(skillsDir, skillName) {
|
|
|
147
139
|
writeFileSync(lockPath, serializeLock(lock));
|
|
148
140
|
invalidateLockCache(skillsDir);
|
|
149
141
|
}
|
|
150
|
-
//#endregion
|
|
151
142
|
export { removeLockEntry as a, readLock as i, parsePackages as n, syncLockfilesToDirs as o, parseSkillFrontmatter as r, writeLock as s, mergeLocks as t };
|
|
152
143
|
|
|
153
144
|
//# sourceMappingURL=lockfile.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lockfile.mjs","names":[],"sources":["../../src/core/lockfile.ts"],"sourcesContent":["import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { join } from 'pathe'\nimport { parseFrontmatter } from './markdown.ts'\nimport { yamlEscape, yamlParseKV } from './yaml.ts'\n\nexport interface SkillInfo {\n packageName?: string\n version?: string\n /** All tracked packages as comma-separated \"name@version\" pairs (multi-package skills) */\n packages?: string\n repo?: string\n source?: string\n syncedAt?: string\n generator?: string\n /** Skill path within repo (git-sourced skills) */\n path?: string\n /** Git ref tracked for updates */\n ref?: string\n /** Git commit SHA at install time */\n commit?: string\n}\n\nexport function parsePackages(packages?: string): Array<{ name: string, version: string }> {\n if (!packages)\n return []\n return packages.split(',').map((s) => {\n const trimmed = s.trim()\n const atIdx = trimmed.lastIndexOf('@')\n if (atIdx <= 0)\n return { name: trimmed, version: '' }\n return { name: trimmed.slice(0, atIdx), version: trimmed.slice(atIdx + 1) }\n }).filter(p => p.name)\n}\n\nexport function serializePackages(pkgs: Array<{ name: string, version: string }>): string {\n return pkgs.map(p => `${p.name}@${p.version}`).join(', ')\n}\n\nexport interface SkilldLock {\n skills: Record<string, SkillInfo>\n}\n\nconst SKILL_FM_KEYS: (keyof SkillInfo)[] = ['packageName', 'version', 'packages', 'repo', 'source', 'syncedAt', 'generator', 'path', 'ref', 'commit']\n\nfunction isSkillInfoKey(key: string): key is keyof SkillInfo {\n return (SKILL_FM_KEYS as readonly string[]).includes(key)\n}\n\nexport function parseSkillFrontmatter(skillPath: string): SkillInfo | null {\n if (!existsSync(skillPath))\n return null\n const content = readFileSync(skillPath, 'utf-8')\n const fm = parseFrontmatter(content)\n if (Object.keys(fm).length === 0)\n return null\n\n const info: SkillInfo = {}\n for (const key of SKILL_FM_KEYS) {\n if (fm[key])\n info[key] = fm[key]\n }\n return info\n}\n\nconst lockCache = new Map<string, SkilldLock>()\n\nexport function invalidateLockCache(skillsDir?: string): void {\n if (skillsDir)\n lockCache.delete(skillsDir)\n else\n lockCache.clear()\n}\n\nexport function readLock(skillsDir: string): SkilldLock | null {\n const cached = lockCache.get(skillsDir)\n if (cached)\n return cached\n const lockPath = join(skillsDir, 'skilld-lock.yaml')\n if (!existsSync(lockPath))\n return null\n const content = readFileSync(lockPath, 'utf-8')\n\n const skills: Record<string, SkillInfo> = {}\n let currentSkill: string | null = null\n\n for (const line of content.split('\\n')) {\n const skillMatch = line.match(/^ {2}(\\S+):$/)\n if (skillMatch) {\n currentSkill = skillMatch[1]!\n skills[currentSkill] = {}\n continue\n }\n if (currentSkill && line.startsWith(' ')) {\n const kv = yamlParseKV(line)\n if (kv && isSkillInfoKey(kv[0]))\n skills[currentSkill]![kv[0]] = kv[1]\n }\n }\n const lock = { skills }\n lockCache.set(skillsDir, lock)\n return { skills: { ...lock.skills } }\n}\n\nfunction serializeLock(lock: SkilldLock): string {\n let yaml = 'skills:\\n'\n for (const [name, skill] of Object.entries(lock.skills)) {\n yaml += ` ${name}:\\n`\n for (const key of SKILL_FM_KEYS) {\n if (skill[key])\n yaml += ` ${key}: ${yamlEscape(skill[key])}\\n`\n }\n }\n return yaml\n}\n\nexport function writeLock(skillsDir: string, skillName: string, info: SkillInfo): void {\n const lockPath = join(skillsDir, 'skilld-lock.yaml')\n let lock: SkilldLock = { skills: {} }\n if (existsSync(lockPath)) {\n lock = readLock(skillsDir) || { skills: {} }\n }\n\n const existing = lock.skills[skillName]\n if (existing && info.packageName) {\n // Merge packages list\n const existingPkgs = parsePackages(existing.packages)\n // Also include existing primary if not yet in packages list\n if (existing.packageName && !existingPkgs.some(p => p.name === existing.packageName)) {\n existingPkgs.unshift({ name: existing.packageName, version: existing.version || '' })\n }\n // Add/update new package\n const idx = existingPkgs.findIndex(p => p.name === info.packageName)\n if (idx >= 0) {\n existingPkgs[idx]!.version = info.version || ''\n }\n else {\n existingPkgs.push({ name: info.packageName, version: info.version || '' })\n }\n info.packages = serializePackages(existingPkgs)\n // Keep primary as first package\n info.packageName = existingPkgs[0]!.name\n info.version = existingPkgs[0]!.version\n // Preserve fields from existing entry that aren't in new info\n if (!info.repo && existing.repo)\n info.repo = existing.repo\n if (!info.source && existing.source)\n info.source = existing.source\n if (!info.generator && existing.generator)\n info.generator = existing.generator\n }\n\n lock.skills[skillName] = info\n writeFileSync(lockPath, serializeLock(lock))\n invalidateLockCache(skillsDir)\n}\n\n/**\n * Merge multiple lockfiles, preferring the most recently synced entry per skill.\n */\nexport function mergeLocks(locks: SkilldLock[]): SkilldLock {\n const merged: Record<string, SkillInfo> = {}\n for (const lock of locks) {\n for (const [name, info] of Object.entries(lock.skills)) {\n const existing = merged[name]\n if (!existing || (info.syncedAt && (!existing.syncedAt || info.syncedAt > existing.syncedAt)))\n merged[name] = info\n }\n }\n return { skills: merged }\n}\n\n/**\n * Sync a lockfile to all other dirs that already have a skilld-lock.yaml.\n * Only updates existing lockfiles — does not create new ones.\n */\nexport function syncLockfilesToDirs(sourceLock: SkilldLock, dirs: string[]): void {\n for (const dir of dirs) {\n const lockPath = join(dir, 'skilld-lock.yaml')\n if (!existsSync(lockPath))\n continue\n const existing = readLock(dir)\n if (!existing)\n continue\n // Merge source into existing\n const merged = mergeLocks([existing, sourceLock])\n writeFileSync(lockPath, serializeLock(merged))\n invalidateLockCache(dir)\n }\n}\n\nexport function removeLockEntry(skillsDir: string, skillName: string): void {\n const lockPath = join(skillsDir, 'skilld-lock.yaml')\n const lock = readLock(skillsDir)\n if (!lock)\n return\n\n delete lock.skills[skillName]\n\n if (Object.keys(lock.skills).length === 0) {\n unlinkSync(lockPath)\n invalidateLockCache(skillsDir)\n return\n }\n\n writeFileSync(lockPath, serializeLock(lock))\n invalidateLockCache(skillsDir)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lockfile.mjs","names":[],"sources":["../../src/core/lockfile.ts"],"sourcesContent":["import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { join } from 'pathe'\nimport { parseFrontmatter } from './markdown.ts'\nimport { yamlEscape, yamlParseKV } from './yaml.ts'\n\nexport interface SkillInfo {\n packageName?: string\n version?: string\n /** All tracked packages as comma-separated \"name@version\" pairs (multi-package skills) */\n packages?: string\n repo?: string\n source?: string\n syncedAt?: string\n generator?: string\n /** Skill path within repo (git-sourced skills) */\n path?: string\n /** Git ref tracked for updates */\n ref?: string\n /** Git commit SHA at install time */\n commit?: string\n}\n\nexport function parsePackages(packages?: string): Array<{ name: string, version: string }> {\n if (!packages)\n return []\n return packages.split(',').map((s) => {\n const trimmed = s.trim()\n const atIdx = trimmed.lastIndexOf('@')\n if (atIdx <= 0)\n return { name: trimmed, version: '' }\n return { name: trimmed.slice(0, atIdx), version: trimmed.slice(atIdx + 1) }\n }).filter(p => p.name)\n}\n\nexport function serializePackages(pkgs: Array<{ name: string, version: string }>): string {\n return pkgs.map(p => `${p.name}@${p.version}`).join(', ')\n}\n\nexport interface SkilldLock {\n skills: Record<string, SkillInfo>\n}\n\nconst SKILL_FM_KEYS: (keyof SkillInfo)[] = ['packageName', 'version', 'packages', 'repo', 'source', 'syncedAt', 'generator', 'path', 'ref', 'commit']\n\nfunction isSkillInfoKey(key: string): key is keyof SkillInfo {\n return (SKILL_FM_KEYS as readonly string[]).includes(key)\n}\n\nexport function parseSkillFrontmatter(skillPath: string): SkillInfo | null {\n if (!existsSync(skillPath))\n return null\n const content = readFileSync(skillPath, 'utf-8')\n const fm = parseFrontmatter(content)\n if (Object.keys(fm).length === 0)\n return null\n\n const info: SkillInfo = {}\n for (const key of SKILL_FM_KEYS) {\n if (fm[key])\n info[key] = fm[key]\n }\n return info\n}\n\nconst lockCache = new Map<string, SkilldLock>()\n\nexport function invalidateLockCache(skillsDir?: string): void {\n if (skillsDir)\n lockCache.delete(skillsDir)\n else\n lockCache.clear()\n}\n\nexport function readLock(skillsDir: string): SkilldLock | null {\n const cached = lockCache.get(skillsDir)\n if (cached)\n return { skills: { ...cached.skills } }\n const lockPath = join(skillsDir, 'skilld-lock.yaml')\n if (!existsSync(lockPath))\n return null\n const content = readFileSync(lockPath, 'utf-8')\n\n const skills: Record<string, SkillInfo> = {}\n let currentSkill: string | null = null\n\n for (const line of content.split('\\n')) {\n const skillMatch = line.match(/^ {2}(\\S+):$/)\n if (skillMatch) {\n currentSkill = skillMatch[1]!\n skills[currentSkill] = {}\n continue\n }\n if (currentSkill && line.startsWith(' ')) {\n const kv = yamlParseKV(line)\n if (kv && isSkillInfoKey(kv[0]))\n skills[currentSkill]![kv[0]] = kv[1]\n }\n }\n const lock = { skills }\n lockCache.set(skillsDir, lock)\n return { skills: { ...lock.skills } }\n}\n\nfunction serializeLock(lock: SkilldLock): string {\n let yaml = 'skills:\\n'\n for (const [name, skill] of Object.entries(lock.skills)) {\n yaml += ` ${name}:\\n`\n for (const key of SKILL_FM_KEYS) {\n if (skill[key])\n yaml += ` ${key}: ${yamlEscape(skill[key])}\\n`\n }\n }\n return yaml\n}\n\nexport function writeLock(skillsDir: string, skillName: string, info: SkillInfo): void {\n const lockPath = join(skillsDir, 'skilld-lock.yaml')\n let lock: SkilldLock = { skills: {} }\n if (existsSync(lockPath)) {\n lock = readLock(skillsDir) || { skills: {} }\n }\n\n const existing = lock.skills[skillName]\n if (existing && info.packageName) {\n // Merge packages list\n const existingPkgs = parsePackages(existing.packages)\n // Also include existing primary if not yet in packages list\n if (existing.packageName && !existingPkgs.some(p => p.name === existing.packageName)) {\n existingPkgs.unshift({ name: existing.packageName, version: existing.version || '' })\n }\n // Add/update new package\n const idx = existingPkgs.findIndex(p => p.name === info.packageName)\n if (idx >= 0) {\n existingPkgs[idx]!.version = info.version || ''\n }\n else {\n existingPkgs.push({ name: info.packageName, version: info.version || '' })\n }\n info.packages = serializePackages(existingPkgs)\n // Keep primary as first package\n info.packageName = existingPkgs[0]!.name\n info.version = existingPkgs[0]!.version\n // Preserve fields from existing entry that aren't in new info\n if (!info.repo && existing.repo)\n info.repo = existing.repo\n if (!info.source && existing.source)\n info.source = existing.source\n if (!info.generator && existing.generator)\n info.generator = existing.generator\n }\n\n lock.skills[skillName] = info\n writeFileSync(lockPath, serializeLock(lock))\n invalidateLockCache(skillsDir)\n}\n\n/**\n * Merge multiple lockfiles, preferring the most recently synced entry per skill.\n */\nexport function mergeLocks(locks: SkilldLock[]): SkilldLock {\n const merged: Record<string, SkillInfo> = {}\n for (const lock of locks) {\n for (const [name, info] of Object.entries(lock.skills)) {\n const existing = merged[name]\n if (!existing || (info.syncedAt && (!existing.syncedAt || info.syncedAt > existing.syncedAt)))\n merged[name] = info\n }\n }\n return { skills: merged }\n}\n\n/**\n * Sync a lockfile to all other dirs that already have a skilld-lock.yaml.\n * Only updates existing lockfiles — does not create new ones.\n */\nexport function syncLockfilesToDirs(sourceLock: SkilldLock, dirs: string[]): void {\n for (const dir of dirs) {\n const lockPath = join(dir, 'skilld-lock.yaml')\n if (!existsSync(lockPath))\n continue\n const existing = readLock(dir)\n if (!existing)\n continue\n // Merge source into existing\n const merged = mergeLocks([existing, sourceLock])\n writeFileSync(lockPath, serializeLock(merged))\n invalidateLockCache(dir)\n }\n}\n\nexport function removeLockEntry(skillsDir: string, skillName: string): void {\n const lockPath = join(skillsDir, 'skilld-lock.yaml')\n const lock = readLock(skillsDir)\n if (!lock)\n return\n\n delete lock.skills[skillName]\n\n if (Object.keys(lock.skills).length === 0) {\n unlinkSync(lockPath)\n invalidateLockCache(skillsDir)\n return\n }\n\n writeFileSync(lockPath, serializeLock(lock))\n invalidateLockCache(skillsDir)\n}\n"],"mappings":"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"}
|
|
@@ -4,8 +4,6 @@ import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
|
|
|
4
4
|
import { toString } from "mdast-util-to-string";
|
|
5
5
|
import { frontmatter } from "micromark-extension-frontmatter";
|
|
6
6
|
import { visit } from "unist-util-visit";
|
|
7
|
-
//#region src/core/markdown.ts
|
|
8
|
-
/** Parse markdown string to AST + frontmatter key-values */
|
|
9
7
|
function parseMd(content) {
|
|
10
8
|
const tree = fromMarkdown(content, {
|
|
11
9
|
extensions: [frontmatter(["yaml"])],
|
|
@@ -23,15 +21,12 @@ function parseMd(content) {
|
|
|
23
21
|
frontmatter: fm
|
|
24
22
|
};
|
|
25
23
|
}
|
|
26
|
-
/** Extract frontmatter key-value pairs only */
|
|
27
24
|
function parseFrontmatter(content) {
|
|
28
25
|
return parseMd(content).frontmatter;
|
|
29
26
|
}
|
|
30
|
-
/** Strip custom heading anchors like {#some-id} */
|
|
31
27
|
function stripHeadingAnchors(text) {
|
|
32
28
|
return text.replace(/\s*\{#[^}]+\}\s*$/, "").trim();
|
|
33
29
|
}
|
|
34
|
-
/** Extract title: frontmatter title > first h1 > null */
|
|
35
30
|
function extractTitle(content) {
|
|
36
31
|
const { tree, frontmatter: fm } = parseMd(content);
|
|
37
32
|
if (fm.title) return fm.title;
|
|
@@ -44,7 +39,6 @@ function extractTitle(content) {
|
|
|
44
39
|
});
|
|
45
40
|
return title;
|
|
46
41
|
}
|
|
47
|
-
/** Extract first paragraph text, 150 char max */
|
|
48
42
|
function extractDescription(content) {
|
|
49
43
|
const { tree } = parseMd(content);
|
|
50
44
|
let desc = null;
|
|
@@ -58,7 +52,6 @@ function extractDescription(content) {
|
|
|
58
52
|
});
|
|
59
53
|
return desc;
|
|
60
54
|
}
|
|
61
|
-
/** Extract all links (deduped by url) */
|
|
62
55
|
function extractLinks(content) {
|
|
63
56
|
const { tree } = parseMd(content);
|
|
64
57
|
const links = [];
|
|
@@ -74,12 +67,10 @@ function extractLinks(content) {
|
|
|
74
67
|
});
|
|
75
68
|
return links;
|
|
76
69
|
}
|
|
77
|
-
/** Strip frontmatter block, return body only */
|
|
78
70
|
function stripFrontmatter(content) {
|
|
79
71
|
const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/);
|
|
80
72
|
return match ? content.slice(match[0].length).trim() : content;
|
|
81
73
|
}
|
|
82
|
-
//#endregion
|
|
83
74
|
export { stripFrontmatter as a, parseFrontmatter as i, extractLinks as n, extractTitle as r, extractDescription as t };
|
|
84
75
|
|
|
85
76
|
//# sourceMappingURL=markdown.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.mjs","names":[],"sources":["../../src/core/markdown.ts"],"sourcesContent":["/**\n * AST-based markdown parsing using mdast/micromark.\n * Replaces scattered regex-based frontmatter/heading/link extraction.\n */\n\nimport type { Nodes, Root } from 'mdast'\nimport { fromMarkdown } from 'mdast-util-from-markdown'\nimport { frontmatterFromMarkdown } from 'mdast-util-frontmatter'\nimport { toString } from 'mdast-util-to-string'\nimport { frontmatter } from 'micromark-extension-frontmatter'\nimport { visit } from 'unist-util-visit'\nimport { yamlParseKV } from './yaml.ts'\n\nexport interface MdHeading {\n depth: number\n text: string\n}\n\nexport interface MdLink {\n title: string\n url: string\n}\n\nexport interface ParsedMd {\n tree: Root\n frontmatter: Record<string, string>\n}\n\n/** Parse markdown string to AST + frontmatter key-values */\nexport function parseMd(content: string): ParsedMd {\n const tree = fromMarkdown(content, {\n extensions: [frontmatter(['yaml'])],\n mdastExtensions: [frontmatterFromMarkdown(['yaml'])],\n })\n\n const fm: Record<string, string> = {}\n visit(tree, 'yaml', (node: Nodes) => {\n if (node.type === 'yaml') {\n for (const line of (node as any).value.split('\\n')) {\n const kv = yamlParseKV(line)\n if (kv)\n fm[kv[0]] = kv[1]\n }\n }\n })\n\n return { tree, frontmatter: fm }\n}\n\n/** Extract frontmatter key-value pairs only */\nexport function parseFrontmatter(content: string): Record<string, string> {\n return parseMd(content).frontmatter\n}\n\n/** Strip custom heading anchors like {#some-id} */\nfunction stripHeadingAnchors(text: string): string {\n return text.replace(/\\s*\\{#[^}]+\\}\\s*$/, '').trim()\n}\n\n/** Extract title: frontmatter title > first h1 > null */\nexport function extractTitle(content: string): string | null {\n const { tree, frontmatter: fm } = parseMd(content)\n if (fm.title)\n return fm.title\n\n let title: string | null = null\n visit(tree, 'heading', (node) => {\n if (node.depth === 1 && !title) {\n // Strip {#id} anchors and leading backslash escapes (e.g. `# \\`)\n const text = stripHeadingAnchors(toString(node)).replace(/^\\\\+\\s*/, '').trim()\n if (text.length > 0)\n title = text\n }\n })\n\n return title\n}\n\n/** Extract first paragraph text, 150 char max */\nexport function extractDescription(content: string): string | null {\n const { tree } = parseMd(content)\n\n let desc: string | null = null\n visit(tree, 'paragraph', (node, _index, parent) => {\n // Only top-level paragraphs (skip blockquote children, list items, etc.)\n if (desc || parent?.type !== 'root')\n return\n\n const text = toString(node).trim()\n if (text.length === 0)\n return\n\n // Strip markdown link syntax remnants and formatting chars\n let clean = text.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1').replace(/[`*_~]/g, '')\n if (clean.length > 150)\n clean = `${clean.slice(0, 147)}...`\n desc = clean\n })\n\n return desc\n}\n\n/** Extract all headings with depth and text */\nexport function extractHeadings(content: string): MdHeading[] {\n const { tree } = parseMd(content)\n const headings: MdHeading[] = []\n\n visit(tree, 'heading', (node) => {\n headings.push({ depth: node.depth, text: stripHeadingAnchors(toString(node)) })\n })\n\n return headings\n}\n\n/** Extract all links (deduped by url) */\nexport function extractLinks(content: string): MdLink[] {\n const { tree } = parseMd(content)\n const links: MdLink[] = []\n const seen = new Set<string>()\n\n visit(tree, 'link', (node) => {\n if (!seen.has(node.url)) {\n seen.add(node.url)\n links.push({ title: toString(node), url: node.url })\n }\n })\n\n return links\n}\n\n/** Strip frontmatter block, return body only */\nexport function stripFrontmatter(content: string): string {\n const match = content.match(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n/)\n return match ? content.slice(match[0].length).trim() : content\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"markdown.mjs","names":[],"sources":["../../src/core/markdown.ts"],"sourcesContent":["/**\n * AST-based markdown parsing using mdast/micromark.\n * Replaces scattered regex-based frontmatter/heading/link extraction.\n */\n\nimport type { Nodes, Root } from 'mdast'\nimport { fromMarkdown } from 'mdast-util-from-markdown'\nimport { frontmatterFromMarkdown } from 'mdast-util-frontmatter'\nimport { toString } from 'mdast-util-to-string'\nimport { frontmatter } from 'micromark-extension-frontmatter'\nimport { visit } from 'unist-util-visit'\nimport { yamlParseKV } from './yaml.ts'\n\nexport interface MdHeading {\n depth: number\n text: string\n}\n\nexport interface MdLink {\n title: string\n url: string\n}\n\nexport interface ParsedMd {\n tree: Root\n frontmatter: Record<string, string>\n}\n\n/** Parse markdown string to AST + frontmatter key-values */\nexport function parseMd(content: string): ParsedMd {\n const tree = fromMarkdown(content, {\n extensions: [frontmatter(['yaml'])],\n mdastExtensions: [frontmatterFromMarkdown(['yaml'])],\n })\n\n const fm: Record<string, string> = {}\n visit(tree, 'yaml', (node: Nodes) => {\n if (node.type === 'yaml') {\n for (const line of (node as any).value.split('\\n')) {\n const kv = yamlParseKV(line)\n if (kv)\n fm[kv[0]] = kv[1]\n }\n }\n })\n\n return { tree, frontmatter: fm }\n}\n\n/** Extract frontmatter key-value pairs only */\nexport function parseFrontmatter(content: string): Record<string, string> {\n return parseMd(content).frontmatter\n}\n\n/** Strip custom heading anchors like {#some-id} */\nfunction stripHeadingAnchors(text: string): string {\n return text.replace(/\\s*\\{#[^}]+\\}\\s*$/, '').trim()\n}\n\n/** Extract title: frontmatter title > first h1 > null */\nexport function extractTitle(content: string): string | null {\n const { tree, frontmatter: fm } = parseMd(content)\n if (fm.title)\n return fm.title\n\n let title: string | null = null\n visit(tree, 'heading', (node) => {\n if (node.depth === 1 && !title) {\n // Strip {#id} anchors and leading backslash escapes (e.g. `# \\`)\n const text = stripHeadingAnchors(toString(node)).replace(/^\\\\+\\s*/, '').trim()\n if (text.length > 0)\n title = text\n }\n })\n\n return title\n}\n\n/** Extract first paragraph text, 150 char max */\nexport function extractDescription(content: string): string | null {\n const { tree } = parseMd(content)\n\n let desc: string | null = null\n visit(tree, 'paragraph', (node, _index, parent) => {\n // Only top-level paragraphs (skip blockquote children, list items, etc.)\n if (desc || parent?.type !== 'root')\n return\n\n const text = toString(node).trim()\n if (text.length === 0)\n return\n\n // Strip markdown link syntax remnants and formatting chars\n let clean = text.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1').replace(/[`*_~]/g, '')\n if (clean.length > 150)\n clean = `${clean.slice(0, 147)}...`\n desc = clean\n })\n\n return desc\n}\n\n/** Extract all headings with depth and text */\nexport function extractHeadings(content: string): MdHeading[] {\n const { tree } = parseMd(content)\n const headings: MdHeading[] = []\n\n visit(tree, 'heading', (node) => {\n headings.push({ depth: node.depth, text: stripHeadingAnchors(toString(node)) })\n })\n\n return headings\n}\n\n/** Extract all links (deduped by url) */\nexport function extractLinks(content: string): MdLink[] {\n const { tree } = parseMd(content)\n const links: MdLink[] = []\n const seen = new Set<string>()\n\n visit(tree, 'link', (node) => {\n if (!seen.has(node.url)) {\n seen.add(node.url)\n links.push({ title: toString(node), url: node.url })\n }\n })\n\n return links\n}\n\n/** Strip frontmatter block, return body only */\nexport function stripFrontmatter(content: string): string {\n const match = content.match(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n/)\n return match ? content.slice(match[0].length).trim() : content\n}\n"],"mappings":";;;;;;AA6BA,SAAgB,QAAQ,SAA2B;CACjD,MAAM,OAAO,aAAa,SAAS;EACjC,YAAY,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;EACnC,iBAAiB,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAA;EACpD,CAAC;CAEF,MAAM,KAA6B,EAAE;AACrC,OAAM,MAAM,SAAS,SAAgB;AACnC,MAAI,KAAK,SAAS,OAChB,MAAK,MAAM,QAAS,KAAa,MAAM,MAAM,KAAK,EAAE;GAClD,MAAM,KAAK,YAAY,KAAK;AAC5B,OAAI,GACF,IAAG,GAAG,MAAM,GAAG;;GAGrB;AAEF,QAAO;EAAE;EAAM,aAAa;EAAI;;AAIlC,SAAgB,iBAAiB,SAAyC;AACxE,QAAO,QAAQ,QAAQ,CAAC;;AAI1B,SAAS,oBAAoB,MAAsB;AACjD,QAAO,KAAK,QAAQ,qBAAqB,GAAG,CAAC,MAAM;;AAIrD,SAAgB,aAAa,SAAgC;CAC3D,MAAM,EAAE,MAAM,aAAa,OAAO,QAAQ,QAAQ;AAClD,KAAI,GAAG,MACL,QAAO,GAAG;CAEZ,IAAI,QAAuB;AAC3B,OAAM,MAAM,YAAY,SAAS;AAC/B,MAAI,KAAK,UAAU,KAAK,CAAC,OAAO;GAE9B,MAAM,OAAO,oBAAoB,SAAS,KAAK,CAAC,CAAC,QAAQ,WAAW,GAAG,CAAC,MAAM;AAC9E,OAAI,KAAK,SAAS,EAChB,SAAQ;;GAEZ;AAEF,QAAO;;AAIT,SAAgB,mBAAmB,SAAgC;CACjE,MAAM,EAAE,SAAS,QAAQ,QAAQ;CAEjC,IAAI,OAAsB;AAC1B,OAAM,MAAM,cAAc,MAAM,QAAQ,WAAW;AAEjD,MAAI,QAAQ,QAAQ,SAAS,OAC3B;EAEF,MAAM,OAAO,SAAS,KAAK,CAAC,MAAM;AAClC,MAAI,KAAK,WAAW,EAClB;EAGF,IAAI,QAAQ,KAAK,QAAQ,0BAA0B,KAAK,CAAC,QAAQ,WAAW,GAAG;AAC/E,MAAI,MAAM,SAAS,IACjB,SAAQ,GAAG,MAAM,MAAM,GAAG,IAAI,CAAC;AACjC,SAAO;GACP;AAEF,QAAO;;AAgBT,SAAgB,aAAa,SAA2B;CACtD,MAAM,EAAE,SAAS,QAAQ,QAAQ;CACjC,MAAM,QAAkB,EAAE;CAC1B,MAAM,uBAAO,IAAI,KAAa;AAE9B,OAAM,MAAM,SAAS,SAAS;AAC5B,MAAI,CAAC,KAAK,IAAI,KAAK,IAAI,EAAE;AACvB,QAAK,IAAI,KAAK,IAAI;AAClB,SAAM,KAAK;IAAE,OAAO,SAAS,KAAK;IAAE,KAAK,KAAK;IAAK,CAAC;;GAEtD;AAEF,QAAO;;AAIT,SAAgB,iBAAiB,SAAyB;CACxD,MAAM,QAAQ,QAAQ,MAAM,iCAAiC;AAC7D,QAAO,QAAQ,QAAQ,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG"}
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { applyEdits, modify, parseTree } from "jsonc-parser";
|
|
3
|
-
//#region src/core/package-json.ts
|
|
4
3
|
const defaultEditOptions = {
|
|
5
4
|
tabSize: 2,
|
|
6
5
|
insertSpaces: true
|
|
7
6
|
};
|
|
8
7
|
const cache = /* @__PURE__ */ new Map();
|
|
9
|
-
/**
|
|
10
|
-
* Read and parse a package.json, returning cached result on repeat calls.
|
|
11
|
-
* Throws if the file does not exist.
|
|
12
|
-
*/
|
|
13
8
|
function readPackageJson(pkgPath) {
|
|
14
9
|
const hit = cache.get(pkgPath);
|
|
15
10
|
if (hit) return hit;
|
|
@@ -21,9 +16,6 @@ function readPackageJson(pkgPath) {
|
|
|
21
16
|
cache.set(pkgPath, entry);
|
|
22
17
|
return entry;
|
|
23
18
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Same as readPackageJson but returns null when the file is missing or unparseable.
|
|
26
|
-
*/
|
|
27
19
|
function readPackageJsonSafe(pkgPath) {
|
|
28
20
|
if (cache.has(pkgPath)) return cache.get(pkgPath);
|
|
29
21
|
if (!existsSync(pkgPath)) return null;
|
|
@@ -33,16 +25,9 @@ function readPackageJsonSafe(pkgPath) {
|
|
|
33
25
|
return null;
|
|
34
26
|
}
|
|
35
27
|
}
|
|
36
|
-
/**
|
|
37
|
-
* Drop any cached entry so the next read hits disk.
|
|
38
|
-
*/
|
|
39
28
|
function invalidatePackageJson(pkgPath) {
|
|
40
29
|
cache.delete(pkgPath);
|
|
41
30
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Set a value at a JSON path, preserving all surrounding formatting.
|
|
44
|
-
* Returns the modified file content as a string.
|
|
45
|
-
*/
|
|
46
31
|
function editJsonProperty(raw, path, value, options) {
|
|
47
32
|
const opts = {
|
|
48
33
|
...defaultEditOptions,
|
|
@@ -53,11 +38,6 @@ function editJsonProperty(raw, path, value, options) {
|
|
|
53
38
|
insertSpaces: opts.insertSpaces
|
|
54
39
|
} }));
|
|
55
40
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Read a package.json, apply an edit function, write it back, and invalidate the cache.
|
|
58
|
-
* The edit function receives the raw text and parsed object,
|
|
59
|
-
* and returns the new raw text (or null to skip writing).
|
|
60
|
-
*/
|
|
61
41
|
function patchPackageJson(pkgPath, editFn) {
|
|
62
42
|
const { raw, parsed } = readPackageJson(pkgPath);
|
|
63
43
|
const result = editFn(raw, parsed);
|
|
@@ -66,10 +46,6 @@ function patchPackageJson(pkgPath, editFn) {
|
|
|
66
46
|
invalidatePackageJson(pkgPath);
|
|
67
47
|
return true;
|
|
68
48
|
}
|
|
69
|
-
/**
|
|
70
|
-
* Append a value to a JSON array at the given path, preserving formatting.
|
|
71
|
-
* Inserts in sorted order if the array contains strings.
|
|
72
|
-
*/
|
|
73
49
|
function appendToJsonArray(raw, path, value, options) {
|
|
74
50
|
const opts = {
|
|
75
51
|
...defaultEditOptions,
|
|
@@ -101,7 +77,6 @@ function appendToJsonArray(raw, path, value, options) {
|
|
|
101
77
|
isArrayInsertion: true
|
|
102
78
|
}));
|
|
103
79
|
}
|
|
104
|
-
//#endregion
|
|
105
80
|
export { readPackageJsonSafe as i, editJsonProperty as n, patchPackageJson as r, appendToJsonArray as t };
|
|
106
81
|
|
|
107
82
|
//# sourceMappingURL=package-json.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-json.mjs","names":[],"sources":["../../src/core/package-json.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { applyEdits, modify, parseTree } from 'jsonc-parser'\n\nexport interface EditOptions {\n /** Formatting options for inserted content */\n tabSize?: number\n insertSpaces?: boolean\n}\n\nconst defaultEditOptions: EditOptions = { tabSize: 2, insertSpaces: true }\n\n// ── Cached reader ──────────────────────────────────────────────\n\nconst cache = new Map<string, { raw: string, parsed: Record<string, unknown> }>()\n\n/**\n * Read and parse a package.json, returning cached result on repeat calls.\n * Throws if the file does not exist.\n */\nexport function readPackageJson(pkgPath: string): { raw: string, parsed: Record<string, unknown> } {\n const hit = cache.get(pkgPath)\n if (hit)\n return hit\n const raw = readFileSync(pkgPath, 'utf-8')\n const parsed = JSON.parse(raw) as Record<string, unknown>\n const entry = { raw, parsed }\n cache.set(pkgPath, entry)\n return entry\n}\n\n/**\n * Same as readPackageJson but returns null when the file is missing or unparseable.\n */\nexport function readPackageJsonSafe(pkgPath: string): { raw: string, parsed: Record<string, unknown> } | null {\n if (cache.has(pkgPath))\n return cache.get(pkgPath)!\n if (!existsSync(pkgPath))\n return null\n try {\n return readPackageJson(pkgPath)\n }\n catch {\n return null\n }\n}\n\n/**\n * Drop any cached entry so the next read hits disk.\n */\nexport function invalidatePackageJson(pkgPath: string): void {\n cache.delete(pkgPath)\n}\n\n/**\n * Clear all cached entries. Useful in tests.\n */\nexport function clearPackageJsonCache(): void {\n cache.clear()\n}\n\n// ── JSON editing helpers ───────────────────────────────────────\n\n/**\n * Set a value at a JSON path, preserving all surrounding formatting.\n * Returns the modified file content as a string.\n */\nexport function editJsonProperty(raw: string, path: (string | number)[], value: unknown, options?: EditOptions): string {\n const opts = { ...defaultEditOptions, ...options }\n const edits = modify(raw, path, value, {\n formattingOptions: { tabSize: opts.tabSize!, insertSpaces: opts.insertSpaces! },\n })\n return applyEdits(raw, edits)\n}\n\n/**\n * Remove a value at a JSON path, preserving all surrounding formatting.\n */\nexport function removeJsonProperty(raw: string, path: (string | number)[]): string {\n const edits = modify(raw, path, undefined, {})\n return applyEdits(raw, edits)\n}\n\n/**\n * Read a package.json, apply an edit function, write it back, and invalidate the cache.\n * The edit function receives the raw text and parsed object,\n * and returns the new raw text (or null to skip writing).\n */\nexport function patchPackageJson(\n pkgPath: string,\n editFn: (raw: string, pkg: Record<string, unknown>) => string | null,\n): boolean {\n const { raw, parsed } = readPackageJson(pkgPath)\n const result = editFn(raw, parsed)\n if (result === null)\n return false\n writeFileSync(pkgPath, result)\n invalidatePackageJson(pkgPath)\n return true\n}\n\n/**\n * Append a value to a JSON array at the given path, preserving formatting.\n * Inserts in sorted order if the array contains strings.\n */\nexport function appendToJsonArray(raw: string, path: string[], value: string, options?: EditOptions): string {\n const opts = { ...defaultEditOptions, ...options }\n const tree = parseTree(raw)\n if (!tree)\n return editJsonProperty(raw, path, [value], opts)\n\n // Walk to the target array node\n let node = tree\n for (const key of path) {\n const child = node.children?.find(c =>\n c.type === 'property' && c.children?.[0]?.value === key,\n )\n if (!child?.children?.[1])\n return editJsonProperty(raw, path, [value], opts)\n node = child.children[1]\n }\n\n if (node.type !== 'array' || !node.children)\n return editJsonProperty(raw, path, [value], opts)\n\n // Find sorted insertion index (only for string-only arrays)\n const allStrings = node.children.every(c => typeof c.value === 'string')\n let idx = node.children.length\n if (allStrings) {\n const items = node.children.map(c => c.value as string)\n for (let i = 0; i < items.length; i++) {\n if (value.localeCompare(items[i]!) < 0) {\n idx = i\n break\n }\n }\n }\n\n const edits = modify(raw, [...path, idx], value, {\n formattingOptions: { tabSize: opts.tabSize!, insertSpaces: opts.insertSpaces! },\n isArrayInsertion: true,\n })\n return applyEdits(raw, edits)\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"package-json.mjs","names":[],"sources":["../../src/core/package-json.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { applyEdits, modify, parseTree } from 'jsonc-parser'\n\nexport interface EditOptions {\n /** Formatting options for inserted content */\n tabSize?: number\n insertSpaces?: boolean\n}\n\nconst defaultEditOptions: EditOptions = { tabSize: 2, insertSpaces: true }\n\n// ── Cached reader ──────────────────────────────────────────────\n\nconst cache = new Map<string, { raw: string, parsed: Record<string, unknown> }>()\n\n/**\n * Read and parse a package.json, returning cached result on repeat calls.\n * Throws if the file does not exist.\n */\nexport function readPackageJson(pkgPath: string): { raw: string, parsed: Record<string, unknown> } {\n const hit = cache.get(pkgPath)\n if (hit)\n return hit\n const raw = readFileSync(pkgPath, 'utf-8')\n const parsed = JSON.parse(raw) as Record<string, unknown>\n const entry = { raw, parsed }\n cache.set(pkgPath, entry)\n return entry\n}\n\n/**\n * Same as readPackageJson but returns null when the file is missing or unparseable.\n */\nexport function readPackageJsonSafe(pkgPath: string): { raw: string, parsed: Record<string, unknown> } | null {\n if (cache.has(pkgPath))\n return cache.get(pkgPath)!\n if (!existsSync(pkgPath))\n return null\n try {\n return readPackageJson(pkgPath)\n }\n catch {\n return null\n }\n}\n\n/**\n * Drop any cached entry so the next read hits disk.\n */\nexport function invalidatePackageJson(pkgPath: string): void {\n cache.delete(pkgPath)\n}\n\n/**\n * Clear all cached entries. Useful in tests.\n */\nexport function clearPackageJsonCache(): void {\n cache.clear()\n}\n\n// ── JSON editing helpers ───────────────────────────────────────\n\n/**\n * Set a value at a JSON path, preserving all surrounding formatting.\n * Returns the modified file content as a string.\n */\nexport function editJsonProperty(raw: string, path: (string | number)[], value: unknown, options?: EditOptions): string {\n const opts = { ...defaultEditOptions, ...options }\n const edits = modify(raw, path, value, {\n formattingOptions: { tabSize: opts.tabSize!, insertSpaces: opts.insertSpaces! },\n })\n return applyEdits(raw, edits)\n}\n\n/**\n * Remove a value at a JSON path, preserving all surrounding formatting.\n */\nexport function removeJsonProperty(raw: string, path: (string | number)[]): string {\n const edits = modify(raw, path, undefined, {})\n return applyEdits(raw, edits)\n}\n\n/**\n * Read a package.json, apply an edit function, write it back, and invalidate the cache.\n * The edit function receives the raw text and parsed object,\n * and returns the new raw text (or null to skip writing).\n */\nexport function patchPackageJson(\n pkgPath: string,\n editFn: (raw: string, pkg: Record<string, unknown>) => string | null,\n): boolean {\n const { raw, parsed } = readPackageJson(pkgPath)\n const result = editFn(raw, parsed)\n if (result === null)\n return false\n writeFileSync(pkgPath, result)\n invalidatePackageJson(pkgPath)\n return true\n}\n\n/**\n * Append a value to a JSON array at the given path, preserving formatting.\n * Inserts in sorted order if the array contains strings.\n */\nexport function appendToJsonArray(raw: string, path: string[], value: string, options?: EditOptions): string {\n const opts = { ...defaultEditOptions, ...options }\n const tree = parseTree(raw)\n if (!tree)\n return editJsonProperty(raw, path, [value], opts)\n\n // Walk to the target array node\n let node = tree\n for (const key of path) {\n const child = node.children?.find(c =>\n c.type === 'property' && c.children?.[0]?.value === key,\n )\n if (!child?.children?.[1])\n return editJsonProperty(raw, path, [value], opts)\n node = child.children[1]\n }\n\n if (node.type !== 'array' || !node.children)\n return editJsonProperty(raw, path, [value], opts)\n\n // Find sorted insertion index (only for string-only arrays)\n const allStrings = node.children.every(c => typeof c.value === 'string')\n let idx = node.children.length\n if (allStrings) {\n const items = node.children.map(c => c.value as string)\n for (let i = 0; i < items.length; i++) {\n if (value.localeCompare(items[i]!) < 0) {\n idx = i\n break\n }\n }\n }\n\n const edits = modify(raw, [...path, idx], value, {\n formattingOptions: { tabSize: opts.tabSize!, insertSpaces: opts.insertSpaces! },\n isArrayInsertion: true,\n })\n return applyEdits(raw, edits)\n}\n"],"mappings":";;AASA,MAAM,qBAAkC;CAAE,SAAS;CAAG,cAAc;CAAM;AAI1E,MAAM,wBAAQ,IAAI,KAA+D;;;;CAMjF,MAAA,MAAgB,aAAgB,SAAmE,QAAA;CACjG,MAAM,QAAM;EACZ;EAEA,QAAM,KAAM,MAAA,IAAA;EAEZ;OAAgB,IAAA,SAAA,MAAA;QAAK;;AAErB,SAAO,oBAAA,SAAA;;;;;SAMO;AACd,SAAI;;;SAOE,sBAAA,SAAA;AACJ,OAAA,OAAO,QAAA;;;;;EAOX,GAAA;EACE;;;;;;SAiBM,iBAAO,SAAA,QAAA;OAAK,EAAA,KAAA,WAAA,gBAAA,QAAA;OAAuB,SAAA,OAAA,KAAA,OAAA;KAAS,WAAA,KAAA,QAAA;AAIlD,eAAO,SAHO,OAAO;uBACgB,QAAA;QAAU;;;;;;;CAkBjD,MAAA,OAAgB,UAAA,IACd;AAGA,KAAA,CAAA,KAAQ,QAAK,iBAAW,KAAgB,MAAA,CAAA,MAAQ,EAAA,KAAA;CAChD,IAAA,OAAM;AACN,MAAI,MAAA,OAAW,MACb;EACF,MAAA,QAAc,KAAA,UAAgB,MAAA,MAAA,EAAA,SAAA,cAAA,EAAA,WAAA,IAAA,UAAA,IAAA;AAC9B,MAAA,CAAA,OAAA,WAAsB,GAAA,QAAQ,iBAAA,KAAA,MAAA,CAAA,MAAA,EAAA,KAAA;AAC9B,SAAO,MAAA,SAAA;;;;;;EAOT,MAAA,QAAgB,KAAA,SAAkB,KAAa,MAAgB,EAAA,MAAe;AAC5E,OAAM,IAAA,IAAO,GAAA,IAAA,MAAA,QAAA,IAAA,KAAA,MAAA,cAAA,MAAA,GAAA,GAAA,GAAA;AAAE,SAAG;AAAoB;;;AAEtC,QAAK,WACI,KAAA,OAAA,KAAiB,CAAA,GAAA,MAAK,IAAO,EAAA,OAAQ;EAG9C,mBAAW;GACX,SAAW,KAAA;GACT,cAAc,KAAK;GAGnB;EAEA,kBAAa;;;SAQX,uBAAoB,GAAA,oBAAA,GAAA,oBAAA,GAAA,qBAAA"}
|
package/dist/_chunks/pool2.mjs
CHANGED
|
@@ -2,7 +2,6 @@ import { dirname, join } from "pathe";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { Worker } from "node:worker_threads";
|
|
5
|
-
//#region src/retriv/pool.ts
|
|
6
5
|
let worker = null;
|
|
7
6
|
let taskId = 0;
|
|
8
7
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -112,7 +111,6 @@ async function shutdownWorker() {
|
|
|
112
111
|
w.postMessage({ type: "shutdown" });
|
|
113
112
|
});
|
|
114
113
|
}
|
|
115
|
-
//#endregion
|
|
116
114
|
export { shutdownWorker as n, createIndexInWorker as t };
|
|
117
115
|
|
|
118
116
|
//# sourceMappingURL=pool2.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool2.mjs","names":[],"sources":["../../src/retriv/pool.ts"],"sourcesContent":["import type { IndexConfig, Document as RetrivDocument } from './types.ts'\nimport type { WorkerMessage, WorkerResponse } from './worker.ts'\nimport { existsSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { Worker } from 'node:worker_threads'\nimport { dirname, join } from 'pathe'\n\ninterface PendingTask {\n id: number\n resolve: () => void\n reject: (err: Error) => void\n onProgress?: IndexConfig['onProgress']\n}\n\nlet worker: Worker | null = null\nlet taskId = 0\nconst pending = new Map<number, PendingTask>()\nconst queue: Array<() => void> = []\nlet running = false\n\nfunction resolveWorkerPath(): { path: string, execArgv?: string[] } {\n const dir = dirname(fileURLToPath(import.meta.url))\n\n // Bundled: dist/retriv/worker.mjs (resolve from package root, not chunk dir)\n for (const candidate of [join(dir, 'worker.mjs'), join(dir, '..', 'retriv', 'worker.mjs')]) {\n if (existsSync(candidate))\n return { path: candidate }\n }\n\n // Dev stub: src/retriv/pool.ts → src/retriv/worker.ts\n return { path: join(dir, 'worker.ts'), execArgv: ['--experimental-strip-types'] }\n}\n\nfunction ensureWorker(): Worker {\n if (worker)\n return worker\n\n const config = resolveWorkerPath()\n const w = new Worker(config.path, {\n execArgv: config.execArgv,\n })\n\n w.on('message', (msg: WorkerResponse) => {\n const task = pending.get(msg.id)\n if (!task)\n return\n\n if (msg.type === 'progress') {\n task.onProgress?.({ phase: msg.phase as any, current: msg.current, total: msg.total })\n }\n else if (msg.type === 'done') {\n pending.delete(msg.id)\n task.resolve()\n }\n else if (msg.type === 'error') {\n pending.delete(msg.id)\n task.reject(new Error(msg.message))\n }\n })\n\n w.on('error', (err: Error) => {\n for (const task of pending.values())\n task.reject(err)\n pending.clear()\n worker = null\n })\n\n w.on('exit', (code) => {\n if (pending.size > 0) {\n const err = new Error(`Worker exited (code ${code}) with ${pending.size} pending tasks`)\n for (const task of pending.values())\n task.reject(err)\n pending.clear()\n }\n worker = null\n })\n\n worker = w\n return w\n}\n\nfunction drainQueue() {\n if (running || queue.length === 0)\n return\n const next = queue.shift()!\n next()\n}\n\nexport async function createIndexInWorker(\n documents: RetrivDocument[],\n config: IndexConfig & { removeIds?: string[] },\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const run = () => {\n running = true\n const id = ++taskId\n\n let w: Worker\n try {\n w = ensureWorker()\n }\n catch (err) {\n running = false\n drainQueue()\n reject(err instanceof Error ? err : new Error(String(err)))\n return\n }\n\n pending.set(id, {\n id,\n resolve: () => {\n running = false\n drainQueue()\n resolve()\n },\n reject: (err) => {\n running = false\n drainQueue()\n reject(err)\n },\n onProgress: config.onProgress,\n })\n\n const msg: WorkerMessage = {\n type: 'index',\n id,\n documents,\n dbPath: config.dbPath,\n removeIds: config.removeIds,\n }\n\n w.postMessage(msg)\n }\n\n if (running) {\n queue.push(run)\n }\n else {\n run()\n }\n })\n}\n\nexport async function shutdownWorker(): Promise<void> {\n if (!worker)\n return\n\n const w = worker\n worker = null\n\n return new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n w.terminate().then(() => resolve(), () => resolve())\n }, 5000)\n\n w.once('exit', () => {\n clearTimeout(timeout)\n resolve()\n })\n\n w.postMessage({ type: 'shutdown' } satisfies WorkerMessage)\n })\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"pool2.mjs","names":[],"sources":["../../src/retriv/pool.ts"],"sourcesContent":["import type { IndexConfig, Document as RetrivDocument } from './types.ts'\nimport type { WorkerMessage, WorkerResponse } from './worker.ts'\nimport { existsSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { Worker } from 'node:worker_threads'\nimport { dirname, join } from 'pathe'\n\ninterface PendingTask {\n id: number\n resolve: () => void\n reject: (err: Error) => void\n onProgress?: IndexConfig['onProgress']\n}\n\nlet worker: Worker | null = null\nlet taskId = 0\nconst pending = new Map<number, PendingTask>()\nconst queue: Array<() => void> = []\nlet running = false\n\nfunction resolveWorkerPath(): { path: string, execArgv?: string[] } {\n const dir = dirname(fileURLToPath(import.meta.url))\n\n // Bundled: dist/retriv/worker.mjs (resolve from package root, not chunk dir)\n for (const candidate of [join(dir, 'worker.mjs'), join(dir, '..', 'retriv', 'worker.mjs')]) {\n if (existsSync(candidate))\n return { path: candidate }\n }\n\n // Dev stub: src/retriv/pool.ts → src/retriv/worker.ts\n return { path: join(dir, 'worker.ts'), execArgv: ['--experimental-strip-types'] }\n}\n\nfunction ensureWorker(): Worker {\n if (worker)\n return worker\n\n const config = resolveWorkerPath()\n const w = new Worker(config.path, {\n execArgv: config.execArgv,\n })\n\n w.on('message', (msg: WorkerResponse) => {\n const task = pending.get(msg.id)\n if (!task)\n return\n\n if (msg.type === 'progress') {\n task.onProgress?.({ phase: msg.phase as any, current: msg.current, total: msg.total })\n }\n else if (msg.type === 'done') {\n pending.delete(msg.id)\n task.resolve()\n }\n else if (msg.type === 'error') {\n pending.delete(msg.id)\n task.reject(new Error(msg.message))\n }\n })\n\n w.on('error', (err: Error) => {\n for (const task of pending.values())\n task.reject(err)\n pending.clear()\n worker = null\n })\n\n w.on('exit', (code) => {\n if (pending.size > 0) {\n const err = new Error(`Worker exited (code ${code}) with ${pending.size} pending tasks`)\n for (const task of pending.values())\n task.reject(err)\n pending.clear()\n }\n worker = null\n })\n\n worker = w\n return w\n}\n\nfunction drainQueue() {\n if (running || queue.length === 0)\n return\n const next = queue.shift()!\n next()\n}\n\nexport async function createIndexInWorker(\n documents: RetrivDocument[],\n config: IndexConfig & { removeIds?: string[] },\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const run = () => {\n running = true\n const id = ++taskId\n\n let w: Worker\n try {\n w = ensureWorker()\n }\n catch (err) {\n running = false\n drainQueue()\n reject(err instanceof Error ? err : new Error(String(err)))\n return\n }\n\n pending.set(id, {\n id,\n resolve: () => {\n running = false\n drainQueue()\n resolve()\n },\n reject: (err) => {\n running = false\n drainQueue()\n reject(err)\n },\n onProgress: config.onProgress,\n })\n\n const msg: WorkerMessage = {\n type: 'index',\n id,\n documents,\n dbPath: config.dbPath,\n removeIds: config.removeIds,\n }\n\n w.postMessage(msg)\n }\n\n if (running) {\n queue.push(run)\n }\n else {\n run()\n }\n })\n}\n\nexport async function shutdownWorker(): Promise<void> {\n if (!worker)\n return\n\n const w = worker\n worker = null\n\n return new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n w.terminate().then(() => resolve(), () => resolve())\n }, 5000)\n\n w.once('exit', () => {\n clearTimeout(timeout)\n resolve()\n })\n\n w.postMessage({ type: 'shutdown' } satisfies WorkerMessage)\n })\n}\n"],"mappings":";;;;AAcA,IAAI,SAAwB;AAC5B,IAAI,SAAS;AACb,MAAM,0BAAU,IAAI,KAA0B;AAC9C,MAAM,QAA2B,EAAE;AACnC,IAAI,UAAU;AAEd,SAAS,oBAA2D;CAClE,MAAM,MAAM,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAGnD,MAAK,MAAM,aAAa,CAAC,KAAK,KAAK,aAAa,EAAE,KAAK,KAAK,MAAM,UAAU,aAAa,CAAC,CACxF,KAAI,WAAW,UAAU,CACvB,QAAO,EAAE,MAAM,WAAW;AAI9B,QAAO;EAAE,MAAM,KAAK,KAAK,YAAY;EAAE,UAAU,CAAC,6BAAA;EAA+B;;AAGnF,SAAS,eAAuB;AAC9B,KAAI,OACF,QAAO;CAET,MAAM,SAAS,mBAAmB;CAClC,MAAM,IAAI,IAAI,OAAO,OAAO,MAAM,EAChC,UAAU,OAAO,UAClB,CAAC;AAEF,GAAE,GAAG,YAAY,QAAwB;EACvC,MAAM,OAAO,QAAQ,IAAI,IAAI,GAAG;AAChC,MAAI,CAAC,KACH;AAEF,MAAI,IAAI,SAAS,WACf,MAAK,aAAa;GAAE,OAAO,IAAI;GAAc,SAAS,IAAI;GAAS,OAAO,IAAI;GAAO,CAAC;WAE/E,IAAI,SAAS,QAAQ;AAC5B,WAAQ,OAAO,IAAI,GAAG;AACtB,QAAK,SAAS;aAEP,IAAI,SAAS,SAAS;AAC7B,WAAQ,OAAO,IAAI,GAAG;AACtB,QAAK,OAAO,IAAI,MAAM,IAAI,QAAQ,CAAC;;GAErC;AAEF,GAAE,GAAG,UAAU,QAAe;AAC5B,OAAK,MAAM,QAAQ,QAAQ,QAAQ,CACjC,MAAK,OAAO,IAAI;AAClB,UAAQ,OAAO;AACf,WAAS;GACT;AAEF,GAAE,GAAG,SAAS,SAAS;AACrB,MAAI,QAAQ,OAAO,GAAG;GACpB,MAAM,sBAAM,IAAI,MAAM,uBAAuB,KAAK,SAAS,QAAQ,KAAK,gBAAgB;AACxF,QAAK,MAAM,QAAQ,QAAQ,QAAQ,CACjC,MAAK,OAAO,IAAI;AAClB,WAAQ,OAAO;;AAEjB,WAAS;GACT;AAEF,UAAS;AACT,QAAO;;AAGT,SAAS,aAAa;AACpB,KAAI,WAAW,MAAM,WAAW,EAC9B;AACW,OAAM,OAAO,EACpB;;AAGR,eAAsB,oBACpB,WACA,QACe;AACf,QAAO,IAAI,SAAe,SAAS,WAAW;EAC5C,MAAM,YAAY;AAChB,aAAU;GACV,MAAM,KAAK,EAAE;GAEb,IAAI;AACJ,OAAI;AACF,QAAI,cAAc;YAEb,KAAK;AACV,cAAU;AACV,gBAAY;AACZ,WAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,CAAC;AAC3D;;AAGF,WAAQ,IAAI,IAAI;IACd;IACA,eAAe;AACb,eAAU;AACV,iBAAY;AACZ,cAAS;;IAEX,SAAS,QAAQ;AACf,eAAU;AACV,iBAAY;AACZ,YAAO,IAAI;;IAEb,YAAY,OAAO;IACpB,CAAC;GAEF,MAAM,MAAqB;IACzB,MAAM;IACN;IACA;IACA,QAAQ,OAAO;IACf,WAAW,OAAO;IACnB;AAED,KAAE,YAAY,IAAI;;AAGpB,MAAI,QACF,OAAM,KAAK,IAAI;MAGf,MAAK;GAEP;;AAGJ,eAAsB,iBAAgC;AACpD,KAAI,CAAC,OACH;CAEF,MAAM,IAAI;AACV,UAAS;AAET,QAAO,IAAI,SAAe,YAAY;EACpC,MAAM,UAAU,iBAAiB;AAC/B,KAAE,WAAW,CAAC,WAAW,SAAS,QAAQ,SAAS,CAAC;KACnD,IAAK;AAER,IAAE,KAAK,cAAc;AACnB,gBAAa,QAAQ;AACrB,YAAS;IACT;AAEF,IAAE,YAAY,EAAE,MAAM,YAAY,CAAyB;GAC3D"}
|
package/dist/_chunks/prepare.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { o as getCacheDir } from "./config.mjs";
|
|
2
2
|
import { join } from "pathe";
|
|
3
3
|
import { existsSync, lstatSync, mkdirSync, readdirSync, rmSync, symlinkSync, unlinkSync } from "node:fs";
|
|
4
|
-
//#region src/core/prepare.ts
|
|
5
|
-
/** Resolve package directory: node_modules first, then global cache */
|
|
6
4
|
function resolvePkgDir(name, cwd, version) {
|
|
7
5
|
const nodeModulesPath = join(cwd, "node_modules", name);
|
|
8
6
|
if (existsSync(nodeModulesPath)) return nodeModulesPath;
|
|
@@ -12,18 +10,23 @@ function resolvePkgDir(name, cwd, version) {
|
|
|
12
10
|
}
|
|
13
11
|
return null;
|
|
14
12
|
}
|
|
15
|
-
/** Restore .skilld/pkg symlink to node_modules if broken */
|
|
16
13
|
function restorePkgSymlink(skillsDir, name, info, cwd) {
|
|
17
14
|
const refsDir = join(skillsDir, name, ".skilld");
|
|
18
15
|
const pkgLink = join(refsDir, "pkg");
|
|
19
16
|
if (!existsSync(join(skillsDir, name))) return;
|
|
20
|
-
|
|
17
|
+
try {
|
|
18
|
+
if (lstatSync(pkgLink).isSymbolicLink()) {
|
|
19
|
+
if (existsSync(pkgLink)) return;
|
|
20
|
+
unlinkSync(pkgLink);
|
|
21
|
+
} else return;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (err.code !== "ENOENT") return;
|
|
24
|
+
}
|
|
21
25
|
const pkgDir = resolvePkgDir(info.packageName || name, cwd, info.version);
|
|
22
26
|
if (!pkgDir) return;
|
|
23
27
|
mkdirSync(refsDir, { recursive: true });
|
|
24
28
|
symlinkSync(pkgDir, pkgLink);
|
|
25
29
|
}
|
|
26
|
-
/** Check if package ships a skills/ directory with SKILL.md or _SKILL.md subdirs */
|
|
27
30
|
function getShippedSkills(name, cwd, version) {
|
|
28
31
|
const pkgPath = resolvePkgDir(name, cwd, version);
|
|
29
32
|
if (!pkgPath) return [];
|
|
@@ -34,7 +37,6 @@ function getShippedSkills(name, cwd, version) {
|
|
|
34
37
|
skillDir: join(skillsPath, d.name)
|
|
35
38
|
}));
|
|
36
39
|
}
|
|
37
|
-
/** Create symlink from skills dir to shipped skill dir */
|
|
38
40
|
function linkShippedSkill(baseDir, skillName, targetDir) {
|
|
39
41
|
const linkPath = join(baseDir, skillName);
|
|
40
42
|
if (existsSync(linkPath)) if (lstatSync(linkPath).isSymbolicLink()) unlinkSync(linkPath);
|
|
@@ -44,7 +46,6 @@ function linkShippedSkill(baseDir, skillName, targetDir) {
|
|
|
44
46
|
});
|
|
45
47
|
symlinkSync(targetDir, linkPath);
|
|
46
48
|
}
|
|
47
|
-
//#endregion
|
|
48
49
|
export { restorePkgSymlink as i, linkShippedSkill as n, resolvePkgDir as r, getShippedSkills as t };
|
|
49
50
|
|
|
50
51
|
//# sourceMappingURL=prepare.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prepare.mjs","names":[],"sources":["../../src/core/prepare.ts"],"sourcesContent":["/**\n * Shared prepare utilities used by both the fast entry (src/prepare.ts)\n * and the full CLI command (src/commands/prepare.ts).\n *\n * Keep this module lightweight: no imports from agent/, cache/storage.ts,\n * or any module that pulls in sanitize/clack/citty.\n */\n\nimport type { SkillInfo } from './lockfile.ts'\nimport { existsSync, lstatSync, mkdirSync, readdirSync, rmSync, symlinkSync, unlinkSync } from 'node:fs'\nimport { join } from 'pathe'\nimport { getCacheDir } from '../cache/version.ts'\n\n/** Resolve package directory: node_modules first, then global cache */\nexport function resolvePkgDir(name: string, cwd: string, version?: string): string | null {\n const nodeModulesPath = join(cwd, 'node_modules', name)\n if (existsSync(nodeModulesPath))\n return nodeModulesPath\n\n if (version) {\n const cachedPkgDir = join(getCacheDir(name, version), 'pkg')\n if (existsSync(join(cachedPkgDir, 'package.json')))\n return cachedPkgDir\n }\n\n return null\n}\n\n/** Restore .skilld/pkg symlink to node_modules if broken */\nexport function restorePkgSymlink(skillsDir: string, name: string, info: SkillInfo, cwd: string): void {\n const refsDir = join(skillsDir, name, '.skilld')\n const pkgLink = join(refsDir, 'pkg')\n\n if (!existsSync(join(skillsDir, name)))\n return\n\n if (existsSync(pkgLink))\n return\n\n const pkgName = info.packageName || name\n const pkgDir = resolvePkgDir(pkgName, cwd, info.version)\n if (!pkgDir)\n return\n\n mkdirSync(refsDir, { recursive: true })\n symlinkSync(pkgDir, pkgLink)\n}\n\nexport interface ShippedSkill {\n skillName: string\n skillDir: string\n}\n\n/** Check if package ships a skills/ directory with SKILL.md or _SKILL.md subdirs */\nexport function getShippedSkills(name: string, cwd: string, version?: string): ShippedSkill[] {\n const pkgPath = resolvePkgDir(name, cwd, version)\n if (!pkgPath)\n return []\n\n const skillsPath = join(pkgPath, 'skills')\n if (!existsSync(skillsPath))\n return []\n\n return readdirSync(skillsPath, { withFileTypes: true })\n .filter(d => d.isDirectory() && (existsSync(join(skillsPath, d.name, 'SKILL.md')) || existsSync(join(skillsPath, d.name, '_SKILL.md'))))\n .map(d => ({ skillName: d.name, skillDir: join(skillsPath, d.name) }))\n}\n\n/** Create symlink from skills dir to shipped skill dir */\nexport function linkShippedSkill(baseDir: string, skillName: string, targetDir: string): void {\n const linkPath = join(baseDir, skillName)\n if (existsSync(linkPath)) {\n const stat = lstatSync(linkPath)\n if (stat.isSymbolicLink())\n unlinkSync(linkPath)\n else rmSync(linkPath, { recursive: true, force: true })\n }\n symlinkSync(targetDir, linkPath)\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"prepare.mjs","names":[],"sources":["../../src/core/prepare.ts"],"sourcesContent":["/**\n * Shared prepare utilities used by both the fast entry (src/prepare.ts)\n * and the full CLI command (src/commands/prepare.ts).\n *\n * Keep this module lightweight: no imports from agent/, cache/storage.ts,\n * or any module that pulls in sanitize/clack/citty.\n */\n\nimport type { SkillInfo } from './lockfile.ts'\nimport { existsSync, lstatSync, mkdirSync, readdirSync, rmSync, symlinkSync, unlinkSync } from 'node:fs'\nimport { join } from 'pathe'\nimport { getCacheDir } from '../cache/version.ts'\n\n/** Resolve package directory: node_modules first, then global cache */\nexport function resolvePkgDir(name: string, cwd: string, version?: string): string | null {\n const nodeModulesPath = join(cwd, 'node_modules', name)\n if (existsSync(nodeModulesPath))\n return nodeModulesPath\n\n if (version) {\n const cachedPkgDir = join(getCacheDir(name, version), 'pkg')\n if (existsSync(join(cachedPkgDir, 'package.json')))\n return cachedPkgDir\n }\n\n return null\n}\n\n/** Restore .skilld/pkg symlink to node_modules if broken */\nexport function restorePkgSymlink(skillsDir: string, name: string, info: SkillInfo, cwd: string): void {\n const refsDir = join(skillsDir, name, '.skilld')\n const pkgLink = join(refsDir, 'pkg')\n\n if (!existsSync(join(skillsDir, name)))\n return\n\n // Use lstatSync to detect dangling symlinks — existsSync follows symlinks\n // and returns false for dangling ones, causing symlinkSync to throw EEXIST\n try {\n const stat = lstatSync(pkgLink)\n if (stat.isSymbolicLink()) {\n if (existsSync(pkgLink))\n return // symlink exists and target is valid\n unlinkSync(pkgLink) // dangling symlink — remove before re-creating\n }\n else {\n return // real file/dir exists at this path\n }\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT')\n return // permission/IO error — bail instead of masking\n }\n\n const pkgName = info.packageName || name\n const pkgDir = resolvePkgDir(pkgName, cwd, info.version)\n if (!pkgDir)\n return\n\n mkdirSync(refsDir, { recursive: true })\n symlinkSync(pkgDir, pkgLink)\n}\n\nexport interface ShippedSkill {\n skillName: string\n skillDir: string\n}\n\n/** Check if package ships a skills/ directory with SKILL.md or _SKILL.md subdirs */\nexport function getShippedSkills(name: string, cwd: string, version?: string): ShippedSkill[] {\n const pkgPath = resolvePkgDir(name, cwd, version)\n if (!pkgPath)\n return []\n\n const skillsPath = join(pkgPath, 'skills')\n if (!existsSync(skillsPath))\n return []\n\n return readdirSync(skillsPath, { withFileTypes: true })\n .filter(d => d.isDirectory() && (existsSync(join(skillsPath, d.name, 'SKILL.md')) || existsSync(join(skillsPath, d.name, '_SKILL.md'))))\n .map(d => ({ skillName: d.name, skillDir: join(skillsPath, d.name) }))\n}\n\n/** Create symlink from skills dir to shipped skill dir */\nexport function linkShippedSkill(baseDir: string, skillName: string, targetDir: string): void {\n const linkPath = join(baseDir, skillName)\n if (existsSync(linkPath)) {\n const stat = lstatSync(linkPath)\n if (stat.isSymbolicLink())\n unlinkSync(linkPath)\n else rmSync(linkPath, { recursive: true, force: true })\n }\n symlinkSync(targetDir, linkPath)\n}\n"],"mappings":";;;AAcA,SAAgB,cAAc,MAAc,KAAa,SAAiC;CACxF,MAAM,kBAAkB,KAAK,KAAK,gBAAgB,KAAK;AACvD,KAAI,WAAW,gBAAgB,CAC7B,QAAO;AAET,KAAI,SAAS;EACX,MAAM,eAAe,KAAK,YAAY,MAAM,QAAQ,EAAE,MAAM;AAC5D,MAAI,WAAW,KAAK,cAAc,eAAe,CAAC,CAChD,QAAO;;AAGX,QAAO;;AAIT,SAAgB,kBAAkB,WAAmB,MAAc,MAAiB,KAAmB;CACrG,MAAM,UAAU,KAAK,WAAW,MAAM,UAAU;CAChD,MAAM,UAAU,KAAK,SAAS,MAAM;AAEpC,KAAI,CAAC,WAAW,KAAK,WAAW,KAAK,CAAC,CACpC;AAIF,KAAI;AAEF,MADa,UAAU,QAAQ,CACtB,gBAAgB,EAAE;AACzB,OAAI,WAAW,QAAQ,CACrB;AACF,cAAW,QAAQ;QAGnB;UAGG,KAAK;AACV,MAAK,IAA8B,SAAS,SAC1C;;CAIJ,MAAM,SAAS,cADC,KAAK,eAAe,MACE,KAAK,KAAK,QAAQ;AACxD,KAAI,CAAC,OACH;AAEF,WAAU,SAAS,EAAE,WAAW,MAAM,CAAC;AACvC,aAAY,QAAQ,QAAQ;;AAS9B,SAAgB,iBAAiB,MAAc,KAAa,SAAkC;CAC5F,MAAM,UAAU,cAAc,MAAM,KAAK,QAAQ;AACjD,KAAI,CAAC,QACH,QAAO,EAAE;CAEX,MAAM,aAAa,KAAK,SAAS,SAAS;AAC1C,KAAI,CAAC,WAAW,WAAW,CACzB,QAAO,EAAE;AAEX,QAAO,YAAY,YAAY,EAAE,eAAe,MAAM,CAAC,CACpD,QAAO,MAAK,EAAE,aAAa,KAAK,WAAW,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC,IAAI,WAAW,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC,EAAE,CACvI,KAAI,OAAM;EAAE,WAAW,EAAE;EAAM,UAAU,KAAK,YAAY,EAAE,KAAA;EAAO,EAAE;;AAI1E,SAAgB,iBAAiB,SAAiB,WAAmB,WAAyB;CAC5F,MAAM,WAAW,KAAK,SAAS,UAAU;AACzC,KAAI,WAAW,SAAS,CAEtB,KADa,UAAU,SAAS,CACvB,gBAAgB,CACvB,YAAW,SAAS;KACjB,QAAO,UAAU;EAAE,WAAW;EAAM,OAAO;EAAM,CAAC;AAEzD,aAAY,WAAW,SAAS"}
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
import "./config.mjs";
|
|
2
|
-
import "./package-json.mjs";
|
|
3
1
|
import { i as restorePkgSymlink, n as linkShippedSkill, t as getShippedSkills } from "./prepare.mjs";
|
|
4
|
-
import "./sanitize.mjs";
|
|
5
|
-
import "./cache.mjs";
|
|
6
|
-
import "./yaml.mjs";
|
|
7
|
-
import "./markdown.mjs";
|
|
8
2
|
import { n as getSharedSkillsDir } from "./shared.mjs";
|
|
9
|
-
import "./sources.mjs";
|
|
10
3
|
import { a as targets } from "./detect.mjs";
|
|
11
4
|
import { i as linkSkillToAgents } from "./prompts.mjs";
|
|
12
5
|
import "./agent.mjs";
|
|
13
|
-
import "./libs/@sinclair/typebox.mjs";
|
|
14
6
|
import { b as resolveAgent } from "./cli-helpers.mjs";
|
|
15
7
|
import { i as readLock, s as writeLock } from "./lockfile.mjs";
|
|
16
8
|
import { t as getProjectState } from "./skills.mjs";
|
|
@@ -18,15 +10,6 @@ import { join } from "pathe";
|
|
|
18
10
|
import { existsSync, mkdirSync } from "node:fs";
|
|
19
11
|
import * as p from "@clack/prompts";
|
|
20
12
|
import { defineCommand } from "citty";
|
|
21
|
-
//#region src/commands/prepare.ts
|
|
22
|
-
/**
|
|
23
|
-
* Prepare command — lightweight hook for package.json "prepare" script.
|
|
24
|
-
*
|
|
25
|
-
* Designed to run on every `pnpm install` / `npm install`. Blocking, fast, no LLM calls.
|
|
26
|
-
* 1. Restore broken symlinks from lockfile (like `install` but skips doc fetching)
|
|
27
|
-
* 2. Auto-install shipped skills from deps (just symlinks + lockfile writes)
|
|
28
|
-
* 3. Report outdated skills count and suggest `skilld update`
|
|
29
|
-
*/
|
|
30
13
|
const prepareCommandDef = defineCommand({
|
|
31
14
|
meta: {
|
|
32
15
|
name: "prepare",
|
|
@@ -89,7 +72,6 @@ const prepareCommandDef = defineCommand({
|
|
|
89
72
|
}
|
|
90
73
|
}
|
|
91
74
|
});
|
|
92
|
-
//#endregion
|
|
93
75
|
export { prepareCommandDef };
|
|
94
76
|
|
|
95
77
|
//# sourceMappingURL=prepare2.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prepare2.mjs","names":["agents"],"sources":["../../src/commands/prepare.ts"],"sourcesContent":["/**\n * Prepare command — lightweight hook for package.json \"prepare\" script.\n *\n * Designed to run on every `pnpm install` / `npm install`. Blocking, fast, no LLM calls.\n * 1. Restore broken symlinks from lockfile (like `install` but skips doc fetching)\n * 2. Auto-install shipped skills from deps (just symlinks + lockfile writes)\n * 3. Report outdated skills count and suggest `skilld update`\n */\n\nimport { existsSync, mkdirSync } from 'node:fs'\nimport * as p from '@clack/prompts'\nimport { defineCommand } from 'citty'\nimport { join } from 'pathe'\nimport { agents, linkSkillToAgents } from '../agent/index.ts'\nimport { resolveAgent } from '../cli-helpers.ts'\nimport { readLock, writeLock } from '../core/lockfile.ts'\nimport { getShippedSkills, linkShippedSkill, restorePkgSymlink } from '../core/prepare.ts'\nimport { getSharedSkillsDir } from '../core/shared.ts'\nimport { getProjectState } from '../core/skills.ts'\n\nexport const prepareCommandDef = defineCommand({\n meta: { name: 'prepare', description: 'Restore references and sync shipped skills (for package.json hooks)' },\n args: {\n agent: {\n type: 'enum' as const,\n options: Object.keys(agents),\n alias: 'a',\n description: 'Target agent',\n },\n },\n async run({ args }) {\n const cwd = process.cwd()\n\n const agent = resolveAgent(args.agent)\n if (!agent || agent === 'none')\n return\n\n const agentConfig = agents[agent]\n const shared = getSharedSkillsDir(cwd)\n const skillsDir = shared || join(cwd, agentConfig.skillsDir)\n\n // ── Fast path: read primary lockfile, check all skills intact ──\n\n const lock = readLock(skillsDir)\n if (lock && Object.keys(lock.skills).length > 0) {\n let allIntact = true\n\n for (const [name, info] of Object.entries(lock.skills)) {\n if (!info.version)\n continue\n\n const skillDir = join(skillsDir, name)\n if (existsSync(skillDir)) {\n // Skill dir exists; for non-shipped, also check .skilld/pkg symlink\n if (info.source !== 'shipped')\n restorePkgSymlink(skillsDir, name, info, cwd)\n continue\n }\n\n // Skill dir missing, needs restore\n allIntact = false\n\n if (info.source === 'shipped') {\n const pkgName = info.packageName || name\n const shipped = getShippedSkills(pkgName, cwd, info.version)\n const match = shipped.find(s => s.skillName === name)\n if (match)\n linkShippedSkill(skillsDir, name, match.skillDir)\n }\n }\n\n // If all skills intact, skip expensive getProjectState entirely\n if (allIntact)\n return\n }\n\n // ── Slow path: discover new shipped skills + report outdated ──\n\n const state = await getProjectState(cwd)\n let shippedCount = 0\n\n if (state.shipped.length > 0) {\n mkdirSync(skillsDir, { recursive: true })\n\n for (const entry of state.shipped) {\n const version = state.deps.get(entry.packageName)?.replace(/^[\\^~>=<]+/, '') || '0.0.0'\n\n for (const skill of entry.skills) {\n linkShippedSkill(skillsDir, skill.skillName, skill.skillDir)\n writeLock(skillsDir, skill.skillName, {\n packageName: entry.packageName,\n version,\n source: 'shipped',\n syncedAt: new Date().toISOString().split('T')[0],\n generator: 'skilld',\n })\n\n if (shared)\n linkSkillToAgents(skill.skillName, shared, cwd, agent)\n\n shippedCount++\n }\n }\n\n if (shippedCount > 0)\n p.log.success(`Installed ${shippedCount} shipped skill${shippedCount > 1 ? 's' : ''}`)\n }\n\n if (state.outdated.length > 0) {\n const n = state.outdated.length\n p.log.info(`${n} package${n > 1 ? 's' : ''} ha${n > 1 ? 've' : 's'} new features and/or breaking changes. Run \\`skilld update\\` to sync.`)\n }\n },\n})\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"prepare2.mjs","names":["agents"],"sources":["../../src/commands/prepare.ts"],"sourcesContent":["/**\n * Prepare command — lightweight hook for package.json \"prepare\" script.\n *\n * Designed to run on every `pnpm install` / `npm install`. Blocking, fast, no LLM calls.\n * 1. Restore broken symlinks from lockfile (like `install` but skips doc fetching)\n * 2. Auto-install shipped skills from deps (just symlinks + lockfile writes)\n * 3. Report outdated skills count and suggest `skilld update`\n */\n\nimport { existsSync, mkdirSync } from 'node:fs'\nimport * as p from '@clack/prompts'\nimport { defineCommand } from 'citty'\nimport { join } from 'pathe'\nimport { agents, linkSkillToAgents } from '../agent/index.ts'\nimport { resolveAgent } from '../cli-helpers.ts'\nimport { readLock, writeLock } from '../core/lockfile.ts'\nimport { getShippedSkills, linkShippedSkill, restorePkgSymlink } from '../core/prepare.ts'\nimport { getSharedSkillsDir } from '../core/shared.ts'\nimport { getProjectState } from '../core/skills.ts'\n\nexport const prepareCommandDef = defineCommand({\n meta: { name: 'prepare', description: 'Restore references and sync shipped skills (for package.json hooks)' },\n args: {\n agent: {\n type: 'enum' as const,\n options: Object.keys(agents),\n alias: 'a',\n description: 'Target agent',\n },\n },\n async run({ args }) {\n const cwd = process.cwd()\n\n const agent = resolveAgent(args.agent)\n if (!agent || agent === 'none')\n return\n\n const agentConfig = agents[agent]\n const shared = getSharedSkillsDir(cwd)\n const skillsDir = shared || join(cwd, agentConfig.skillsDir)\n\n // ── Fast path: read primary lockfile, check all skills intact ──\n\n const lock = readLock(skillsDir)\n if (lock && Object.keys(lock.skills).length > 0) {\n let allIntact = true\n\n for (const [name, info] of Object.entries(lock.skills)) {\n if (!info.version)\n continue\n\n const skillDir = join(skillsDir, name)\n if (existsSync(skillDir)) {\n // Skill dir exists; for non-shipped, also check .skilld/pkg symlink\n if (info.source !== 'shipped')\n restorePkgSymlink(skillsDir, name, info, cwd)\n continue\n }\n\n // Skill dir missing, needs restore\n allIntact = false\n\n if (info.source === 'shipped') {\n const pkgName = info.packageName || name\n const shipped = getShippedSkills(pkgName, cwd, info.version)\n const match = shipped.find(s => s.skillName === name)\n if (match)\n linkShippedSkill(skillsDir, name, match.skillDir)\n }\n }\n\n // If all skills intact, skip expensive getProjectState entirely\n if (allIntact)\n return\n }\n\n // ── Slow path: discover new shipped skills + report outdated ──\n\n const state = await getProjectState(cwd)\n let shippedCount = 0\n\n if (state.shipped.length > 0) {\n mkdirSync(skillsDir, { recursive: true })\n\n for (const entry of state.shipped) {\n const version = state.deps.get(entry.packageName)?.replace(/^[\\^~>=<]+/, '') || '0.0.0'\n\n for (const skill of entry.skills) {\n linkShippedSkill(skillsDir, skill.skillName, skill.skillDir)\n writeLock(skillsDir, skill.skillName, {\n packageName: entry.packageName,\n version,\n source: 'shipped',\n syncedAt: new Date().toISOString().split('T')[0],\n generator: 'skilld',\n })\n\n if (shared)\n linkSkillToAgents(skill.skillName, shared, cwd, agent)\n\n shippedCount++\n }\n }\n\n if (shippedCount > 0)\n p.log.success(`Installed ${shippedCount} shipped skill${shippedCount > 1 ? 's' : ''}`)\n }\n\n if (state.outdated.length > 0) {\n const n = state.outdated.length\n p.log.info(`${n} package${n > 1 ? 's' : ''} ha${n > 1 ? 've' : 's'} new features and/or breaking changes. Run \\`skilld update\\` to sync.`)\n }\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAoBA,SAAa,OAAA,KAAA,QAAoB;EAC/B,OAAM;EAAE,aAAM;EAAW,EAAA;OAAoF,IAAA,EAAA,QAAA;EAC7G,MAAM,MACJ,QAAO,KAAA;EACL,MAAM,QAAA,aAAA,KAAA,MAAA;AACN,MAAA,CAAA,SAAS,UAAYA,OAAO;EAC5B,MAAA,cAAO,QAAA;EACP,MAAA,SAAa,mBAAA,IAAA;EACd,MACF,YAAA,UAAA,KAAA,KAAA,YAAA,UAAA;EACD,MAAM,OAAM,SAAQ,UAAA;AAClB,MAAA,QAAY,OAAA,KAAQ,KAAK,OAAA,CAAA,SAAA,GAAA;GAEzB,IAAA,YAAc;AACd,QAAK,MAAA,CAAA,MAAS,SAAU,OACtB,QAAA,KAAA,OAAA,EAAA;AAEF,QAAM,CAAA,KAAA,QAAcA;AACpB,QAAM,WAAS,KAAA,WAAA,KAAuB,CAAA,EAAA;AACtC,SAAM,KAAA,WAAY,UAAe,mBAAiB,WAAU,MAAA,MAAA,IAAA;AAI5D;;AAEE,gBAAI;AAEJ,QAAK,KAAA,WAAa,WAAS;KACzB,MAAK,QAAK,iBACR,KAAA,eAAA,MAAA,KAAA,KAAA,QAAA,CAAA,MAAA,MAAA,EAAA,cAAA,KAAA;AAGF,SAAI,MAAA,kBADkB,WACI,MAAA,MAAA,SAAA;;;;;EAU1B,MAAI,QAAK,MAAA,gBAAsB,IAAA;MAG7B,eADgB;AAEhB,MAAA,MAAI,QACF,SAAA,GAAA;;;IAKN,MAAI,UACF,MAAA,KAAA,IAAA,MAAA,YAAA,EAAA,QAAA,cAAA,GAAA,IAAA;;AAKJ,sBAAoB,WAAA,MAAgB,WAAI,MAAA,SAAA;AACxC,eAAI,WAAe,MAAA,WAAA;MAEf,aAAM,MAAQ;MAChB;MAEA,QAAW;MACT,2BAA2B,IAAI,MAAM,EAAA,aAAc,CAAA,MAAQ,IAAA,CAAA;MAE3D,WAAW;MACT,CAAA;AACA,SAAA,OAAU,mBAAiB,MAAA,WAAW,QAAA,KAAA,MAAA;;;;OAIpC,eAAA,EAAA,GAAA,IAAA,QAAc,aAAO,aAAoB,gBAAK,eAAA,IAAA,MAAA,KAAA;;MAE/C,MAAC,SAAA,SAAA,GAAA;GAEF,MAAI,IAAA,MACF,SAAA;AAEF,KAAA,IAAA,KAAA,GAAA,EAAA,UAAA,IAAA,IAAA,MAAA,GAAA,KAAA,IAAA,IAAA,OAAA,IAAA,uEAAA;;;CAIJ,CAAA;AAIF,SAAI"}
|