@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
package/src/lib/skills.js CHANGED
@@ -9,11 +9,13 @@ import {
9
9
  readNodeStatus,
10
10
  } from '../domain/skills/skill-graph.js';
11
11
  import { readInstallState } from '../infrastructure/fs/install-state-repository.js';
12
+ import { readDevSession, writeDevSession, removeDevSession } from '../infrastructure/fs/dev-session-repository.js';
12
13
  import {
13
14
  ensureSkillLink,
14
15
  rebuildInstallState,
15
16
  removePathIfExists,
16
17
  removeSkillLinks,
18
+ removeSkillLinksByPaths,
17
19
  removeSkillLinksByNames,
18
20
  } from '../infrastructure/runtime/materialize-skills.js';
19
21
  import {
@@ -29,6 +31,7 @@ import {
29
31
  readBuildState,
30
32
  writeBuildState,
31
33
  } from '../domain/skills/skill-provenance.js';
34
+ import { startSkillDevWorkbench } from '../application/skills/start-skill-dev-workbench.js';
32
35
  import { AgentpackError, EXIT_CODES, NetworkError, NotFoundError, ValidationError } from '../utils/errors.js';
33
36
 
34
37
  const GITHUB_PACKAGES_REGISTRY = 'https://npm.pkg.github.com';
@@ -170,6 +173,100 @@ function readPackageJson(packageDir) {
170
173
  };
171
174
  }
172
175
 
176
+ function isProcessAlive(pid) {
177
+ if (!Number.isInteger(pid) || pid <= 0) return false;
178
+ try {
179
+ process.kill(pid, 0);
180
+ return true;
181
+ } catch (error) {
182
+ if (error.code === 'EPERM') return true;
183
+ if (error.code === 'ESRCH') return false;
184
+ return false;
185
+ }
186
+ }
187
+
188
+ function buildDevSessionNextSteps(command) {
189
+ return [{
190
+ action: 'run_command',
191
+ command,
192
+ reason: 'Use the dev session cleanup flow to remove recorded linked skills for this repo',
193
+ }];
194
+ }
195
+
196
+ function toDevSessionRecord(repoRoot, target, result, existing = null) {
197
+ const now = new Date().toISOString();
198
+ const rootSkill = result.linkedSkills.find((entry) => entry.name === result.name) || result.linkedSkills[0] || null;
199
+ return {
200
+ version: 1,
201
+ session_id: existing?.session_id || `dev-${now.replaceAll(':', '-').replaceAll('.', '-')}`,
202
+ status: 'active',
203
+ pid: process.pid,
204
+ repo_root: repoRoot,
205
+ target,
206
+ root_skill: rootSkill
207
+ ? {
208
+ name: rootSkill.name,
209
+ package_name: rootSkill.packageName,
210
+ path: rootSkill.path,
211
+ }
212
+ : null,
213
+ linked_skills: result.linkedSkills.map((entry) => ({
214
+ name: entry.name,
215
+ package_name: entry.packageName,
216
+ path: entry.path,
217
+ })),
218
+ links: result.links,
219
+ started_at: existing?.started_at || now,
220
+ updated_at: now,
221
+ };
222
+ }
223
+
224
+ function cleanupRecordedDevSession(repoRoot, session, status = 'stale') {
225
+ if (!session) {
226
+ return {
227
+ cleaned: false,
228
+ removed: [],
229
+ session: null,
230
+ };
231
+ }
232
+
233
+ writeDevSession(repoRoot, {
234
+ ...session,
235
+ status,
236
+ updated_at: new Date().toISOString(),
237
+ });
238
+ const removed = removeSkillLinksByPaths(repoRoot, session.links || [], normalizeDisplayPath);
239
+ removeDevSession(repoRoot);
240
+ return {
241
+ cleaned: removed.length > 0 || Boolean(session),
242
+ removed,
243
+ session,
244
+ };
245
+ }
246
+
247
+ function reconcileDevSession(repoRoot) {
248
+ const session = readDevSession(repoRoot);
249
+ if (!session) return null;
250
+
251
+ if (session.status === 'active' && isProcessAlive(session.pid)) {
252
+ throw new AgentpackError('A skills dev session is already active in this repo', {
253
+ code: 'skills_dev_session_active',
254
+ exitCode: EXIT_CODES.GENERAL,
255
+ nextSteps: [
256
+ ...buildDevSessionNextSteps('agentpack skills dev cleanup'),
257
+ ...buildDevSessionNextSteps('agentpack skills dev cleanup --force'),
258
+ ],
259
+ details: {
260
+ rootSkill: session.root_skill?.name || null,
261
+ pid: session.pid,
262
+ startedAt: session.started_at || null,
263
+ },
264
+ });
265
+ }
266
+
267
+ return cleanupRecordedDevSession(repoRoot, session, 'stale');
268
+ }
269
+
173
270
  export function syncSkillDependencies(skillDir) {
174
271
  const skillFile = join(skillDir, 'SKILL.md');
175
272
  const metadata = parseSkillFrontmatterFile(skillFile);
@@ -263,25 +360,42 @@ export function devSkill(target, {
263
360
  export function startSkillDev(target, {
264
361
  cwd = process.cwd(),
265
362
  sync = true,
363
+ dashboard = true,
266
364
  onStart = () => {},
267
365
  onRebuild = () => {},
268
366
  } = {}) {
269
- const repoRoot = findRepoRoot(cwd);
270
- const { skillDir } = resolveLocalPackagedSkillDir(repoRoot, target);
367
+ const outerRepoRoot = findRepoRoot(cwd);
368
+ const { skillDir } = resolveLocalPackagedSkillDir(outerRepoRoot, target);
369
+ const repoRoot = findRepoRoot(skillDir);
370
+ reconcileDevSession(repoRoot);
271
371
  let closed = false;
272
372
  let timer = null;
273
373
  let currentNames = [];
274
374
  let watcher = null;
375
+ let workbench = null;
376
+ let initialResult = null;
377
+ let sessionRecord = null;
275
378
 
276
379
  const cleanup = () => {
277
380
  if (closed) return { name: currentNames[0] || null, unlinked: false, removed: [] };
278
381
  closed = true;
279
382
  clearTimeout(timer);
280
383
  if (watcher) watcher.close();
281
- const removed = removeSkillLinksByNames(repoRoot, currentNames, normalizeDisplayPath);
384
+ if (workbench) workbench.close();
385
+ if (sessionRecord) {
386
+ writeDevSession(repoRoot, {
387
+ ...sessionRecord,
388
+ status: 'cleaning',
389
+ updated_at: new Date().toISOString(),
390
+ });
391
+ }
392
+ const removed = sessionRecord
393
+ ? removeSkillLinksByPaths(repoRoot, sessionRecord.links || [], normalizeDisplayPath)
394
+ : removeSkillLinksByNames(repoRoot, currentNames, normalizeDisplayPath);
395
+ removeDevSession(repoRoot);
282
396
  detachProcessCleanup();
283
397
  return {
284
- name: currentNames[0] || null,
398
+ name: sessionRecord?.root_skill?.name || currentNames[0] || null,
285
399
  unlinked: removed.length > 0,
286
400
  removed,
287
401
  };
@@ -289,9 +403,14 @@ export function startSkillDev(target, {
289
403
 
290
404
  const processCleanupHandlers = new Map();
291
405
  const attachProcessCleanup = () => {
292
- for (const eventName of ['exit', 'beforeExit', 'SIGINT', 'SIGTERM', 'SIGHUP']) {
406
+ const exitHandler = () => cleanup();
407
+ processCleanupHandlers.set('exit', exitHandler);
408
+ process.once('exit', exitHandler);
409
+
410
+ for (const eventName of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
293
411
  const handler = () => {
294
412
  cleanup();
413
+ process.exit(0);
295
414
  };
296
415
  processCleanupHandlers.set(eventName, handler);
297
416
  process.once(eventName, handler);
@@ -305,19 +424,58 @@ export function startSkillDev(target, {
305
424
  processCleanupHandlers.clear();
306
425
  };
307
426
 
308
- const run = () => {
309
- const result = devSkill(target, { cwd, sync });
427
+ const enrichResult = (result) => ({
428
+ ...result,
429
+ workbench: workbench
430
+ ? {
431
+ enabled: true,
432
+ url: workbench.url,
433
+ port: workbench.port,
434
+ }
435
+ : {
436
+ enabled: false,
437
+ url: null,
438
+ port: null,
439
+ },
440
+ });
441
+
442
+ const applyDevResult = (result) => {
310
443
  const nextNames = result.linkedSkills.map((entry) => entry.name);
311
444
  const staleNames = currentNames.filter((name) => !nextNames.includes(name));
312
445
  if (staleNames.length > 0) {
313
446
  removeSkillLinksByNames(repoRoot, staleNames, normalizeDisplayPath);
314
447
  }
315
448
  currentNames = nextNames;
449
+ sessionRecord = toDevSessionRecord(repoRoot, target, result, sessionRecord);
450
+ writeDevSession(repoRoot, sessionRecord);
316
451
  return result;
317
452
  };
318
453
 
319
- const initialResult = run();
320
- onStart(initialResult);
454
+ const startOrRefreshWorkbench = async () => {
455
+ if (dashboard && !workbench) {
456
+ workbench = await startSkillDevWorkbench({
457
+ repoRoot,
458
+ skillDir,
459
+ open: true,
460
+ disableBrowser: process.env.AGENTPACK_DISABLE_BROWSER === '1',
461
+ });
462
+ } else if (workbench) {
463
+ workbench.refresh();
464
+ }
465
+ };
466
+
467
+ initialResult = enrichResult(applyDevResult(devSkill(target, { cwd, sync })));
468
+ const ready = Promise.resolve(startOrRefreshWorkbench())
469
+ .then(() => {
470
+ const result = enrichResult(initialResult);
471
+ initialResult = result;
472
+ onStart(result);
473
+ return result;
474
+ })
475
+ .catch((error) => {
476
+ cleanup();
477
+ throw error;
478
+ });
321
479
 
322
480
  attachProcessCleanup();
323
481
 
@@ -325,25 +483,59 @@ export function startSkillDev(target, {
325
483
  if (closed) return;
326
484
  clearTimeout(timer);
327
485
  timer = setTimeout(() => {
328
- try {
329
- const result = run();
330
- onRebuild(result);
331
- } catch (error) {
332
- onRebuild({ error });
333
- }
486
+ Promise.resolve()
487
+ .then(() => applyDevResult(devSkill(target, { cwd, sync })))
488
+ .then(async (result) => {
489
+ await startOrRefreshWorkbench();
490
+ return enrichResult(result);
491
+ })
492
+ .then(onRebuild)
493
+ .catch((error) => {
494
+ onRebuild({ error });
495
+ });
334
496
  }, 100);
335
497
  });
336
498
 
337
499
  return {
338
500
  initialResult,
501
+ ready,
339
502
  close() {
340
503
  return cleanup();
341
504
  },
342
505
  };
343
506
  }
344
507
 
345
- export function unlinkSkill(name, { cwd = process.cwd() } = {}) {
508
+ export function unlinkSkill(name, { cwd = process.cwd(), recursive = false } = {}) {
346
509
  const repoRoot = findRepoRoot(cwd);
510
+ const session = readDevSession(repoRoot);
511
+
512
+ if (recursive) {
513
+ if (!session || session.root_skill?.name !== name) {
514
+ throw new AgentpackError('Recursive unlink requires the active dev-session root skill', {
515
+ code: 'linked_skill_recursive_unlink_requires_root',
516
+ exitCode: EXIT_CODES.GENERAL,
517
+ nextSteps: session?.root_skill?.name
518
+ ? [{
519
+ action: 'run_command',
520
+ command: `agentpack skills unlink ${session.root_skill.name} --recursive`,
521
+ reason: 'Recursive unlink in v1 only works for the recorded dev-session root skill',
522
+ }]
523
+ : buildDevSessionNextSteps('agentpack skills dev cleanup --force'),
524
+ details: {
525
+ rootSkill: session?.root_skill?.name || null,
526
+ },
527
+ });
528
+ }
529
+
530
+ const removed = removeSkillLinksByPaths(repoRoot, session.links || [], normalizeDisplayPath);
531
+ removeDevSession(repoRoot);
532
+ return {
533
+ name,
534
+ unlinked: removed.length > 0,
535
+ removed,
536
+ };
537
+ }
538
+
347
539
  const existing = [
348
540
  join(repoRoot, '.claude', 'skills', name),
349
541
  join(repoRoot, '.agents', 'skills', name),
@@ -365,6 +557,43 @@ export function unlinkSkill(name, { cwd = process.cwd() } = {}) {
365
557
  };
366
558
  }
367
559
 
560
+ export function cleanupSkillDevSession({ cwd = process.cwd(), force = false } = {}) {
561
+ const repoRoot = findRepoRoot(cwd);
562
+ const session = readDevSession(repoRoot);
563
+ if (!session) {
564
+ return {
565
+ cleaned: false,
566
+ active: false,
567
+ removed: [],
568
+ };
569
+ }
570
+
571
+ if (!force && session.status === 'active' && isProcessAlive(session.pid)) {
572
+ throw new AgentpackError('A skills dev session is still active in this repo', {
573
+ code: 'skills_dev_session_active',
574
+ exitCode: EXIT_CODES.GENERAL,
575
+ nextSteps: [
576
+ ...buildDevSessionNextSteps('agentpack skills dev cleanup'),
577
+ ...buildDevSessionNextSteps('agentpack skills dev cleanup --force'),
578
+ ],
579
+ details: {
580
+ rootSkill: session.root_skill?.name || null,
581
+ pid: session.pid,
582
+ startedAt: session.started_at || null,
583
+ },
584
+ });
585
+ }
586
+
587
+ const result = cleanupRecordedDevSession(repoRoot, session, 'stale');
588
+ return {
589
+ cleaned: true,
590
+ active: false,
591
+ forced: force,
592
+ name: session.root_skill?.name || null,
593
+ removed: result.removed,
594
+ };
595
+ }
596
+
368
597
  function buildValidateNextSteps(packageMetadata, valid) {
369
598
  if (!valid || !packageMetadata.packageName) return [];
370
599
 
@@ -14,18 +14,31 @@ export const EXIT_CODES = {
14
14
  * Carries a machine-readable code and mapped exit code.
15
15
  */
16
16
  export class AgentpackError extends Error {
17
- constructor(message, { code, exitCode = EXIT_CODES.GENERAL, suggestion } = {}) {
17
+ constructor(message, {
18
+ code,
19
+ exitCode = EXIT_CODES.GENERAL,
20
+ suggestion,
21
+ path,
22
+ nextSteps,
23
+ details,
24
+ } = {}) {
18
25
  super(message);
19
26
  this.name = 'AgentpackError';
20
27
  this.code = code || 'general_error';
21
28
  this.exitCode = exitCode;
22
29
  this.suggestion = suggestion;
30
+ this.path = path;
31
+ this.nextSteps = nextSteps || [];
32
+ this.details = details || {};
23
33
  }
24
34
 
25
35
  toJSON() {
26
36
  return {
27
37
  error: this.code,
28
38
  message: this.message,
39
+ ...(this.path && { path: this.path }),
40
+ ...(this.nextSteps.length > 0 && { nextSteps: this.nextSteps }),
41
+ ...(Object.keys(this.details).length > 0 && { details: this.details }),
29
42
  ...(this.suggestion && { suggestion: this.suggestion }),
30
43
  };
31
44
  }
@@ -58,6 +71,25 @@ export class NotFoundError extends AgentpackError {
58
71
  export function formatError(err) {
59
72
  if (err instanceof AgentpackError) {
60
73
  let msg = `Error: ${err.message}`;
74
+ if (err.path) {
75
+ msg += `\nPath: ${err.path}`;
76
+ }
77
+ if (err.nextSteps?.length) {
78
+ for (const step of err.nextSteps) {
79
+ const actionLabel = step.action === 'create_file'
80
+ ? `Create ${step.path}`
81
+ : step.action === 'edit_file'
82
+ ? `Edit ${step.path}`
83
+ : step.reason;
84
+ msg += `\nNext: ${actionLabel}`;
85
+ if (step.reason && step.reason !== actionLabel) {
86
+ msg += `\nWhy: ${step.reason}`;
87
+ }
88
+ if (step.example) {
89
+ msg += `\nExample:\n${JSON.stringify(step.example, null, 2)}`;
90
+ }
91
+ }
92
+ }
61
93
  if (err.suggestion) {
62
94
  msg += `\n\nSuggestion: ${err.suggestion}`;
63
95
  }