@a5c-ai/babysitter-sdk 0.0.169 → 0.0.170-staging.00aac85c

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 (76) hide show
  1. package/dist/cli/commands/configure.d.ts +124 -0
  2. package/dist/cli/commands/configure.d.ts.map +1 -0
  3. package/dist/cli/commands/configure.js +514 -0
  4. package/dist/cli/commands/health.d.ts +89 -0
  5. package/dist/cli/commands/health.d.ts.map +1 -0
  6. package/dist/cli/commands/health.js +579 -0
  7. package/dist/cli/commands/hookLog.d.ts +15 -0
  8. package/dist/cli/commands/hookLog.d.ts.map +1 -0
  9. package/dist/cli/commands/hookLog.js +286 -0
  10. package/dist/cli/commands/hookRun.d.ts +20 -0
  11. package/dist/cli/commands/hookRun.d.ts.map +1 -0
  12. package/dist/cli/commands/hookRun.js +544 -0
  13. package/dist/cli/commands/runExecuteTasks.d.ts +42 -0
  14. package/dist/cli/commands/runExecuteTasks.d.ts.map +1 -0
  15. package/dist/cli/commands/runExecuteTasks.js +377 -0
  16. package/dist/cli/commands/runIterate.d.ts +5 -1
  17. package/dist/cli/commands/runIterate.d.ts.map +1 -1
  18. package/dist/cli/commands/runIterate.js +75 -6
  19. package/dist/cli/commands/session.d.ts +97 -0
  20. package/dist/cli/commands/session.d.ts.map +1 -0
  21. package/dist/cli/commands/session.js +922 -0
  22. package/dist/cli/commands/skill.d.ts +87 -0
  23. package/dist/cli/commands/skill.d.ts.map +1 -0
  24. package/dist/cli/commands/skill.js +869 -0
  25. package/dist/cli/completionProof.d.ts +4 -0
  26. package/dist/cli/completionProof.d.ts.map +1 -0
  27. package/dist/cli/{completionSecret.js → completionProof.js} +7 -7
  28. package/dist/cli/main.d.ts +14 -0
  29. package/dist/cli/main.d.ts.map +1 -1
  30. package/dist/cli/main.js +649 -16
  31. package/dist/config/defaults.d.ts +165 -0
  32. package/dist/config/defaults.d.ts.map +1 -0
  33. package/dist/config/defaults.js +281 -0
  34. package/dist/config/index.d.ts +25 -0
  35. package/dist/config/index.d.ts.map +1 -0
  36. package/dist/config/index.js +35 -0
  37. package/dist/hooks/dispatcher.d.ts.map +1 -1
  38. package/dist/hooks/dispatcher.js +2 -1
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -0
  42. package/dist/runtime/constants.d.ts +1 -1
  43. package/dist/runtime/constants.d.ts.map +1 -1
  44. package/dist/runtime/createRun.d.ts.map +1 -1
  45. package/dist/runtime/createRun.js +7 -3
  46. package/dist/runtime/exceptions.d.ts +186 -3
  47. package/dist/runtime/exceptions.d.ts.map +1 -1
  48. package/dist/runtime/exceptions.js +416 -15
  49. package/dist/runtime/types.d.ts +1 -0
  50. package/dist/runtime/types.d.ts.map +1 -1
  51. package/dist/session/index.d.ts +9 -0
  52. package/dist/session/index.d.ts.map +1 -0
  53. package/dist/session/index.js +30 -0
  54. package/dist/session/parse.d.ts +45 -0
  55. package/dist/session/parse.d.ts.map +1 -0
  56. package/dist/session/parse.js +159 -0
  57. package/dist/session/types.d.ts +194 -0
  58. package/dist/session/types.d.ts.map +1 -0
  59. package/dist/session/types.js +45 -0
  60. package/dist/session/write.d.ts +50 -0
  61. package/dist/session/write.d.ts.map +1 -0
  62. package/dist/session/write.js +196 -0
  63. package/dist/storage/createRunDir.d.ts.map +1 -1
  64. package/dist/storage/createRunDir.js +1 -0
  65. package/dist/storage/paths.d.ts +5 -1
  66. package/dist/storage/paths.d.ts.map +1 -1
  67. package/dist/storage/paths.js +6 -1
  68. package/dist/storage/types.d.ts +3 -1
  69. package/dist/storage/types.d.ts.map +1 -1
  70. package/dist/tasks/kinds/index.d.ts.map +1 -1
  71. package/dist/tasks/kinds/index.js +6 -1
  72. package/dist/testing/runHarness.d.ts.map +1 -1
  73. package/dist/testing/runHarness.js +5 -1
  74. package/package.json +1 -2
  75. package/dist/cli/completionSecret.d.ts +0 -4
  76. package/dist/cli/completionSecret.d.ts.map +0 -1
@@ -0,0 +1,869 @@
1
+ "use strict";
2
+ /**
3
+ * Skill, agent, and process discovery CLI commands.
4
+ * Replaces bash logic from skill-context-resolver.sh and skill-discovery.sh
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.discoverSkillsInternal = discoverSkillsInternal;
41
+ exports.handleSkillDiscover = handleSkillDiscover;
42
+ exports.handleSkillFetchRemote = handleSkillFetchRemote;
43
+ const node_fs_1 = require("node:fs");
44
+ const path = __importStar(require("node:path"));
45
+ const os = __importStar(require("node:os"));
46
+ const DEFAULT_CACHE_TTL = 300; // 5 minutes
47
+ const CACHE_DIR = path.join(os.tmpdir(), 'babysitter-skill-cache');
48
+ // ---------------------------------------------------------------------------
49
+ // Frontmatter parsing
50
+ // ---------------------------------------------------------------------------
51
+ /**
52
+ * Parse scalar key:value pairs from YAML frontmatter.
53
+ * Ignores array items (lines starting with "- ").
54
+ */
55
+ function parseFrontmatter(content) {
56
+ const lines = content.split('\n');
57
+ let inFrontmatter = false;
58
+ const fields = {};
59
+ for (const line of lines) {
60
+ const trimmed = line.trim();
61
+ if (trimmed === '---') {
62
+ if (!inFrontmatter) {
63
+ inFrontmatter = true;
64
+ continue;
65
+ }
66
+ else {
67
+ break;
68
+ }
69
+ }
70
+ if (inFrontmatter && trimmed && !trimmed.startsWith('- ')) {
71
+ const colonIndex = trimmed.indexOf(':');
72
+ if (colonIndex > 0) {
73
+ const key = trimmed.slice(0, colonIndex).trim();
74
+ let value = trimmed.slice(colonIndex + 1).trim();
75
+ // Remove surrounding quotes
76
+ if ((value.startsWith('"') && value.endsWith('"')) ||
77
+ (value.startsWith("'") && value.endsWith("'"))) {
78
+ value = value.slice(1, -1);
79
+ }
80
+ if (value) {
81
+ fields[key] = value;
82
+ }
83
+ }
84
+ }
85
+ }
86
+ return fields;
87
+ }
88
+ /**
89
+ * Parse YAML frontmatter from a SKILL.md file content.
90
+ */
91
+ function parseSkillFrontmatter(content) {
92
+ const fields = parseFrontmatter(content);
93
+ const name = fields.name;
94
+ if (!name)
95
+ return null;
96
+ return {
97
+ name,
98
+ description: fields.description || '',
99
+ category: fields.category || fields.domain || '',
100
+ };
101
+ }
102
+ /**
103
+ * Parse YAML frontmatter from an AGENT.md file content.
104
+ */
105
+ function parseAgentFrontmatter(content) {
106
+ const fields = parseFrontmatter(content);
107
+ const name = fields.name;
108
+ if (!name)
109
+ return null;
110
+ return {
111
+ name,
112
+ description: fields.description || '',
113
+ role: fields.role || undefined,
114
+ category: fields.category || fields.domain || '',
115
+ };
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // File scanning
119
+ // ---------------------------------------------------------------------------
120
+ /**
121
+ * Recursively find files matching a target name in a directory.
122
+ */
123
+ async function findMarkdownFiles(dir, targetName, maxDepth = 5) {
124
+ const results = [];
125
+ async function scan(currentDir, depth) {
126
+ if (depth > maxDepth)
127
+ return;
128
+ let entries;
129
+ try {
130
+ entries = await node_fs_1.promises.readdir(currentDir, { withFileTypes: true });
131
+ }
132
+ catch {
133
+ return;
134
+ }
135
+ for (const entry of entries) {
136
+ const fullPath = path.join(currentDir, entry.name);
137
+ if (entry.isFile() && entry.name === targetName) {
138
+ results.push(fullPath);
139
+ }
140
+ else if (entry.isDirectory() && !entry.name.startsWith('.')) {
141
+ await scan(fullPath, depth + 1);
142
+ }
143
+ }
144
+ }
145
+ await scan(dir, 0);
146
+ return results;
147
+ }
148
+ /**
149
+ * Recursively find all SKILL.md files in a directory.
150
+ */
151
+ async function findSkillFiles(dir, maxDepth = 5) {
152
+ return findMarkdownFiles(dir, 'SKILL.md', maxDepth);
153
+ }
154
+ /**
155
+ * Recursively find all AGENT.md files in a directory.
156
+ */
157
+ async function findAgentFiles(dir, maxDepth = 5) {
158
+ return findMarkdownFiles(dir, 'AGENT.md', maxDepth);
159
+ }
160
+ /**
161
+ * Find *.js process files in a directory (non-recursive, depth 1 only).
162
+ */
163
+ async function findProcessFiles(dir) {
164
+ try {
165
+ const entries = await node_fs_1.promises.readdir(dir, { withFileTypes: true });
166
+ return entries
167
+ .filter(e => e.isFile() && e.name.endsWith('.js'))
168
+ .map(e => path.join(dir, e.name));
169
+ }
170
+ catch {
171
+ return [];
172
+ }
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // Directory scanning
176
+ // ---------------------------------------------------------------------------
177
+ /**
178
+ * Read and parse skills from a directory.
179
+ */
180
+ async function scanSkillsDirectory(dir, source, maxFiles = 50) {
181
+ const skills = [];
182
+ const skillFiles = await findSkillFiles(dir);
183
+ for (const file of skillFiles.slice(0, maxFiles)) {
184
+ try {
185
+ const content = await node_fs_1.promises.readFile(file, 'utf8');
186
+ const parsed = parseSkillFrontmatter(content);
187
+ if (parsed) {
188
+ skills.push({
189
+ ...parsed,
190
+ description: parsed.description.slice(0, 80),
191
+ source,
192
+ file,
193
+ });
194
+ }
195
+ }
196
+ catch {
197
+ // Skip files that can't be read
198
+ }
199
+ }
200
+ return skills;
201
+ }
202
+ /**
203
+ * Read and parse agents from a directory.
204
+ */
205
+ async function scanAgentsDirectory(dir, source, maxFiles = 50) {
206
+ const agents = [];
207
+ const agentFiles = await findAgentFiles(dir);
208
+ for (const file of agentFiles.slice(0, maxFiles)) {
209
+ try {
210
+ const content = await node_fs_1.promises.readFile(file, 'utf8');
211
+ const parsed = parseAgentFrontmatter(content);
212
+ if (parsed) {
213
+ agents.push({
214
+ ...parsed,
215
+ description: parsed.description.slice(0, 80),
216
+ source,
217
+ file,
218
+ });
219
+ }
220
+ }
221
+ catch {
222
+ // Skip files that can't be read
223
+ }
224
+ }
225
+ return agents;
226
+ }
227
+ /**
228
+ * Read process file names from a directory and return ProcessMetadata.
229
+ */
230
+ async function scanProcessesDirectory(dir, category, source) {
231
+ const jsFiles = await findProcessFiles(dir);
232
+ return jsFiles.map(file => ({
233
+ name: path.basename(file, '.js'),
234
+ category,
235
+ source,
236
+ file,
237
+ }));
238
+ }
239
+ // ---------------------------------------------------------------------------
240
+ // Specialization scoping
241
+ // ---------------------------------------------------------------------------
242
+ /**
243
+ * Given a process path like "specializations/web-development/api-integration-testing.js"
244
+ * or a full path containing "specializations/<name>/", extract the specialization name.
245
+ */
246
+ function extractSpecializationFromProcessPath(processPath) {
247
+ const normalized = processPath.replace(/\\/g, '/');
248
+ const match = normalized.match(/specializations\/([^/]+)/);
249
+ return match ? match[1] : null;
250
+ }
251
+ // ---------------------------------------------------------------------------
252
+ // Cache
253
+ // ---------------------------------------------------------------------------
254
+ /**
255
+ * Get cache file path for a run ID.
256
+ */
257
+ function getCachePath(runId, suffix) {
258
+ const safeId = runId || 'default';
259
+ return path.join(CACHE_DIR, `${safeId}.${suffix}`);
260
+ }
261
+ /**
262
+ * Read cached discovery results if valid.
263
+ * Returns null on cache miss or if the entry is missing the agents field (legacy format).
264
+ */
265
+ async function readCache(runId, ttl) {
266
+ const cachePath = getCachePath(runId, 'json');
267
+ try {
268
+ const content = await node_fs_1.promises.readFile(cachePath, 'utf8');
269
+ const entry = JSON.parse(content);
270
+ // Require agents field to be present (invalidates old skills-only cache)
271
+ if (!Array.isArray(entry.agents))
272
+ return null;
273
+ const age = (Date.now() - entry.timestamp) / 1000;
274
+ if (age < ttl) {
275
+ return entry;
276
+ }
277
+ }
278
+ catch {
279
+ // Cache miss
280
+ }
281
+ return null;
282
+ }
283
+ /**
284
+ * Write cache entry.
285
+ */
286
+ async function writeCache(runId, entry) {
287
+ try {
288
+ await node_fs_1.promises.mkdir(CACHE_DIR, { recursive: true });
289
+ const cachePath = getCachePath(runId, 'json');
290
+ await node_fs_1.promises.writeFile(cachePath, JSON.stringify(entry), 'utf8');
291
+ const summaryPath = getCachePath(runId, 'summary');
292
+ await node_fs_1.promises.writeFile(summaryPath, entry.summary, 'utf8');
293
+ }
294
+ catch {
295
+ // Cache write failure is non-fatal
296
+ }
297
+ }
298
+ // ---------------------------------------------------------------------------
299
+ // Domain detection and sorting
300
+ // ---------------------------------------------------------------------------
301
+ /**
302
+ * Detect domain/category from run process definition.
303
+ */
304
+ async function detectRunDomain(runId, runsDir) {
305
+ if (!runId)
306
+ return '';
307
+ const runDir = path.join(runsDir, runId);
308
+ try {
309
+ const files = await node_fs_1.promises.readdir(runDir);
310
+ const jsFile = files.find(f => f.endsWith('.js'));
311
+ if (jsFile) {
312
+ const content = await node_fs_1.promises.readFile(path.join(runDir, jsFile), 'utf8');
313
+ const match = content.match(/(?:domain|category|specialization)[:\s]*["']?([a-z-]+)/i);
314
+ if (match) {
315
+ return match[1].toLowerCase();
316
+ }
317
+ }
318
+ }
319
+ catch {
320
+ // Ignore errors
321
+ }
322
+ return '';
323
+ }
324
+ /**
325
+ * Generate compact summary string from skills and agents.
326
+ */
327
+ function generateSummary(skills, agents) {
328
+ const parts = [];
329
+ if (skills.length > 0) {
330
+ const skillPart = skills
331
+ .map(s => `${s.name} (${s.description.slice(0, 60) || 'no description'})`)
332
+ .join(', ');
333
+ parts.push(skillPart);
334
+ }
335
+ if (agents.length > 0) {
336
+ const agentPart = agents
337
+ .map(a => `${a.name} (${a.description.slice(0, 60) || 'no description'})`)
338
+ .join(', ');
339
+ parts.push(agentPart);
340
+ }
341
+ return parts.join(', ');
342
+ }
343
+ /**
344
+ * Deduplicate skills by name, keeping first occurrence.
345
+ */
346
+ function deduplicateSkills(skills) {
347
+ const seen = new Set();
348
+ return skills.filter(s => {
349
+ if (seen.has(s.name))
350
+ return false;
351
+ seen.add(s.name);
352
+ return true;
353
+ });
354
+ }
355
+ /**
356
+ * Deduplicate agents by name, keeping first occurrence.
357
+ */
358
+ function deduplicateAgents(agents) {
359
+ const seen = new Set();
360
+ return agents.filter(a => {
361
+ if (seen.has(a.name))
362
+ return false;
363
+ seen.add(a.name);
364
+ return true;
365
+ });
366
+ }
367
+ /**
368
+ * Sort skills by domain relevance if domain is provided.
369
+ */
370
+ function sortSkillsByDomain(skills, domain) {
371
+ if (!domain)
372
+ return skills;
373
+ const lowerDomain = domain.toLowerCase();
374
+ return [...skills].sort((a, b) => {
375
+ const aMatch = a.category.toLowerCase().includes(lowerDomain) ? 0 : 1;
376
+ const bMatch = b.category.toLowerCase().includes(lowerDomain) ? 0 : 1;
377
+ return aMatch - bMatch;
378
+ });
379
+ }
380
+ /**
381
+ * Sort agents by domain relevance if domain is provided.
382
+ */
383
+ function sortAgentsByDomain(agents, domain) {
384
+ if (!domain)
385
+ return agents;
386
+ const lowerDomain = domain.toLowerCase();
387
+ return [...agents].sort((a, b) => {
388
+ const aMatch = a.category.toLowerCase().includes(lowerDomain) ? 0 : 1;
389
+ const bMatch = b.category.toLowerCase().includes(lowerDomain) ? 0 : 1;
390
+ return aMatch - bMatch;
391
+ });
392
+ }
393
+ /**
394
+ * Internal discovery logic, extracted for reuse by other CLI commands
395
+ * (e.g. session:iteration-message, hookRun stop handler).
396
+ *
397
+ * Returns a structured result instead of writing to stdout.
398
+ */
399
+ async function discoverSkillsInternal(options) {
400
+ const { pluginRoot, runId = '', cacheTtl = DEFAULT_CACHE_TTL, runsDir = '.a5c/runs', includeRemote = false, processPath, includeProcesses = false, } = options;
401
+ // Bypass cache when processPath is set (specialization-scoped queries)
402
+ if (!processPath) {
403
+ const cached = await readCache(runId, cacheTtl);
404
+ if (cached) {
405
+ return { skills: cached.skills, agents: cached.agents, summary: cached.summary, cached: true };
406
+ }
407
+ }
408
+ // Determine domain for sorting — from processPath or run metadata
409
+ let domain = '';
410
+ if (processPath) {
411
+ domain = extractSpecializationFromProcessPath(processPath) ?? '';
412
+ }
413
+ if (!domain) {
414
+ domain = await detectRunDomain(runId, runsDir);
415
+ }
416
+ const specializationsDir = path.join(pluginRoot, 'skills', 'babysit', 'process', 'specializations');
417
+ // ------------------------------------------------------------------
418
+ // Skills
419
+ // ------------------------------------------------------------------
420
+ const allSkills = [];
421
+ // 1. Scan specializations directory
422
+ const specializationSkills = await scanSkillsDirectory(specializationsDir, 'local');
423
+ allSkills.push(...specializationSkills);
424
+ // 2. Scan plugin-level skills
425
+ const pluginSkillsDir = path.join(pluginRoot, 'skills');
426
+ const pluginSkills = await scanSkillsDirectory(pluginSkillsDir, 'local-plugin');
427
+ const filteredPluginSkills = pluginSkills.filter(s => !s.file?.includes('/specializations/'));
428
+ allSkills.push(...filteredPluginSkills);
429
+ // 3. Scan repo-level skills (.a5c/skills)
430
+ const repoSkillsDir = '.a5c/skills';
431
+ try {
432
+ await node_fs_1.promises.access(repoSkillsDir);
433
+ const repoSkills = await scanSkillsDirectory(repoSkillsDir, 'local');
434
+ allSkills.push(...repoSkills);
435
+ }
436
+ catch {
437
+ // Repo skills dir doesn't exist, skip
438
+ }
439
+ // 4. Optionally fetch remote skills
440
+ if (includeRemote) {
441
+ const remoteSkills = await fetchRemoteSkillSources(pluginRoot);
442
+ allSkills.push(...remoteSkills);
443
+ }
444
+ // ------------------------------------------------------------------
445
+ // Agents
446
+ // ------------------------------------------------------------------
447
+ const allAgents = [];
448
+ // 1. Scan specializations directory for agents
449
+ const specializationAgents = await scanAgentsDirectory(specializationsDir, 'local');
450
+ allAgents.push(...specializationAgents);
451
+ // 2. Scan repo-level agents (.a5c/agents)
452
+ const repoAgentsDir = '.a5c/agents';
453
+ try {
454
+ await node_fs_1.promises.access(repoAgentsDir);
455
+ const repoAgents = await scanAgentsDirectory(repoAgentsDir, 'local');
456
+ allAgents.push(...repoAgents);
457
+ }
458
+ catch {
459
+ // Repo agents dir doesn't exist, skip
460
+ }
461
+ // ------------------------------------------------------------------
462
+ // Processes (only when explicitly requested — not for hooks/session)
463
+ // ------------------------------------------------------------------
464
+ let processes;
465
+ if (includeProcesses) {
466
+ const allProcesses = [];
467
+ // 1. Specialization processes
468
+ try {
469
+ const specDirs = await node_fs_1.promises.readdir(specializationsDir, { withFileTypes: true });
470
+ for (const entry of specDirs) {
471
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
472
+ const specDir = path.join(specializationsDir, entry.name);
473
+ const procs = await scanProcessesDirectory(specDir, entry.name, 'library');
474
+ allProcesses.push(...procs);
475
+ }
476
+ }
477
+ }
478
+ catch {
479
+ // Specializations dir may not exist
480
+ }
481
+ // 2. Methodology processes
482
+ const methodologiesDir = path.join(pluginRoot, 'skills', 'babysit', 'process', 'methodologies');
483
+ try {
484
+ // Top-level methodology files
485
+ const topProcs = await scanProcessesDirectory(methodologiesDir, 'methodologies', 'library');
486
+ allProcesses.push(...topProcs);
487
+ // Methodology subdirectories
488
+ const methodDirs = await node_fs_1.promises.readdir(methodologiesDir, { withFileTypes: true });
489
+ for (const entry of methodDirs) {
490
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
491
+ const methodDir = path.join(methodologiesDir, entry.name);
492
+ const procs = await scanProcessesDirectory(methodDir, entry.name, 'library');
493
+ allProcesses.push(...procs);
494
+ }
495
+ }
496
+ }
497
+ catch {
498
+ // Methodologies dir may not exist
499
+ }
500
+ // 3. Repo-level processes (.a5c/processes)
501
+ const repoProcessesDir = '.a5c/processes';
502
+ try {
503
+ await node_fs_1.promises.access(repoProcessesDir);
504
+ const repoProcs = await scanProcessesDirectory(repoProcessesDir, 'project', 'repo');
505
+ allProcesses.push(...repoProcs);
506
+ }
507
+ catch {
508
+ // Repo processes dir doesn't exist
509
+ }
510
+ processes = allProcesses;
511
+ }
512
+ // ------------------------------------------------------------------
513
+ // Specialization scoping
514
+ // ------------------------------------------------------------------
515
+ let skills = deduplicateSkills(allSkills);
516
+ let agents = deduplicateAgents(allAgents);
517
+ if (processPath && domain) {
518
+ // Filter to matching specialization
519
+ const lowerDomain = domain.toLowerCase();
520
+ const matchesSpec = (filePath) => {
521
+ if (!filePath)
522
+ return false;
523
+ const normalized = filePath.replace(/\\/g, '/').toLowerCase();
524
+ return normalized.includes(`/specializations/${lowerDomain}/`);
525
+ };
526
+ skills = skills.filter(s => matchesSpec(s.file));
527
+ agents = agents.filter(a => matchesSpec(a.file));
528
+ if (processes) {
529
+ processes = processes.filter(p => p.category.toLowerCase() === lowerDomain);
530
+ }
531
+ }
532
+ else {
533
+ // Sort by domain relevance
534
+ skills = sortSkillsByDomain(skills, domain);
535
+ agents = sortAgentsByDomain(agents, domain);
536
+ }
537
+ // Limit for context window efficiency
538
+ skills = skills.slice(0, 30);
539
+ agents = agents.slice(0, 30);
540
+ // Generate summary
541
+ const summary = generateSummary(skills, agents);
542
+ // Cache results (only for non-scoped queries)
543
+ if (!processPath) {
544
+ const cacheEntry = {
545
+ skills,
546
+ agents,
547
+ summary,
548
+ timestamp: Date.now(),
549
+ };
550
+ await writeCache(runId, cacheEntry);
551
+ }
552
+ return { skills, agents, processes, summary, cached: false };
553
+ }
554
+ // ---------------------------------------------------------------------------
555
+ // Remote sources
556
+ // ---------------------------------------------------------------------------
557
+ /**
558
+ * Fetch skills from remote sources defined in .a5c/skill-sources.json
559
+ * and a default GitHub source.
560
+ */
561
+ async function fetchRemoteSkillSources(_pluginRoot) {
562
+ const remoteSkills = [];
563
+ const sources = [
564
+ { type: 'github', url: 'https://github.com/MaTriXy/babysitter/tree/main/plugins/babysitter/skills' },
565
+ ];
566
+ // Check for additional sources in .a5c/skill-sources.json
567
+ try {
568
+ const content = await node_fs_1.promises.readFile('.a5c/skill-sources.json', 'utf8');
569
+ const parsed = JSON.parse(content);
570
+ if (parsed.sources && Array.isArray(parsed.sources)) {
571
+ for (const s of parsed.sources) {
572
+ if ((s.type === 'github' || s.type === 'well-known') && typeof s.url === 'string') {
573
+ sources.push({ type: s.type, url: s.url });
574
+ }
575
+ }
576
+ }
577
+ }
578
+ catch {
579
+ // No external sources file, that's fine
580
+ }
581
+ for (const source of sources) {
582
+ try {
583
+ let skills = [];
584
+ if (source.type === 'github') {
585
+ skills = await discoverGitHub(source.url);
586
+ }
587
+ else if (source.type === 'well-known') {
588
+ skills = await discoverWellKnown(source.url);
589
+ }
590
+ remoteSkills.push(...skills);
591
+ }
592
+ catch {
593
+ // Skip failed remote sources
594
+ }
595
+ }
596
+ return remoteSkills;
597
+ }
598
+ // ---------------------------------------------------------------------------
599
+ // CLI command handlers
600
+ // ---------------------------------------------------------------------------
601
+ /**
602
+ * Handle skill:discover command.
603
+ * Scans for available skills, agents, and processes in plugin and repo directories.
604
+ * Thin wrapper around discoverSkillsInternal that handles CLI I/O.
605
+ */
606
+ async function handleSkillDiscover(args) {
607
+ const { pluginRoot, runId, cacheTtl, runsDir, json, includeRemote, summaryOnly, processPath, } = args;
608
+ if (!pluginRoot) {
609
+ const error = { error: 'MISSING_PLUGIN_ROOT', message: '--plugin-root is required' };
610
+ if (json) {
611
+ console.error(JSON.stringify(error));
612
+ }
613
+ else {
614
+ console.error('Error: --plugin-root is required');
615
+ }
616
+ return 1;
617
+ }
618
+ const result = await discoverSkillsInternal({
619
+ pluginRoot,
620
+ runId,
621
+ cacheTtl,
622
+ runsDir,
623
+ includeRemote,
624
+ processPath,
625
+ includeProcesses: true,
626
+ });
627
+ if (summaryOnly) {
628
+ console.log(result.summary || '');
629
+ return 0;
630
+ }
631
+ if (json) {
632
+ console.log(JSON.stringify({
633
+ skills: result.skills,
634
+ agents: result.agents,
635
+ processes: result.processes,
636
+ summary: result.summary,
637
+ cached: result.cached,
638
+ }));
639
+ }
640
+ else {
641
+ if (result.skills.length === 0 && result.agents.length === 0) {
642
+ console.log('(no skills or agents found)');
643
+ }
644
+ else {
645
+ if (result.skills.length > 0) {
646
+ console.log(`Skills (${result.skills.length}):`);
647
+ for (const skill of result.skills) {
648
+ console.log(` - ${skill.name}: ${skill.description || '(no description)'}${skill.file ? ` [${skill.file}]` : ''}`);
649
+ }
650
+ }
651
+ if (result.agents.length > 0) {
652
+ console.log(`Agents (${result.agents.length}):`);
653
+ for (const agent of result.agents) {
654
+ console.log(` - ${agent.name}: ${agent.description || '(no description)'}${agent.file ? ` [${agent.file}]` : ''}`);
655
+ }
656
+ }
657
+ if (result.processes && result.processes.length > 0) {
658
+ console.log(`Processes (${result.processes.length}):`);
659
+ for (const proc of result.processes) {
660
+ console.log(` - ${proc.name} [${proc.category}]: ${proc.file}`);
661
+ }
662
+ }
663
+ }
664
+ }
665
+ return 0;
666
+ }
667
+ // ---------------------------------------------------------------------------
668
+ // Remote discovery helpers
669
+ // ---------------------------------------------------------------------------
670
+ /**
671
+ * Convert GitHub web URL to API URL.
672
+ */
673
+ function githubWebToApi(url) {
674
+ // https://github.com/OWNER/REPO/tree/BRANCH/PATH
675
+ const treeMatch = url.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
676
+ if (treeMatch) {
677
+ const [, owner, repo, branch, treePath] = treeMatch;
678
+ return {
679
+ apiUrl: `https://api.github.com/repos/${owner}/${repo}/contents/${treePath}?ref=${branch}`,
680
+ rawBase: `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${treePath}`,
681
+ };
682
+ }
683
+ // https://github.com/OWNER/REPO
684
+ const repoMatch = url.match(/github\.com\/([^/]+)\/([^/]+)\/?$/);
685
+ if (repoMatch) {
686
+ const [, owner, repo] = repoMatch;
687
+ return {
688
+ apiUrl: `https://api.github.com/repos/${owner}/${repo}/contents/skills?ref=main`,
689
+ rawBase: `https://raw.githubusercontent.com/${owner}/${repo}/main/skills`,
690
+ };
691
+ }
692
+ return null;
693
+ }
694
+ /**
695
+ * Fetch URL with timeout.
696
+ */
697
+ async function fetchWithTimeout(url, timeout = 10000) {
698
+ const controller = new AbortController();
699
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
700
+ try {
701
+ const response = await fetch(url, {
702
+ signal: controller.signal,
703
+ headers: {
704
+ 'User-Agent': 'babysitter-sdk',
705
+ },
706
+ });
707
+ clearTimeout(timeoutId);
708
+ if (!response.ok)
709
+ return null;
710
+ return await response.text();
711
+ }
712
+ catch {
713
+ clearTimeout(timeoutId);
714
+ return null;
715
+ }
716
+ }
717
+ /**
718
+ * Discover skills from GitHub repository.
719
+ */
720
+ async function discoverGitHub(url) {
721
+ const parsed = githubWebToApi(url);
722
+ if (!parsed)
723
+ return [];
724
+ const { apiUrl, rawBase } = parsed;
725
+ const skills = [];
726
+ const listingText = await fetchWithTimeout(apiUrl);
727
+ if (!listingText)
728
+ return [];
729
+ let listing;
730
+ try {
731
+ listing = JSON.parse(listingText);
732
+ }
733
+ catch {
734
+ return [];
735
+ }
736
+ const dirs = listing.filter(e => e.type === 'dir').map(e => e.name);
737
+ const skillFile = listing.find(e => e.name === 'SKILL.md');
738
+ if (skillFile?.download_url) {
739
+ const content = await fetchWithTimeout(skillFile.download_url);
740
+ if (content) {
741
+ const parsed = parseSkillFrontmatter(content);
742
+ if (parsed) {
743
+ skills.push({
744
+ ...parsed,
745
+ source: 'remote',
746
+ url,
747
+ });
748
+ }
749
+ }
750
+ return skills;
751
+ }
752
+ let count = 0;
753
+ for (const dir of dirs) {
754
+ if (count >= 20)
755
+ break;
756
+ count++;
757
+ const skillUrl = `${rawBase}/${dir}/SKILL.md`;
758
+ const content = await fetchWithTimeout(skillUrl);
759
+ if (content) {
760
+ const parsed = parseSkillFrontmatter(content);
761
+ if (parsed) {
762
+ skills.push({
763
+ ...parsed,
764
+ source: 'remote',
765
+ url: skillUrl,
766
+ });
767
+ }
768
+ }
769
+ }
770
+ return skills;
771
+ }
772
+ /**
773
+ * Discover skills from well-known endpoint.
774
+ */
775
+ async function discoverWellKnown(url) {
776
+ const baseUrl = url.replace(/\/$/, '');
777
+ const skills = [];
778
+ let indexUrl = `${baseUrl}/.well-known/skills/index.json`;
779
+ let content = await fetchWithTimeout(indexUrl);
780
+ if (!content) {
781
+ const hostMatch = baseUrl.match(/^https?:\/\/([^/]+)/);
782
+ if (hostMatch) {
783
+ indexUrl = `https://${hostMatch[1]}/.well-known/skills/index.json`;
784
+ content = await fetchWithTimeout(indexUrl);
785
+ }
786
+ }
787
+ if (!content)
788
+ return [];
789
+ try {
790
+ const index = JSON.parse(content);
791
+ if (index.skills) {
792
+ for (const s of index.skills) {
793
+ skills.push({
794
+ name: s.name,
795
+ description: s.description || '',
796
+ category: '',
797
+ source: 'remote',
798
+ url: baseUrl,
799
+ });
800
+ }
801
+ }
802
+ }
803
+ catch {
804
+ // Invalid JSON
805
+ }
806
+ return skills;
807
+ }
808
+ /**
809
+ * Handle skill:fetch-remote command.
810
+ * Fetches skills from external sources (GitHub or well-known).
811
+ */
812
+ async function handleSkillFetchRemote(args) {
813
+ const { sourceType, url, json } = args;
814
+ if (!sourceType) {
815
+ const error = { error: 'MISSING_SOURCE_TYPE', message: '--source-type is required (github or well-known)' };
816
+ if (json) {
817
+ console.error(JSON.stringify(error));
818
+ }
819
+ else {
820
+ console.error('Error: --source-type is required (github or well-known)');
821
+ }
822
+ return 1;
823
+ }
824
+ if (!url) {
825
+ const error = { error: 'MISSING_URL', message: '--url is required' };
826
+ if (json) {
827
+ console.error(JSON.stringify(error));
828
+ }
829
+ else {
830
+ console.error('Error: --url is required');
831
+ }
832
+ return 1;
833
+ }
834
+ let skills = [];
835
+ switch (sourceType) {
836
+ case 'github':
837
+ skills = await discoverGitHub(url);
838
+ break;
839
+ case 'well-known':
840
+ skills = await discoverWellKnown(url);
841
+ break;
842
+ default: {
843
+ const _exhaustive = sourceType;
844
+ const unknownType = _exhaustive;
845
+ const error = { error: 'INVALID_SOURCE_TYPE', message: `Unknown source type: ${unknownType}` };
846
+ if (json) {
847
+ console.error(JSON.stringify(error));
848
+ }
849
+ else {
850
+ console.error(`Error: Unknown source type: ${unknownType}`);
851
+ }
852
+ return 1;
853
+ }
854
+ }
855
+ if (json) {
856
+ console.log(JSON.stringify({ skills }));
857
+ }
858
+ else {
859
+ if (skills.length === 0) {
860
+ console.log('[]');
861
+ }
862
+ else {
863
+ for (const skill of skills) {
864
+ console.log(`- ${skill.name}: ${skill.description || '(no description)'}`);
865
+ }
866
+ }
867
+ }
868
+ return 0;
869
+ }