@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.
Files changed (46) hide show
  1. package/README.md +35 -1
  2. package/bin/intent.js +20 -0
  3. package/package.json +15 -5
  4. package/skills/agentpack-cli/SKILL.md +9 -1
  5. package/skills/authoring-skillgraphs-from-knowledge/SKILL.md +148 -0
  6. package/skills/authoring-skillgraphs-from-knowledge/references/authored-metadata.md +6 -0
  7. package/skills/developing-and-testing-skills/SKILL.md +127 -0
  8. package/skills/developing-and-testing-skills/references/local-workbench.md +7 -0
  9. package/skills/getting-started-skillgraphs/SKILL.md +115 -0
  10. package/skills/getting-started-skillgraphs/references/command-routing.md +7 -0
  11. package/skills/identifying-skill-opportunities/SKILL.md +119 -0
  12. package/skills/identifying-skill-opportunities/references/capability-boundaries.md +6 -0
  13. package/skills/maintaining-skillgraph-freshness/SKILL.md +110 -0
  14. package/skills/repairing-broken-skill-or-plugin-state/SKILL.md +116 -0
  15. package/skills/repairing-broken-skill-or-plugin-state/references/diagnostic-flows.md +6 -0
  16. package/skills/shipping-production-plugins-and-packages/SKILL.md +123 -0
  17. package/skills/shipping-production-plugins-and-packages/references/plugin-delivery.md +6 -0
  18. package/skills/sync-state.json +83 -0
  19. package/src/application/skills/build-skill-workbench-model.js +194 -0
  20. package/src/application/skills/run-skill-workbench-action.js +23 -0
  21. package/src/application/skills/start-skill-dev-workbench.js +192 -0
  22. package/src/cli.js +1 -1
  23. package/src/commands/skills.js +34 -10
  24. package/src/dashboard/App.jsx +343 -0
  25. package/src/dashboard/components/Breadcrumbs.jsx +45 -0
  26. package/src/dashboard/components/ControlStrip.jsx +153 -0
  27. package/src/dashboard/components/InspectorPanel.jsx +203 -0
  28. package/src/dashboard/components/SkillGraph.jsx +567 -0
  29. package/src/dashboard/components/Tooltip.jsx +111 -0
  30. package/src/dashboard/dist/dashboard.js +26692 -0
  31. package/src/dashboard/index.html +81 -0
  32. package/src/dashboard/lib/api.js +19 -0
  33. package/src/dashboard/lib/router.js +15 -0
  34. package/src/dashboard/main.jsx +4 -0
  35. package/src/domain/plugins/load-plugin-definition.js +163 -0
  36. package/src/domain/plugins/plugin-diagnostic-error.js +18 -0
  37. package/src/domain/plugins/plugin-requirements.js +15 -0
  38. package/src/domain/skills/skill-graph.js +1 -0
  39. package/src/infrastructure/fs/dev-session-repository.js +25 -0
  40. package/src/infrastructure/runtime/materialize-skills.js +18 -1
  41. package/src/infrastructure/runtime/open-browser.js +20 -0
  42. package/src/infrastructure/runtime/skill-dev-workbench-server.js +96 -0
  43. package/src/infrastructure/runtime/watch-skill-workbench.js +68 -0
  44. package/src/lib/plugins.js +19 -28
  45. package/src/lib/skills.js +245 -16
  46. 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({ error: err.code, message: err.message });
71
+ output.json(err.toJSON());
72
72
  } else {
73
73
  output.error(formatError(err));
74
74
  }
@@ -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
- .argument('<target>', 'Packaged skill directory or SKILL.md path')
30
- .action((target, opts, command) => {
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
- const stop = () => {
69
- session.close();
70
- process.exit(0);
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
- process.once('SIGTERM', stop);
74
- process.once('SIGINT', stop);
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);