agent-skillboard 0.2.16 → 0.2.18
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/CHANGELOG.md +33 -0
- package/README.md +75 -49
- package/bin/postinstall.mjs +2 -2
- package/docs/install.md +37 -42
- package/docs/reference.md +27 -18
- package/docs/user-flow.md +9 -17
- package/docs/versioning.md +1 -1
- package/package.json +1 -1
- package/src/agent-integration-cli.mjs +2 -2
- package/src/agent-integration-content.mjs +1 -1
- package/src/cli.mjs +118 -113
- package/src/doctor.mjs +2 -2
- package/src/source-profiles.mjs +144 -144
- package/src/workspace.mjs +79 -79
package/src/source-profiles.mjs
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
|
-
import YAML from "yaml";
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
4
|
import { readString, requireRecord } from "./config-helpers.mjs";
|
|
5
5
|
import { textChangePlan } from "./change-plan.mjs";
|
|
6
6
|
import { loadSourceProfile } from "./source-profile-loader.mjs";
|
|
7
|
-
|
|
8
|
-
export { loadSourceProfile };
|
|
9
|
-
|
|
10
|
-
export async function importSource(options) {
|
|
11
|
-
const sourceRoot = resolve(options.sourceRoot);
|
|
12
|
-
const profile = options.profile;
|
|
13
|
-
const files = await findSkillFiles(sourceRoot);
|
|
14
|
-
const matchedFiles = files
|
|
15
|
-
.map((file) => ({
|
|
16
|
-
file,
|
|
7
|
+
|
|
8
|
+
export { loadSourceProfile };
|
|
9
|
+
|
|
10
|
+
export async function importSource(options) {
|
|
11
|
+
const sourceRoot = resolve(options.sourceRoot);
|
|
12
|
+
const profile = options.profile;
|
|
13
|
+
const files = await findSkillFiles(sourceRoot);
|
|
14
|
+
const matchedFiles = files
|
|
15
|
+
.map((file) => ({
|
|
16
|
+
file,
|
|
17
17
|
relativeFile: relative(sourceRoot, file).replace(/\\/g, "/")
|
|
18
|
-
}))
|
|
19
|
-
.filter((entry) => matchesAnyProfilePattern(entry.relativeFile, profile.skillPaths))
|
|
20
|
-
.sort((left, right) => left.relativeFile.localeCompare(right.relativeFile));
|
|
21
|
-
const skills = [];
|
|
22
|
-
const warnings = [];
|
|
23
|
-
|
|
18
|
+
}))
|
|
19
|
+
.filter((entry) => matchesAnyProfilePattern(entry.relativeFile, profile.skillPaths))
|
|
20
|
+
.sort((left, right) => left.relativeFile.localeCompare(right.relativeFile));
|
|
21
|
+
const skills = [];
|
|
22
|
+
const warnings = [];
|
|
23
|
+
|
|
24
24
|
for (const entry of matchedFiles) {
|
|
25
25
|
const frontmatter = parseSkillFrontmatter(await readFile(entry.file, "utf8"));
|
|
26
26
|
const pathRule = matchingPathRule(entry.relativeFile, profile);
|
|
@@ -37,17 +37,17 @@ export async function importSource(options) {
|
|
|
37
37
|
ownerInstallUnit: profile.id,
|
|
38
38
|
description: frontmatter.description
|
|
39
39
|
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (skills.length === 0) {
|
|
43
|
-
warnings.push(`No SKILL.md files matched profile ${profile.id} under ${sourceRoot}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
profile,
|
|
48
|
-
sourceRoot,
|
|
49
|
-
skills,
|
|
50
|
-
installUnit: {
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (skills.length === 0) {
|
|
43
|
+
warnings.push(`No SKILL.md files matched profile ${profile.id} under ${sourceRoot}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
profile,
|
|
48
|
+
sourceRoot,
|
|
49
|
+
skills,
|
|
50
|
+
installUnit: {
|
|
51
51
|
id: profile.id,
|
|
52
52
|
kind: profile.kind,
|
|
53
53
|
sourceClass: profile.sourceClass,
|
|
@@ -58,31 +58,31 @@ export async function importSource(options) {
|
|
|
58
58
|
publicKey: profile.publicKey,
|
|
59
59
|
verifiedAt: profile.verifiedAt,
|
|
60
60
|
source: profile.source || sourceRoot,
|
|
61
|
-
scope: profile.scope,
|
|
62
|
-
manifestPath: profile.manifestPath,
|
|
63
|
-
cachePath: profile.cachePath,
|
|
64
|
-
providedComponents: profile.providedComponents,
|
|
65
|
-
components: {
|
|
66
|
-
skills: skills.map((skill) => skill.id),
|
|
67
|
-
commands: profile.components.commands,
|
|
68
|
-
hooks: profile.components.hooks,
|
|
69
|
-
mcpServers: profile.components.mcpServers
|
|
70
|
-
},
|
|
71
|
-
modifiedConfigFiles: profile.modifiedConfigFiles,
|
|
72
|
-
autoUpdate: profile.autoUpdate,
|
|
73
|
-
enabled: profile.enabled,
|
|
74
|
-
workflowDependencies: profile.workflowDependencies,
|
|
75
|
-
permissionRisk: profile.permissionRisk,
|
|
76
|
-
rollback: profile.rollback
|
|
77
|
-
},
|
|
78
|
-
warnings
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function renderImportFragment(imported) {
|
|
83
|
-
return YAML.stringify(importFragment(imported));
|
|
84
|
-
}
|
|
85
|
-
|
|
61
|
+
scope: profile.scope,
|
|
62
|
+
manifestPath: profile.manifestPath,
|
|
63
|
+
cachePath: profile.cachePath,
|
|
64
|
+
providedComponents: profile.providedComponents,
|
|
65
|
+
components: {
|
|
66
|
+
skills: skills.map((skill) => skill.id),
|
|
67
|
+
commands: profile.components.commands,
|
|
68
|
+
hooks: profile.components.hooks,
|
|
69
|
+
mcpServers: profile.components.mcpServers
|
|
70
|
+
},
|
|
71
|
+
modifiedConfigFiles: profile.modifiedConfigFiles,
|
|
72
|
+
autoUpdate: profile.autoUpdate,
|
|
73
|
+
enabled: profile.enabled,
|
|
74
|
+
workflowDependencies: profile.workflowDependencies,
|
|
75
|
+
permissionRisk: profile.permissionRisk,
|
|
76
|
+
rollback: profile.rollback
|
|
77
|
+
},
|
|
78
|
+
warnings
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function renderImportFragment(imported) {
|
|
83
|
+
return YAML.stringify(importFragment(imported));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
86
|
export function mergeImportFragment(configText, imported, options = {}) {
|
|
87
87
|
const document = YAML.parseDocument(configText);
|
|
88
88
|
if (document.errors.length > 0) {
|
|
@@ -117,7 +117,7 @@ export function mergeImportFragment(configText, imported, options = {}) {
|
|
|
117
117
|
addedSkills: skillIds,
|
|
118
118
|
addedInstallUnits: unitIds,
|
|
119
119
|
replacedSkills: duplicateSkills,
|
|
120
|
-
replacedInstallUnits: duplicateUnits
|
|
120
|
+
replacedInstallUnits: duplicateUnits
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -141,20 +141,20 @@ function requireYamlMap(value, label) {
|
|
|
141
141
|
function preserveLineEndings(text, reference) {
|
|
142
142
|
return reference.includes("\r\n") ? text.replace(/\n/g, "\r\n") : text;
|
|
143
143
|
}
|
|
144
|
-
|
|
144
|
+
|
|
145
145
|
function importFragment(imported) {
|
|
146
|
-
const skills = {};
|
|
147
|
-
for (const skill of imported.skills) {
|
|
148
|
-
skills[skill.id] = {
|
|
149
|
-
path: skill.path,
|
|
150
|
-
status: skill.status,
|
|
151
|
-
invocation: skill.invocation,
|
|
152
|
-
exposure: skill.exposure,
|
|
153
|
-
category: skill.category,
|
|
154
|
-
owner_install_unit: skill.ownerInstallUnit
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
const unit = imported.installUnit;
|
|
146
|
+
const skills = {};
|
|
147
|
+
for (const skill of imported.skills) {
|
|
148
|
+
skills[skill.id] = {
|
|
149
|
+
path: skill.path,
|
|
150
|
+
status: skill.status,
|
|
151
|
+
invocation: skill.invocation,
|
|
152
|
+
exposure: skill.exposure,
|
|
153
|
+
category: skill.category,
|
|
154
|
+
owner_install_unit: skill.ownerInstallUnit
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const unit = imported.installUnit;
|
|
158
158
|
return stripUndefined({
|
|
159
159
|
skills,
|
|
160
160
|
install_units: {
|
|
@@ -168,64 +168,64 @@ function importFragment(imported) {
|
|
|
168
168
|
public_key: unit.publicKey,
|
|
169
169
|
verified_at: unit.verifiedAt,
|
|
170
170
|
source: unit.source,
|
|
171
|
-
scope: unit.scope,
|
|
172
|
-
manifest_path: unit.manifestPath,
|
|
173
|
-
cache_path: unit.cachePath,
|
|
174
|
-
provided_components: unit.providedComponents,
|
|
175
|
-
components: {
|
|
176
|
-
skills: unit.components.skills,
|
|
177
|
-
commands: unit.components.commands,
|
|
178
|
-
hooks: unit.components.hooks,
|
|
179
|
-
mcp_servers: unit.components.mcpServers
|
|
180
|
-
},
|
|
181
|
-
modified_config_files: unit.modifiedConfigFiles,
|
|
182
|
-
auto_update: unit.autoUpdate,
|
|
183
|
-
enabled: unit.enabled,
|
|
184
|
-
workflow_dependencies: unit.workflowDependencies,
|
|
185
|
-
permission_risk: unit.permissionRisk,
|
|
171
|
+
scope: unit.scope,
|
|
172
|
+
manifest_path: unit.manifestPath,
|
|
173
|
+
cache_path: unit.cachePath,
|
|
174
|
+
provided_components: unit.providedComponents,
|
|
175
|
+
components: {
|
|
176
|
+
skills: unit.components.skills,
|
|
177
|
+
commands: unit.components.commands,
|
|
178
|
+
hooks: unit.components.hooks,
|
|
179
|
+
mcp_servers: unit.components.mcpServers
|
|
180
|
+
},
|
|
181
|
+
modified_config_files: unit.modifiedConfigFiles,
|
|
182
|
+
auto_update: unit.autoUpdate,
|
|
183
|
+
enabled: unit.enabled,
|
|
184
|
+
workflow_dependencies: unit.workflowDependencies,
|
|
185
|
+
permission_risk: unit.permissionRisk,
|
|
186
186
|
rollback: unit.rollback
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
});
|
|
190
190
|
}
|
|
191
|
-
|
|
192
|
-
function duplicateMessage(skillIds, unitIds) {
|
|
193
|
-
const parts = [];
|
|
194
|
-
if (skillIds.length > 0) {
|
|
195
|
-
parts.push(`skills already exist: ${skillIds.join(", ")}`);
|
|
196
|
-
}
|
|
197
|
-
if (unitIds.length > 0) {
|
|
198
|
-
parts.push(`install units already exist: ${unitIds.join(", ")}`);
|
|
199
|
-
}
|
|
200
|
-
return `${parts.join("; ")}. Re-run with --replace to overwrite.`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async function findSkillFiles(root) {
|
|
204
|
-
const files = [];
|
|
205
|
-
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
206
|
-
for (const entry of entries) {
|
|
207
|
-
const path = join(root, entry.name);
|
|
208
|
-
if (entry.isDirectory()) {
|
|
209
|
-
files.push(...(await findSkillFiles(path)));
|
|
210
|
-
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
|
211
|
-
files.push(path);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return files;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function parseSkillFrontmatter(text) {
|
|
218
|
-
const match = /^---[ \t]*\r?\n(?<body>[\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(text);
|
|
219
|
-
if (match?.groups === undefined) {
|
|
220
|
-
throw new Error("SKILL.md is missing YAML frontmatter");
|
|
221
|
-
}
|
|
222
|
-
const raw = requireRecord(YAML.parse(match.groups.body), "SKILL.md frontmatter");
|
|
223
|
-
return {
|
|
224
|
-
name: readString(raw, "name", ""),
|
|
225
|
-
description: readString(raw, "description", "")
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
191
|
+
|
|
192
|
+
function duplicateMessage(skillIds, unitIds) {
|
|
193
|
+
const parts = [];
|
|
194
|
+
if (skillIds.length > 0) {
|
|
195
|
+
parts.push(`skills already exist: ${skillIds.join(", ")}`);
|
|
196
|
+
}
|
|
197
|
+
if (unitIds.length > 0) {
|
|
198
|
+
parts.push(`install units already exist: ${unitIds.join(", ")}`);
|
|
199
|
+
}
|
|
200
|
+
return `${parts.join("; ")}. Re-run with --replace to overwrite.`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function findSkillFiles(root) {
|
|
204
|
+
const files = [];
|
|
205
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
206
|
+
for (const entry of entries) {
|
|
207
|
+
const path = join(root, entry.name);
|
|
208
|
+
if (entry.isDirectory()) {
|
|
209
|
+
files.push(...(await findSkillFiles(path)));
|
|
210
|
+
} else if (entry.isFile() && entry.name === "SKILL.md") {
|
|
211
|
+
files.push(path);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return files;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseSkillFrontmatter(text) {
|
|
218
|
+
const match = /^---[ \t]*\r?\n(?<body>[\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(text);
|
|
219
|
+
if (match?.groups === undefined) {
|
|
220
|
+
throw new Error("SKILL.md is missing YAML frontmatter");
|
|
221
|
+
}
|
|
222
|
+
const raw = requireRecord(YAML.parse(match.groups.body), "SKILL.md frontmatter");
|
|
223
|
+
return {
|
|
224
|
+
name: readString(raw, "name", ""),
|
|
225
|
+
description: readString(raw, "description", "")
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
229
|
function matchesAnyProfilePattern(file, patterns) {
|
|
230
230
|
const activePatterns = patterns.length === 0 ? ["**/SKILL.md"] : patterns;
|
|
231
231
|
return activePatterns.some((pattern) => patternToRegExp(pattern).test(file));
|
|
@@ -234,16 +234,16 @@ function matchesAnyProfilePattern(file, patterns) {
|
|
|
234
234
|
function matchingPathRule(file, profile) {
|
|
235
235
|
return (profile.pathRules ?? []).find((rule) => patternToRegExp(rule.pattern).test(file));
|
|
236
236
|
}
|
|
237
|
-
|
|
238
|
-
function patternToRegExp(pattern) {
|
|
239
|
-
const escaped = pattern
|
|
240
|
-
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
237
|
+
|
|
238
|
+
function patternToRegExp(pattern) {
|
|
239
|
+
const escaped = pattern
|
|
240
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
241
241
|
.split("**").join("\0")
|
|
242
242
|
.split("*").join("[^/]*")
|
|
243
243
|
.split("\0").join(".*");
|
|
244
|
-
return new RegExp(`^${escaped}$`);
|
|
245
|
-
}
|
|
246
|
-
|
|
244
|
+
return new RegExp(`^${escaped}$`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
247
|
function skillSlug(relativeFile, frontmatter, profile) {
|
|
248
248
|
if (profile.idStrategy === "frontmatter-name" && frontmatter.name.length > 0) {
|
|
249
249
|
const segments = frontmatter.name.split(".");
|
|
@@ -251,14 +251,14 @@ function skillSlug(relativeFile, frontmatter, profile) {
|
|
|
251
251
|
}
|
|
252
252
|
return normalizeSlug(basename(dirname(relativeFile)));
|
|
253
253
|
}
|
|
254
|
-
|
|
255
|
-
function skillId(slug, frontmatter, profile) {
|
|
256
|
-
if (profile.idStrategy === "frontmatter-name" && frontmatter.name.includes(".")) {
|
|
257
|
-
return frontmatter.name;
|
|
258
|
-
}
|
|
259
|
-
return profile.namespace.length === 0 ? slug : `${profile.namespace}.${slug}`;
|
|
260
|
-
}
|
|
261
|
-
|
|
254
|
+
|
|
255
|
+
function skillId(slug, frontmatter, profile) {
|
|
256
|
+
if (profile.idStrategy === "frontmatter-name" && frontmatter.name.includes(".")) {
|
|
257
|
+
return frontmatter.name;
|
|
258
|
+
}
|
|
259
|
+
return profile.namespace.length === 0 ? slug : `${profile.namespace}.${slug}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
262
|
function skillTargetPath(slug, profile) {
|
|
263
263
|
return profile.targetPathPrefix.length === 0 ? slug : `${profile.targetPathPrefix}/${slug}`;
|
|
264
264
|
}
|
|
@@ -276,13 +276,13 @@ function categoryFromPath(relativeFile, profile) {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
function normalizeSlug(value) {
|
|
279
|
-
return value
|
|
280
|
-
.trim()
|
|
281
|
-
.toLowerCase()
|
|
282
|
-
.replace(/[^a-z0-9._-]+/g, "-")
|
|
283
|
-
.replace(/^-+|-+$/g, "");
|
|
284
|
-
}
|
|
285
|
-
|
|
279
|
+
return value
|
|
280
|
+
.trim()
|
|
281
|
+
.toLowerCase()
|
|
282
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
283
|
+
.replace(/^-+|-+$/g, "");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
286
|
function safeDefaultInvocation(invocation) {
|
|
287
287
|
return invocation === "global-auto" ? "blocked" : invocation;
|
|
288
288
|
}
|
package/src/workspace.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
-
import YAML from "yaml";
|
|
4
|
+
import YAML from "yaml";
|
|
5
5
|
import {
|
|
6
6
|
readBoolean,
|
|
7
7
|
readOptionalRecord,
|
|
@@ -21,8 +21,8 @@ import { parseInstallUnits } from "./install-units.mjs";
|
|
|
21
21
|
import { normalizeSkillPath } from "./skill-paths.mjs";
|
|
22
22
|
|
|
23
23
|
export async function loadWorkspace(options) {
|
|
24
|
-
const configText = await readFile(options.configPath, "utf8");
|
|
25
|
-
const parsed = YAML.parse(configText);
|
|
24
|
+
const configText = await readFile(options.configPath, "utf8");
|
|
25
|
+
const parsed = YAML.parse(configText);
|
|
26
26
|
const config = requireRecord(parsed, "config root");
|
|
27
27
|
const version = parseVersion(config.version);
|
|
28
28
|
const skills = parseSkills(config.skills);
|
|
@@ -123,7 +123,7 @@ function resolveStoredPath(value, options) {
|
|
|
123
123
|
}
|
|
124
124
|
return resolve(dirname(options.configPath ?? "."), value);
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
async function findSkillFiles(root, seen = new Set()) {
|
|
128
128
|
const files = [];
|
|
129
129
|
const resolvedRoot = await realpath(root).catch(() => root);
|
|
@@ -148,8 +148,8 @@ async function findSkillFiles(root, seen = new Set()) {
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
return files;
|
|
151
|
-
}
|
|
152
|
-
|
|
151
|
+
}
|
|
152
|
+
|
|
153
153
|
function parseSkillFrontmatter(text) {
|
|
154
154
|
const match = /^---[ \t]*\r?\n(?<body>[\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(text);
|
|
155
155
|
if (match?.groups === undefined) {
|
|
@@ -170,49 +170,49 @@ function parseSkillFrontmatter(text) {
|
|
|
170
170
|
description: readString(raw, "description", "")
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
|
-
|
|
174
|
-
function parseVersion(value) {
|
|
175
|
-
if (value === undefined) {
|
|
176
|
-
return 1;
|
|
177
|
-
}
|
|
178
|
-
if (value !== 1) {
|
|
179
|
-
throw new Error(`Unsupported config version: ${value}`);
|
|
180
|
-
}
|
|
181
|
-
return value;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function parseDefaults(value) {
|
|
185
|
-
const raw = requireRecord(value ?? {}, "defaults");
|
|
186
|
-
return {
|
|
187
|
-
invocationPolicy: readString(raw, "invocation_policy", "deny-by-default"),
|
|
188
|
-
allowModelInvocation: readBoolean(raw, "allow_model_invocation", false),
|
|
189
|
-
requireExplicitWorkflow: readBoolean(raw, "require_explicit_workflow", true)
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function parseSkills(value) {
|
|
194
|
-
const raw = requireRecord(value ?? {}, "skills");
|
|
195
|
-
return Object.entries(raw).map(([id, entry]) => {
|
|
196
|
-
const skill = requireRecord(entry, `skills.${id}`);
|
|
197
|
-
const status = readString(skill, "status", "vendor");
|
|
198
|
-
const invocation = readString(skill, "invocation", "manual-only");
|
|
199
|
-
const exposure = readString(skill, "exposure", "exported");
|
|
200
|
-
if (!STATUS_VALUES.has(status)) {
|
|
201
|
-
throw new Error(`Unsupported status for ${id}: ${status}`);
|
|
202
|
-
}
|
|
203
|
-
if (!INVOCATION_VALUES.has(invocation)) {
|
|
204
|
-
throw new Error(`Unsupported invocation for ${id}: ${invocation}`);
|
|
205
|
-
}
|
|
206
|
-
if (!EXPOSURE_VALUES.has(exposure)) {
|
|
207
|
-
throw new Error(`Unsupported exposure for ${id}: ${exposure}`);
|
|
208
|
-
}
|
|
173
|
+
|
|
174
|
+
function parseVersion(value) {
|
|
175
|
+
if (value === undefined) {
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
if (value !== 1) {
|
|
179
|
+
throw new Error(`Unsupported config version: ${value}`);
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function parseDefaults(value) {
|
|
185
|
+
const raw = requireRecord(value ?? {}, "defaults");
|
|
186
|
+
return {
|
|
187
|
+
invocationPolicy: readString(raw, "invocation_policy", "deny-by-default"),
|
|
188
|
+
allowModelInvocation: readBoolean(raw, "allow_model_invocation", false),
|
|
189
|
+
requireExplicitWorkflow: readBoolean(raw, "require_explicit_workflow", true)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parseSkills(value) {
|
|
194
|
+
const raw = requireRecord(value ?? {}, "skills");
|
|
195
|
+
return Object.entries(raw).map(([id, entry]) => {
|
|
196
|
+
const skill = requireRecord(entry, `skills.${id}`);
|
|
197
|
+
const status = readString(skill, "status", "vendor");
|
|
198
|
+
const invocation = readString(skill, "invocation", "manual-only");
|
|
199
|
+
const exposure = readString(skill, "exposure", "exported");
|
|
200
|
+
if (!STATUS_VALUES.has(status)) {
|
|
201
|
+
throw new Error(`Unsupported status for ${id}: ${status}`);
|
|
202
|
+
}
|
|
203
|
+
if (!INVOCATION_VALUES.has(invocation)) {
|
|
204
|
+
throw new Error(`Unsupported invocation for ${id}: ${invocation}`);
|
|
205
|
+
}
|
|
206
|
+
if (!EXPOSURE_VALUES.has(exposure)) {
|
|
207
|
+
throw new Error(`Unsupported exposure for ${id}: ${exposure}`);
|
|
208
|
+
}
|
|
209
209
|
return {
|
|
210
210
|
id,
|
|
211
211
|
path: normalizeSkillPath(readString(skill, "path", id), `skills.${id}.path`),
|
|
212
|
-
status,
|
|
213
|
-
invocation,
|
|
214
|
-
exposure,
|
|
215
|
-
category: readString(skill, "category", "uncategorized"),
|
|
212
|
+
status,
|
|
213
|
+
invocation,
|
|
214
|
+
exposure,
|
|
215
|
+
category: readString(skill, "category", "uncategorized"),
|
|
216
216
|
canonicalFor: readStringList(skill, "canonical_for"),
|
|
217
217
|
conflictsWith: readStringList(skill, "conflicts_with"),
|
|
218
218
|
replacedBy: readOptionalString(skill, "replaced_by"),
|
|
@@ -266,39 +266,39 @@ function parseCapabilities(value) {
|
|
|
266
266
|
};
|
|
267
267
|
});
|
|
268
268
|
}
|
|
269
|
-
|
|
270
|
-
function parseHarnesses(value) {
|
|
271
|
-
const raw = requireRecord(value ?? {}, "harnesses");
|
|
272
|
-
return Object.entries(raw).map(([name, entry]) => {
|
|
273
|
-
const harness = requireRecord(entry, `harnesses.${name}`);
|
|
274
|
-
const status = readString(harness, "status", "available");
|
|
275
|
-
if (!HARNESS_STATUS_VALUES.has(status)) {
|
|
276
|
-
throw new Error(`Unsupported harness status for ${name}: ${status}`);
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
name,
|
|
280
|
-
status,
|
|
281
|
-
workflows: readStringList(harness, "workflows"),
|
|
282
|
-
commands: readStringList(harness, "commands")
|
|
283
|
-
};
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function parseWorkflows(value) {
|
|
288
|
-
const raw = requireRecord(value ?? {}, "workflows");
|
|
289
|
-
return Object.entries(raw).map(([name, entry]) => {
|
|
290
|
-
const workflow = requireRecord(entry, `workflows.${name}`);
|
|
291
|
-
return {
|
|
292
|
-
name,
|
|
293
|
-
harness: readString(workflow, "harness", "unspecified"),
|
|
294
|
-
activeSkills: readStringList(workflow, "active_skills"),
|
|
295
|
-
blockedSkills: readStringList(workflow, "blocked_skills"),
|
|
296
|
-
requiredOutputs: readStringList(workflow, "required_outputs"),
|
|
297
|
-
requiredCapabilities: parseRequiredCapabilities(workflow.required_capabilities, `workflows.${name}.required_capabilities`)
|
|
298
|
-
};
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
269
|
+
|
|
270
|
+
function parseHarnesses(value) {
|
|
271
|
+
const raw = requireRecord(value ?? {}, "harnesses");
|
|
272
|
+
return Object.entries(raw).map(([name, entry]) => {
|
|
273
|
+
const harness = requireRecord(entry, `harnesses.${name}`);
|
|
274
|
+
const status = readString(harness, "status", "available");
|
|
275
|
+
if (!HARNESS_STATUS_VALUES.has(status)) {
|
|
276
|
+
throw new Error(`Unsupported harness status for ${name}: ${status}`);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
name,
|
|
280
|
+
status,
|
|
281
|
+
workflows: readStringList(harness, "workflows"),
|
|
282
|
+
commands: readStringList(harness, "commands")
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function parseWorkflows(value) {
|
|
288
|
+
const raw = requireRecord(value ?? {}, "workflows");
|
|
289
|
+
return Object.entries(raw).map(([name, entry]) => {
|
|
290
|
+
const workflow = requireRecord(entry, `workflows.${name}`);
|
|
291
|
+
return {
|
|
292
|
+
name,
|
|
293
|
+
harness: readString(workflow, "harness", "unspecified"),
|
|
294
|
+
activeSkills: readStringList(workflow, "active_skills"),
|
|
295
|
+
blockedSkills: readStringList(workflow, "blocked_skills"),
|
|
296
|
+
requiredOutputs: readStringList(workflow, "required_outputs"),
|
|
297
|
+
requiredCapabilities: parseRequiredCapabilities(workflow.required_capabilities, `workflows.${name}.required_capabilities`)
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
302
|
function parseRequiredCapabilities(value, label) {
|
|
303
303
|
const raw = requireRecord(value ?? {}, label);
|
|
304
304
|
return Object.entries(raw).map(([name, entry]) => {
|