@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.
- package/dist/cli/commands/configure.d.ts +124 -0
- package/dist/cli/commands/configure.d.ts.map +1 -0
- package/dist/cli/commands/configure.js +514 -0
- package/dist/cli/commands/health.d.ts +89 -0
- package/dist/cli/commands/health.d.ts.map +1 -0
- package/dist/cli/commands/health.js +579 -0
- package/dist/cli/commands/hookLog.d.ts +15 -0
- package/dist/cli/commands/hookLog.d.ts.map +1 -0
- package/dist/cli/commands/hookLog.js +286 -0
- package/dist/cli/commands/hookRun.d.ts +20 -0
- package/dist/cli/commands/hookRun.d.ts.map +1 -0
- package/dist/cli/commands/hookRun.js +544 -0
- package/dist/cli/commands/runExecuteTasks.d.ts +42 -0
- package/dist/cli/commands/runExecuteTasks.d.ts.map +1 -0
- package/dist/cli/commands/runExecuteTasks.js +377 -0
- package/dist/cli/commands/runIterate.d.ts +5 -1
- package/dist/cli/commands/runIterate.d.ts.map +1 -1
- package/dist/cli/commands/runIterate.js +75 -6
- package/dist/cli/commands/session.d.ts +97 -0
- package/dist/cli/commands/session.d.ts.map +1 -0
- package/dist/cli/commands/session.js +922 -0
- package/dist/cli/commands/skill.d.ts +87 -0
- package/dist/cli/commands/skill.d.ts.map +1 -0
- package/dist/cli/commands/skill.js +869 -0
- package/dist/cli/completionProof.d.ts +4 -0
- package/dist/cli/completionProof.d.ts.map +1 -0
- package/dist/cli/{completionSecret.js → completionProof.js} +7 -7
- package/dist/cli/main.d.ts +14 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +649 -16
- package/dist/config/defaults.d.ts +165 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +281 -0
- package/dist/config/index.d.ts +25 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +35 -0
- package/dist/hooks/dispatcher.d.ts.map +1 -1
- package/dist/hooks/dispatcher.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/runtime/constants.d.ts +1 -1
- package/dist/runtime/constants.d.ts.map +1 -1
- package/dist/runtime/createRun.d.ts.map +1 -1
- package/dist/runtime/createRun.js +7 -3
- package/dist/runtime/exceptions.d.ts +186 -3
- package/dist/runtime/exceptions.d.ts.map +1 -1
- package/dist/runtime/exceptions.js +416 -15
- package/dist/runtime/types.d.ts +1 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/session/index.d.ts +9 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +30 -0
- package/dist/session/parse.d.ts +45 -0
- package/dist/session/parse.d.ts.map +1 -0
- package/dist/session/parse.js +159 -0
- package/dist/session/types.d.ts +194 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +45 -0
- package/dist/session/write.d.ts +50 -0
- package/dist/session/write.d.ts.map +1 -0
- package/dist/session/write.js +196 -0
- package/dist/storage/createRunDir.d.ts.map +1 -1
- package/dist/storage/createRunDir.js +1 -0
- package/dist/storage/paths.d.ts +5 -1
- package/dist/storage/paths.d.ts.map +1 -1
- package/dist/storage/paths.js +6 -1
- package/dist/storage/types.d.ts +3 -1
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/tasks/kinds/index.d.ts.map +1 -1
- package/dist/tasks/kinds/index.js +6 -1
- package/dist/testing/runHarness.d.ts.map +1 -1
- package/dist/testing/runHarness.js +5 -1
- package/package.json +1 -2
- package/dist/cli/completionSecret.d.ts +0 -4
- 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
|
+
}
|