@alavida/agentpack 0.1.2 → 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 +11 -1
- package/bin/intent.js +20 -0
- package/package.json +11 -5
- package/skills/agentpack-cli/SKILL.md +4 -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/skills/build-skill-workbench-model.js +194 -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/cli.js +1 -1
- package/src/commands/skills.js +7 -1
- 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 +1 -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/lib/plugins.js +19 -28
- package/src/lib/skills.js +60 -12
- package/src/utils/errors.js +33 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Skill Dev Workbench</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Noto+Sans+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
:root {
|
|
12
|
+
color-scheme: dark;
|
|
13
|
+
--bg: #1a1916;
|
|
14
|
+
--surface: #252320;
|
|
15
|
+
--border: rgba(255, 255, 255, 0.06);
|
|
16
|
+
--border-bright: rgba(255, 255, 255, 0.12);
|
|
17
|
+
--text: #e8e4dc;
|
|
18
|
+
--text-dim: #9a9488;
|
|
19
|
+
--text-faint: #6b655c;
|
|
20
|
+
|
|
21
|
+
--status-current: #8fa67e;
|
|
22
|
+
--status-stale: #d4a45e;
|
|
23
|
+
--status-affected: #c4956e;
|
|
24
|
+
--status-unknown: #9a9488;
|
|
25
|
+
|
|
26
|
+
--edge-provenance: #7a9abb;
|
|
27
|
+
--edge-requires: #8fa67e;
|
|
28
|
+
--accent: #ffbf47;
|
|
29
|
+
|
|
30
|
+
--font-body: 'Crimson Pro', Georgia, 'Times New Roman', serif;
|
|
31
|
+
--font-mono: 'Noto Sans Mono', 'Courier New', monospace;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
[data-theme="light"] {
|
|
35
|
+
color-scheme: light;
|
|
36
|
+
--bg: #f5f2ed;
|
|
37
|
+
--surface: #e8e4dc;
|
|
38
|
+
--border: rgba(0, 0, 0, 0.10);
|
|
39
|
+
--border-bright: rgba(0, 0, 0, 0.20);
|
|
40
|
+
--text: #1a1816;
|
|
41
|
+
--text-dim: #3d3830;
|
|
42
|
+
--text-faint: #6b655c;
|
|
43
|
+
|
|
44
|
+
--status-current: #3d6e2c;
|
|
45
|
+
--status-stale: #a07020;
|
|
46
|
+
--status-affected: #8c5530;
|
|
47
|
+
--status-unknown: #6b655c;
|
|
48
|
+
|
|
49
|
+
--edge-provenance: #2e6080;
|
|
50
|
+
--edge-requires: #3d6e2c;
|
|
51
|
+
--accent: #a07020;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
55
|
+
|
|
56
|
+
body {
|
|
57
|
+
font-family: var(--font-body);
|
|
58
|
+
background: var(--bg);
|
|
59
|
+
color: var(--text);
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
height: 100vh;
|
|
62
|
+
-webkit-font-smoothing: antialiased;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#app {
|
|
66
|
+
height: 100vh;
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@keyframes stale-pulse {
|
|
72
|
+
0%, 100% { opacity: 0.4; }
|
|
73
|
+
50% { opacity: 0.8; }
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
76
|
+
</head>
|
|
77
|
+
<body>
|
|
78
|
+
<div id="app" data-app-root></div>
|
|
79
|
+
<script type="module" src="/assets/dashboard.js"></script>
|
|
80
|
+
</body>
|
|
81
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export async function fetchWorkbenchModel(skillPackageName) {
|
|
2
|
+
const params = skillPackageName ? `?skill=${encodeURIComponent(skillPackageName)}` : '';
|
|
3
|
+
const response = await fetch(`/api/model${params}`);
|
|
4
|
+
if (!response.ok) {
|
|
5
|
+
const body = await response.json().catch(() => ({}));
|
|
6
|
+
throw new Error(body.error || `Failed to load workbench model: ${response.status}`);
|
|
7
|
+
}
|
|
8
|
+
return response.json();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function runWorkbenchAction(action) {
|
|
12
|
+
const response = await fetch(`/api/actions/${action}`, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(`Failed to run ${action}: ${response.status}`);
|
|
17
|
+
}
|
|
18
|
+
return response.json();
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function getSkillFromHash() {
|
|
2
|
+
const hash = window.location.hash;
|
|
3
|
+
const match = hash.match(/^#\/skill\/(.+)$/);
|
|
4
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function setSkillHash(packageName) {
|
|
8
|
+
window.location.hash = `#/skill/${encodeURIComponent(packageName)}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function onHashChange(callback) {
|
|
12
|
+
window.addEventListener('hashchange', () => {
|
|
13
|
+
callback(getSkillFromHash());
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join, resolve } from 'node:path';
|
|
3
|
+
import { normalizeRepoPath } from '../skills/skill-model.js';
|
|
4
|
+
import { findRepoRoot } from '../../lib/context.js';
|
|
5
|
+
import { NotFoundError } from '../../utils/errors.js';
|
|
6
|
+
import { PluginDiagnosticError } from './plugin-diagnostic-error.js';
|
|
7
|
+
import { getPluginRequirementLevel } from './plugin-requirements.js';
|
|
8
|
+
|
|
9
|
+
function resolvePluginDir(repoRoot, target) {
|
|
10
|
+
const absoluteTarget = resolve(repoRoot, target);
|
|
11
|
+
if (!existsSync(absoluteTarget)) {
|
|
12
|
+
throw new NotFoundError('plugin not found', {
|
|
13
|
+
code: 'plugin_not_found',
|
|
14
|
+
suggestion: `Target: ${target}`,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return absoluteTarget;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function inferPluginPackageName(pluginDir) {
|
|
22
|
+
return `@alavida-ai/plugin-${basename(pluginDir)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readPluginPackageJson(repoRoot, pluginDir, requirementLevel) {
|
|
26
|
+
const packageJsonPath = join(pluginDir, 'package.json');
|
|
27
|
+
const displayPath = normalizeRepoPath(repoRoot, packageJsonPath);
|
|
28
|
+
const example = {
|
|
29
|
+
name: inferPluginPackageName(pluginDir),
|
|
30
|
+
version: '0.1.0',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (!existsSync(packageJsonPath)) {
|
|
34
|
+
throw new PluginDiagnosticError('No package.json found for plugin target', {
|
|
35
|
+
code: 'missing_plugin_package_json',
|
|
36
|
+
path: displayPath,
|
|
37
|
+
nextSteps: [
|
|
38
|
+
{
|
|
39
|
+
action: 'create_file',
|
|
40
|
+
path: displayPath,
|
|
41
|
+
reason: 'A plugin must declare package metadata before it can be inspected',
|
|
42
|
+
example,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
details: {
|
|
46
|
+
requirementLevel,
|
|
47
|
+
missing: ['package.json'],
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let pkg;
|
|
53
|
+
try {
|
|
54
|
+
pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
55
|
+
} catch {
|
|
56
|
+
throw new PluginDiagnosticError('Plugin package.json contains invalid JSON', {
|
|
57
|
+
code: 'invalid_plugin_package_json',
|
|
58
|
+
path: displayPath,
|
|
59
|
+
nextSteps: [
|
|
60
|
+
{
|
|
61
|
+
action: 'edit_file',
|
|
62
|
+
path: displayPath,
|
|
63
|
+
reason: 'Inspect and validate require a readable package.json file',
|
|
64
|
+
example,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
details: {
|
|
68
|
+
requirementLevel,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const missingFields = ['name', 'version'].filter((field) => !pkg[field]);
|
|
74
|
+
if (missingFields.length > 0) {
|
|
75
|
+
throw new PluginDiagnosticError(`Plugin package.json missing required fields: ${missingFields.join(', ')}`, {
|
|
76
|
+
code: 'missing_plugin_package_fields',
|
|
77
|
+
path: displayPath,
|
|
78
|
+
nextSteps: [
|
|
79
|
+
{
|
|
80
|
+
action: 'edit_file',
|
|
81
|
+
path: displayPath,
|
|
82
|
+
reason: 'A plugin package.json must include required package metadata before inspection can continue',
|
|
83
|
+
example: {
|
|
84
|
+
...example,
|
|
85
|
+
...('name' in pkg ? { name: pkg.name } : {}),
|
|
86
|
+
...('version' in pkg ? { version: pkg.version } : {}),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
details: {
|
|
91
|
+
requirementLevel,
|
|
92
|
+
missingFields,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
packageJsonPath,
|
|
99
|
+
packageJson: pkg,
|
|
100
|
+
packageName: pkg.name,
|
|
101
|
+
packageVersion: pkg.version,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readPluginManifest(repoRoot, pluginDir, requirementLevel) {
|
|
106
|
+
const pluginManifestPath = join(pluginDir, '.claude-plugin', 'plugin.json');
|
|
107
|
+
const displayPath = normalizeRepoPath(repoRoot, pluginManifestPath);
|
|
108
|
+
|
|
109
|
+
if (!existsSync(pluginManifestPath)) {
|
|
110
|
+
throw new PluginDiagnosticError('Plugin manifest is missing', {
|
|
111
|
+
code: 'missing_plugin_manifest',
|
|
112
|
+
path: displayPath,
|
|
113
|
+
nextSteps: [
|
|
114
|
+
{
|
|
115
|
+
action: 'create_file',
|
|
116
|
+
path: displayPath,
|
|
117
|
+
reason: 'Plugin commands require .claude-plugin/plugin.json to describe the runtime plugin entrypoint',
|
|
118
|
+
example: {
|
|
119
|
+
name: basename(pluginDir),
|
|
120
|
+
description: 'Describe what this plugin provides.',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
details: {
|
|
125
|
+
requirementLevel,
|
|
126
|
+
missing: ['.claude-plugin/plugin.json'],
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
pluginManifestPath,
|
|
133
|
+
pluginManifest: JSON.parse(readFileSync(pluginManifestPath, 'utf-8')),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function loadPluginDefinition(target, {
|
|
138
|
+
cwd = process.cwd(),
|
|
139
|
+
requirementLevel = 'inspect',
|
|
140
|
+
} = {}) {
|
|
141
|
+
const repoRoot = findRepoRoot(cwd);
|
|
142
|
+
const pluginDir = resolvePluginDir(repoRoot, target);
|
|
143
|
+
const requirements = getPluginRequirementLevel(requirementLevel);
|
|
144
|
+
|
|
145
|
+
const packageData = readPluginPackageJson(repoRoot, pluginDir, requirementLevel);
|
|
146
|
+
const manifestData = requirements.pluginManifest
|
|
147
|
+
? readPluginManifest(repoRoot, pluginDir, requirementLevel)
|
|
148
|
+
: {
|
|
149
|
+
pluginManifestPath: join(pluginDir, '.claude-plugin', 'plugin.json'),
|
|
150
|
+
pluginManifest: null,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
repoRoot,
|
|
155
|
+
pluginDir,
|
|
156
|
+
packageJsonPath: packageData.packageJsonPath,
|
|
157
|
+
packageJson: packageData.packageJson,
|
|
158
|
+
packageName: packageData.packageName,
|
|
159
|
+
packageVersion: packageData.packageVersion,
|
|
160
|
+
pluginManifestPath: manifestData.pluginManifestPath,
|
|
161
|
+
pluginManifest: manifestData.pluginManifest,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ValidationError } from '../../utils/errors.js';
|
|
2
|
+
|
|
3
|
+
export class PluginDiagnosticError extends ValidationError {
|
|
4
|
+
constructor(message, {
|
|
5
|
+
code,
|
|
6
|
+
path,
|
|
7
|
+
nextSteps = [],
|
|
8
|
+
details = {},
|
|
9
|
+
} = {}) {
|
|
10
|
+
super(message, {
|
|
11
|
+
code,
|
|
12
|
+
path,
|
|
13
|
+
nextSteps,
|
|
14
|
+
details,
|
|
15
|
+
});
|
|
16
|
+
this.name = 'PluginDiagnosticError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const PLUGIN_REQUIREMENTS = {
|
|
2
|
+
inspect: {
|
|
3
|
+
pluginManifest: true,
|
|
4
|
+
},
|
|
5
|
+
validate: {
|
|
6
|
+
pluginManifest: true,
|
|
7
|
+
},
|
|
8
|
+
build: {
|
|
9
|
+
pluginManifest: true,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function getPluginRequirementLevel(level = 'inspect') {
|
|
14
|
+
return PLUGIN_REQUIREMENTS[level] || PLUGIN_REQUIREMENTS.inspect;
|
|
15
|
+
}
|
|
@@ -26,6 +26,7 @@ export function readSkillGraphNode(repoRoot, packageDir, {
|
|
|
26
26
|
|
|
27
27
|
return {
|
|
28
28
|
name: skillMetadata.name,
|
|
29
|
+
description: skillMetadata.description,
|
|
29
30
|
packageName: packageMetadata.packageName,
|
|
30
31
|
packageVersion: packageMetadata.packageVersion,
|
|
31
32
|
skillPath: normalizeDisplayPath(repoRoot, packageDir),
|
|
@@ -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
|
+
}
|
package/src/lib/plugins.js
CHANGED
|
@@ -3,6 +3,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { resolveDependencyClosure } from '../domain/skills/skill-graph.js';
|
|
5
5
|
import { normalizeRepoPath, parseSkillFrontmatterFile, readPackageMetadata } from '../domain/skills/skill-model.js';
|
|
6
|
+
import { loadPluginDefinition } from '../domain/plugins/load-plugin-definition.js';
|
|
6
7
|
import { watchDirectoryTree } from '../infrastructure/runtime/watch-tree.js';
|
|
7
8
|
import {
|
|
8
9
|
findPackageDirByName,
|
|
@@ -216,27 +217,16 @@ export function startPluginDev(target, {
|
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
export function inspectPluginBundle(target, { cwd = process.cwd() } = {}) {
|
|
219
|
-
const
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
code: 'missing_plugin_package_metadata',
|
|
230
|
-
suggestion: normalizeRepoPath(repoRoot, join(pluginDir, 'package.json')),
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (!pluginManifest) {
|
|
235
|
-
throw new ValidationError('plugin missing .claude-plugin/plugin.json', {
|
|
236
|
-
code: 'missing_plugin_manifest',
|
|
237
|
-
suggestion: normalizeRepoPath(repoRoot, join(pluginDir, '.claude-plugin', 'plugin.json')),
|
|
238
|
-
});
|
|
239
|
-
}
|
|
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;
|
|
240
230
|
|
|
241
231
|
const localSkills = collectPluginLocalSkills(pluginDir);
|
|
242
232
|
const directRequires = [...new Set(localSkills.flatMap((skill) => skill.requires))].sort();
|
|
@@ -244,9 +234,9 @@ export function inspectPluginBundle(target, { cwd = process.cwd() } = {}) {
|
|
|
244
234
|
const directPackageSet = new Set(directRequires);
|
|
245
235
|
|
|
246
236
|
return {
|
|
247
|
-
pluginName: pluginManifest.name ||
|
|
248
|
-
packageName
|
|
249
|
-
packageVersion
|
|
237
|
+
pluginName: pluginManifest.name || packageName,
|
|
238
|
+
packageName,
|
|
239
|
+
packageVersion,
|
|
250
240
|
pluginPath: normalizeRepoPath(repoRoot, pluginDir),
|
|
251
241
|
pluginManifestPath: normalizeRepoPath(repoRoot, pluginManifestPath),
|
|
252
242
|
localSkills: localSkills.map((skill) => ({
|
|
@@ -279,10 +269,11 @@ export function inspectPluginBundle(target, { cwd = process.cwd() } = {}) {
|
|
|
279
269
|
}
|
|
280
270
|
|
|
281
271
|
export function validatePluginBundle(target, { cwd = process.cwd() } = {}) {
|
|
282
|
-
const repoRoot =
|
|
283
|
-
|
|
272
|
+
const { repoRoot, pluginDir, packageJson } = loadPluginDefinition(target, {
|
|
273
|
+
cwd,
|
|
274
|
+
requirementLevel: 'validate',
|
|
275
|
+
});
|
|
284
276
|
const result = inspectPluginBundle(target, { cwd });
|
|
285
|
-
const packageMetadata = readPackageMetadata(pluginDir);
|
|
286
277
|
const issues = [];
|
|
287
278
|
const localSkillNames = new Set(result.localSkills.map((skill) => skill.localName));
|
|
288
279
|
const bundledSkillNames = new Map();
|
|
@@ -299,7 +290,7 @@ export function validatePluginBundle(target, { cwd = process.cwd() } = {}) {
|
|
|
299
290
|
|
|
300
291
|
for (const localSkill of result.localSkills) {
|
|
301
292
|
for (const packageName of localSkill.requires) {
|
|
302
|
-
const declared =
|
|
293
|
+
const declared = packageJson.devDependencies?.[packageName];
|
|
303
294
|
const resolvedPackage = resolvePackageDir(repoRoot, pluginDir, packageName);
|
|
304
295
|
if (!declared || !resolvedPackage) {
|
|
305
296
|
coveredPackages.add(packageName);
|