@alavida/agentpack 0.1.2 → 0.1.4
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 +35 -1
- package/bin/intent.js +20 -0
- package/package.json +15 -5
- package/skills/agentpack-cli/SKILL.md +9 -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 +127 -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 +116 -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/skills/sync-state.json +83 -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 +34 -10
- 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/fs/dev-session-repository.js +25 -0
- package/src/infrastructure/runtime/materialize-skills.js +18 -1
- 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 +245 -16
- package/src/utils/errors.js +33 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
function explainNodeStatus(status) {
|
|
2
|
+
if (status === 'affected') return 'Affected by upstream authored state changes';
|
|
3
|
+
if (status === 'changed') return 'Changed since recorded build-state';
|
|
4
|
+
if (status === 'stale') return 'Stale against recorded build-state';
|
|
5
|
+
return 'No current lifecycle issue detected';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function buildSkillWorkbenchModel({
|
|
9
|
+
repoRoot,
|
|
10
|
+
selectedSkill,
|
|
11
|
+
dependencyRecords = [],
|
|
12
|
+
sourceStatuses = new Map(),
|
|
13
|
+
selectedStatus = 'unknown',
|
|
14
|
+
}) {
|
|
15
|
+
const selectedNode = {
|
|
16
|
+
id: selectedSkill.packageName,
|
|
17
|
+
type: 'skill',
|
|
18
|
+
repoRoot,
|
|
19
|
+
packageName: selectedSkill.packageName,
|
|
20
|
+
name: selectedSkill.name,
|
|
21
|
+
skillFile: selectedSkill.skillFile,
|
|
22
|
+
status: selectedStatus,
|
|
23
|
+
explanation: selectedStatus === 'stale'
|
|
24
|
+
? `Stale because one or more recorded sources changed: ${selectedSkill.sources.join(', ')}`
|
|
25
|
+
: 'Current against recorded build-state',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const sourceNodes = selectedSkill.sources.map((source) => ({
|
|
29
|
+
id: `source:${source}`,
|
|
30
|
+
type: 'source',
|
|
31
|
+
path: source,
|
|
32
|
+
status: sourceStatuses.get(source) || 'unknown',
|
|
33
|
+
explanation: explainNodeStatus(sourceStatuses.get(source) || 'unknown'),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const dependencyNodes = dependencyRecords.map((dependency) => ({
|
|
37
|
+
id: dependency.packageName,
|
|
38
|
+
type: 'dependency',
|
|
39
|
+
packageName: dependency.packageName,
|
|
40
|
+
status: dependency.status || 'unknown',
|
|
41
|
+
explanation: explainNodeStatus(dependency.status || 'unknown'),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
selected: selectedNode,
|
|
46
|
+
nodes: [selectedNode, ...sourceNodes, ...dependencyNodes],
|
|
47
|
+
edges: [
|
|
48
|
+
...sourceNodes.map((node) => ({
|
|
49
|
+
source: node.id,
|
|
50
|
+
target: selectedNode.id,
|
|
51
|
+
kind: 'provenance',
|
|
52
|
+
})),
|
|
53
|
+
...dependencyNodes.map((node) => ({
|
|
54
|
+
source: selectedNode.id,
|
|
55
|
+
target: node.id,
|
|
56
|
+
kind: 'requires',
|
|
57
|
+
})),
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function buildTransitiveSkillWorkbenchModel({
|
|
63
|
+
repoRoot,
|
|
64
|
+
targetPackageName,
|
|
65
|
+
skillGraph,
|
|
66
|
+
statusMap,
|
|
67
|
+
changedSources = new Set(),
|
|
68
|
+
resolveSkillSources,
|
|
69
|
+
resolveSkillRequires,
|
|
70
|
+
}) {
|
|
71
|
+
const targetGraphNode = skillGraph.get(targetPackageName);
|
|
72
|
+
if (!targetGraphNode) return null;
|
|
73
|
+
|
|
74
|
+
const depthMap = new Map();
|
|
75
|
+
const parentMap = new Map();
|
|
76
|
+
const bfsOrder = [];
|
|
77
|
+
const queue = [{ name: targetPackageName, depth: 0, parent: null }];
|
|
78
|
+
|
|
79
|
+
while (queue.length > 0) {
|
|
80
|
+
const { name, depth, parent } = queue.shift();
|
|
81
|
+
if (depthMap.has(name)) continue;
|
|
82
|
+
|
|
83
|
+
depthMap.set(name, depth);
|
|
84
|
+
if (parent !== null) parentMap.set(name, parent);
|
|
85
|
+
bfsOrder.push(name);
|
|
86
|
+
|
|
87
|
+
const graphNode = skillGraph.get(name);
|
|
88
|
+
|
|
89
|
+
// Use graph dependencies if available, fall back to declared requires
|
|
90
|
+
const deps = graphNode
|
|
91
|
+
? graphNode.dependencies
|
|
92
|
+
: [];
|
|
93
|
+
|
|
94
|
+
// Also include declared requires that aren't in graph dependencies
|
|
95
|
+
const declared = resolveSkillRequires ? resolveSkillRequires(name) : [];
|
|
96
|
+
const allDeps = [...new Set([...deps, ...declared])];
|
|
97
|
+
|
|
98
|
+
for (const dep of allDeps) {
|
|
99
|
+
if (!depthMap.has(dep)) {
|
|
100
|
+
queue.push({ name: dep, depth: depth + 1, parent: name });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const nodes = [];
|
|
106
|
+
const edges = [];
|
|
107
|
+
const sourceTracker = new Map();
|
|
108
|
+
|
|
109
|
+
for (const packageName of bfsOrder) {
|
|
110
|
+
const graphNode = skillGraph.get(packageName);
|
|
111
|
+
if (!graphNode) {
|
|
112
|
+
nodes.push({
|
|
113
|
+
id: packageName,
|
|
114
|
+
type: 'dependency',
|
|
115
|
+
packageName,
|
|
116
|
+
name: packageName.split('/').pop(),
|
|
117
|
+
description: null,
|
|
118
|
+
version: null,
|
|
119
|
+
status: 'unknown',
|
|
120
|
+
explanation: 'Package not found in skill graph',
|
|
121
|
+
depth: depthMap.get(packageName),
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const isTarget = packageName === targetPackageName;
|
|
127
|
+
const status = statusMap?.get(packageName) || 'unknown';
|
|
128
|
+
|
|
129
|
+
nodes.push({
|
|
130
|
+
id: packageName,
|
|
131
|
+
type: isTarget ? 'skill' : 'dependency',
|
|
132
|
+
packageName,
|
|
133
|
+
name: graphNode.name,
|
|
134
|
+
description: graphNode.description || null,
|
|
135
|
+
version: graphNode.packageVersion || null,
|
|
136
|
+
status,
|
|
137
|
+
explanation: explainNodeStatus(status),
|
|
138
|
+
depth: depthMap.get(packageName),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const sources = resolveSkillSources(packageName);
|
|
142
|
+
for (const sourcePath of sources) {
|
|
143
|
+
const sourceId = `source:${sourcePath}`;
|
|
144
|
+
if (!sourceTracker.has(sourceId)) {
|
|
145
|
+
sourceTracker.set(sourceId, { path: sourcePath, usedBy: [] });
|
|
146
|
+
}
|
|
147
|
+
sourceTracker.get(sourceId).usedBy.push(packageName);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const deps = graphNode ? graphNode.dependencies : [];
|
|
151
|
+
const declared = resolveSkillRequires ? resolveSkillRequires(packageName) : [];
|
|
152
|
+
const allDeps = [...new Set([...deps, ...declared])];
|
|
153
|
+
|
|
154
|
+
for (const dep of allDeps) {
|
|
155
|
+
if (depthMap.has(dep)) {
|
|
156
|
+
edges.push({
|
|
157
|
+
source: packageName,
|
|
158
|
+
target: dep,
|
|
159
|
+
kind: 'requires',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const sourceNodes = [];
|
|
166
|
+
for (const [sourceId, sourceData] of sourceTracker) {
|
|
167
|
+
const isChanged = changedSources.has(sourceData.path);
|
|
168
|
+
sourceNodes.push({
|
|
169
|
+
id: sourceId,
|
|
170
|
+
type: 'source',
|
|
171
|
+
path: sourceData.path,
|
|
172
|
+
status: isChanged ? 'changed' : 'current',
|
|
173
|
+
explanation: isChanged ? 'Changed since recorded build-state' : 'No current lifecycle issue detected',
|
|
174
|
+
depth: 0,
|
|
175
|
+
usedBy: sourceData.usedBy,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
for (const skillPackageName of sourceData.usedBy) {
|
|
179
|
+
edges.push({
|
|
180
|
+
source: sourceId,
|
|
181
|
+
target: skillPackageName,
|
|
182
|
+
kind: 'provenance',
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const selectedNode = nodes.find((n) => n.id === targetPackageName);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
selected: selectedNode,
|
|
191
|
+
nodes: [...sourceNodes, ...nodes],
|
|
192
|
+
edges,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { inspectSkillDependencies } from '../../lib/skills.js';
|
|
2
|
+
import { inspectStaleSkillUseCase } from './list-stale-skills.js';
|
|
3
|
+
import { validateSkillsUseCase } from './validate-skills.js';
|
|
4
|
+
|
|
5
|
+
export function runSkillWorkbenchAction(action, context) {
|
|
6
|
+
if (action === 'check-stale') {
|
|
7
|
+
return inspectStaleSkillUseCase(context.packageName, { cwd: context.cwd });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (action === 'show-dependencies') {
|
|
11
|
+
return inspectSkillDependencies(context.packageName, { cwd: context.cwd });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (action === 'validate-skill') {
|
|
15
|
+
return validateSkillsUseCase(context.target, { cwd: context.cwd });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (action === 'refresh') {
|
|
19
|
+
return { refreshed: true };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
throw new Error(`Unsupported workbench action: ${action}`);
|
|
23
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { parseSkillFrontmatterFile, readPackageMetadata, normalizeDisplayPath } from '../../domain/skills/skill-model.js';
|
|
4
|
+
import { readBuildState, compareRecordedSources } from '../../domain/skills/skill-provenance.js';
|
|
5
|
+
import { buildSkillGraph, buildSkillStatusMap } from '../../domain/skills/skill-graph.js';
|
|
6
|
+
import { buildTransitiveSkillWorkbenchModel } from './build-skill-workbench-model.js';
|
|
7
|
+
import { startSkillDevWorkbenchServer } from '../../infrastructure/runtime/skill-dev-workbench-server.js';
|
|
8
|
+
import { openBrowser } from '../../infrastructure/runtime/open-browser.js';
|
|
9
|
+
import { watchSkillWorkbench } from '../../infrastructure/runtime/watch-skill-workbench.js';
|
|
10
|
+
import { runSkillWorkbenchAction } from './run-skill-workbench-action.js';
|
|
11
|
+
|
|
12
|
+
function listPackagedSkillDirs(repoRoot) {
|
|
13
|
+
const stack = [repoRoot];
|
|
14
|
+
const results = [];
|
|
15
|
+
|
|
16
|
+
while (stack.length > 0) {
|
|
17
|
+
const current = stack.pop();
|
|
18
|
+
let entries = [];
|
|
19
|
+
try {
|
|
20
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
21
|
+
} catch {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let hasSkillFile = false;
|
|
26
|
+
let hasPackageFile = false;
|
|
27
|
+
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
if (entry.name === '.git' || entry.name === 'node_modules' || entry.name === '.agentpack') continue;
|
|
30
|
+
const fullPath = join(current, entry.name);
|
|
31
|
+
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
stack.push(fullPath);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (entry.name === 'SKILL.md') hasSkillFile = true;
|
|
38
|
+
if (entry.name === 'package.json') hasPackageFile = true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (hasSkillFile && hasPackageFile) {
|
|
42
|
+
results.push(current);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return results.sort();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function findPackageDirByName(repoRoot, packageName) {
|
|
50
|
+
const stack = [repoRoot];
|
|
51
|
+
|
|
52
|
+
while (stack.length > 0) {
|
|
53
|
+
const current = stack.pop();
|
|
54
|
+
let entries = [];
|
|
55
|
+
try {
|
|
56
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
57
|
+
} catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (entry.name === '.git' || entry.name === 'node_modules') continue;
|
|
63
|
+
const fullPath = join(current, entry.name);
|
|
64
|
+
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
stack.push(fullPath);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (entry.name !== 'package.json') continue;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const pkg = JSON.parse(readFileSync(fullPath, 'utf-8'));
|
|
74
|
+
if (pkg.name === packageName) return dirname(fullPath);
|
|
75
|
+
} catch {
|
|
76
|
+
// skip
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const nodeModulesPath = join(repoRoot, 'node_modules', ...packageName.split('/'));
|
|
82
|
+
if (existsSync(join(nodeModulesPath, 'SKILL.md'))) return nodeModulesPath;
|
|
83
|
+
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildModelForSkill(repoRoot, targetPackageName) {
|
|
88
|
+
const packageDirs = listPackagedSkillDirs(repoRoot);
|
|
89
|
+
const skillGraph = buildSkillGraph(repoRoot, packageDirs, {
|
|
90
|
+
parseSkillFrontmatterFile,
|
|
91
|
+
readPackageMetadata,
|
|
92
|
+
findPackageDirByName: (root, name) => findPackageDirByName(root, name),
|
|
93
|
+
normalizeDisplayPath,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const buildState = readBuildState(repoRoot);
|
|
97
|
+
const staleSkills = new Set();
|
|
98
|
+
const changedSources = new Set(); // track individual changed source paths
|
|
99
|
+
|
|
100
|
+
for (const [packageName, record] of Object.entries(buildState.skills || buildState)) {
|
|
101
|
+
if (typeof record !== 'object' || !record.sources) continue;
|
|
102
|
+
try {
|
|
103
|
+
const changes = compareRecordedSources(repoRoot, record);
|
|
104
|
+
if (changes.length > 0) {
|
|
105
|
+
staleSkills.add(packageName);
|
|
106
|
+
for (const change of changes) changedSources.add(change.path);
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
// skip
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const statusMap = buildSkillStatusMap(skillGraph, staleSkills);
|
|
114
|
+
|
|
115
|
+
function resolveSkillSources(packageName) {
|
|
116
|
+
const graphNode = skillGraph.get(packageName);
|
|
117
|
+
if (!graphNode) return [];
|
|
118
|
+
|
|
119
|
+
const skillFilePath = join(repoRoot, graphNode.skillFile);
|
|
120
|
+
try {
|
|
121
|
+
const metadata = parseSkillFrontmatterFile(skillFilePath);
|
|
122
|
+
return metadata.sources || [];
|
|
123
|
+
} catch {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function resolveSkillRequires(packageName) {
|
|
129
|
+
const graphNode = skillGraph.get(packageName);
|
|
130
|
+
if (!graphNode) return [];
|
|
131
|
+
|
|
132
|
+
const skillFilePath = join(repoRoot, graphNode.skillFile);
|
|
133
|
+
try {
|
|
134
|
+
const metadata = parseSkillFrontmatterFile(skillFilePath);
|
|
135
|
+
return metadata.requires || [];
|
|
136
|
+
} catch {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return buildTransitiveSkillWorkbenchModel({
|
|
142
|
+
repoRoot,
|
|
143
|
+
targetPackageName,
|
|
144
|
+
skillGraph,
|
|
145
|
+
statusMap,
|
|
146
|
+
changedSources,
|
|
147
|
+
resolveSkillSources,
|
|
148
|
+
resolveSkillRequires,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function startSkillDevWorkbench({
|
|
153
|
+
repoRoot,
|
|
154
|
+
skillDir,
|
|
155
|
+
open = true,
|
|
156
|
+
disableBrowser = false,
|
|
157
|
+
}) {
|
|
158
|
+
const packageMetadata = readPackageMetadata(skillDir);
|
|
159
|
+
const defaultSkill = packageMetadata.packageName;
|
|
160
|
+
|
|
161
|
+
const server = await startSkillDevWorkbenchServer({
|
|
162
|
+
buildModel: (skillPackageName) => buildModelForSkill(repoRoot, skillPackageName),
|
|
163
|
+
defaultSkill,
|
|
164
|
+
onAction(action) {
|
|
165
|
+
return runSkillWorkbenchAction(action, {
|
|
166
|
+
cwd: repoRoot,
|
|
167
|
+
target: skillDir,
|
|
168
|
+
packageName: defaultSkill,
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const watcher = watchSkillWorkbench(repoRoot, skillDir, () => {
|
|
174
|
+
// Model is rebuilt on each request, so no cache to invalidate
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (open && !disableBrowser) {
|
|
178
|
+
openBrowser(server.url);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
url: server.url,
|
|
183
|
+
port: server.port,
|
|
184
|
+
refresh() {
|
|
185
|
+
// no-op: models are built on demand per request
|
|
186
|
+
},
|
|
187
|
+
close() {
|
|
188
|
+
watcher.close();
|
|
189
|
+
server.close();
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -68,7 +68,7 @@ export function run(argv) {
|
|
|
68
68
|
if (err instanceof AgentpackError) {
|
|
69
69
|
const opts = program.opts?.() || {};
|
|
70
70
|
if (opts.json) {
|
|
71
|
-
output.json(
|
|
71
|
+
output.json(err.toJSON());
|
|
72
72
|
} else {
|
|
73
73
|
output.error(formatError(err));
|
|
74
74
|
}
|
package/src/commands/skills.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
installSkills,
|
|
12
12
|
listOutdatedSkills,
|
|
13
13
|
resolveInstallTargets,
|
|
14
|
+
cleanupSkillDevSession,
|
|
14
15
|
startSkillDev,
|
|
15
16
|
unlinkSkill,
|
|
16
17
|
uninstallSkills,
|
|
@@ -22,15 +23,20 @@ export function skillsCommand() {
|
|
|
22
23
|
const cmd = new Command('skills')
|
|
23
24
|
.description('Inspect and manage package-backed skills');
|
|
24
25
|
|
|
25
|
-
cmd
|
|
26
|
+
const devCmd = cmd
|
|
26
27
|
.command('dev')
|
|
27
28
|
.description('Link one local packaged skill for local Claude and agent discovery')
|
|
28
29
|
.option('--no-sync', 'Skip syncing managed package dependencies from requires')
|
|
29
|
-
.
|
|
30
|
-
.
|
|
30
|
+
.option('--no-dashboard', 'Skip starting the local skill development workbench')
|
|
31
|
+
.argument('[target]', 'Packaged skill directory or SKILL.md path')
|
|
32
|
+
.action(async (target, opts, command) => {
|
|
33
|
+
if (!target) {
|
|
34
|
+
command.help({ error: true });
|
|
35
|
+
}
|
|
31
36
|
const globalOpts = command.optsWithGlobals();
|
|
32
37
|
const session = startSkillDev(target, {
|
|
33
38
|
sync: opts.sync,
|
|
39
|
+
dashboard: opts.dashboard,
|
|
34
40
|
onStart(result) {
|
|
35
41
|
if (globalOpts.json) {
|
|
36
42
|
output.json(result);
|
|
@@ -45,6 +51,9 @@ export function skillsCommand() {
|
|
|
45
51
|
for (const link of result.links) {
|
|
46
52
|
output.write(`Linked: ${link}`);
|
|
47
53
|
}
|
|
54
|
+
if (result.workbench?.enabled) {
|
|
55
|
+
output.write(`Workbench URL: ${result.workbench.url}`);
|
|
56
|
+
}
|
|
48
57
|
output.write('Note: if your current agent session was already running, start a fresh session to pick up newly linked skills.');
|
|
49
58
|
if (result.unresolved.length > 0) {
|
|
50
59
|
output.write('Unresolved Dependencies:');
|
|
@@ -65,22 +74,37 @@ export function skillsCommand() {
|
|
|
65
74
|
},
|
|
66
75
|
});
|
|
67
76
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
await session.ready;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
devCmd
|
|
81
|
+
.command('cleanup')
|
|
82
|
+
.description('Remove recorded skills dev links for a stale session')
|
|
83
|
+
.option('--force', 'Remove recorded links even if the session pid still appears alive')
|
|
84
|
+
.action((opts, command) => {
|
|
85
|
+
const globalOpts = command.optsWithGlobals();
|
|
86
|
+
const result = cleanupSkillDevSession({ force: opts.force });
|
|
87
|
+
|
|
88
|
+
if (globalOpts.json) {
|
|
89
|
+
output.json(result);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
72
92
|
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
output.write(`Cleaned: ${result.cleaned}`);
|
|
94
|
+
if (result.name) output.write(`Root Skill: ${result.name}`);
|
|
95
|
+
for (const removed of result.removed) {
|
|
96
|
+
output.write(`Removed: ${removed}`);
|
|
97
|
+
}
|
|
75
98
|
});
|
|
76
99
|
|
|
77
100
|
cmd
|
|
78
101
|
.command('unlink')
|
|
79
102
|
.description('Remove one locally linked skill from Claude and agent discovery paths')
|
|
103
|
+
.option('--recursive', 'Remove the active dev root and its recorded transitive links')
|
|
80
104
|
.argument('<name>', 'Skill frontmatter name')
|
|
81
105
|
.action((name, opts, command) => {
|
|
82
106
|
const globalOpts = command.optsWithGlobals();
|
|
83
|
-
const result = unlinkSkill(name);
|
|
107
|
+
const result = unlinkSkill(name, { recursive: opts.recursive });
|
|
84
108
|
|
|
85
109
|
if (globalOpts.json) {
|
|
86
110
|
output.json(result);
|