@fro.bot/systematic 3.9.0 → 3.9.1
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.js +1 -1
- package/dist/{index-hjbt4p5s.js → index-y8nc60tz.js} +297 -299
- package/dist/index.js +121 -149
- package/dist/lib/skill-catalog.d.ts +0 -13
- package/dist/lib/skill-tool.d.ts +0 -6
- package/dist/pi.js +173 -202
- package/package.json +3 -3
|
@@ -15,253 +15,10 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
var __require = import.meta.require;
|
|
17
17
|
|
|
18
|
-
// src/lib/skills.ts
|
|
19
|
-
import fs2 from "fs";
|
|
20
|
-
import path2 from "path";
|
|
21
|
-
|
|
22
|
-
// src/lib/frontmatter.ts
|
|
23
|
-
import yaml from "js-yaml";
|
|
24
|
-
function parseFrontmatter(content) {
|
|
25
|
-
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
|
|
26
|
-
const match = content.match(frontmatterRegex);
|
|
27
|
-
if (!match) {
|
|
28
|
-
return {
|
|
29
|
-
data: {},
|
|
30
|
-
body: content,
|
|
31
|
-
hadFrontmatter: false,
|
|
32
|
-
parseError: false
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
const yamlContent = match[1];
|
|
36
|
-
const body = match[2];
|
|
37
|
-
try {
|
|
38
|
-
const parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
|
|
39
|
-
const data = parsed ?? {};
|
|
40
|
-
return { data, body, hadFrontmatter: true, parseError: false };
|
|
41
|
-
} catch {
|
|
42
|
-
return { data: {}, body, hadFrontmatter: true, parseError: true };
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// src/lib/validation.ts
|
|
47
|
-
function isRecord(value) {
|
|
48
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49
|
-
}
|
|
50
|
-
function isPermissionSetting(value) {
|
|
51
|
-
return value === "ask" || value === "allow" || value === "deny";
|
|
52
|
-
}
|
|
53
|
-
function isToolsMap(value) {
|
|
54
|
-
if (!isRecord(value))
|
|
55
|
-
return false;
|
|
56
|
-
return Object.values(value).every((entry) => typeof entry === "boolean");
|
|
57
|
-
}
|
|
58
|
-
function isAgentMode(value) {
|
|
59
|
-
return value === "subagent" || value === "primary" || value === "all";
|
|
60
|
-
}
|
|
61
|
-
function extractSimplePermission(data, key) {
|
|
62
|
-
if (!(key in data))
|
|
63
|
-
return;
|
|
64
|
-
const value = data[key];
|
|
65
|
-
return isPermissionSetting(value) ? value : null;
|
|
66
|
-
}
|
|
67
|
-
function extractBashPermission(data) {
|
|
68
|
-
if (!("bash" in data))
|
|
69
|
-
return;
|
|
70
|
-
const bash = data.bash;
|
|
71
|
-
if (isPermissionSetting(bash))
|
|
72
|
-
return bash;
|
|
73
|
-
if (isRecord(bash)) {
|
|
74
|
-
const entries = Object.entries(bash);
|
|
75
|
-
if (entries.every(([, setting]) => isPermissionSetting(setting))) {
|
|
76
|
-
return Object.fromEntries(entries);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill) {
|
|
82
|
-
const permission = {};
|
|
83
|
-
if (edit)
|
|
84
|
-
permission.edit = edit;
|
|
85
|
-
if (bash)
|
|
86
|
-
permission.bash = bash;
|
|
87
|
-
if (webfetch)
|
|
88
|
-
permission.webfetch = webfetch;
|
|
89
|
-
if (doom_loop)
|
|
90
|
-
permission.doom_loop = doom_loop;
|
|
91
|
-
if (external_directory)
|
|
92
|
-
permission.external_directory = external_directory;
|
|
93
|
-
if (task)
|
|
94
|
-
permission.task = task;
|
|
95
|
-
if (skill)
|
|
96
|
-
permission.skill = skill;
|
|
97
|
-
return Object.keys(permission).length > 0 ? permission : undefined;
|
|
98
|
-
}
|
|
99
|
-
function normalizePermission(value) {
|
|
100
|
-
if (!isRecord(value))
|
|
101
|
-
return;
|
|
102
|
-
const bash = extractBashPermission(value);
|
|
103
|
-
if (bash === null)
|
|
104
|
-
return;
|
|
105
|
-
const edit = extractSimplePermission(value, "edit");
|
|
106
|
-
if (edit === null)
|
|
107
|
-
return;
|
|
108
|
-
const webfetch = extractSimplePermission(value, "webfetch");
|
|
109
|
-
if (webfetch === null)
|
|
110
|
-
return;
|
|
111
|
-
const doom_loop = extractSimplePermission(value, "doom_loop");
|
|
112
|
-
if (doom_loop === null)
|
|
113
|
-
return;
|
|
114
|
-
const external_directory = extractSimplePermission(value, "external_directory");
|
|
115
|
-
if (external_directory === null)
|
|
116
|
-
return;
|
|
117
|
-
const task = extractSimplePermission(value, "task");
|
|
118
|
-
if (task === null)
|
|
119
|
-
return;
|
|
120
|
-
const skill = extractSimplePermission(value, "skill");
|
|
121
|
-
if (skill === null)
|
|
122
|
-
return;
|
|
123
|
-
return buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill);
|
|
124
|
-
}
|
|
125
|
-
function extractString(data, key, fallback = "") {
|
|
126
|
-
const value = data[key];
|
|
127
|
-
return typeof value === "string" ? value : fallback;
|
|
128
|
-
}
|
|
129
|
-
function extractNonEmptyString(data, key) {
|
|
130
|
-
const value = data[key];
|
|
131
|
-
if (typeof value !== "string")
|
|
132
|
-
return;
|
|
133
|
-
const trimmed = value.trim();
|
|
134
|
-
return trimmed !== "" ? trimmed : undefined;
|
|
135
|
-
}
|
|
136
|
-
function extractNumber(data, key) {
|
|
137
|
-
const value = data[key];
|
|
138
|
-
return typeof value === "number" ? value : undefined;
|
|
139
|
-
}
|
|
140
|
-
function extractBoolean(data, key) {
|
|
141
|
-
const value = data[key];
|
|
142
|
-
if (typeof value === "boolean")
|
|
143
|
-
return value;
|
|
144
|
-
if (typeof value === "string") {
|
|
145
|
-
const normalized = value.trim().toLowerCase();
|
|
146
|
-
if (normalized === "true")
|
|
147
|
-
return true;
|
|
148
|
-
if (normalized === "false")
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// src/lib/walk-dir.ts
|
|
155
|
-
import fs from "fs";
|
|
156
|
-
import path from "path";
|
|
157
|
-
function walkDir(rootDir, options = {}) {
|
|
158
|
-
const { maxDepth = 3, filter } = options;
|
|
159
|
-
const results = [];
|
|
160
|
-
if (!fs.existsSync(rootDir))
|
|
161
|
-
return results;
|
|
162
|
-
function recurse(currentDir, depth, category) {
|
|
163
|
-
if (depth > maxDepth)
|
|
164
|
-
return;
|
|
165
|
-
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
166
|
-
for (const entry of entries) {
|
|
167
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
168
|
-
const walkEntry = {
|
|
169
|
-
path: fullPath,
|
|
170
|
-
name: entry.name,
|
|
171
|
-
isDirectory: entry.isDirectory(),
|
|
172
|
-
depth,
|
|
173
|
-
category
|
|
174
|
-
};
|
|
175
|
-
if (!filter || filter(walkEntry)) {
|
|
176
|
-
results.push(walkEntry);
|
|
177
|
-
}
|
|
178
|
-
if (entry.isDirectory()) {
|
|
179
|
-
recurse(fullPath, depth + 1, entry.name);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
recurse(rootDir, 0);
|
|
184
|
-
return results;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// src/lib/skills.ts
|
|
188
|
-
function parseMetadata(data) {
|
|
189
|
-
const metadataRaw = data.metadata;
|
|
190
|
-
if (!isRecord(metadataRaw)) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
const entries = Object.entries(metadataRaw);
|
|
194
|
-
if (!entries.every(([, v]) => typeof v === "string")) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
return Object.fromEntries(entries);
|
|
198
|
-
}
|
|
199
|
-
function extractFrontmatterFromContent(content) {
|
|
200
|
-
const { data, parseError } = parseFrontmatter(content);
|
|
201
|
-
if (parseError) {
|
|
202
|
-
return { name: "", description: "" };
|
|
203
|
-
}
|
|
204
|
-
const metadata = parseMetadata(data);
|
|
205
|
-
const argumentHintRaw = extractNonEmptyString(data, "argument-hint");
|
|
206
|
-
const argumentHint = argumentHintRaw?.replace(/^["']|["']$/g, "") || undefined;
|
|
207
|
-
return {
|
|
208
|
-
name: extractString(data, "name"),
|
|
209
|
-
description: extractString(data, "description"),
|
|
210
|
-
license: extractNonEmptyString(data, "license"),
|
|
211
|
-
compatibility: extractNonEmptyString(data, "compatibility"),
|
|
212
|
-
metadata,
|
|
213
|
-
disableModelInvocation: extractBoolean(data, "disable-model-invocation"),
|
|
214
|
-
userInvocable: extractBoolean(data, "user-invocable"),
|
|
215
|
-
subtask: data.context === "fork" ? true : extractBoolean(data, "subtask") ?? undefined,
|
|
216
|
-
agent: extractNonEmptyString(data, "agent"),
|
|
217
|
-
model: extractNonEmptyString(data, "model"),
|
|
218
|
-
argumentHint: argumentHint !== "" ? argumentHint : undefined,
|
|
219
|
-
allowedTools: extractNonEmptyString(data, "allowed-tools")
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
function extractFrontmatter(filePath) {
|
|
223
|
-
try {
|
|
224
|
-
const content = fs2.readFileSync(filePath, "utf8");
|
|
225
|
-
return extractFrontmatterFromContent(content);
|
|
226
|
-
} catch {
|
|
227
|
-
return { name: "", description: "" };
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
function findSkillsInDir(dir, maxDepth = 3) {
|
|
231
|
-
const skills = [];
|
|
232
|
-
const entries = walkDir(dir, {
|
|
233
|
-
maxDepth,
|
|
234
|
-
filter: (e) => e.isDirectory
|
|
235
|
-
});
|
|
236
|
-
for (const entry of entries) {
|
|
237
|
-
const skillFile = path2.join(entry.path, "SKILL.md");
|
|
238
|
-
if (fs2.existsSync(skillFile)) {
|
|
239
|
-
const frontmatter = extractFrontmatter(skillFile);
|
|
240
|
-
skills.push({
|
|
241
|
-
path: entry.path,
|
|
242
|
-
skillFile,
|
|
243
|
-
name: frontmatter.name || entry.name,
|
|
244
|
-
description: frontmatter.description || "",
|
|
245
|
-
license: frontmatter.license,
|
|
246
|
-
compatibility: frontmatter.compatibility,
|
|
247
|
-
metadata: frontmatter.metadata,
|
|
248
|
-
disableModelInvocation: frontmatter.disableModelInvocation,
|
|
249
|
-
userInvocable: frontmatter.userInvocable,
|
|
250
|
-
subtask: frontmatter.subtask,
|
|
251
|
-
agent: frontmatter.agent,
|
|
252
|
-
model: frontmatter.model,
|
|
253
|
-
argumentHint: frontmatter.argumentHint,
|
|
254
|
-
allowedTools: frontmatter.allowedTools
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
return skills;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
18
|
// src/lib/config.ts
|
|
262
|
-
import
|
|
19
|
+
import fs from "fs";
|
|
263
20
|
import os from "os";
|
|
264
|
-
import
|
|
21
|
+
import path from "path";
|
|
265
22
|
|
|
266
23
|
// node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
267
24
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -1063,12 +820,12 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
|
|
|
1063
820
|
}
|
|
1064
821
|
return result;
|
|
1065
822
|
}
|
|
1066
|
-
function findNodeAtLocation(root,
|
|
823
|
+
function findNodeAtLocation(root, path) {
|
|
1067
824
|
if (!root) {
|
|
1068
825
|
return;
|
|
1069
826
|
}
|
|
1070
827
|
let node = root;
|
|
1071
|
-
for (let segment of
|
|
828
|
+
for (let segment of path) {
|
|
1072
829
|
if (typeof segment === "string") {
|
|
1073
830
|
if (node.type !== "object" || !Array.isArray(node.children)) {
|
|
1074
831
|
return;
|
|
@@ -1369,14 +1126,14 @@ function getNodeType(value) {
|
|
|
1369
1126
|
|
|
1370
1127
|
// node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/edit.js
|
|
1371
1128
|
function setProperty(text, originalPath, value, options) {
|
|
1372
|
-
const
|
|
1129
|
+
const path = originalPath.slice();
|
|
1373
1130
|
const errors = [];
|
|
1374
1131
|
const root = parseTree(text, errors);
|
|
1375
1132
|
let parent = undefined;
|
|
1376
1133
|
let lastSegment = undefined;
|
|
1377
|
-
while (
|
|
1378
|
-
lastSegment =
|
|
1379
|
-
parent = findNodeAtLocation(root,
|
|
1134
|
+
while (path.length > 0) {
|
|
1135
|
+
lastSegment = path.pop();
|
|
1136
|
+
parent = findNodeAtLocation(root, path);
|
|
1380
1137
|
if (parent === undefined && value !== undefined) {
|
|
1381
1138
|
if (typeof lastSegment === "string") {
|
|
1382
1139
|
value = { [lastSegment]: value };
|
|
@@ -1600,8 +1357,8 @@ function printParseErrorCode(code) {
|
|
|
1600
1357
|
}
|
|
1601
1358
|
return "<unknown ParseErrorCode>";
|
|
1602
1359
|
}
|
|
1603
|
-
function modify(text,
|
|
1604
|
-
return setProperty(text,
|
|
1360
|
+
function modify(text, path, value, options) {
|
|
1361
|
+
return setProperty(text, path, value, options);
|
|
1605
1362
|
}
|
|
1606
1363
|
function applyEdits(text, edits) {
|
|
1607
1364
|
let sortedEdits = edits.slice(0).sort((a, b) => {
|
|
@@ -2501,10 +2258,10 @@ function mergeDefs(...defs) {
|
|
|
2501
2258
|
function cloneDef(schema) {
|
|
2502
2259
|
return mergeDefs(schema._zod.def);
|
|
2503
2260
|
}
|
|
2504
|
-
function getElementAtPath(obj,
|
|
2505
|
-
if (!
|
|
2261
|
+
function getElementAtPath(obj, path) {
|
|
2262
|
+
if (!path)
|
|
2506
2263
|
return obj;
|
|
2507
|
-
return
|
|
2264
|
+
return path.reduce((acc, key) => acc?.[key], obj);
|
|
2508
2265
|
}
|
|
2509
2266
|
function promiseAllObject(promisesObj) {
|
|
2510
2267
|
const keys = Object.keys(promisesObj);
|
|
@@ -2912,11 +2669,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
2912
2669
|
}
|
|
2913
2670
|
return false;
|
|
2914
2671
|
}
|
|
2915
|
-
function prefixIssues(
|
|
2672
|
+
function prefixIssues(path, issues) {
|
|
2916
2673
|
return issues.map((iss) => {
|
|
2917
2674
|
var _a2;
|
|
2918
2675
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
2919
|
-
iss.path.unshift(
|
|
2676
|
+
iss.path.unshift(path);
|
|
2920
2677
|
return iss;
|
|
2921
2678
|
});
|
|
2922
2679
|
}
|
|
@@ -3063,16 +2820,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
|
|
|
3063
2820
|
}
|
|
3064
2821
|
function formatError(error, mapper = (issue2) => issue2.message) {
|
|
3065
2822
|
const fieldErrors = { _errors: [] };
|
|
3066
|
-
const processError = (error2,
|
|
2823
|
+
const processError = (error2, path = []) => {
|
|
3067
2824
|
for (const issue2 of error2.issues) {
|
|
3068
2825
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
3069
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2826
|
+
issue2.errors.map((issues) => processError({ issues }, [...path, ...issue2.path]));
|
|
3070
2827
|
} else if (issue2.code === "invalid_key") {
|
|
3071
|
-
processError({ issues: issue2.issues }, [...
|
|
2828
|
+
processError({ issues: issue2.issues }, [...path, ...issue2.path]);
|
|
3072
2829
|
} else if (issue2.code === "invalid_element") {
|
|
3073
|
-
processError({ issues: issue2.issues }, [...
|
|
2830
|
+
processError({ issues: issue2.issues }, [...path, ...issue2.path]);
|
|
3074
2831
|
} else {
|
|
3075
|
-
const fullpath = [...
|
|
2832
|
+
const fullpath = [...path, ...issue2.path];
|
|
3076
2833
|
if (fullpath.length === 0) {
|
|
3077
2834
|
fieldErrors._errors.push(mapper(issue2));
|
|
3078
2835
|
} else {
|
|
@@ -3099,17 +2856,17 @@ function formatError(error, mapper = (issue2) => issue2.message) {
|
|
|
3099
2856
|
}
|
|
3100
2857
|
function treeifyError(error, mapper = (issue2) => issue2.message) {
|
|
3101
2858
|
const result = { errors: [] };
|
|
3102
|
-
const processError = (error2,
|
|
2859
|
+
const processError = (error2, path = []) => {
|
|
3103
2860
|
var _a2, _b;
|
|
3104
2861
|
for (const issue2 of error2.issues) {
|
|
3105
2862
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
3106
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2863
|
+
issue2.errors.map((issues) => processError({ issues }, [...path, ...issue2.path]));
|
|
3107
2864
|
} else if (issue2.code === "invalid_key") {
|
|
3108
|
-
processError({ issues: issue2.issues }, [...
|
|
2865
|
+
processError({ issues: issue2.issues }, [...path, ...issue2.path]);
|
|
3109
2866
|
} else if (issue2.code === "invalid_element") {
|
|
3110
|
-
processError({ issues: issue2.issues }, [...
|
|
2867
|
+
processError({ issues: issue2.issues }, [...path, ...issue2.path]);
|
|
3111
2868
|
} else {
|
|
3112
|
-
const fullpath = [...
|
|
2869
|
+
const fullpath = [...path, ...issue2.path];
|
|
3113
2870
|
if (fullpath.length === 0) {
|
|
3114
2871
|
result.errors.push(mapper(issue2));
|
|
3115
2872
|
continue;
|
|
@@ -3141,8 +2898,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
|
|
|
3141
2898
|
}
|
|
3142
2899
|
function toDotPath(_path) {
|
|
3143
2900
|
const segs = [];
|
|
3144
|
-
const
|
|
3145
|
-
for (const seg of
|
|
2901
|
+
const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
2902
|
+
for (const seg of path) {
|
|
3146
2903
|
if (typeof seg === "number")
|
|
3147
2904
|
segs.push(`[${seg}]`);
|
|
3148
2905
|
else if (typeof seg === "symbol")
|
|
@@ -15601,13 +15358,13 @@ function resolveRef(ref, ctx) {
|
|
|
15601
15358
|
if (!ref.startsWith("#")) {
|
|
15602
15359
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
15603
15360
|
}
|
|
15604
|
-
const
|
|
15605
|
-
if (
|
|
15361
|
+
const path = ref.slice(1).split("/").filter(Boolean);
|
|
15362
|
+
if (path.length === 0) {
|
|
15606
15363
|
return ctx.rootSchema;
|
|
15607
15364
|
}
|
|
15608
15365
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
15609
|
-
if (
|
|
15610
|
-
const key =
|
|
15366
|
+
if (path[0] === defsKey) {
|
|
15367
|
+
const key = path[1];
|
|
15611
15368
|
if (!key || !ctx.defs[key]) {
|
|
15612
15369
|
throw new Error(`Reference not found: ${ref}`);
|
|
15613
15370
|
}
|
|
@@ -16414,10 +16171,10 @@ function warnDroppedNames(dropped, field, warned, removalVersion, warningSink =
|
|
|
16414
16171
|
}
|
|
16415
16172
|
}
|
|
16416
16173
|
function resolveConfigPath(dir, basename) {
|
|
16417
|
-
const jsoncPath =
|
|
16418
|
-
if (
|
|
16174
|
+
const jsoncPath = path.join(dir, `${basename}.jsonc`);
|
|
16175
|
+
if (fs.existsSync(jsoncPath))
|
|
16419
16176
|
return jsoncPath;
|
|
16420
|
-
return
|
|
16177
|
+
return path.join(dir, `${basename}.json`);
|
|
16421
16178
|
}
|
|
16422
16179
|
function isErrorWithCode(error51) {
|
|
16423
16180
|
return error51 instanceof Error && "code" in error51;
|
|
@@ -16425,7 +16182,7 @@ function isErrorWithCode(error51) {
|
|
|
16425
16182
|
function loadJsoncFile(filePath) {
|
|
16426
16183
|
let content;
|
|
16427
16184
|
try {
|
|
16428
|
-
content =
|
|
16185
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
16429
16186
|
} catch (error51) {
|
|
16430
16187
|
if (isErrorWithCode(error51) && error51.code === "ENOENT")
|
|
16431
16188
|
return null;
|
|
@@ -16438,7 +16195,7 @@ function loadJsoncFile(filePath) {
|
|
|
16438
16195
|
const message = error51 ? `${printParseErrorCode(error51.error)} at offset ${error51.offset}` : "unknown parse error";
|
|
16439
16196
|
throw new Error(`Invalid Systematic config in ${filePath}: JSONC parse error: ${message}`);
|
|
16440
16197
|
}
|
|
16441
|
-
if (!
|
|
16198
|
+
if (!isRecord(parsed)) {
|
|
16442
16199
|
throw new Error(`Invalid Systematic config in ${filePath}: root must be an object`);
|
|
16443
16200
|
}
|
|
16444
16201
|
return parsed;
|
|
@@ -16478,9 +16235,9 @@ function enrichUnrecognizedKeyIssues(issues, rawInput) {
|
|
|
16478
16235
|
return issue2;
|
|
16479
16236
|
});
|
|
16480
16237
|
}
|
|
16481
|
-
function resolveValueAtPath(root,
|
|
16238
|
+
function resolveValueAtPath(root, path2) {
|
|
16482
16239
|
let current = root;
|
|
16483
|
-
for (const segment of
|
|
16240
|
+
for (const segment of path2) {
|
|
16484
16241
|
if (typeof segment === "symbol") {
|
|
16485
16242
|
return;
|
|
16486
16243
|
}
|
|
@@ -16489,7 +16246,7 @@ function resolveValueAtPath(root, path4) {
|
|
|
16489
16246
|
return;
|
|
16490
16247
|
current = current[segment];
|
|
16491
16248
|
} else {
|
|
16492
|
-
if (!
|
|
16249
|
+
if (!isRecord(current))
|
|
16493
16250
|
return;
|
|
16494
16251
|
current = current[segment];
|
|
16495
16252
|
}
|
|
@@ -16566,13 +16323,13 @@ function classifyConfigSourceError(error51) {
|
|
|
16566
16323
|
return "source-invalid";
|
|
16567
16324
|
}
|
|
16568
16325
|
function isConfigSchemaError(error51) {
|
|
16569
|
-
return
|
|
16326
|
+
return isRecord(error51) && error51._tag === "ConfigSchemaError";
|
|
16570
16327
|
}
|
|
16571
16328
|
function resolveConfigSourcePath(filePath) {
|
|
16572
16329
|
try {
|
|
16573
|
-
return
|
|
16330
|
+
return fs.realpathSync(filePath);
|
|
16574
16331
|
} catch {
|
|
16575
|
-
return
|
|
16332
|
+
return path.resolve(filePath);
|
|
16576
16333
|
}
|
|
16577
16334
|
}
|
|
16578
16335
|
function collectProjectProtectedFields(rawConfig, trust) {
|
|
@@ -16591,9 +16348,9 @@ function collectProjectProtectedFields(rawConfig, trust) {
|
|
|
16591
16348
|
];
|
|
16592
16349
|
}
|
|
16593
16350
|
function collectOverlayProtectedFields(overlayMap, mapKey) {
|
|
16594
|
-
if (!
|
|
16351
|
+
if (!isRecord(overlayMap))
|
|
16595
16352
|
return [];
|
|
16596
|
-
return Object.values(overlayMap).flatMap((value) =>
|
|
16353
|
+
return Object.values(overlayMap).flatMap((value) => isRecord(value) ? collectProtectedOverlayValue(value, mapKey) : []);
|
|
16597
16354
|
}
|
|
16598
16355
|
function collectProtectedOverlayValue(value, mapKey) {
|
|
16599
16356
|
return [...SECURITY_OVERLAY_FIELDS].filter((field) => Object.hasOwn(value, field)).map((field) => ({
|
|
@@ -16609,7 +16366,7 @@ function stripProjectProtectedFields(rawConfig) {
|
|
|
16609
16366
|
}
|
|
16610
16367
|
return config2;
|
|
16611
16368
|
}
|
|
16612
|
-
function
|
|
16369
|
+
function isRecord(value) {
|
|
16613
16370
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16614
16371
|
}
|
|
16615
16372
|
function mergeArraysUnique(arr1, arr2) {
|
|
@@ -16753,7 +16510,7 @@ function hasConfigField(config2, fieldPath) {
|
|
|
16753
16510
|
if (nested === undefined)
|
|
16754
16511
|
return config2[topLevel] !== undefined;
|
|
16755
16512
|
const value = config2[topLevel];
|
|
16756
|
-
return
|
|
16513
|
+
return isRecord(value) && value[nested] !== undefined;
|
|
16757
16514
|
}
|
|
16758
16515
|
function sortAuthorities(authorities) {
|
|
16759
16516
|
return [...authorities].sort((left, right) => left.fieldPath === right.fieldPath ? left.sourceKind.localeCompare(right.sourceKind) : left.fieldPath.localeCompare(right.fieldPath));
|
|
@@ -16776,12 +16533,12 @@ function mergeOverlayMap(target, source, mapKey) {
|
|
|
16776
16533
|
const overlayMap = source.config[mapKey];
|
|
16777
16534
|
if (overlayMap === undefined)
|
|
16778
16535
|
return;
|
|
16779
|
-
if (!
|
|
16536
|
+
if (!isRecord(overlayMap)) {
|
|
16780
16537
|
throwInvalidOverlay(source.path, mapKey);
|
|
16781
16538
|
}
|
|
16782
16539
|
for (const [key, value] of Object.entries(overlayMap)) {
|
|
16783
16540
|
const keyPath = `${mapKey}.${key}`;
|
|
16784
|
-
if (!
|
|
16541
|
+
if (!isRecord(value)) {
|
|
16785
16542
|
throwInvalidOverlay(source.path, keyPath);
|
|
16786
16543
|
}
|
|
16787
16544
|
if (source.trust === "project") {
|
|
@@ -16846,12 +16603,12 @@ function mergePiSubagentsOverlayMap(target, source, mapKey) {
|
|
|
16846
16603
|
const overlayMap = source.config.pi_subagents?.[mapKey];
|
|
16847
16604
|
if (overlayMap === undefined)
|
|
16848
16605
|
return;
|
|
16849
|
-
if (!
|
|
16606
|
+
if (!isRecord(overlayMap)) {
|
|
16850
16607
|
throwInvalidOverlay(source.path, `pi_subagents.${mapKey}`);
|
|
16851
16608
|
}
|
|
16852
16609
|
for (const [key, rawValue] of Object.entries(overlayMap)) {
|
|
16853
16610
|
const keyPath = `pi_subagents.${mapKey}.${key}`;
|
|
16854
|
-
if (!
|
|
16611
|
+
if (!isRecord(rawValue)) {
|
|
16855
16612
|
throwInvalidOverlay(source.path, keyPath);
|
|
16856
16613
|
}
|
|
16857
16614
|
const previous = target[key];
|
|
@@ -16875,21 +16632,186 @@ function throwInvalidOverlay(sourcePath, keyPath) {
|
|
|
16875
16632
|
}
|
|
16876
16633
|
function getConfigPaths(projectDir, options) {
|
|
16877
16634
|
const homeDir = options?.homeDir ?? os.homedir();
|
|
16878
|
-
const userConfigDir = options?.userConfigDir ??
|
|
16635
|
+
const userConfigDir = options?.userConfigDir ?? path.join(homeDir, ".config/opencode");
|
|
16879
16636
|
const customConfigDir = options !== undefined && Object.hasOwn(options, "customConfigDir") ? options.customConfigDir?.trim() : process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
16880
16637
|
const result = {
|
|
16881
16638
|
userConfig: resolveConfigPath(userConfigDir, "systematic"),
|
|
16882
|
-
projectConfig: resolveConfigPath(
|
|
16883
|
-
userDir:
|
|
16884
|
-
projectDir:
|
|
16639
|
+
projectConfig: resolveConfigPath(path.join(projectDir, ".opencode"), "systematic"),
|
|
16640
|
+
userDir: path.join(homeDir, ".config/opencode/systematic"),
|
|
16641
|
+
projectDir: path.join(projectDir, ".opencode/systematic"),
|
|
16885
16642
|
...customConfigDir && {
|
|
16886
16643
|
customConfig: resolveConfigPath(customConfigDir, "systematic"),
|
|
16887
|
-
customDir:
|
|
16644
|
+
customDir: path.join(customConfigDir, "systematic")
|
|
16888
16645
|
}
|
|
16889
16646
|
};
|
|
16890
16647
|
return result;
|
|
16891
16648
|
}
|
|
16892
16649
|
|
|
16650
|
+
// src/lib/frontmatter.ts
|
|
16651
|
+
import yaml from "js-yaml";
|
|
16652
|
+
function parseFrontmatter(content) {
|
|
16653
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
|
|
16654
|
+
const match = content.match(frontmatterRegex);
|
|
16655
|
+
if (!match) {
|
|
16656
|
+
return {
|
|
16657
|
+
data: {},
|
|
16658
|
+
body: content,
|
|
16659
|
+
hadFrontmatter: false,
|
|
16660
|
+
parseError: false
|
|
16661
|
+
};
|
|
16662
|
+
}
|
|
16663
|
+
const yamlContent = match[1];
|
|
16664
|
+
const body = match[2];
|
|
16665
|
+
try {
|
|
16666
|
+
const parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
|
|
16667
|
+
const data = parsed ?? {};
|
|
16668
|
+
return { data, body, hadFrontmatter: true, parseError: false };
|
|
16669
|
+
} catch {
|
|
16670
|
+
return { data: {}, body, hadFrontmatter: true, parseError: true };
|
|
16671
|
+
}
|
|
16672
|
+
}
|
|
16673
|
+
|
|
16674
|
+
// src/lib/validation.ts
|
|
16675
|
+
function isRecord2(value) {
|
|
16676
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16677
|
+
}
|
|
16678
|
+
function isPermissionSetting(value) {
|
|
16679
|
+
return value === "ask" || value === "allow" || value === "deny";
|
|
16680
|
+
}
|
|
16681
|
+
function isToolsMap(value) {
|
|
16682
|
+
if (!isRecord2(value))
|
|
16683
|
+
return false;
|
|
16684
|
+
return Object.values(value).every((entry) => typeof entry === "boolean");
|
|
16685
|
+
}
|
|
16686
|
+
function isAgentMode(value) {
|
|
16687
|
+
return value === "subagent" || value === "primary" || value === "all";
|
|
16688
|
+
}
|
|
16689
|
+
function extractSimplePermission(data, key) {
|
|
16690
|
+
if (!(key in data))
|
|
16691
|
+
return;
|
|
16692
|
+
const value = data[key];
|
|
16693
|
+
return isPermissionSetting(value) ? value : null;
|
|
16694
|
+
}
|
|
16695
|
+
function extractBashPermission(data) {
|
|
16696
|
+
if (!("bash" in data))
|
|
16697
|
+
return;
|
|
16698
|
+
const bash = data.bash;
|
|
16699
|
+
if (isPermissionSetting(bash))
|
|
16700
|
+
return bash;
|
|
16701
|
+
if (isRecord2(bash)) {
|
|
16702
|
+
const entries = Object.entries(bash);
|
|
16703
|
+
if (entries.every(([, setting]) => isPermissionSetting(setting))) {
|
|
16704
|
+
return Object.fromEntries(entries);
|
|
16705
|
+
}
|
|
16706
|
+
}
|
|
16707
|
+
return null;
|
|
16708
|
+
}
|
|
16709
|
+
function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill) {
|
|
16710
|
+
const permission = {};
|
|
16711
|
+
if (edit)
|
|
16712
|
+
permission.edit = edit;
|
|
16713
|
+
if (bash)
|
|
16714
|
+
permission.bash = bash;
|
|
16715
|
+
if (webfetch)
|
|
16716
|
+
permission.webfetch = webfetch;
|
|
16717
|
+
if (doom_loop)
|
|
16718
|
+
permission.doom_loop = doom_loop;
|
|
16719
|
+
if (external_directory)
|
|
16720
|
+
permission.external_directory = external_directory;
|
|
16721
|
+
if (task)
|
|
16722
|
+
permission.task = task;
|
|
16723
|
+
if (skill)
|
|
16724
|
+
permission.skill = skill;
|
|
16725
|
+
return Object.keys(permission).length > 0 ? permission : undefined;
|
|
16726
|
+
}
|
|
16727
|
+
function normalizePermission(value) {
|
|
16728
|
+
if (!isRecord2(value))
|
|
16729
|
+
return;
|
|
16730
|
+
const bash = extractBashPermission(value);
|
|
16731
|
+
if (bash === null)
|
|
16732
|
+
return;
|
|
16733
|
+
const edit = extractSimplePermission(value, "edit");
|
|
16734
|
+
if (edit === null)
|
|
16735
|
+
return;
|
|
16736
|
+
const webfetch = extractSimplePermission(value, "webfetch");
|
|
16737
|
+
if (webfetch === null)
|
|
16738
|
+
return;
|
|
16739
|
+
const doom_loop = extractSimplePermission(value, "doom_loop");
|
|
16740
|
+
if (doom_loop === null)
|
|
16741
|
+
return;
|
|
16742
|
+
const external_directory = extractSimplePermission(value, "external_directory");
|
|
16743
|
+
if (external_directory === null)
|
|
16744
|
+
return;
|
|
16745
|
+
const task = extractSimplePermission(value, "task");
|
|
16746
|
+
if (task === null)
|
|
16747
|
+
return;
|
|
16748
|
+
const skill = extractSimplePermission(value, "skill");
|
|
16749
|
+
if (skill === null)
|
|
16750
|
+
return;
|
|
16751
|
+
return buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill);
|
|
16752
|
+
}
|
|
16753
|
+
function extractString(data, key, fallback = "") {
|
|
16754
|
+
const value = data[key];
|
|
16755
|
+
return typeof value === "string" ? value : fallback;
|
|
16756
|
+
}
|
|
16757
|
+
function extractNonEmptyString(data, key) {
|
|
16758
|
+
const value = data[key];
|
|
16759
|
+
if (typeof value !== "string")
|
|
16760
|
+
return;
|
|
16761
|
+
const trimmed = value.trim();
|
|
16762
|
+
return trimmed !== "" ? trimmed : undefined;
|
|
16763
|
+
}
|
|
16764
|
+
function extractNumber(data, key) {
|
|
16765
|
+
const value = data[key];
|
|
16766
|
+
return typeof value === "number" ? value : undefined;
|
|
16767
|
+
}
|
|
16768
|
+
function extractBoolean(data, key) {
|
|
16769
|
+
const value = data[key];
|
|
16770
|
+
if (typeof value === "boolean")
|
|
16771
|
+
return value;
|
|
16772
|
+
if (typeof value === "string") {
|
|
16773
|
+
const normalized = value.trim().toLowerCase();
|
|
16774
|
+
if (normalized === "true")
|
|
16775
|
+
return true;
|
|
16776
|
+
if (normalized === "false")
|
|
16777
|
+
return false;
|
|
16778
|
+
}
|
|
16779
|
+
return;
|
|
16780
|
+
}
|
|
16781
|
+
|
|
16782
|
+
// src/lib/walk-dir.ts
|
|
16783
|
+
import fs2 from "fs";
|
|
16784
|
+
import path2 from "path";
|
|
16785
|
+
function walkDir(rootDir, options = {}) {
|
|
16786
|
+
const { maxDepth = 3, filter } = options;
|
|
16787
|
+
const results = [];
|
|
16788
|
+
if (!fs2.existsSync(rootDir))
|
|
16789
|
+
return results;
|
|
16790
|
+
function recurse(currentDir, depth, category) {
|
|
16791
|
+
if (depth > maxDepth)
|
|
16792
|
+
return;
|
|
16793
|
+
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
16794
|
+
for (const entry of entries) {
|
|
16795
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
16796
|
+
const walkEntry = {
|
|
16797
|
+
path: fullPath,
|
|
16798
|
+
name: entry.name,
|
|
16799
|
+
isDirectory: entry.isDirectory(),
|
|
16800
|
+
depth,
|
|
16801
|
+
category
|
|
16802
|
+
};
|
|
16803
|
+
if (!filter || filter(walkEntry)) {
|
|
16804
|
+
results.push(walkEntry);
|
|
16805
|
+
}
|
|
16806
|
+
if (entry.isDirectory()) {
|
|
16807
|
+
recurse(fullPath, depth + 1, entry.name);
|
|
16808
|
+
}
|
|
16809
|
+
}
|
|
16810
|
+
}
|
|
16811
|
+
recurse(rootDir, 0);
|
|
16812
|
+
return results;
|
|
16813
|
+
}
|
|
16814
|
+
|
|
16893
16815
|
// src/lib/agents.ts
|
|
16894
16816
|
function findAgentsInDir(dir, maxDepth = 2) {
|
|
16895
16817
|
const entries = walkDir(dir, {
|
|
@@ -16964,6 +16886,82 @@ function extractCommandFrontmatter(content) {
|
|
|
16964
16886
|
};
|
|
16965
16887
|
}
|
|
16966
16888
|
|
|
16889
|
+
// src/lib/skills.ts
|
|
16890
|
+
import fs3 from "fs";
|
|
16891
|
+
import path3 from "path";
|
|
16892
|
+
function parseMetadata(data) {
|
|
16893
|
+
const metadataRaw = data.metadata;
|
|
16894
|
+
if (!isRecord2(metadataRaw)) {
|
|
16895
|
+
return;
|
|
16896
|
+
}
|
|
16897
|
+
const entries = Object.entries(metadataRaw);
|
|
16898
|
+
if (!entries.every(([, v]) => typeof v === "string")) {
|
|
16899
|
+
return;
|
|
16900
|
+
}
|
|
16901
|
+
return Object.fromEntries(entries);
|
|
16902
|
+
}
|
|
16903
|
+
function extractFrontmatterFromContent(content) {
|
|
16904
|
+
const { data, parseError } = parseFrontmatter(content);
|
|
16905
|
+
if (parseError) {
|
|
16906
|
+
return { name: "", description: "" };
|
|
16907
|
+
}
|
|
16908
|
+
const metadata = parseMetadata(data);
|
|
16909
|
+
const argumentHintRaw = extractNonEmptyString(data, "argument-hint");
|
|
16910
|
+
const argumentHint = argumentHintRaw?.replace(/^["']|["']$/g, "") || undefined;
|
|
16911
|
+
return {
|
|
16912
|
+
name: extractString(data, "name"),
|
|
16913
|
+
description: extractString(data, "description"),
|
|
16914
|
+
license: extractNonEmptyString(data, "license"),
|
|
16915
|
+
compatibility: extractNonEmptyString(data, "compatibility"),
|
|
16916
|
+
metadata,
|
|
16917
|
+
disableModelInvocation: extractBoolean(data, "disable-model-invocation"),
|
|
16918
|
+
userInvocable: extractBoolean(data, "user-invocable"),
|
|
16919
|
+
subtask: data.context === "fork" ? true : extractBoolean(data, "subtask") ?? undefined,
|
|
16920
|
+
agent: extractNonEmptyString(data, "agent"),
|
|
16921
|
+
model: extractNonEmptyString(data, "model"),
|
|
16922
|
+
argumentHint: argumentHint !== "" ? argumentHint : undefined,
|
|
16923
|
+
allowedTools: extractNonEmptyString(data, "allowed-tools")
|
|
16924
|
+
};
|
|
16925
|
+
}
|
|
16926
|
+
function extractFrontmatter(filePath) {
|
|
16927
|
+
try {
|
|
16928
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
16929
|
+
return extractFrontmatterFromContent(content);
|
|
16930
|
+
} catch {
|
|
16931
|
+
return { name: "", description: "" };
|
|
16932
|
+
}
|
|
16933
|
+
}
|
|
16934
|
+
function findSkillsInDir(dir, maxDepth = 3) {
|
|
16935
|
+
const skills = [];
|
|
16936
|
+
const entries = walkDir(dir, {
|
|
16937
|
+
maxDepth,
|
|
16938
|
+
filter: (e) => e.isDirectory
|
|
16939
|
+
});
|
|
16940
|
+
for (const entry of entries) {
|
|
16941
|
+
const skillFile = path3.join(entry.path, "SKILL.md");
|
|
16942
|
+
if (fs3.existsSync(skillFile)) {
|
|
16943
|
+
const frontmatter = extractFrontmatter(skillFile);
|
|
16944
|
+
skills.push({
|
|
16945
|
+
path: entry.path,
|
|
16946
|
+
skillFile,
|
|
16947
|
+
name: frontmatter.name || entry.name,
|
|
16948
|
+
description: frontmatter.description || "",
|
|
16949
|
+
license: frontmatter.license,
|
|
16950
|
+
compatibility: frontmatter.compatibility,
|
|
16951
|
+
metadata: frontmatter.metadata,
|
|
16952
|
+
disableModelInvocation: frontmatter.disableModelInvocation,
|
|
16953
|
+
userInvocable: frontmatter.userInvocable,
|
|
16954
|
+
subtask: frontmatter.subtask,
|
|
16955
|
+
agent: frontmatter.agent,
|
|
16956
|
+
model: frontmatter.model,
|
|
16957
|
+
argumentHint: frontmatter.argumentHint,
|
|
16958
|
+
allowedTools: frontmatter.allowedTools
|
|
16959
|
+
});
|
|
16960
|
+
}
|
|
16961
|
+
}
|
|
16962
|
+
return skills;
|
|
16963
|
+
}
|
|
16964
|
+
|
|
16967
16965
|
// src/lib/discovered-skills.ts
|
|
16968
16966
|
import fs4 from "fs";
|
|
16969
16967
|
import path4 from "path";
|
|
@@ -17096,4 +17094,4 @@ function discoverSkills(options) {
|
|
|
17096
17094
|
return Array.from(byName.values());
|
|
17097
17095
|
}
|
|
17098
17096
|
|
|
17099
|
-
export { __require, parseFrontmatter,
|
|
17097
|
+
export { __require, parseFrontmatter, parse2 as parse, parseTree2 as parseTree, modify, applyEdits, exports_external, AgentOverlaySchema, CategoryOverlaySchema, loadConfig, loadConfigWithSources, getConfigPaths, isRecord2 as isRecord, extractString, findAgentsInDir, extractAgentFrontmatter, findCommandsInDir, extractCommandFrontmatter, findSkillsInDir, discoverSkills };
|