@alavida/agentpack 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -2
- package/bin/intent.js +20 -0
- package/package.json +11 -5
- package/skills/agentpack-cli/SKILL.md +16 -1
- package/skills/agentpack-cli/references/skill-lifecycle.md +21 -1
- package/skills/authoring-skillgraphs-from-knowledge/SKILL.md +148 -0
- package/skills/authoring-skillgraphs-from-knowledge/references/authored-metadata.md +6 -0
- package/skills/developing-and-testing-skills/SKILL.md +109 -0
- package/skills/developing-and-testing-skills/references/local-workbench.md +7 -0
- package/skills/getting-started-skillgraphs/SKILL.md +115 -0
- package/skills/getting-started-skillgraphs/references/command-routing.md +7 -0
- package/skills/identifying-skill-opportunities/SKILL.md +119 -0
- package/skills/identifying-skill-opportunities/references/capability-boundaries.md +6 -0
- package/skills/maintaining-skillgraph-freshness/SKILL.md +110 -0
- package/skills/repairing-broken-skill-or-plugin-state/SKILL.md +112 -0
- package/skills/repairing-broken-skill-or-plugin-state/references/diagnostic-flows.md +6 -0
- package/skills/shipping-production-plugins-and-packages/SKILL.md +123 -0
- package/skills/shipping-production-plugins-and-packages/references/plugin-delivery.md +6 -0
- package/src/application/plugins/build-plugin.js +5 -0
- package/src/application/plugins/inspect-plugin-bundle.js +5 -0
- package/src/application/plugins/validate-plugin-bundle.js +5 -0
- package/src/application/skills/build-skill-workbench-model.js +194 -0
- package/src/application/skills/inspect-skill.js +5 -0
- package/src/application/skills/list-stale-skills.js +9 -0
- package/src/application/skills/run-skill-workbench-action.js +23 -0
- package/src/application/skills/start-skill-dev-workbench.js +192 -0
- package/src/application/skills/validate-skills.js +5 -0
- package/src/cli.js +1 -1
- package/src/commands/plugin.js +7 -4
- package/src/commands/skills.js +14 -9
- package/src/dashboard/App.jsx +343 -0
- package/src/dashboard/components/Breadcrumbs.jsx +45 -0
- package/src/dashboard/components/ControlStrip.jsx +153 -0
- package/src/dashboard/components/InspectorPanel.jsx +203 -0
- package/src/dashboard/components/SkillGraph.jsx +567 -0
- package/src/dashboard/components/Tooltip.jsx +111 -0
- package/src/dashboard/dist/dashboard.js +26692 -0
- package/src/dashboard/index.html +81 -0
- package/src/dashboard/lib/api.js +19 -0
- package/src/dashboard/lib/router.js +15 -0
- package/src/dashboard/main.jsx +4 -0
- package/src/domain/plugins/load-plugin-definition.js +163 -0
- package/src/domain/plugins/plugin-diagnostic-error.js +18 -0
- package/src/domain/plugins/plugin-requirements.js +15 -0
- package/src/domain/skills/skill-graph.js +137 -0
- package/src/domain/skills/skill-model.js +187 -0
- package/src/domain/skills/skill-provenance.js +69 -0
- package/src/infrastructure/fs/build-state-repository.js +16 -0
- package/src/infrastructure/fs/install-state-repository.js +16 -0
- package/src/infrastructure/runtime/materialize-skills.js +117 -0
- package/src/infrastructure/runtime/open-browser.js +20 -0
- package/src/infrastructure/runtime/skill-dev-workbench-server.js +96 -0
- package/src/infrastructure/runtime/watch-skill-workbench.js +68 -0
- package/src/infrastructure/runtime/watch-tree.js +44 -0
- package/src/lib/plugins.js +46 -117
- package/src/lib/skills.js +141 -459
- package/src/utils/errors.js +33 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
readBuildState as readBuildStateRecord,
|
|
6
|
+
writeBuildState as writeBuildStateRecord,
|
|
7
|
+
} from '../../infrastructure/fs/build-state-repository.js';
|
|
8
|
+
|
|
9
|
+
export function hashFile(filePath) {
|
|
10
|
+
const digest = createHash('sha256').update(readFileSync(filePath)).digest('hex');
|
|
11
|
+
return `sha256:${digest}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function readBuildState(repoRoot) {
|
|
15
|
+
return readBuildStateRecord(repoRoot);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function writeBuildState(repoRoot, state) {
|
|
19
|
+
writeBuildStateRecord(repoRoot, state);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function compareRecordedSources(repoRoot, record) {
|
|
23
|
+
const changes = [];
|
|
24
|
+
const recordedSources = record.sources || {};
|
|
25
|
+
|
|
26
|
+
for (const [sourcePath, sourceRecord] of Object.entries(recordedSources)) {
|
|
27
|
+
const absoluteSourcePath = join(repoRoot, sourcePath);
|
|
28
|
+
const currentHash = hashFile(absoluteSourcePath);
|
|
29
|
+
const recordedHash = sourceRecord.hash;
|
|
30
|
+
|
|
31
|
+
if (currentHash !== recordedHash) {
|
|
32
|
+
changes.push({
|
|
33
|
+
path: sourcePath,
|
|
34
|
+
recorded: recordedHash,
|
|
35
|
+
current: currentHash,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return changes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function buildStateRecordForPackageDir(repoRoot, packageDir, {
|
|
44
|
+
parseSkillFrontmatterFile,
|
|
45
|
+
readPackageMetadata,
|
|
46
|
+
normalizeDisplayPath,
|
|
47
|
+
} = {}) {
|
|
48
|
+
const skillFile = join(packageDir, 'SKILL.md');
|
|
49
|
+
const metadata = parseSkillFrontmatterFile(skillFile);
|
|
50
|
+
const packageMetadata = readPackageMetadata(packageDir);
|
|
51
|
+
const sources = {};
|
|
52
|
+
|
|
53
|
+
for (const sourcePath of metadata.sources) {
|
|
54
|
+
sources[sourcePath] = {
|
|
55
|
+
hash: hashFile(join(repoRoot, sourcePath)),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
packageName: packageMetadata.packageName,
|
|
61
|
+
record: {
|
|
62
|
+
package_version: packageMetadata.packageVersion,
|
|
63
|
+
skill_path: normalizeDisplayPath(repoRoot, packageDir),
|
|
64
|
+
skill_file: normalizeDisplayPath(repoRoot, skillFile),
|
|
65
|
+
sources,
|
|
66
|
+
requires: metadata.requires,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function readBuildState(repoRoot) {
|
|
5
|
+
const buildStatePath = join(repoRoot, '.agentpack', 'build-state.json');
|
|
6
|
+
if (!existsSync(buildStatePath)) {
|
|
7
|
+
return { version: 1, skills: {} };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return JSON.parse(readFileSync(buildStatePath, 'utf-8'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function writeBuildState(repoRoot, state) {
|
|
14
|
+
mkdirSync(join(repoRoot, '.agentpack'), { recursive: true });
|
|
15
|
+
writeFileSync(join(repoRoot, '.agentpack', 'build-state.json'), JSON.stringify(state, null, 2) + '\n');
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function readInstallState(repoRoot) {
|
|
5
|
+
const installStatePath = join(repoRoot, '.agentpack', 'install.json');
|
|
6
|
+
if (!existsSync(installStatePath)) {
|
|
7
|
+
return { version: 1, installs: {} };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return JSON.parse(readFileSync(installStatePath, 'utf-8'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function writeInstallState(repoRoot, state) {
|
|
14
|
+
mkdirSync(join(repoRoot, '.agentpack'), { recursive: true });
|
|
15
|
+
writeFileSync(join(repoRoot, '.agentpack', 'install.json'), JSON.stringify(state, null, 2) + '\n');
|
|
16
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, relative } from 'node:path';
|
|
3
|
+
import { writeInstallState } from '../fs/install-state-repository.js';
|
|
4
|
+
|
|
5
|
+
function ensureDir(pathValue) {
|
|
6
|
+
mkdirSync(pathValue, { recursive: true });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function removePathIfExists(pathValue) {
|
|
10
|
+
rmSync(pathValue, { recursive: true, force: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ensureSkillLink(repoRoot, baseDir, skillName, skillDir, normalizeDisplayPath) {
|
|
14
|
+
const skillsDir = join(repoRoot, baseDir, 'skills');
|
|
15
|
+
ensureDir(skillsDir);
|
|
16
|
+
const linkPath = join(skillsDir, skillName);
|
|
17
|
+
removePathIfExists(linkPath);
|
|
18
|
+
symlinkSync(skillDir, linkPath, 'dir');
|
|
19
|
+
return normalizeDisplayPath(repoRoot, linkPath);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function removeSkillLinks(repoRoot, name, normalizeDisplayPath) {
|
|
23
|
+
const removed = [];
|
|
24
|
+
for (const pathValue of [
|
|
25
|
+
join(repoRoot, '.claude', 'skills', name),
|
|
26
|
+
join(repoRoot, '.agents', 'skills', name),
|
|
27
|
+
]) {
|
|
28
|
+
if (!existsSync(pathValue)) continue;
|
|
29
|
+
removePathIfExists(pathValue);
|
|
30
|
+
removed.push(normalizeDisplayPath(repoRoot, pathValue));
|
|
31
|
+
}
|
|
32
|
+
return removed;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function removeSkillLinksByNames(repoRoot, names, normalizeDisplayPath) {
|
|
36
|
+
const removed = [];
|
|
37
|
+
for (const name of names) {
|
|
38
|
+
removed.push(...removeSkillLinks(repoRoot, name, normalizeDisplayPath));
|
|
39
|
+
}
|
|
40
|
+
return [...new Set(removed)];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ensureSymlink(targetPath, linkPath) {
|
|
44
|
+
removePathIfExists(linkPath);
|
|
45
|
+
mkdirSync(dirname(linkPath), { recursive: true });
|
|
46
|
+
symlinkSync(targetPath, linkPath, 'dir');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildInstallRecord(repoRoot, packageDir, directTargetMap, {
|
|
50
|
+
parseSkillFrontmatterFile,
|
|
51
|
+
readPackageMetadata,
|
|
52
|
+
normalizeRelativePath,
|
|
53
|
+
} = {}) {
|
|
54
|
+
const packageMetadata = readPackageMetadata(packageDir);
|
|
55
|
+
if (!packageMetadata.packageName) return null;
|
|
56
|
+
|
|
57
|
+
const skillMetadata = parseSkillFrontmatterFile(join(packageDir, 'SKILL.md'));
|
|
58
|
+
const skillDirName = skillMetadata.name;
|
|
59
|
+
const materializations = [];
|
|
60
|
+
|
|
61
|
+
const claudeTargetAbs = join(repoRoot, '.claude', 'skills', skillDirName);
|
|
62
|
+
ensureSymlink(packageDir, claudeTargetAbs);
|
|
63
|
+
materializations.push({
|
|
64
|
+
target: normalizeRelativePath(relative(repoRoot, claudeTargetAbs)),
|
|
65
|
+
mode: 'symlink',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const agentsTargetAbs = join(repoRoot, '.agents', 'skills', skillDirName);
|
|
69
|
+
ensureSymlink(packageDir, agentsTargetAbs);
|
|
70
|
+
materializations.push({
|
|
71
|
+
target: normalizeRelativePath(relative(repoRoot, agentsTargetAbs)),
|
|
72
|
+
mode: 'symlink',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
packageName: packageMetadata.packageName,
|
|
77
|
+
direct: directTargetMap.has(packageMetadata.packageName),
|
|
78
|
+
requestedTarget: directTargetMap.get(packageMetadata.packageName) || null,
|
|
79
|
+
packageVersion: packageMetadata.packageVersion,
|
|
80
|
+
sourcePackagePath: normalizeRelativePath(relative(repoRoot, packageDir)),
|
|
81
|
+
materializations,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function rebuildInstallState(repoRoot, directTargetMap, {
|
|
86
|
+
listInstalledPackageDirs,
|
|
87
|
+
parseSkillFrontmatterFile,
|
|
88
|
+
readPackageMetadata,
|
|
89
|
+
normalizeRelativePath,
|
|
90
|
+
} = {}) {
|
|
91
|
+
const packageDirs = listInstalledPackageDirs(join(repoRoot, 'node_modules'));
|
|
92
|
+
const installs = {};
|
|
93
|
+
|
|
94
|
+
for (const packageDir of packageDirs) {
|
|
95
|
+
const skillFile = join(packageDir, 'SKILL.md');
|
|
96
|
+
if (!existsSync(skillFile)) continue;
|
|
97
|
+
|
|
98
|
+
const record = buildInstallRecord(repoRoot, packageDir, directTargetMap, {
|
|
99
|
+
parseSkillFrontmatterFile,
|
|
100
|
+
readPackageMetadata,
|
|
101
|
+
normalizeRelativePath,
|
|
102
|
+
});
|
|
103
|
+
if (!record) continue;
|
|
104
|
+
|
|
105
|
+
installs[record.packageName] = {
|
|
106
|
+
direct: record.direct,
|
|
107
|
+
requested_target: record.requestedTarget,
|
|
108
|
+
package_version: record.packageVersion,
|
|
109
|
+
source_package_path: record.sourcePackagePath,
|
|
110
|
+
materializations: record.materializations,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const state = { version: 1, installs };
|
|
115
|
+
writeInstallState(repoRoot, state);
|
|
116
|
+
return state;
|
|
117
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export function openBrowser(url) {
|
|
4
|
+
if (process.env.AGENTPACK_DISABLE_BROWSER === '1') return;
|
|
5
|
+
|
|
6
|
+
const command = process.platform === 'darwin'
|
|
7
|
+
? 'open'
|
|
8
|
+
: process.platform === 'win32'
|
|
9
|
+
? 'start'
|
|
10
|
+
: 'xdg-open';
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
spawn(command, [url], {
|
|
14
|
+
detached: true,
|
|
15
|
+
stdio: 'ignore',
|
|
16
|
+
}).unref();
|
|
17
|
+
} catch {
|
|
18
|
+
// Browser launch failure should not fail the core dev workflow.
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { AgentpackError } from '../../utils/errors.js';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const projectRoot = join(__dirname, '..', '..', '..');
|
|
9
|
+
const htmlPath = join(projectRoot, 'src', 'dashboard', 'index.html');
|
|
10
|
+
const bundlePath = process.env.AGENTPACK_DASHBOARD_BUNDLE_PATH
|
|
11
|
+
? resolve(process.cwd(), process.env.AGENTPACK_DASHBOARD_BUNDLE_PATH)
|
|
12
|
+
: join(projectRoot, 'src', 'dashboard', 'dist', 'dashboard.js');
|
|
13
|
+
|
|
14
|
+
function ensureDashboardBundle() {
|
|
15
|
+
if (existsSync(bundlePath)) return;
|
|
16
|
+
|
|
17
|
+
throw new AgentpackError('Skill workbench bundle is missing from this install', {
|
|
18
|
+
code: 'missing_skill_workbench_bundle',
|
|
19
|
+
suggestion: 'Reinstall agentpack or rebuild the dashboard bundle before starting skills dev.',
|
|
20
|
+
path: bundlePath,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function startSkillDevWorkbenchServer({ buildModel, defaultSkill, onAction = null }) {
|
|
25
|
+
ensureDashboardBundle();
|
|
26
|
+
|
|
27
|
+
const server = http.createServer(async (req, res) => {
|
|
28
|
+
if (!req.url) {
|
|
29
|
+
res.writeHead(404);
|
|
30
|
+
res.end();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
35
|
+
|
|
36
|
+
if (parsedUrl.pathname === '/api/model') {
|
|
37
|
+
const skillParam = parsedUrl.searchParams.get('skill') || defaultSkill;
|
|
38
|
+
try {
|
|
39
|
+
const model = buildModel(skillParam);
|
|
40
|
+
if (!model) {
|
|
41
|
+
res.writeHead(404, { 'content-type': 'application/json' });
|
|
42
|
+
res.end(JSON.stringify({ error: 'Skill not found' }));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
46
|
+
res.end(JSON.stringify(model));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
res.writeHead(500, { 'content-type': 'application/json' });
|
|
49
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (req.method === 'POST' && parsedUrl.pathname.startsWith('/api/actions/')) {
|
|
55
|
+
const action = parsedUrl.pathname.slice('/api/actions/'.length);
|
|
56
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
57
|
+
try {
|
|
58
|
+
const result = onAction ? await onAction(action) : { refreshed: true };
|
|
59
|
+
res.end(JSON.stringify({ ok: true, action, result }));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
res.statusCode = 500;
|
|
62
|
+
res.end(JSON.stringify({ ok: false, action, error: error.message }));
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (parsedUrl.pathname === '/') {
|
|
68
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
69
|
+
res.end(readFileSync(htmlPath, 'utf-8'));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (parsedUrl.pathname === '/assets/dashboard.js') {
|
|
74
|
+
res.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8' });
|
|
75
|
+
res.end(readFileSync(bundlePath, 'utf-8'));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
res.writeHead(404);
|
|
80
|
+
res.end();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
server.once('error', reject);
|
|
85
|
+
server.listen(0, '127.0.0.1', () => {
|
|
86
|
+
const address = server.address();
|
|
87
|
+
resolve({
|
|
88
|
+
port: address.port,
|
|
89
|
+
url: `http://127.0.0.1:${address.port}`,
|
|
90
|
+
close() {
|
|
91
|
+
server.close();
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { watch } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parseSkillFrontmatterFile } from '../../domain/skills/skill-model.js';
|
|
4
|
+
|
|
5
|
+
export function watchSkillWorkbench(repoRoot, skillDir, onRefresh) {
|
|
6
|
+
const staticWatchers = [];
|
|
7
|
+
const sourceWatchers = new Map();
|
|
8
|
+
let timer = null;
|
|
9
|
+
|
|
10
|
+
const refresh = () => {
|
|
11
|
+
clearTimeout(timer);
|
|
12
|
+
timer = setTimeout(() => {
|
|
13
|
+
onRefresh();
|
|
14
|
+
}, 50);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const watchPath = (pathValue) => {
|
|
18
|
+
try {
|
|
19
|
+
const watcher = watch(pathValue, refresh);
|
|
20
|
+
return watcher;
|
|
21
|
+
} catch {
|
|
22
|
+
// Ignore paths that cannot be watched for now.
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const syncSourceWatchers = () => {
|
|
28
|
+
let metadata;
|
|
29
|
+
try {
|
|
30
|
+
metadata = parseSkillFrontmatterFile(skillFile);
|
|
31
|
+
} catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const nextSources = new Set(metadata.sources.map((source) => join(repoRoot, source)));
|
|
36
|
+
|
|
37
|
+
for (const [pathValue, watcher] of sourceWatchers.entries()) {
|
|
38
|
+
if (nextSources.has(pathValue)) continue;
|
|
39
|
+
watcher.close();
|
|
40
|
+
sourceWatchers.delete(pathValue);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const pathValue of nextSources) {
|
|
44
|
+
if (sourceWatchers.has(pathValue)) continue;
|
|
45
|
+
const watcher = watchPath(pathValue);
|
|
46
|
+
if (watcher) sourceWatchers.set(pathValue, watcher);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const skillFile = join(skillDir, 'SKILL.md');
|
|
51
|
+
const packageJsonPath = join(skillDir, 'package.json');
|
|
52
|
+
const skillFileWatcher = watch(skillFile, () => {
|
|
53
|
+
syncSourceWatchers();
|
|
54
|
+
refresh();
|
|
55
|
+
});
|
|
56
|
+
staticWatchers.push(skillFileWatcher);
|
|
57
|
+
const packageWatcher = watchPath(packageJsonPath);
|
|
58
|
+
if (packageWatcher) staticWatchers.push(packageWatcher);
|
|
59
|
+
syncSourceWatchers();
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
close() {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
for (const watcher of staticWatchers) watcher.close();
|
|
65
|
+
for (const watcher of sourceWatchers.values()) watcher.close();
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { existsSync, readdirSync, watch } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function watchDirectoryTree(rootDir, onChange) {
|
|
5
|
+
const watchers = new Map();
|
|
6
|
+
|
|
7
|
+
const watchDir = (dirPath) => {
|
|
8
|
+
if (watchers.has(dirPath) || !existsSync(dirPath)) return;
|
|
9
|
+
|
|
10
|
+
let entries = [];
|
|
11
|
+
try {
|
|
12
|
+
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
13
|
+
} catch {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const watcher = watch(dirPath, (_eventType, filename) => {
|
|
18
|
+
if (filename) {
|
|
19
|
+
const changedPath = join(dirPath, String(filename));
|
|
20
|
+
if (existsSync(changedPath)) {
|
|
21
|
+
watchDir(changedPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onChange();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
watchers.set(dirPath, watcher);
|
|
29
|
+
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (!entry.isDirectory()) continue;
|
|
32
|
+
watchDir(join(dirPath, entry.name));
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
watchDir(rootDir);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
close() {
|
|
40
|
+
for (const watcher of watchers.values()) watcher.close();
|
|
41
|
+
watchers.clear();
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
package/src/lib/plugins.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync,
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
+
import { resolveDependencyClosure } from '../domain/skills/skill-graph.js';
|
|
5
|
+
import { normalizeRepoPath, parseSkillFrontmatterFile, readPackageMetadata } from '../domain/skills/skill-model.js';
|
|
6
|
+
import { loadPluginDefinition } from '../domain/plugins/load-plugin-definition.js';
|
|
7
|
+
import { watchDirectoryTree } from '../infrastructure/runtime/watch-tree.js';
|
|
4
8
|
import {
|
|
5
9
|
findPackageDirByName,
|
|
6
|
-
normalizeRepoPath,
|
|
7
|
-
parseSkillFrontmatterFile,
|
|
8
|
-
readPackageMetadata,
|
|
9
10
|
syncSkillDependencies,
|
|
10
11
|
} from './skills.js';
|
|
11
12
|
import { findRepoRoot } from './context.js';
|
|
@@ -76,50 +77,30 @@ function collectPluginLocalSkillDirs(pluginDir) {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
function resolveBundleClosure(repoRoot, pluginDir, directRequires) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const metadata = parseSkillFrontmatterFile(skillFile);
|
|
102
|
-
const packageMetadata = readPackageMetadata(resolvedPackage.packageDir);
|
|
103
|
-
|
|
104
|
-
bundled.push({
|
|
105
|
-
packageName,
|
|
106
|
-
packageVersion: packageMetadata.packageVersion,
|
|
107
|
-
skillName: metadata.name,
|
|
108
|
-
skillFile,
|
|
109
|
-
packageDir: resolvedPackage.packageDir,
|
|
110
|
-
source: resolvedPackage.source,
|
|
111
|
-
requires: metadata.requires,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
for (const requirement of metadata.requires) {
|
|
115
|
-
if (!seen.has(requirement)) queue.push(requirement);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
bundled.sort((a, b) => a.packageName.localeCompare(b.packageName));
|
|
120
|
-
unresolved.sort();
|
|
80
|
+
const { resolved, unresolved } = resolveDependencyClosure(directRequires, {
|
|
81
|
+
resolveNode(packageName) {
|
|
82
|
+
const resolvedPackage = resolvePackageDir(repoRoot, pluginDir, packageName);
|
|
83
|
+
if (!resolvedPackage) return null;
|
|
84
|
+
|
|
85
|
+
const skillFile = join(resolvedPackage.packageDir, 'SKILL.md');
|
|
86
|
+
if (!existsSync(skillFile)) return null;
|
|
87
|
+
|
|
88
|
+
const metadata = parseSkillFrontmatterFile(skillFile);
|
|
89
|
+
const packageMetadata = readPackageMetadata(resolvedPackage.packageDir);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
packageName,
|
|
93
|
+
packageVersion: packageMetadata.packageVersion,
|
|
94
|
+
skillName: metadata.name,
|
|
95
|
+
skillFile,
|
|
96
|
+
packageDir: resolvedPackage.packageDir,
|
|
97
|
+
source: resolvedPackage.source,
|
|
98
|
+
requires: metadata.requires,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
});
|
|
121
102
|
|
|
122
|
-
return { bundled, unresolved };
|
|
103
|
+
return { bundled: resolved, unresolved };
|
|
123
104
|
}
|
|
124
105
|
|
|
125
106
|
function stagePluginRuntimeFiles(pluginDir, stageDir) {
|
|
@@ -130,48 +111,6 @@ function stagePluginRuntimeFiles(pluginDir, stageDir) {
|
|
|
130
111
|
}
|
|
131
112
|
}
|
|
132
113
|
|
|
133
|
-
function watchDirectoryTree(rootDir, onChange) {
|
|
134
|
-
const watchers = new Map();
|
|
135
|
-
|
|
136
|
-
const watchDir = (dirPath) => {
|
|
137
|
-
if (watchers.has(dirPath) || !existsSync(dirPath)) return;
|
|
138
|
-
|
|
139
|
-
let entries = [];
|
|
140
|
-
try {
|
|
141
|
-
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
142
|
-
} catch {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const watcher = watch(dirPath, (_eventType, filename) => {
|
|
147
|
-
if (filename) {
|
|
148
|
-
const changedPath = join(dirPath, String(filename));
|
|
149
|
-
if (existsSync(changedPath)) {
|
|
150
|
-
watchDir(changedPath);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
onChange();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
watchers.set(dirPath, watcher);
|
|
158
|
-
|
|
159
|
-
for (const entry of entries) {
|
|
160
|
-
if (!entry.isDirectory()) continue;
|
|
161
|
-
watchDir(join(dirPath, entry.name));
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
watchDir(rootDir);
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
close() {
|
|
169
|
-
for (const watcher of watchers.values()) watcher.close();
|
|
170
|
-
watchers.clear();
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
114
|
export function buildPlugin(target, {
|
|
176
115
|
cwd = process.cwd(),
|
|
177
116
|
clean = false,
|
|
@@ -278,27 +217,16 @@ export function startPluginDev(target, {
|
|
|
278
217
|
}
|
|
279
218
|
|
|
280
219
|
export function inspectPluginBundle(target, { cwd = process.cwd() } = {}) {
|
|
281
|
-
const
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
code: 'missing_plugin_package_metadata',
|
|
292
|
-
suggestion: normalizeRepoPath(repoRoot, join(pluginDir, 'package.json')),
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (!pluginManifest) {
|
|
297
|
-
throw new ValidationError('plugin missing .claude-plugin/plugin.json', {
|
|
298
|
-
code: 'missing_plugin_manifest',
|
|
299
|
-
suggestion: normalizeRepoPath(repoRoot, join(pluginDir, '.claude-plugin', 'plugin.json')),
|
|
300
|
-
});
|
|
301
|
-
}
|
|
220
|
+
const definition = loadPluginDefinition(target, { cwd, requirementLevel: 'inspect' });
|
|
221
|
+
const {
|
|
222
|
+
repoRoot,
|
|
223
|
+
pluginDir,
|
|
224
|
+
packageJson,
|
|
225
|
+
packageName,
|
|
226
|
+
packageVersion,
|
|
227
|
+
pluginManifest,
|
|
228
|
+
pluginManifestPath,
|
|
229
|
+
} = definition;
|
|
302
230
|
|
|
303
231
|
const localSkills = collectPluginLocalSkills(pluginDir);
|
|
304
232
|
const directRequires = [...new Set(localSkills.flatMap((skill) => skill.requires))].sort();
|
|
@@ -306,9 +234,9 @@ export function inspectPluginBundle(target, { cwd = process.cwd() } = {}) {
|
|
|
306
234
|
const directPackageSet = new Set(directRequires);
|
|
307
235
|
|
|
308
236
|
return {
|
|
309
|
-
pluginName: pluginManifest.name ||
|
|
310
|
-
packageName
|
|
311
|
-
packageVersion
|
|
237
|
+
pluginName: pluginManifest.name || packageName,
|
|
238
|
+
packageName,
|
|
239
|
+
packageVersion,
|
|
312
240
|
pluginPath: normalizeRepoPath(repoRoot, pluginDir),
|
|
313
241
|
pluginManifestPath: normalizeRepoPath(repoRoot, pluginManifestPath),
|
|
314
242
|
localSkills: localSkills.map((skill) => ({
|
|
@@ -341,10 +269,11 @@ export function inspectPluginBundle(target, { cwd = process.cwd() } = {}) {
|
|
|
341
269
|
}
|
|
342
270
|
|
|
343
271
|
export function validatePluginBundle(target, { cwd = process.cwd() } = {}) {
|
|
344
|
-
const repoRoot =
|
|
345
|
-
|
|
272
|
+
const { repoRoot, pluginDir, packageJson } = loadPluginDefinition(target, {
|
|
273
|
+
cwd,
|
|
274
|
+
requirementLevel: 'validate',
|
|
275
|
+
});
|
|
346
276
|
const result = inspectPluginBundle(target, { cwd });
|
|
347
|
-
const packageMetadata = readPackageMetadata(pluginDir);
|
|
348
277
|
const issues = [];
|
|
349
278
|
const localSkillNames = new Set(result.localSkills.map((skill) => skill.localName));
|
|
350
279
|
const bundledSkillNames = new Map();
|
|
@@ -361,7 +290,7 @@ export function validatePluginBundle(target, { cwd = process.cwd() } = {}) {
|
|
|
361
290
|
|
|
362
291
|
for (const localSkill of result.localSkills) {
|
|
363
292
|
for (const packageName of localSkill.requires) {
|
|
364
|
-
const declared =
|
|
293
|
+
const declared = packageJson.devDependencies?.[packageName];
|
|
365
294
|
const resolvedPackage = resolvePackageDir(repoRoot, pluginDir, packageName);
|
|
366
295
|
if (!declared || !resolvedPackage) {
|
|
367
296
|
coveredPackages.add(packageName);
|