@fro.bot/systematic 2.13.2 → 2.14.0
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-4qhjf4tb.js → index-7100fhg3.js} +1277 -1262
- package/dist/index.js +186 -136
- package/dist/lib/bootstrap.d.ts +8 -3
- package/dist/lib/skill-catalog.d.ts +33 -0
- package/dist/schemas/systematic-config.schema.json +34 -0
- package/package.json +4 -4
|
@@ -14,205 +14,742 @@ var __export = (target, all) => {
|
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
// src/lib/
|
|
17
|
+
// src/lib/converter.ts
|
|
18
18
|
import fs from "fs";
|
|
19
|
-
import os from "os";
|
|
20
|
-
import path from "path";
|
|
21
19
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
value2 = value2 * 16 + ch - 65 + 10;
|
|
35
|
-
} else if (ch >= 97 && ch <= 102) {
|
|
36
|
-
value2 = value2 * 16 + ch - 97 + 10;
|
|
37
|
-
} else {
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
pos++;
|
|
41
|
-
digits++;
|
|
42
|
-
}
|
|
43
|
-
if (digits < count) {
|
|
44
|
-
value2 = -1;
|
|
45
|
-
}
|
|
46
|
-
return value2;
|
|
20
|
+
// src/lib/frontmatter.ts
|
|
21
|
+
import yaml from "js-yaml";
|
|
22
|
+
function parseFrontmatter(content) {
|
|
23
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
|
|
24
|
+
const match = content.match(frontmatterRegex);
|
|
25
|
+
if (!match) {
|
|
26
|
+
return {
|
|
27
|
+
data: {},
|
|
28
|
+
body: content,
|
|
29
|
+
hadFrontmatter: false,
|
|
30
|
+
parseError: false
|
|
31
|
+
};
|
|
47
32
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
33
|
+
const yamlContent = match[1];
|
|
34
|
+
const body = match[2];
|
|
35
|
+
try {
|
|
36
|
+
const parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
|
|
37
|
+
const data = parsed ?? {};
|
|
38
|
+
return { data, body, hadFrontmatter: true, parseError: false };
|
|
39
|
+
} catch {
|
|
40
|
+
return { data: {}, body, hadFrontmatter: true, parseError: true };
|
|
54
41
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
pos++;
|
|
61
|
-
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
62
|
-
pos++;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (pos < text.length && text.charCodeAt(pos) === 46) {
|
|
66
|
-
pos++;
|
|
67
|
-
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
68
|
-
pos++;
|
|
69
|
-
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
70
|
-
pos++;
|
|
71
|
-
}
|
|
72
|
-
} else {
|
|
73
|
-
scanError = 3;
|
|
74
|
-
return text.substring(start, pos);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
let end = pos;
|
|
78
|
-
if (pos < text.length && (text.charCodeAt(pos) === 69 || text.charCodeAt(pos) === 101)) {
|
|
79
|
-
pos++;
|
|
80
|
-
if (pos < text.length && text.charCodeAt(pos) === 43 || text.charCodeAt(pos) === 45) {
|
|
81
|
-
pos++;
|
|
82
|
-
}
|
|
83
|
-
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
84
|
-
pos++;
|
|
85
|
-
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
86
|
-
pos++;
|
|
87
|
-
}
|
|
88
|
-
end = pos;
|
|
89
|
-
} else {
|
|
90
|
-
scanError = 3;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return text.substring(start, end);
|
|
42
|
+
}
|
|
43
|
+
function formatFrontmatter(data) {
|
|
44
|
+
if (Object.keys(data).length === 0) {
|
|
45
|
+
return ["---", "---"].join(`
|
|
46
|
+
`);
|
|
94
47
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
`;
|
|
136
|
-
break;
|
|
137
|
-
case 114:
|
|
138
|
-
result += "\r";
|
|
139
|
-
break;
|
|
140
|
-
case 116:
|
|
141
|
-
result += "\t";
|
|
142
|
-
break;
|
|
143
|
-
case 117:
|
|
144
|
-
const ch3 = scanHexDigits(4, true);
|
|
145
|
-
if (ch3 >= 0) {
|
|
146
|
-
result += String.fromCharCode(ch3);
|
|
147
|
-
} else {
|
|
148
|
-
scanError = 4;
|
|
149
|
-
}
|
|
150
|
-
break;
|
|
151
|
-
default:
|
|
152
|
-
scanError = 5;
|
|
153
|
-
}
|
|
154
|
-
start = pos;
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
if (ch >= 0 && ch <= 31) {
|
|
158
|
-
if (isLineBreak(ch)) {
|
|
159
|
-
result += text.substring(start, pos);
|
|
160
|
-
scanError = 2;
|
|
161
|
-
break;
|
|
162
|
-
} else {
|
|
163
|
-
scanError = 6;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
pos++;
|
|
48
|
+
const yamlContent = yaml.dump(data, {
|
|
49
|
+
schema: yaml.JSON_SCHEMA,
|
|
50
|
+
lineWidth: -1,
|
|
51
|
+
noRefs: true
|
|
52
|
+
}).trimEnd();
|
|
53
|
+
return ["---", yamlContent, "---"].join(`
|
|
54
|
+
`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/lib/validation.ts
|
|
58
|
+
function isRecord(value) {
|
|
59
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
60
|
+
}
|
|
61
|
+
function isPermissionSetting(value) {
|
|
62
|
+
return value === "ask" || value === "allow" || value === "deny";
|
|
63
|
+
}
|
|
64
|
+
function isToolsMap(value) {
|
|
65
|
+
if (!isRecord(value))
|
|
66
|
+
return false;
|
|
67
|
+
return Object.values(value).every((entry) => typeof entry === "boolean");
|
|
68
|
+
}
|
|
69
|
+
function isAgentMode(value) {
|
|
70
|
+
return value === "subagent" || value === "primary" || value === "all";
|
|
71
|
+
}
|
|
72
|
+
function extractSimplePermission(data, key) {
|
|
73
|
+
if (!(key in data))
|
|
74
|
+
return;
|
|
75
|
+
const value = data[key];
|
|
76
|
+
return isPermissionSetting(value) ? value : null;
|
|
77
|
+
}
|
|
78
|
+
function extractBashPermission(data) {
|
|
79
|
+
if (!("bash" in data))
|
|
80
|
+
return;
|
|
81
|
+
const bash = data.bash;
|
|
82
|
+
if (isPermissionSetting(bash))
|
|
83
|
+
return bash;
|
|
84
|
+
if (isRecord(bash)) {
|
|
85
|
+
const entries = Object.entries(bash);
|
|
86
|
+
if (entries.every(([, setting]) => isPermissionSetting(setting))) {
|
|
87
|
+
return Object.fromEntries(entries);
|
|
167
88
|
}
|
|
168
|
-
return result;
|
|
169
89
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill) {
|
|
93
|
+
const permission = {};
|
|
94
|
+
if (edit)
|
|
95
|
+
permission.edit = edit;
|
|
96
|
+
if (bash)
|
|
97
|
+
permission.bash = bash;
|
|
98
|
+
if (webfetch)
|
|
99
|
+
permission.webfetch = webfetch;
|
|
100
|
+
if (doom_loop)
|
|
101
|
+
permission.doom_loop = doom_loop;
|
|
102
|
+
if (external_directory)
|
|
103
|
+
permission.external_directory = external_directory;
|
|
104
|
+
if (task)
|
|
105
|
+
permission.task = task;
|
|
106
|
+
if (skill)
|
|
107
|
+
permission.skill = skill;
|
|
108
|
+
return Object.keys(permission).length > 0 ? permission : undefined;
|
|
109
|
+
}
|
|
110
|
+
function normalizePermission(value) {
|
|
111
|
+
if (!isRecord(value))
|
|
112
|
+
return;
|
|
113
|
+
const bash = extractBashPermission(value);
|
|
114
|
+
if (bash === null)
|
|
115
|
+
return;
|
|
116
|
+
const edit = extractSimplePermission(value, "edit");
|
|
117
|
+
if (edit === null)
|
|
118
|
+
return;
|
|
119
|
+
const webfetch = extractSimplePermission(value, "webfetch");
|
|
120
|
+
if (webfetch === null)
|
|
121
|
+
return;
|
|
122
|
+
const doom_loop = extractSimplePermission(value, "doom_loop");
|
|
123
|
+
if (doom_loop === null)
|
|
124
|
+
return;
|
|
125
|
+
const external_directory = extractSimplePermission(value, "external_directory");
|
|
126
|
+
if (external_directory === null)
|
|
127
|
+
return;
|
|
128
|
+
const task = extractSimplePermission(value, "task");
|
|
129
|
+
if (task === null)
|
|
130
|
+
return;
|
|
131
|
+
const skill = extractSimplePermission(value, "skill");
|
|
132
|
+
if (skill === null)
|
|
133
|
+
return;
|
|
134
|
+
return buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill);
|
|
135
|
+
}
|
|
136
|
+
function extractString(data, key, fallback = "") {
|
|
137
|
+
const value = data[key];
|
|
138
|
+
return typeof value === "string" ? value : fallback;
|
|
139
|
+
}
|
|
140
|
+
function extractNonEmptyString(data, key) {
|
|
141
|
+
const value = data[key];
|
|
142
|
+
if (typeof value !== "string")
|
|
143
|
+
return;
|
|
144
|
+
const trimmed = value.trim();
|
|
145
|
+
return trimmed !== "" ? trimmed : undefined;
|
|
146
|
+
}
|
|
147
|
+
function extractNumber(data, key) {
|
|
148
|
+
const value = data[key];
|
|
149
|
+
return typeof value === "number" ? value : undefined;
|
|
150
|
+
}
|
|
151
|
+
function extractBoolean(data, key) {
|
|
152
|
+
const value = data[key];
|
|
153
|
+
if (typeof value === "boolean")
|
|
154
|
+
return value;
|
|
155
|
+
if (typeof value === "string") {
|
|
156
|
+
const normalized = value.trim().toLowerCase();
|
|
157
|
+
if (normalized === "true")
|
|
158
|
+
return true;
|
|
159
|
+
if (normalized === "false")
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/lib/converter.ts
|
|
166
|
+
var CONVERTER_VERSION = 2;
|
|
167
|
+
var cache = new Map;
|
|
168
|
+
var TOOL_MAPPINGS = [
|
|
169
|
+
[/\bTask\s+tool\b/gi, "task tool"],
|
|
170
|
+
[/\bTask\s+([\w-]+)\s*:/g, "task $1:"],
|
|
171
|
+
[/\bTask\s+([\w-]+)\s*\(/g, "task $1("],
|
|
172
|
+
[/\bTask\s*\(/g, "task("],
|
|
173
|
+
[/\bTask\b(?=\s+to\s+\w)/g, "task"],
|
|
174
|
+
[/\bTodoWrite\b/g, "todowrite"],
|
|
175
|
+
[/\bAskUserQuestion\b/g, "question"],
|
|
176
|
+
[/\bWebSearch\b/g, "google_search"],
|
|
177
|
+
[/\bRead\b(?=\s+tool|\s+to\s+|\()/g, "read"],
|
|
178
|
+
[/\bWrite\b(?=\s+tool|\s+to\s+|\()/g, "write"],
|
|
179
|
+
[/\bEdit\b(?=\s+tool|\s+to\s+|\()/g, "edit"],
|
|
180
|
+
[/\bBash\b(?=\s+tool|\s+to\s+|\()/g, "bash"],
|
|
181
|
+
[/\bGrep\b(?=\s+tool|\s+to\s+|\()/g, "grep"],
|
|
182
|
+
[/\bGlob\b(?=\s+tool|\s+to\s+|\()/g, "glob"],
|
|
183
|
+
[/\bWebFetch\b/g, "webfetch"],
|
|
184
|
+
[/\bSkill\b(?=\s+tool|\s*\()/g, "skill"]
|
|
185
|
+
];
|
|
186
|
+
var PATH_REPLACEMENTS = [
|
|
187
|
+
[/\.claude\/skills\//g, ".opencode/skills/"],
|
|
188
|
+
[/\.claude\/commands\//g, ".opencode/commands/"],
|
|
189
|
+
[/\.claude\/agents\//g, ".opencode/agents/"],
|
|
190
|
+
[/~\/\.claude\//g, "~/.config/opencode/"],
|
|
191
|
+
[/CLAUDE\.md/g, "AGENTS.md"],
|
|
192
|
+
[/\/compound-engineering:/g, "/systematic:"],
|
|
193
|
+
[/compound-engineering:/g, "systematic:"]
|
|
194
|
+
];
|
|
195
|
+
var TOOL_NAME_MAP = {
|
|
196
|
+
task: "task",
|
|
197
|
+
todowrite: "todowrite",
|
|
198
|
+
askuserquestion: "question",
|
|
199
|
+
websearch: "google_search",
|
|
200
|
+
webfetch: "webfetch",
|
|
201
|
+
skill: "skill",
|
|
202
|
+
read: "read",
|
|
203
|
+
write: "write",
|
|
204
|
+
edit: "edit",
|
|
205
|
+
bash: "bash",
|
|
206
|
+
grep: "grep",
|
|
207
|
+
glob: "glob"
|
|
208
|
+
};
|
|
209
|
+
var PERMISSION_MODE_MAP = {
|
|
210
|
+
full: {
|
|
211
|
+
edit: "allow",
|
|
212
|
+
bash: "allow",
|
|
213
|
+
webfetch: "allow"
|
|
214
|
+
},
|
|
215
|
+
default: {
|
|
216
|
+
edit: "ask",
|
|
217
|
+
bash: "ask",
|
|
218
|
+
webfetch: "ask"
|
|
219
|
+
},
|
|
220
|
+
plan: {
|
|
221
|
+
edit: "deny",
|
|
222
|
+
bash: "deny",
|
|
223
|
+
webfetch: "ask"
|
|
224
|
+
},
|
|
225
|
+
bypassPermissions: {
|
|
226
|
+
edit: "allow",
|
|
227
|
+
bash: "allow",
|
|
228
|
+
webfetch: "allow"
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
function inferTemperature(name, description) {
|
|
232
|
+
const sample = `${name} ${description ?? ""}`.toLowerCase();
|
|
233
|
+
if (/(review|audit|security|sentinel|oracle|lint|verification|guardian)/.test(sample)) {
|
|
234
|
+
return 0.1;
|
|
235
|
+
}
|
|
236
|
+
if (/(plan|planning|architecture|strategist|analysis|research)/.test(sample)) {
|
|
237
|
+
return 0.2;
|
|
238
|
+
}
|
|
239
|
+
if (/(doc|readme|changelog|editor|writer)/.test(sample)) {
|
|
240
|
+
return 0.3;
|
|
241
|
+
}
|
|
242
|
+
if (/(brainstorm|creative|ideate|design|concept)/.test(sample)) {
|
|
243
|
+
return 0.6;
|
|
244
|
+
}
|
|
245
|
+
return 0.3;
|
|
246
|
+
}
|
|
247
|
+
var CODE_BLOCK_PATTERN = /```[\s\S]*?```|`[^`\n]+`/g;
|
|
248
|
+
function transformBody(body) {
|
|
249
|
+
const codeBlocks = [];
|
|
250
|
+
let placeholderIndex = 0;
|
|
251
|
+
const withPlaceholders = body.replace(CODE_BLOCK_PATTERN, (match) => {
|
|
252
|
+
codeBlocks.push(match);
|
|
253
|
+
return `__CODE_BLOCK_${placeholderIndex++}__`;
|
|
254
|
+
});
|
|
255
|
+
let result = withPlaceholders;
|
|
256
|
+
for (const [pattern, replacement] of TOOL_MAPPINGS) {
|
|
257
|
+
result = result.replace(pattern, replacement);
|
|
258
|
+
}
|
|
259
|
+
for (const [pattern, replacement] of PATH_REPLACEMENTS) {
|
|
260
|
+
result = result.replace(pattern, replacement);
|
|
261
|
+
}
|
|
262
|
+
for (let i = 0;i < codeBlocks.length; i++) {
|
|
263
|
+
result = result.replace(`__CODE_BLOCK_${i}__`, codeBlocks[i]);
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
function normalizeModel(model) {
|
|
268
|
+
if (model.includes("/"))
|
|
269
|
+
return model;
|
|
270
|
+
if (model === "inherit")
|
|
271
|
+
return model;
|
|
272
|
+
if (/^claude-/.test(model))
|
|
273
|
+
return `anthropic/${model}`;
|
|
274
|
+
if (/^(gpt-|o1-|o3-)/.test(model))
|
|
275
|
+
return `openai/${model}`;
|
|
276
|
+
if (/^gemini-/.test(model))
|
|
277
|
+
return `google/${model}`;
|
|
278
|
+
return `anthropic/${model}`;
|
|
279
|
+
}
|
|
280
|
+
function canonicalizeToolName(name) {
|
|
281
|
+
const lower = name.trim().toLowerCase();
|
|
282
|
+
return TOOL_NAME_MAP[lower] ?? lower;
|
|
283
|
+
}
|
|
284
|
+
function isValidSteps(value) {
|
|
285
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0;
|
|
286
|
+
}
|
|
287
|
+
function mapStepsField(data) {
|
|
288
|
+
if (data.steps !== undefined) {
|
|
289
|
+
if (isValidSteps(data.steps)) {
|
|
290
|
+
delete data.maxTurns;
|
|
291
|
+
delete data.maxSteps;
|
|
292
|
+
}
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const candidates = [];
|
|
296
|
+
if (isValidSteps(data.maxTurns))
|
|
297
|
+
candidates.push(data.maxTurns);
|
|
298
|
+
if (isValidSteps(data.maxSteps))
|
|
299
|
+
candidates.push(data.maxSteps);
|
|
300
|
+
if (candidates.length > 0) {
|
|
301
|
+
data.steps = Math.min(...candidates);
|
|
302
|
+
delete data.maxTurns;
|
|
303
|
+
delete data.maxSteps;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function mapToolsField(data) {
|
|
307
|
+
if (data.tools !== undefined && !Array.isArray(data.tools)) {
|
|
308
|
+
if (isToolsMap(data.tools)) {
|
|
309
|
+
mergeDisallowedTools(data);
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (Array.isArray(data.tools)) {
|
|
314
|
+
const toolsMap = {};
|
|
315
|
+
for (const tool of data.tools) {
|
|
316
|
+
if (typeof tool === "string") {
|
|
317
|
+
toolsMap[canonicalizeToolName(tool)] = true;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (Object.keys(toolsMap).length > 0) {
|
|
321
|
+
data.tools = toolsMap;
|
|
322
|
+
} else {
|
|
323
|
+
delete data.tools;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
mergeDisallowedTools(data);
|
|
327
|
+
}
|
|
328
|
+
function mergeDisallowedTools(data) {
|
|
329
|
+
if (!Array.isArray(data.disallowedTools))
|
|
330
|
+
return;
|
|
331
|
+
const existing = isToolsMap(data.tools) ? data.tools : {};
|
|
332
|
+
for (const tool of data.disallowedTools) {
|
|
333
|
+
if (typeof tool === "string") {
|
|
334
|
+
existing[canonicalizeToolName(tool)] = false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (Object.keys(existing).length > 0) {
|
|
338
|
+
data.tools = existing;
|
|
339
|
+
}
|
|
340
|
+
delete data.disallowedTools;
|
|
341
|
+
}
|
|
342
|
+
function mapPermissionMode(data) {
|
|
343
|
+
if (data.permission !== undefined) {
|
|
344
|
+
const normalized = normalizePermission(data.permission);
|
|
345
|
+
if (normalized) {
|
|
346
|
+
data.permission = normalized;
|
|
347
|
+
delete data.permissionMode;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (typeof data.permissionMode !== "string")
|
|
352
|
+
return;
|
|
353
|
+
const mapped = PERMISSION_MODE_MAP[data.permissionMode];
|
|
354
|
+
data.permission = mapped ?? { edit: "ask", bash: "ask", webfetch: "ask" };
|
|
355
|
+
delete data.permissionMode;
|
|
356
|
+
}
|
|
357
|
+
function mapHiddenField(data) {
|
|
358
|
+
if (data["disable-model-invocation"] === true || data.disableModelInvocation === true) {
|
|
359
|
+
data.hidden = true;
|
|
360
|
+
delete data["disable-model-invocation"];
|
|
361
|
+
delete data.disableModelInvocation;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function normalizeModelField(data) {
|
|
365
|
+
if (typeof data.model === "string" && data.model !== "inherit") {
|
|
366
|
+
data.model = normalizeModel(data.model);
|
|
367
|
+
} else if (data.model === "inherit") {
|
|
368
|
+
delete data.model;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function transformAgentFrontmatter(data, agentMode) {
|
|
372
|
+
const result = { ...data };
|
|
373
|
+
result.mode = isAgentMode(data.mode) ? data.mode : agentMode;
|
|
374
|
+
const name = typeof data.name === "string" ? data.name : "";
|
|
375
|
+
const description = typeof data.description === "string" ? data.description : "";
|
|
376
|
+
if (description) {
|
|
377
|
+
result.description = description;
|
|
378
|
+
} else if (name) {
|
|
379
|
+
result.description = `${name} agent`;
|
|
380
|
+
}
|
|
381
|
+
normalizeModelField(result);
|
|
382
|
+
result.temperature = typeof data.temperature === "number" ? data.temperature : inferTemperature(name, description);
|
|
383
|
+
mapStepsField(result);
|
|
384
|
+
mapToolsField(result);
|
|
385
|
+
mapPermissionMode(result);
|
|
386
|
+
mapHiddenField(result);
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
function transformSkillFrontmatter(data) {
|
|
390
|
+
const result = { ...data };
|
|
391
|
+
normalizeModelField(result);
|
|
392
|
+
if (result.context === "fork") {
|
|
393
|
+
result.subtask = true;
|
|
394
|
+
}
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
function transformCommandFrontmatter(data) {
|
|
398
|
+
const result = { ...data };
|
|
399
|
+
normalizeModelField(result);
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
function convertContent(content, type, options = {}) {
|
|
403
|
+
if (content === "")
|
|
404
|
+
return "";
|
|
405
|
+
const { data, body, hadFrontmatter, parseError } = parseFrontmatter(content);
|
|
406
|
+
if (!hadFrontmatter) {
|
|
407
|
+
return options.skipBodyTransform ? content : transformBody(content);
|
|
408
|
+
}
|
|
409
|
+
if (parseError) {
|
|
410
|
+
return options.skipBodyTransform ? content : transformBody(content);
|
|
411
|
+
}
|
|
412
|
+
const shouldTransformBody = !options.skipBodyTransform;
|
|
413
|
+
const transformedBody = shouldTransformBody ? transformBody(body) : body;
|
|
414
|
+
if (type === "agent") {
|
|
415
|
+
const agentMode = options.agentMode ?? "subagent";
|
|
416
|
+
const transformedData = transformAgentFrontmatter(data, agentMode);
|
|
417
|
+
return `${formatFrontmatter(transformedData)}
|
|
418
|
+
${transformedBody}`;
|
|
419
|
+
}
|
|
420
|
+
if (type === "skill") {
|
|
421
|
+
const transformedData = transformSkillFrontmatter(data);
|
|
422
|
+
return `${formatFrontmatter(transformedData)}
|
|
423
|
+
${transformedBody}`;
|
|
424
|
+
}
|
|
425
|
+
if (type === "command") {
|
|
426
|
+
const transformedData = transformCommandFrontmatter(data);
|
|
427
|
+
return `${formatFrontmatter(transformedData)}
|
|
428
|
+
${transformedBody}`;
|
|
429
|
+
}
|
|
430
|
+
return content;
|
|
431
|
+
}
|
|
432
|
+
function convertFileWithCache(filePath, type, options = {}) {
|
|
433
|
+
const fd = fs.openSync(filePath, "r");
|
|
434
|
+
try {
|
|
435
|
+
const stats = fs.fstatSync(fd);
|
|
436
|
+
const cacheKey = `${CONVERTER_VERSION}:${filePath}:${type}:${options.source ?? "bundled"}:${options.agentMode ?? "subagent"}:${options.skipBodyTransform ?? false}`;
|
|
437
|
+
const cached = cache.get(cacheKey);
|
|
438
|
+
if (cached != null && cached.mtimeMs === stats.mtimeMs) {
|
|
439
|
+
return cached.converted;
|
|
440
|
+
}
|
|
441
|
+
const content = fs.readFileSync(fd, "utf8");
|
|
442
|
+
const converted = convertContent(content, type, options);
|
|
443
|
+
cache.set(cacheKey, { mtimeMs: stats.mtimeMs, converted });
|
|
444
|
+
return converted;
|
|
445
|
+
} finally {
|
|
446
|
+
fs.closeSync(fd);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/lib/skills.ts
|
|
451
|
+
import fs3 from "fs";
|
|
452
|
+
import path2 from "path";
|
|
453
|
+
|
|
454
|
+
// src/lib/walk-dir.ts
|
|
455
|
+
import fs2 from "fs";
|
|
456
|
+
import path from "path";
|
|
457
|
+
function walkDir(rootDir, options = {}) {
|
|
458
|
+
const { maxDepth = 3, filter } = options;
|
|
459
|
+
const results = [];
|
|
460
|
+
if (!fs2.existsSync(rootDir))
|
|
461
|
+
return results;
|
|
462
|
+
function recurse(currentDir, depth, category) {
|
|
463
|
+
if (depth > maxDepth)
|
|
464
|
+
return;
|
|
465
|
+
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
466
|
+
for (const entry of entries) {
|
|
467
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
468
|
+
const walkEntry = {
|
|
469
|
+
path: fullPath,
|
|
470
|
+
name: entry.name,
|
|
471
|
+
isDirectory: entry.isDirectory(),
|
|
472
|
+
depth,
|
|
473
|
+
category
|
|
474
|
+
};
|
|
475
|
+
if (!filter || filter(walkEntry)) {
|
|
476
|
+
results.push(walkEntry);
|
|
477
|
+
}
|
|
478
|
+
if (entry.isDirectory()) {
|
|
479
|
+
recurse(fullPath, depth + 1, entry.name);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
recurse(rootDir, 0);
|
|
484
|
+
return results;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/lib/skills.ts
|
|
488
|
+
function extractFrontmatter(filePath) {
|
|
489
|
+
try {
|
|
490
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
491
|
+
const { data, parseError } = parseFrontmatter(content);
|
|
492
|
+
if (parseError) {
|
|
493
|
+
return { name: "", description: "" };
|
|
494
|
+
}
|
|
495
|
+
const metadataRaw = data.metadata;
|
|
496
|
+
let metadata;
|
|
497
|
+
if (isRecord(metadataRaw)) {
|
|
498
|
+
const entries = Object.entries(metadataRaw);
|
|
499
|
+
if (entries.every(([, v]) => typeof v === "string")) {
|
|
500
|
+
metadata = Object.fromEntries(entries);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const argumentHintRaw = extractNonEmptyString(data, "argument-hint");
|
|
504
|
+
const argumentHint = argumentHintRaw?.replace(/^["']|["']$/g, "") || undefined;
|
|
505
|
+
return {
|
|
506
|
+
name: extractString(data, "name"),
|
|
507
|
+
description: extractString(data, "description"),
|
|
508
|
+
license: extractNonEmptyString(data, "license"),
|
|
509
|
+
compatibility: extractNonEmptyString(data, "compatibility"),
|
|
510
|
+
metadata,
|
|
511
|
+
disableModelInvocation: extractBoolean(data, "disable-model-invocation"),
|
|
512
|
+
userInvocable: extractBoolean(data, "user-invocable"),
|
|
513
|
+
subtask: data.context === "fork" ? true : extractBoolean(data, "subtask") ?? undefined,
|
|
514
|
+
agent: extractNonEmptyString(data, "agent"),
|
|
515
|
+
model: extractNonEmptyString(data, "model"),
|
|
516
|
+
argumentHint: argumentHint !== "" ? argumentHint : undefined,
|
|
517
|
+
allowedTools: extractNonEmptyString(data, "allowed-tools")
|
|
518
|
+
};
|
|
519
|
+
} catch {
|
|
520
|
+
return { name: "", description: "" };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function findSkillsInDir(dir, maxDepth = 3) {
|
|
524
|
+
const skills = [];
|
|
525
|
+
const entries = walkDir(dir, {
|
|
526
|
+
maxDepth,
|
|
527
|
+
filter: (e) => e.isDirectory
|
|
528
|
+
});
|
|
529
|
+
for (const entry of entries) {
|
|
530
|
+
const skillFile = path2.join(entry.path, "SKILL.md");
|
|
531
|
+
if (fs3.existsSync(skillFile)) {
|
|
532
|
+
const frontmatter = extractFrontmatter(skillFile);
|
|
533
|
+
skills.push({
|
|
534
|
+
path: entry.path,
|
|
535
|
+
skillFile,
|
|
536
|
+
name: frontmatter.name || entry.name,
|
|
537
|
+
description: frontmatter.description || "",
|
|
538
|
+
license: frontmatter.license,
|
|
539
|
+
compatibility: frontmatter.compatibility,
|
|
540
|
+
metadata: frontmatter.metadata,
|
|
541
|
+
disableModelInvocation: frontmatter.disableModelInvocation,
|
|
542
|
+
userInvocable: frontmatter.userInvocable,
|
|
543
|
+
subtask: frontmatter.subtask,
|
|
544
|
+
agent: frontmatter.agent,
|
|
545
|
+
model: frontmatter.model,
|
|
546
|
+
argumentHint: frontmatter.argumentHint,
|
|
547
|
+
allowedTools: frontmatter.allowedTools
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return skills;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/lib/config.ts
|
|
555
|
+
import fs4 from "fs";
|
|
556
|
+
import os from "os";
|
|
557
|
+
import path3 from "path";
|
|
558
|
+
|
|
559
|
+
// node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
560
|
+
function createScanner(text, ignoreTrivia = false) {
|
|
561
|
+
const len = text.length;
|
|
562
|
+
let pos = 0, value = "", tokenOffset = 0, token = 16, lineNumber = 0, lineStartOffset = 0, tokenLineStartOffset = 0, prevTokenLineStartOffset = 0, scanError = 0;
|
|
563
|
+
function scanHexDigits(count, exact) {
|
|
564
|
+
let digits = 0;
|
|
565
|
+
let value2 = 0;
|
|
566
|
+
while (digits < count || !exact) {
|
|
567
|
+
let ch = text.charCodeAt(pos);
|
|
568
|
+
if (ch >= 48 && ch <= 57) {
|
|
569
|
+
value2 = value2 * 16 + ch - 48;
|
|
570
|
+
} else if (ch >= 65 && ch <= 70) {
|
|
571
|
+
value2 = value2 * 16 + ch - 65 + 10;
|
|
572
|
+
} else if (ch >= 97 && ch <= 102) {
|
|
573
|
+
value2 = value2 * 16 + ch - 97 + 10;
|
|
574
|
+
} else {
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
pos++;
|
|
578
|
+
digits++;
|
|
579
|
+
}
|
|
580
|
+
if (digits < count) {
|
|
581
|
+
value2 = -1;
|
|
582
|
+
}
|
|
583
|
+
return value2;
|
|
584
|
+
}
|
|
585
|
+
function setPosition(newPosition) {
|
|
586
|
+
pos = newPosition;
|
|
587
|
+
value = "";
|
|
588
|
+
tokenOffset = 0;
|
|
589
|
+
token = 16;
|
|
590
|
+
scanError = 0;
|
|
591
|
+
}
|
|
592
|
+
function scanNumber() {
|
|
593
|
+
let start = pos;
|
|
594
|
+
if (text.charCodeAt(pos) === 48) {
|
|
595
|
+
pos++;
|
|
596
|
+
} else {
|
|
597
|
+
pos++;
|
|
598
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
599
|
+
pos++;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (pos < text.length && text.charCodeAt(pos) === 46) {
|
|
603
|
+
pos++;
|
|
604
|
+
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
605
|
+
pos++;
|
|
606
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
607
|
+
pos++;
|
|
608
|
+
}
|
|
609
|
+
} else {
|
|
610
|
+
scanError = 3;
|
|
611
|
+
return text.substring(start, pos);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
let end = pos;
|
|
615
|
+
if (pos < text.length && (text.charCodeAt(pos) === 69 || text.charCodeAt(pos) === 101)) {
|
|
616
|
+
pos++;
|
|
617
|
+
if (pos < text.length && text.charCodeAt(pos) === 43 || text.charCodeAt(pos) === 45) {
|
|
618
|
+
pos++;
|
|
619
|
+
}
|
|
620
|
+
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
621
|
+
pos++;
|
|
622
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
623
|
+
pos++;
|
|
624
|
+
}
|
|
625
|
+
end = pos;
|
|
626
|
+
} else {
|
|
627
|
+
scanError = 3;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return text.substring(start, end);
|
|
631
|
+
}
|
|
632
|
+
function scanString() {
|
|
633
|
+
let result = "", start = pos;
|
|
634
|
+
while (true) {
|
|
635
|
+
if (pos >= len) {
|
|
636
|
+
result += text.substring(start, pos);
|
|
637
|
+
scanError = 2;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
const ch = text.charCodeAt(pos);
|
|
641
|
+
if (ch === 34) {
|
|
642
|
+
result += text.substring(start, pos);
|
|
643
|
+
pos++;
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
if (ch === 92) {
|
|
647
|
+
result += text.substring(start, pos);
|
|
648
|
+
pos++;
|
|
649
|
+
if (pos >= len) {
|
|
650
|
+
scanError = 2;
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
const ch2 = text.charCodeAt(pos++);
|
|
654
|
+
switch (ch2) {
|
|
655
|
+
case 34:
|
|
656
|
+
result += '"';
|
|
657
|
+
break;
|
|
658
|
+
case 92:
|
|
659
|
+
result += "\\";
|
|
660
|
+
break;
|
|
661
|
+
case 47:
|
|
662
|
+
result += "/";
|
|
663
|
+
break;
|
|
664
|
+
case 98:
|
|
665
|
+
result += "\b";
|
|
666
|
+
break;
|
|
667
|
+
case 102:
|
|
668
|
+
result += "\f";
|
|
669
|
+
break;
|
|
670
|
+
case 110:
|
|
671
|
+
result += `
|
|
672
|
+
`;
|
|
673
|
+
break;
|
|
674
|
+
case 114:
|
|
675
|
+
result += "\r";
|
|
676
|
+
break;
|
|
677
|
+
case 116:
|
|
678
|
+
result += "\t";
|
|
679
|
+
break;
|
|
680
|
+
case 117:
|
|
681
|
+
const ch3 = scanHexDigits(4, true);
|
|
682
|
+
if (ch3 >= 0) {
|
|
683
|
+
result += String.fromCharCode(ch3);
|
|
684
|
+
} else {
|
|
685
|
+
scanError = 4;
|
|
686
|
+
}
|
|
687
|
+
break;
|
|
688
|
+
default:
|
|
689
|
+
scanError = 5;
|
|
690
|
+
}
|
|
691
|
+
start = pos;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (ch >= 0 && ch <= 31) {
|
|
695
|
+
if (isLineBreak(ch)) {
|
|
696
|
+
result += text.substring(start, pos);
|
|
697
|
+
scanError = 2;
|
|
698
|
+
break;
|
|
699
|
+
} else {
|
|
700
|
+
scanError = 6;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
pos++;
|
|
704
|
+
}
|
|
705
|
+
return result;
|
|
706
|
+
}
|
|
707
|
+
function scanNext() {
|
|
708
|
+
value = "";
|
|
709
|
+
scanError = 0;
|
|
710
|
+
tokenOffset = pos;
|
|
711
|
+
lineStartOffset = lineNumber;
|
|
712
|
+
prevTokenLineStartOffset = tokenLineStartOffset;
|
|
713
|
+
if (pos >= len) {
|
|
714
|
+
tokenOffset = len;
|
|
715
|
+
return token = 17;
|
|
716
|
+
}
|
|
717
|
+
let code = text.charCodeAt(pos);
|
|
718
|
+
if (isWhiteSpace(code)) {
|
|
719
|
+
do {
|
|
720
|
+
pos++;
|
|
721
|
+
value += String.fromCharCode(code);
|
|
722
|
+
code = text.charCodeAt(pos);
|
|
723
|
+
} while (isWhiteSpace(code));
|
|
724
|
+
return token = 15;
|
|
725
|
+
}
|
|
726
|
+
if (isLineBreak(code)) {
|
|
727
|
+
pos++;
|
|
728
|
+
value += String.fromCharCode(code);
|
|
729
|
+
if (code === 13 && text.charCodeAt(pos) === 10) {
|
|
730
|
+
pos++;
|
|
731
|
+
value += `
|
|
732
|
+
`;
|
|
733
|
+
}
|
|
734
|
+
lineNumber++;
|
|
735
|
+
tokenLineStartOffset = pos;
|
|
736
|
+
return token = 14;
|
|
737
|
+
}
|
|
738
|
+
switch (code) {
|
|
739
|
+
case 123:
|
|
740
|
+
pos++;
|
|
741
|
+
return token = 1;
|
|
742
|
+
case 125:
|
|
743
|
+
pos++;
|
|
744
|
+
return token = 2;
|
|
745
|
+
case 91:
|
|
746
|
+
pos++;
|
|
747
|
+
return token = 3;
|
|
748
|
+
case 93:
|
|
749
|
+
pos++;
|
|
750
|
+
return token = 4;
|
|
751
|
+
case 58:
|
|
752
|
+
pos++;
|
|
216
753
|
return token = 6;
|
|
217
754
|
case 44:
|
|
218
755
|
pos++;
|
|
@@ -1624,10 +2161,10 @@ function mergeDefs(...defs) {
|
|
|
1624
2161
|
function cloneDef(schema) {
|
|
1625
2162
|
return mergeDefs(schema._zod.def);
|
|
1626
2163
|
}
|
|
1627
|
-
function getElementAtPath(obj,
|
|
1628
|
-
if (!
|
|
2164
|
+
function getElementAtPath(obj, path3) {
|
|
2165
|
+
if (!path3)
|
|
1629
2166
|
return obj;
|
|
1630
|
-
return
|
|
2167
|
+
return path3.reduce((acc, key) => acc?.[key], obj);
|
|
1631
2168
|
}
|
|
1632
2169
|
function promiseAllObject(promisesObj) {
|
|
1633
2170
|
const keys = Object.keys(promisesObj);
|
|
@@ -2035,11 +2572,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
2035
2572
|
}
|
|
2036
2573
|
return false;
|
|
2037
2574
|
}
|
|
2038
|
-
function prefixIssues(
|
|
2575
|
+
function prefixIssues(path3, issues) {
|
|
2039
2576
|
return issues.map((iss) => {
|
|
2040
2577
|
var _a2;
|
|
2041
2578
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
2042
|
-
iss.path.unshift(
|
|
2579
|
+
iss.path.unshift(path3);
|
|
2043
2580
|
return iss;
|
|
2044
2581
|
});
|
|
2045
2582
|
}
|
|
@@ -2186,16 +2723,16 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
|
|
|
2186
2723
|
}
|
|
2187
2724
|
function formatError(error, mapper = (issue2) => issue2.message) {
|
|
2188
2725
|
const fieldErrors = { _errors: [] };
|
|
2189
|
-
const processError = (error2,
|
|
2726
|
+
const processError = (error2, path3 = []) => {
|
|
2190
2727
|
for (const issue2 of error2.issues) {
|
|
2191
2728
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2192
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2729
|
+
issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path]));
|
|
2193
2730
|
} else if (issue2.code === "invalid_key") {
|
|
2194
|
-
processError({ issues: issue2.issues }, [...
|
|
2731
|
+
processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
|
|
2195
2732
|
} else if (issue2.code === "invalid_element") {
|
|
2196
|
-
processError({ issues: issue2.issues }, [...
|
|
2733
|
+
processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
|
|
2197
2734
|
} else {
|
|
2198
|
-
const fullpath = [...
|
|
2735
|
+
const fullpath = [...path3, ...issue2.path];
|
|
2199
2736
|
if (fullpath.length === 0) {
|
|
2200
2737
|
fieldErrors._errors.push(mapper(issue2));
|
|
2201
2738
|
} else {
|
|
@@ -2222,17 +2759,17 @@ function formatError(error, mapper = (issue2) => issue2.message) {
|
|
|
2222
2759
|
}
|
|
2223
2760
|
function treeifyError(error, mapper = (issue2) => issue2.message) {
|
|
2224
2761
|
const result = { errors: [] };
|
|
2225
|
-
const processError = (error2,
|
|
2762
|
+
const processError = (error2, path3 = []) => {
|
|
2226
2763
|
var _a2, _b;
|
|
2227
2764
|
for (const issue2 of error2.issues) {
|
|
2228
2765
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2229
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2766
|
+
issue2.errors.map((issues) => processError({ issues }, [...path3, ...issue2.path]));
|
|
2230
2767
|
} else if (issue2.code === "invalid_key") {
|
|
2231
|
-
processError({ issues: issue2.issues }, [...
|
|
2768
|
+
processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
|
|
2232
2769
|
} else if (issue2.code === "invalid_element") {
|
|
2233
|
-
processError({ issues: issue2.issues }, [...
|
|
2770
|
+
processError({ issues: issue2.issues }, [...path3, ...issue2.path]);
|
|
2234
2771
|
} else {
|
|
2235
|
-
const fullpath = [...
|
|
2772
|
+
const fullpath = [...path3, ...issue2.path];
|
|
2236
2773
|
if (fullpath.length === 0) {
|
|
2237
2774
|
result.errors.push(mapper(issue2));
|
|
2238
2775
|
continue;
|
|
@@ -2264,8 +2801,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
|
|
|
2264
2801
|
}
|
|
2265
2802
|
function toDotPath(_path) {
|
|
2266
2803
|
const segs = [];
|
|
2267
|
-
const
|
|
2268
|
-
for (const seg of
|
|
2804
|
+
const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
2805
|
+
for (const seg of path3) {
|
|
2269
2806
|
if (typeof seg === "number")
|
|
2270
2807
|
segs.push(`[${seg}]`);
|
|
2271
2808
|
else if (typeof seg === "symbol")
|
|
@@ -14724,13 +15261,13 @@ function resolveRef(ref, ctx) {
|
|
|
14724
15261
|
if (!ref.startsWith("#")) {
|
|
14725
15262
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
14726
15263
|
}
|
|
14727
|
-
const
|
|
14728
|
-
if (
|
|
15264
|
+
const path3 = ref.slice(1).split("/").filter(Boolean);
|
|
15265
|
+
if (path3.length === 0) {
|
|
14729
15266
|
return ctx.rootSchema;
|
|
14730
15267
|
}
|
|
14731
15268
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
14732
|
-
if (
|
|
14733
|
-
const key =
|
|
15269
|
+
if (path3[0] === defsKey) {
|
|
15270
|
+
const key = path3[1];
|
|
14734
15271
|
if (!key || !ctx.defs[key]) {
|
|
14735
15272
|
throw new Error(`Reference not found: ${ref}`);
|
|
14736
15273
|
}
|
|
@@ -14981,1117 +15518,595 @@ function convertBaseSchema(schema, ctx) {
|
|
|
14981
15518
|
const rest = items && typeof items === "object" && !Array.isArray(items) ? convertSchema(items, ctx) : undefined;
|
|
14982
15519
|
if (rest) {
|
|
14983
15520
|
zodSchema = z.tuple(tupleItems).rest(rest);
|
|
14984
|
-
} else {
|
|
14985
|
-
zodSchema = z.tuple(tupleItems);
|
|
14986
|
-
}
|
|
14987
|
-
if (typeof schema.minItems === "number") {
|
|
14988
|
-
zodSchema = zodSchema.check(z.minLength(schema.minItems));
|
|
14989
|
-
}
|
|
14990
|
-
if (typeof schema.maxItems === "number") {
|
|
14991
|
-
zodSchema = zodSchema.check(z.maxLength(schema.maxItems));
|
|
14992
|
-
}
|
|
14993
|
-
} else if (Array.isArray(items)) {
|
|
14994
|
-
const tupleItems = items.map((item) => convertSchema(item, ctx));
|
|
14995
|
-
const rest = schema.additionalItems && typeof schema.additionalItems === "object" ? convertSchema(schema.additionalItems, ctx) : undefined;
|
|
14996
|
-
if (rest) {
|
|
14997
|
-
zodSchema = z.tuple(tupleItems).rest(rest);
|
|
14998
|
-
} else {
|
|
14999
|
-
zodSchema = z.tuple(tupleItems);
|
|
15000
|
-
}
|
|
15001
|
-
if (typeof schema.minItems === "number") {
|
|
15002
|
-
zodSchema = zodSchema.check(z.minLength(schema.minItems));
|
|
15003
|
-
}
|
|
15004
|
-
if (typeof schema.maxItems === "number") {
|
|
15005
|
-
zodSchema = zodSchema.check(z.maxLength(schema.maxItems));
|
|
15006
|
-
}
|
|
15007
|
-
} else if (items !== undefined) {
|
|
15008
|
-
const element = convertSchema(items, ctx);
|
|
15009
|
-
let arraySchema = z.array(element);
|
|
15010
|
-
if (typeof schema.minItems === "number") {
|
|
15011
|
-
arraySchema = arraySchema.min(schema.minItems);
|
|
15012
|
-
}
|
|
15013
|
-
if (typeof schema.maxItems === "number") {
|
|
15014
|
-
arraySchema = arraySchema.max(schema.maxItems);
|
|
15015
|
-
}
|
|
15016
|
-
zodSchema = arraySchema;
|
|
15017
|
-
} else {
|
|
15018
|
-
zodSchema = z.array(z.any());
|
|
15019
|
-
}
|
|
15020
|
-
break;
|
|
15021
|
-
}
|
|
15022
|
-
default:
|
|
15023
|
-
throw new Error(`Unsupported type: ${type}`);
|
|
15024
|
-
}
|
|
15025
|
-
return zodSchema;
|
|
15026
|
-
}
|
|
15027
|
-
function convertSchema(schema, ctx) {
|
|
15028
|
-
if (typeof schema === "boolean") {
|
|
15029
|
-
return schema ? z.any() : z.never();
|
|
15030
|
-
}
|
|
15031
|
-
let baseSchema = convertBaseSchema(schema, ctx);
|
|
15032
|
-
const hasExplicitType = schema.type || schema.enum !== undefined || schema.const !== undefined;
|
|
15033
|
-
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
15034
|
-
const options = schema.anyOf.map((s) => convertSchema(s, ctx));
|
|
15035
|
-
const anyOfUnion = z.union(options);
|
|
15036
|
-
baseSchema = hasExplicitType ? z.intersection(baseSchema, anyOfUnion) : anyOfUnion;
|
|
15037
|
-
}
|
|
15038
|
-
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
15039
|
-
const options = schema.oneOf.map((s) => convertSchema(s, ctx));
|
|
15040
|
-
const oneOfUnion = z.xor(options);
|
|
15041
|
-
baseSchema = hasExplicitType ? z.intersection(baseSchema, oneOfUnion) : oneOfUnion;
|
|
15042
|
-
}
|
|
15043
|
-
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
15044
|
-
if (schema.allOf.length === 0) {
|
|
15045
|
-
baseSchema = hasExplicitType ? baseSchema : z.any();
|
|
15046
|
-
} else {
|
|
15047
|
-
let result = hasExplicitType ? baseSchema : convertSchema(schema.allOf[0], ctx);
|
|
15048
|
-
const startIdx = hasExplicitType ? 0 : 1;
|
|
15049
|
-
for (let i = startIdx;i < schema.allOf.length; i++) {
|
|
15050
|
-
result = z.intersection(result, convertSchema(schema.allOf[i], ctx));
|
|
15051
|
-
}
|
|
15052
|
-
baseSchema = result;
|
|
15053
|
-
}
|
|
15054
|
-
}
|
|
15055
|
-
if (schema.nullable === true && ctx.version === "openapi-3.0") {
|
|
15056
|
-
baseSchema = z.nullable(baseSchema);
|
|
15057
|
-
}
|
|
15058
|
-
if (schema.readOnly === true) {
|
|
15059
|
-
baseSchema = z.readonly(baseSchema);
|
|
15060
|
-
}
|
|
15061
|
-
if (schema.default !== undefined) {
|
|
15062
|
-
baseSchema = baseSchema.default(schema.default);
|
|
15063
|
-
}
|
|
15064
|
-
const extraMeta = {};
|
|
15065
|
-
const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
|
|
15066
|
-
for (const key of coreMetadataKeys) {
|
|
15067
|
-
if (key in schema) {
|
|
15068
|
-
extraMeta[key] = schema[key];
|
|
15069
|
-
}
|
|
15070
|
-
}
|
|
15071
|
-
const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
|
|
15072
|
-
for (const key of contentMetadataKeys) {
|
|
15073
|
-
if (key in schema) {
|
|
15074
|
-
extraMeta[key] = schema[key];
|
|
15075
|
-
}
|
|
15076
|
-
}
|
|
15077
|
-
for (const key of Object.keys(schema)) {
|
|
15078
|
-
if (!RECOGNIZED_KEYS.has(key)) {
|
|
15079
|
-
extraMeta[key] = schema[key];
|
|
15080
|
-
}
|
|
15081
|
-
}
|
|
15082
|
-
if (Object.keys(extraMeta).length > 0) {
|
|
15083
|
-
ctx.registry.add(baseSchema, extraMeta);
|
|
15084
|
-
}
|
|
15085
|
-
if (schema.description) {
|
|
15086
|
-
baseSchema = baseSchema.describe(schema.description);
|
|
15087
|
-
}
|
|
15088
|
-
return baseSchema;
|
|
15089
|
-
}
|
|
15090
|
-
function fromJSONSchema(schema, params) {
|
|
15091
|
-
if (typeof schema === "boolean") {
|
|
15092
|
-
return schema ? z.any() : z.never();
|
|
15093
|
-
}
|
|
15094
|
-
let normalized;
|
|
15095
|
-
try {
|
|
15096
|
-
normalized = JSON.parse(JSON.stringify(schema));
|
|
15097
|
-
} catch {
|
|
15098
|
-
throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
|
|
15099
|
-
}
|
|
15100
|
-
const version2 = detectVersion(normalized, params?.defaultTarget);
|
|
15101
|
-
const defs = normalized.$defs || normalized.definitions || {};
|
|
15102
|
-
const ctx = {
|
|
15103
|
-
version: version2,
|
|
15104
|
-
defs,
|
|
15105
|
-
refs: new Map,
|
|
15106
|
-
processing: new Set,
|
|
15107
|
-
rootSchema: normalized,
|
|
15108
|
-
registry: params?.registry ?? globalRegistry
|
|
15109
|
-
};
|
|
15110
|
-
return convertSchema(normalized, ctx);
|
|
15111
|
-
}
|
|
15112
|
-
// node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/coerce.js
|
|
15113
|
-
var exports_coerce = {};
|
|
15114
|
-
__export(exports_coerce, {
|
|
15115
|
-
string: () => string3,
|
|
15116
|
-
number: () => number3,
|
|
15117
|
-
date: () => date4,
|
|
15118
|
-
boolean: () => boolean3,
|
|
15119
|
-
bigint: () => bigint3
|
|
15120
|
-
});
|
|
15121
|
-
function string3(params) {
|
|
15122
|
-
return _coercedString(ZodString, params);
|
|
15123
|
-
}
|
|
15124
|
-
function number3(params) {
|
|
15125
|
-
return _coercedNumber(ZodNumber, params);
|
|
15126
|
-
}
|
|
15127
|
-
function boolean3(params) {
|
|
15128
|
-
return _coercedBoolean(ZodBoolean, params);
|
|
15129
|
-
}
|
|
15130
|
-
function bigint3(params) {
|
|
15131
|
-
return _coercedBigint(ZodBigInt, params);
|
|
15132
|
-
}
|
|
15133
|
-
function date4(params) {
|
|
15134
|
-
return _coercedDate(ZodDate, params);
|
|
15135
|
-
}
|
|
15136
|
-
|
|
15137
|
-
// node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
15138
|
-
config(en_default());
|
|
15139
|
-
// src/lib/agent-colors.ts
|
|
15140
|
-
var OPENCODE_AGENT_COLOR_TOKENS = [
|
|
15141
|
-
"primary",
|
|
15142
|
-
"secondary",
|
|
15143
|
-
"accent",
|
|
15144
|
-
"success",
|
|
15145
|
-
"warning",
|
|
15146
|
-
"error",
|
|
15147
|
-
"info"
|
|
15148
|
-
];
|
|
15149
|
-
|
|
15150
|
-
// src/lib/config-schema.ts
|
|
15151
|
-
var permissionSettingSchema = exports_external.enum(["ask", "allow", "deny"]);
|
|
15152
|
-
var permissionRuleSchema = exports_external.union([
|
|
15153
|
-
permissionSettingSchema,
|
|
15154
|
-
exports_external.record(exports_external.string(), permissionSettingSchema)
|
|
15155
|
-
]);
|
|
15156
|
-
var permissionSchema = exports_external.record(exports_external.string(), permissionRuleSchema).meta({
|
|
15157
|
-
description: "Permission overrides per tool",
|
|
15158
|
-
examples: [{ edit: "allow", bash: { curl: "allow", rm: "deny" } }]
|
|
15159
|
-
});
|
|
15160
|
-
var MODEL_FORMAT_MESSAGE = 'must be in provider/model format (e.g., "anthropic/claude-sonnet-4")';
|
|
15161
|
-
var MODEL_FORMAT_REGEX = /^[^\s/]+\/\S+$/;
|
|
15162
|
-
var modelSchema = exports_external.string().min(1).regex(MODEL_FORMAT_REGEX, MODEL_FORMAT_MESSAGE).nullable().meta({
|
|
15163
|
-
description: "Model identifier in provider/model format, or null to inherit parent model",
|
|
15164
|
-
examples: ["anthropic/claude-sonnet-4", null]
|
|
15165
|
-
});
|
|
15166
|
-
var variantSchema = exports_external.string().min(1).max(128, "variant must be at most 128 characters").regex(/^\S+$/, "must be a non-empty string without whitespace").meta({
|
|
15167
|
-
description: "Model variant identifier",
|
|
15168
|
-
examples: ["v2", "extended"]
|
|
15169
|
-
});
|
|
15170
|
-
var temperatureSchema = exports_external.number().min(0).meta({
|
|
15171
|
-
description: "Sampling temperature (\u22650; 0 = deterministic)",
|
|
15172
|
-
examples: [0.1, 0.7, 0]
|
|
15173
|
-
});
|
|
15174
|
-
var topPSchema = exports_external.number().min(0).max(1).meta({
|
|
15175
|
-
description: "Nucleus sampling parameter (0 to 1)",
|
|
15176
|
-
examples: [0.9, 0.1, 1]
|
|
15177
|
-
});
|
|
15178
|
-
var modeSchema = exports_external.enum(["subagent", "primary", "all"]).meta({
|
|
15179
|
-
description: "Agent execution mode",
|
|
15180
|
-
examples: ["subagent", "primary", "all"]
|
|
15181
|
-
});
|
|
15182
|
-
var colorSchema = exports_external.union([
|
|
15183
|
-
exports_external.enum(OPENCODE_AGENT_COLOR_TOKENS),
|
|
15184
|
-
exports_external.string().regex(/^#[0-9a-fA-F]{6}$/, "must be a 6-digit hex color (#RRGGBB)")
|
|
15185
|
-
]).meta({
|
|
15186
|
-
description: "Agent color \u2014 named token from OpenCode or 6-digit hex color (#RRGGBB)",
|
|
15187
|
-
examples: ["primary", "#ff6600"]
|
|
15188
|
-
});
|
|
15189
|
-
var stepsSchema = exports_external.number().int().positive().meta({
|
|
15190
|
-
description: "Maximum execution steps (positive integer)",
|
|
15191
|
-
examples: [10, 50]
|
|
15192
|
-
});
|
|
15193
|
-
var hiddenSchema = exports_external.boolean().meta({
|
|
15194
|
-
description: "Hide agent from UI",
|
|
15195
|
-
examples: [true, false]
|
|
15196
|
-
});
|
|
15197
|
-
var disableSchema = exports_external.boolean().meta({
|
|
15198
|
-
description: "Disable this agent overlay",
|
|
15199
|
-
examples: [true, false]
|
|
15200
|
-
});
|
|
15201
|
-
var skillsSchema = exports_external.array(exports_external.string().min(1)).meta({
|
|
15202
|
-
description: "Skills enabled for this agent",
|
|
15203
|
-
examples: [["ce:plan", "ce:review"]]
|
|
15204
|
-
});
|
|
15205
|
-
function trustAny(schema) {
|
|
15206
|
-
return schema.meta({ trust: "any" });
|
|
15207
|
-
}
|
|
15208
|
-
function trustProtected(schema) {
|
|
15209
|
-
return schema.meta({ trust: "project-or-higher" });
|
|
15210
|
-
}
|
|
15211
|
-
var AgentOverlaySchema = exports_external.object({
|
|
15212
|
-
model: trustProtected(modelSchema).optional(),
|
|
15213
|
-
variant: trustProtected(variantSchema).optional(),
|
|
15214
|
-
temperature: trustAny(temperatureSchema).optional(),
|
|
15215
|
-
top_p: trustAny(topPSchema).optional(),
|
|
15216
|
-
mode: modeSchema.optional(),
|
|
15217
|
-
color: colorSchema.optional(),
|
|
15218
|
-
steps: stepsSchema.optional(),
|
|
15219
|
-
hidden: hiddenSchema.optional(),
|
|
15220
|
-
disable: disableSchema.optional(),
|
|
15221
|
-
skills: trustProtected(skillsSchema).optional(),
|
|
15222
|
-
permission: trustProtected(permissionSchema).optional()
|
|
15223
|
-
}).strict().meta({
|
|
15224
|
-
description: "Per-agent configuration overlay",
|
|
15225
|
-
examples: [
|
|
15226
|
-
{
|
|
15227
|
-
model: "anthropic/claude-opus-4-7",
|
|
15228
|
-
temperature: 0.1,
|
|
15229
|
-
mode: "subagent"
|
|
15521
|
+
} else {
|
|
15522
|
+
zodSchema = z.tuple(tupleItems);
|
|
15523
|
+
}
|
|
15524
|
+
if (typeof schema.minItems === "number") {
|
|
15525
|
+
zodSchema = zodSchema.check(z.minLength(schema.minItems));
|
|
15526
|
+
}
|
|
15527
|
+
if (typeof schema.maxItems === "number") {
|
|
15528
|
+
zodSchema = zodSchema.check(z.maxLength(schema.maxItems));
|
|
15529
|
+
}
|
|
15530
|
+
} else if (Array.isArray(items)) {
|
|
15531
|
+
const tupleItems = items.map((item) => convertSchema(item, ctx));
|
|
15532
|
+
const rest = schema.additionalItems && typeof schema.additionalItems === "object" ? convertSchema(schema.additionalItems, ctx) : undefined;
|
|
15533
|
+
if (rest) {
|
|
15534
|
+
zodSchema = z.tuple(tupleItems).rest(rest);
|
|
15535
|
+
} else {
|
|
15536
|
+
zodSchema = z.tuple(tupleItems);
|
|
15537
|
+
}
|
|
15538
|
+
if (typeof schema.minItems === "number") {
|
|
15539
|
+
zodSchema = zodSchema.check(z.minLength(schema.minItems));
|
|
15540
|
+
}
|
|
15541
|
+
if (typeof schema.maxItems === "number") {
|
|
15542
|
+
zodSchema = zodSchema.check(z.maxLength(schema.maxItems));
|
|
15543
|
+
}
|
|
15544
|
+
} else if (items !== undefined) {
|
|
15545
|
+
const element = convertSchema(items, ctx);
|
|
15546
|
+
let arraySchema = z.array(element);
|
|
15547
|
+
if (typeof schema.minItems === "number") {
|
|
15548
|
+
arraySchema = arraySchema.min(schema.minItems);
|
|
15549
|
+
}
|
|
15550
|
+
if (typeof schema.maxItems === "number") {
|
|
15551
|
+
arraySchema = arraySchema.max(schema.maxItems);
|
|
15552
|
+
}
|
|
15553
|
+
zodSchema = arraySchema;
|
|
15554
|
+
} else {
|
|
15555
|
+
zodSchema = z.array(z.any());
|
|
15556
|
+
}
|
|
15557
|
+
break;
|
|
15230
15558
|
}
|
|
15231
|
-
|
|
15232
|
-
});
|
|
15233
|
-
var CategoryOverlaySchema = exports_external.object({
|
|
15234
|
-
model: trustProtected(modelSchema).optional(),
|
|
15235
|
-
variant: trustProtected(variantSchema).optional(),
|
|
15236
|
-
temperature: trustAny(temperatureSchema).optional(),
|
|
15237
|
-
top_p: trustAny(topPSchema).optional(),
|
|
15238
|
-
mode: modeSchema.optional(),
|
|
15239
|
-
color: colorSchema.optional(),
|
|
15240
|
-
steps: stepsSchema.optional(),
|
|
15241
|
-
hidden: hiddenSchema.optional(),
|
|
15242
|
-
skills: trustProtected(skillsSchema).optional(),
|
|
15243
|
-
permission: trustProtected(permissionSchema).optional()
|
|
15244
|
-
}).strict().meta({
|
|
15245
|
-
description: "Per-category configuration overlay (same fields as agent minus disable)",
|
|
15246
|
-
examples: [{ model: "anthropic/claude-opus-4-7", temperature: 0.1 }]
|
|
15247
|
-
});
|
|
15248
|
-
var BootstrapSchema = exports_external.object({
|
|
15249
|
-
enabled: exports_external.boolean().default(true).meta({
|
|
15250
|
-
description: "Enable bootstrap prompt injection into every conversation",
|
|
15251
|
-
examples: [true, false]
|
|
15252
|
-
}),
|
|
15253
|
-
file: exports_external.string().optional().meta({
|
|
15254
|
-
description: "Path to a custom bootstrap prompt file",
|
|
15255
|
-
examples: ["~/.config/opencode/bootstrap.md"]
|
|
15256
|
-
})
|
|
15257
|
-
}).strict().meta({
|
|
15258
|
-
description: "Bootstrap prompt configuration",
|
|
15259
|
-
examples: [{ enabled: true }, { enabled: false }]
|
|
15260
|
-
});
|
|
15261
|
-
var SystematicConfigSchema = exports_external.object({
|
|
15262
|
-
$schema: exports_external.string().url().optional().meta({
|
|
15263
|
-
description: "JSON Schema URL for IDE autocomplete. The value is informational only \u2014 the loader does not fetch or validate against it. Add this to enable IDE schema activation and field-level autocomplete in editors that support JSON Schema (VSCode, Zed, IntelliJ).",
|
|
15264
|
-
examples: [
|
|
15265
|
-
"https://fro.bot/systematic/schemas/v2/systematic-config.schema.json"
|
|
15266
|
-
]
|
|
15267
|
-
}),
|
|
15268
|
-
agents: exports_external.record(exports_external.string(), AgentOverlaySchema).default({}).meta({
|
|
15269
|
-
description: "Per-agent configuration overlays keyed by agent name",
|
|
15270
|
-
examples: [{ "correctness-reviewer": { temperature: 0.1 } }, {}]
|
|
15271
|
-
}),
|
|
15272
|
-
categories: exports_external.record(exports_external.string(), CategoryOverlaySchema).default({}).meta({
|
|
15273
|
-
description: "Per-category configuration overlays keyed by category name",
|
|
15274
|
-
examples: [{ review: { model: "anthropic/claude-opus-4-7" } }, {}]
|
|
15275
|
-
}),
|
|
15276
|
-
disabled_skills: exports_external.array(exports_external.string()).default([]).meta({
|
|
15277
|
-
description: "Array of skill names to disable globally",
|
|
15278
|
-
examples: [["ce:plan", "ce:review"]]
|
|
15279
|
-
}),
|
|
15280
|
-
disabled_agents: exports_external.array(exports_external.string()).default([]).meta({
|
|
15281
|
-
description: "Array of agent names to disable globally",
|
|
15282
|
-
examples: [["previous-comments-reviewer", "cli-readiness-reviewer"]]
|
|
15283
|
-
}),
|
|
15284
|
-
disabled_commands: exports_external.array(exports_external.string()).default([]).meta({
|
|
15285
|
-
description: "Array of command names to disable globally",
|
|
15286
|
-
examples: [["deprecated-migration-helper"]]
|
|
15287
|
-
}),
|
|
15288
|
-
bootstrap: BootstrapSchema.default({ enabled: true }).meta({
|
|
15289
|
-
description: "Bootstrap prompt configuration",
|
|
15290
|
-
examples: [
|
|
15291
|
-
{ enabled: true },
|
|
15292
|
-
{ enabled: false, file: ".opencode/custom-prompt.md" }
|
|
15293
|
-
]
|
|
15294
|
-
})
|
|
15295
|
-
}).strict().meta({
|
|
15296
|
-
description: "Systematic user configuration file (systematic.json / systematic.jsonc)",
|
|
15297
|
-
examples: [{ disabled_skills: ["ce:plan"], bootstrap: { enabled: false } }]
|
|
15298
|
-
});
|
|
15299
|
-
var SECURITY_OVERLAY_FIELDS = [
|
|
15300
|
-
"model",
|
|
15301
|
-
"variant",
|
|
15302
|
-
"skills",
|
|
15303
|
-
"permission"
|
|
15304
|
-
];
|
|
15305
|
-
|
|
15306
|
-
// src/lib/config.ts
|
|
15307
|
-
var DEFAULT_CONFIG = {
|
|
15308
|
-
disabled_skills: [],
|
|
15309
|
-
disabled_agents: [],
|
|
15310
|
-
disabled_commands: [],
|
|
15311
|
-
bootstrap: {
|
|
15312
|
-
enabled: true
|
|
15313
|
-
},
|
|
15314
|
-
agents: {},
|
|
15315
|
-
categories: {}
|
|
15316
|
-
};
|
|
15317
|
-
var SECURITY_OVERLAY_FIELDS2 = new Set(SECURITY_OVERLAY_FIELDS);
|
|
15318
|
-
function resolveConfigPath(dir, basename) {
|
|
15319
|
-
const jsoncPath = path.join(dir, `${basename}.jsonc`);
|
|
15320
|
-
if (fs.existsSync(jsoncPath))
|
|
15321
|
-
return jsoncPath;
|
|
15322
|
-
return path.join(dir, `${basename}.json`);
|
|
15323
|
-
}
|
|
15324
|
-
function isErrorWithCode(error51) {
|
|
15325
|
-
return error51 instanceof Error && "code" in error51;
|
|
15326
|
-
}
|
|
15327
|
-
function loadJsoncFile(filePath) {
|
|
15328
|
-
let content;
|
|
15329
|
-
try {
|
|
15330
|
-
content = fs.readFileSync(filePath, "utf-8");
|
|
15331
|
-
} catch (error51) {
|
|
15332
|
-
if (isErrorWithCode(error51) && error51.code === "ENOENT")
|
|
15333
|
-
return null;
|
|
15334
|
-
throw new Error(`Invalid Systematic config in ${filePath}: unable to read file`, { cause: error51 });
|
|
15335
|
-
}
|
|
15336
|
-
const errors3 = [];
|
|
15337
|
-
const parsed = parse2(content, errors3);
|
|
15338
|
-
if (errors3.length > 0) {
|
|
15339
|
-
const error51 = errors3[0];
|
|
15340
|
-
const message = error51 ? `${printParseErrorCode(error51.error)} at offset ${error51.offset}` : "unknown parse error";
|
|
15341
|
-
throw new Error(`Invalid Systematic config in ${filePath}: JSONC parse error: ${message}`);
|
|
15342
|
-
}
|
|
15343
|
-
if (!isRecord(parsed)) {
|
|
15344
|
-
throw new Error(`Invalid Systematic config in ${filePath}: root must be an object`);
|
|
15345
|
-
}
|
|
15346
|
-
return parsed;
|
|
15347
|
-
}
|
|
15348
|
-
function throwTopLevelConfigSchemaError(filePath, trust, issues) {
|
|
15349
|
-
const issue2 = issues[0];
|
|
15350
|
-
if (!issue2) {
|
|
15351
|
-
throw Object.assign(new Error(`Invalid Systematic config in ${filePath}: schema validation failed`), { _tag: "ConfigSchemaError", filePath, trust, issues });
|
|
15559
|
+
default:
|
|
15560
|
+
throw new Error(`Unsupported type: ${type}`);
|
|
15352
15561
|
}
|
|
15353
|
-
|
|
15354
|
-
const message = fieldPath ? `Invalid Systematic config in ${filePath}: ${fieldPath} ${issue2.message}` : `Invalid Systematic config in ${filePath}: ${issue2.message}`;
|
|
15355
|
-
throw Object.assign(new Error(message), {
|
|
15356
|
-
_tag: "ConfigSchemaError",
|
|
15357
|
-
filePath,
|
|
15358
|
-
trust,
|
|
15359
|
-
issues
|
|
15360
|
-
});
|
|
15562
|
+
return zodSchema;
|
|
15361
15563
|
}
|
|
15362
|
-
function
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
return null;
|
|
15366
|
-
const result = SystematicConfigSchema.safeParse(rawConfig);
|
|
15367
|
-
if (!result.success) {
|
|
15368
|
-
throwTopLevelConfigSchemaError(filePath, trust, result.error.issues);
|
|
15564
|
+
function convertSchema(schema, ctx) {
|
|
15565
|
+
if (typeof schema === "boolean") {
|
|
15566
|
+
return schema ? z.any() : z.never();
|
|
15369
15567
|
}
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
const set2 = new Set;
|
|
15377
|
-
if (arr1)
|
|
15378
|
-
for (const item of arr1)
|
|
15379
|
-
set2.add(item);
|
|
15380
|
-
if (arr2)
|
|
15381
|
-
for (const item of arr2)
|
|
15382
|
-
set2.add(item);
|
|
15383
|
-
return Array.from(set2);
|
|
15384
|
-
}
|
|
15385
|
-
function loadConfig(projectDir) {
|
|
15386
|
-
return loadConfigWithSources(projectDir).config;
|
|
15387
|
-
}
|
|
15388
|
-
function loadConfigWithSources(projectDir) {
|
|
15389
|
-
const paths = getConfigPaths(projectDir);
|
|
15390
|
-
const userSource = loadConfigSource(paths.userConfig, "user");
|
|
15391
|
-
const projectSource = loadConfigSource(paths.projectConfig, "project");
|
|
15392
|
-
const customSource = paths.customConfig ? loadConfigSource(paths.customConfig, "custom") : null;
|
|
15393
|
-
const sources = [userSource, projectSource, customSource].filter((source) => source !== null);
|
|
15394
|
-
const overlays = mergeOverlaySources(sources);
|
|
15395
|
-
const userConfig = userSource?.config;
|
|
15396
|
-
const projectConfig = projectSource?.config;
|
|
15397
|
-
const customConfig = customSource?.config;
|
|
15398
|
-
const result = {
|
|
15399
|
-
disabled_skills: mergeArraysUnique(mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_skills, userConfig?.disabled_skills), projectConfig?.disabled_skills), customConfig?.disabled_skills),
|
|
15400
|
-
disabled_agents: mergeArraysUnique(mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_agents, userConfig?.disabled_agents), projectConfig?.disabled_agents), customConfig?.disabled_agents),
|
|
15401
|
-
disabled_commands: mergeArraysUnique(mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_commands, userConfig?.disabled_commands), projectConfig?.disabled_commands), customConfig?.disabled_commands),
|
|
15402
|
-
bootstrap: {
|
|
15403
|
-
...DEFAULT_CONFIG.bootstrap,
|
|
15404
|
-
...userConfig?.bootstrap,
|
|
15405
|
-
...projectConfig?.bootstrap,
|
|
15406
|
-
...customConfig?.bootstrap
|
|
15407
|
-
},
|
|
15408
|
-
agents: overlayValues(overlays.agents),
|
|
15409
|
-
categories: overlayValues(overlays.categories)
|
|
15410
|
-
};
|
|
15411
|
-
return { config: result, overlays };
|
|
15412
|
-
}
|
|
15413
|
-
function mergeOverlaySources(sources) {
|
|
15414
|
-
const result = {
|
|
15415
|
-
agents: {},
|
|
15416
|
-
categories: {}
|
|
15417
|
-
};
|
|
15418
|
-
for (const source of sources) {
|
|
15419
|
-
mergeOverlayMap(result.agents, source, "agents");
|
|
15420
|
-
mergeOverlayMap(result.categories, source, "categories");
|
|
15568
|
+
let baseSchema = convertBaseSchema(schema, ctx);
|
|
15569
|
+
const hasExplicitType = schema.type || schema.enum !== undefined || schema.const !== undefined;
|
|
15570
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
15571
|
+
const options = schema.anyOf.map((s) => convertSchema(s, ctx));
|
|
15572
|
+
const anyOfUnion = z.union(options);
|
|
15573
|
+
baseSchema = hasExplicitType ? z.intersection(baseSchema, anyOfUnion) : anyOfUnion;
|
|
15421
15574
|
}
|
|
15422
|
-
|
|
15423
|
-
|
|
15424
|
-
|
|
15425
|
-
|
|
15426
|
-
if (overlayMap === undefined)
|
|
15427
|
-
return;
|
|
15428
|
-
if (!isRecord(overlayMap)) {
|
|
15429
|
-
throwInvalidOverlay(source.path, mapKey);
|
|
15575
|
+
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
15576
|
+
const options = schema.oneOf.map((s) => convertSchema(s, ctx));
|
|
15577
|
+
const oneOfUnion = z.xor(options);
|
|
15578
|
+
baseSchema = hasExplicitType ? z.intersection(baseSchema, oneOfUnion) : oneOfUnion;
|
|
15430
15579
|
}
|
|
15431
|
-
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15580
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
15581
|
+
if (schema.allOf.length === 0) {
|
|
15582
|
+
baseSchema = hasExplicitType ? baseSchema : z.any();
|
|
15583
|
+
} else {
|
|
15584
|
+
let result = hasExplicitType ? baseSchema : convertSchema(schema.allOf[0], ctx);
|
|
15585
|
+
const startIdx = hasExplicitType ? 0 : 1;
|
|
15586
|
+
for (let i = startIdx;i < schema.allOf.length; i++) {
|
|
15587
|
+
result = z.intersection(result, convertSchema(schema.allOf[i], ctx));
|
|
15588
|
+
}
|
|
15589
|
+
baseSchema = result;
|
|
15438
15590
|
}
|
|
15439
|
-
const previous = target[key];
|
|
15440
|
-
const nextValue = source.trust === "project" && previous ? preserveSecurityFields(previous.value, value) : value;
|
|
15441
|
-
target[key] = {
|
|
15442
|
-
value: nextValue,
|
|
15443
|
-
sourcePath: source.path,
|
|
15444
|
-
keyPath
|
|
15445
|
-
};
|
|
15446
15591
|
}
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
for (const field of SECURITY_OVERLAY_FIELDS2) {
|
|
15450
|
-
if (Object.hasOwn(value, field)) {
|
|
15451
|
-
throw new Error(`Invalid Systematic config in ${sourcePath}: ${keyPath}.${field} is only valid in user config or OPENCODE_CONFIG_DIR config`);
|
|
15452
|
-
}
|
|
15592
|
+
if (schema.nullable === true && ctx.version === "openapi-3.0") {
|
|
15593
|
+
baseSchema = z.nullable(baseSchema);
|
|
15453
15594
|
}
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
const result = { ...next };
|
|
15457
|
-
for (const field of SECURITY_OVERLAY_FIELDS2) {
|
|
15458
|
-
if (Object.hasOwn(previous, field)) {
|
|
15459
|
-
result[field] = previous[field];
|
|
15460
|
-
}
|
|
15595
|
+
if (schema.readOnly === true) {
|
|
15596
|
+
baseSchema = z.readonly(baseSchema);
|
|
15461
15597
|
}
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
function overlayValues(overlays) {
|
|
15465
|
-
const result = {};
|
|
15466
|
-
for (const [key, overlay] of Object.entries(overlays)) {
|
|
15467
|
-
result[key] = overlay.value;
|
|
15598
|
+
if (schema.default !== undefined) {
|
|
15599
|
+
baseSchema = baseSchema.default(schema.default);
|
|
15468
15600
|
}
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
15472
|
-
|
|
15473
|
-
|
|
15474
|
-
function getConfigPaths(projectDir) {
|
|
15475
|
-
const homeDir = os.homedir();
|
|
15476
|
-
const customConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
15477
|
-
const result = {
|
|
15478
|
-
userConfig: resolveConfigPath(path.join(homeDir, ".config/opencode"), "systematic"),
|
|
15479
|
-
projectConfig: resolveConfigPath(path.join(projectDir, ".opencode"), "systematic"),
|
|
15480
|
-
userDir: path.join(homeDir, ".config/opencode/systematic"),
|
|
15481
|
-
projectDir: path.join(projectDir, ".opencode/systematic"),
|
|
15482
|
-
...customConfigDir && {
|
|
15483
|
-
customConfig: resolveConfigPath(customConfigDir, "systematic"),
|
|
15484
|
-
customDir: path.join(customConfigDir, "systematic")
|
|
15601
|
+
const extraMeta = {};
|
|
15602
|
+
const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
|
|
15603
|
+
for (const key of coreMetadataKeys) {
|
|
15604
|
+
if (key in schema) {
|
|
15605
|
+
extraMeta[key] = schema[key];
|
|
15485
15606
|
}
|
|
15486
|
-
};
|
|
15487
|
-
return result;
|
|
15488
|
-
}
|
|
15489
|
-
|
|
15490
|
-
// src/lib/frontmatter.ts
|
|
15491
|
-
import yaml from "js-yaml";
|
|
15492
|
-
function parseFrontmatter(content) {
|
|
15493
|
-
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
|
|
15494
|
-
const match = content.match(frontmatterRegex);
|
|
15495
|
-
if (!match) {
|
|
15496
|
-
return {
|
|
15497
|
-
data: {},
|
|
15498
|
-
body: content,
|
|
15499
|
-
hadFrontmatter: false,
|
|
15500
|
-
parseError: false
|
|
15501
|
-
};
|
|
15502
|
-
}
|
|
15503
|
-
const yamlContent = match[1];
|
|
15504
|
-
const body = match[2];
|
|
15505
|
-
try {
|
|
15506
|
-
const parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
|
|
15507
|
-
const data = parsed ?? {};
|
|
15508
|
-
return { data, body, hadFrontmatter: true, parseError: false };
|
|
15509
|
-
} catch {
|
|
15510
|
-
return { data: {}, body, hadFrontmatter: true, parseError: true };
|
|
15511
15607
|
}
|
|
15512
|
-
|
|
15513
|
-
|
|
15514
|
-
|
|
15515
|
-
|
|
15516
|
-
|
|
15608
|
+
const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
|
|
15609
|
+
for (const key of contentMetadataKeys) {
|
|
15610
|
+
if (key in schema) {
|
|
15611
|
+
extraMeta[key] = schema[key];
|
|
15612
|
+
}
|
|
15517
15613
|
}
|
|
15518
|
-
const
|
|
15519
|
-
|
|
15520
|
-
|
|
15521
|
-
noRefs: true
|
|
15522
|
-
}).trimEnd();
|
|
15523
|
-
return ["---", yamlContent, "---"].join(`
|
|
15524
|
-
`);
|
|
15525
|
-
}
|
|
15526
|
-
|
|
15527
|
-
// src/lib/validation.ts
|
|
15528
|
-
function isRecord2(value) {
|
|
15529
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15530
|
-
}
|
|
15531
|
-
function isPermissionSetting(value) {
|
|
15532
|
-
return value === "ask" || value === "allow" || value === "deny";
|
|
15533
|
-
}
|
|
15534
|
-
function isToolsMap(value) {
|
|
15535
|
-
if (!isRecord2(value))
|
|
15536
|
-
return false;
|
|
15537
|
-
return Object.values(value).every((entry) => typeof entry === "boolean");
|
|
15538
|
-
}
|
|
15539
|
-
function isAgentMode(value) {
|
|
15540
|
-
return value === "subagent" || value === "primary" || value === "all";
|
|
15541
|
-
}
|
|
15542
|
-
function extractSimplePermission(data, key) {
|
|
15543
|
-
if (!(key in data))
|
|
15544
|
-
return;
|
|
15545
|
-
const value = data[key];
|
|
15546
|
-
return isPermissionSetting(value) ? value : null;
|
|
15547
|
-
}
|
|
15548
|
-
function extractBashPermission(data) {
|
|
15549
|
-
if (!("bash" in data))
|
|
15550
|
-
return;
|
|
15551
|
-
const bash = data.bash;
|
|
15552
|
-
if (isPermissionSetting(bash))
|
|
15553
|
-
return bash;
|
|
15554
|
-
if (isRecord2(bash)) {
|
|
15555
|
-
const entries = Object.entries(bash);
|
|
15556
|
-
if (entries.every(([, setting]) => isPermissionSetting(setting))) {
|
|
15557
|
-
return Object.fromEntries(entries);
|
|
15614
|
+
for (const key of Object.keys(schema)) {
|
|
15615
|
+
if (!RECOGNIZED_KEYS.has(key)) {
|
|
15616
|
+
extraMeta[key] = schema[key];
|
|
15558
15617
|
}
|
|
15559
15618
|
}
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill) {
|
|
15563
|
-
const permission = {};
|
|
15564
|
-
if (edit)
|
|
15565
|
-
permission.edit = edit;
|
|
15566
|
-
if (bash)
|
|
15567
|
-
permission.bash = bash;
|
|
15568
|
-
if (webfetch)
|
|
15569
|
-
permission.webfetch = webfetch;
|
|
15570
|
-
if (doom_loop)
|
|
15571
|
-
permission.doom_loop = doom_loop;
|
|
15572
|
-
if (external_directory)
|
|
15573
|
-
permission.external_directory = external_directory;
|
|
15574
|
-
if (task)
|
|
15575
|
-
permission.task = task;
|
|
15576
|
-
if (skill)
|
|
15577
|
-
permission.skill = skill;
|
|
15578
|
-
return Object.keys(permission).length > 0 ? permission : undefined;
|
|
15579
|
-
}
|
|
15580
|
-
function normalizePermission(value) {
|
|
15581
|
-
if (!isRecord2(value))
|
|
15582
|
-
return;
|
|
15583
|
-
const bash = extractBashPermission(value);
|
|
15584
|
-
if (bash === null)
|
|
15585
|
-
return;
|
|
15586
|
-
const edit = extractSimplePermission(value, "edit");
|
|
15587
|
-
if (edit === null)
|
|
15588
|
-
return;
|
|
15589
|
-
const webfetch = extractSimplePermission(value, "webfetch");
|
|
15590
|
-
if (webfetch === null)
|
|
15591
|
-
return;
|
|
15592
|
-
const doom_loop = extractSimplePermission(value, "doom_loop");
|
|
15593
|
-
if (doom_loop === null)
|
|
15594
|
-
return;
|
|
15595
|
-
const external_directory = extractSimplePermission(value, "external_directory");
|
|
15596
|
-
if (external_directory === null)
|
|
15597
|
-
return;
|
|
15598
|
-
const task = extractSimplePermission(value, "task");
|
|
15599
|
-
if (task === null)
|
|
15600
|
-
return;
|
|
15601
|
-
const skill = extractSimplePermission(value, "skill");
|
|
15602
|
-
if (skill === null)
|
|
15603
|
-
return;
|
|
15604
|
-
return buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill);
|
|
15605
|
-
}
|
|
15606
|
-
function extractString(data, key, fallback = "") {
|
|
15607
|
-
const value = data[key];
|
|
15608
|
-
return typeof value === "string" ? value : fallback;
|
|
15609
|
-
}
|
|
15610
|
-
function extractNonEmptyString(data, key) {
|
|
15611
|
-
const value = data[key];
|
|
15612
|
-
if (typeof value !== "string")
|
|
15613
|
-
return;
|
|
15614
|
-
const trimmed = value.trim();
|
|
15615
|
-
return trimmed !== "" ? trimmed : undefined;
|
|
15616
|
-
}
|
|
15617
|
-
function extractNumber(data, key) {
|
|
15618
|
-
const value = data[key];
|
|
15619
|
-
return typeof value === "number" ? value : undefined;
|
|
15620
|
-
}
|
|
15621
|
-
function extractBoolean(data, key) {
|
|
15622
|
-
const value = data[key];
|
|
15623
|
-
if (typeof value === "boolean")
|
|
15624
|
-
return value;
|
|
15625
|
-
if (typeof value === "string") {
|
|
15626
|
-
const normalized = value.trim().toLowerCase();
|
|
15627
|
-
if (normalized === "true")
|
|
15628
|
-
return true;
|
|
15629
|
-
if (normalized === "false")
|
|
15630
|
-
return false;
|
|
15619
|
+
if (Object.keys(extraMeta).length > 0) {
|
|
15620
|
+
ctx.registry.add(baseSchema, extraMeta);
|
|
15631
15621
|
}
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
// src/lib/walk-dir.ts
|
|
15636
|
-
import fs2 from "fs";
|
|
15637
|
-
import path2 from "path";
|
|
15638
|
-
function walkDir(rootDir, options = {}) {
|
|
15639
|
-
const { maxDepth = 3, filter } = options;
|
|
15640
|
-
const results = [];
|
|
15641
|
-
if (!fs2.existsSync(rootDir))
|
|
15642
|
-
return results;
|
|
15643
|
-
function recurse(currentDir, depth, category) {
|
|
15644
|
-
if (depth > maxDepth)
|
|
15645
|
-
return;
|
|
15646
|
-
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
15647
|
-
for (const entry of entries) {
|
|
15648
|
-
const fullPath = path2.join(currentDir, entry.name);
|
|
15649
|
-
const walkEntry = {
|
|
15650
|
-
path: fullPath,
|
|
15651
|
-
name: entry.name,
|
|
15652
|
-
isDirectory: entry.isDirectory(),
|
|
15653
|
-
depth,
|
|
15654
|
-
category
|
|
15655
|
-
};
|
|
15656
|
-
if (!filter || filter(walkEntry)) {
|
|
15657
|
-
results.push(walkEntry);
|
|
15658
|
-
}
|
|
15659
|
-
if (entry.isDirectory()) {
|
|
15660
|
-
recurse(fullPath, depth + 1, entry.name);
|
|
15661
|
-
}
|
|
15662
|
-
}
|
|
15622
|
+
if (schema.description) {
|
|
15623
|
+
baseSchema = baseSchema.describe(schema.description);
|
|
15663
15624
|
}
|
|
15664
|
-
|
|
15665
|
-
return results;
|
|
15666
|
-
}
|
|
15667
|
-
|
|
15668
|
-
// src/lib/agents.ts
|
|
15669
|
-
function findAgentsInDir(dir, maxDepth = 2) {
|
|
15670
|
-
const entries = walkDir(dir, {
|
|
15671
|
-
maxDepth,
|
|
15672
|
-
filter: (e) => !e.isDirectory && e.name.endsWith(".md")
|
|
15673
|
-
});
|
|
15674
|
-
return entries.map((entry) => ({
|
|
15675
|
-
name: entry.name.replace(/\.md$/, ""),
|
|
15676
|
-
file: entry.path,
|
|
15677
|
-
category: entry.category
|
|
15678
|
-
}));
|
|
15625
|
+
return baseSchema;
|
|
15679
15626
|
}
|
|
15680
|
-
function
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15627
|
+
function fromJSONSchema(schema, params) {
|
|
15628
|
+
if (typeof schema === "boolean") {
|
|
15629
|
+
return schema ? z.any() : z.never();
|
|
15630
|
+
}
|
|
15631
|
+
let normalized;
|
|
15632
|
+
try {
|
|
15633
|
+
normalized = JSON.parse(JSON.stringify(schema));
|
|
15634
|
+
} catch {
|
|
15635
|
+
throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
|
|
15684
15636
|
}
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
disable: extractBoolean(data, "disable"),
|
|
15695
|
-
mode: isAgentMode(data.mode) ? data.mode : undefined,
|
|
15696
|
-
color: extractNonEmptyString(data, "color"),
|
|
15697
|
-
steps: extractNumber(data, "steps"),
|
|
15698
|
-
hidden: extractBoolean(data, "hidden") ?? undefined,
|
|
15699
|
-
permission: normalizePermission(data.permission)
|
|
15637
|
+
const version2 = detectVersion(normalized, params?.defaultTarget);
|
|
15638
|
+
const defs = normalized.$defs || normalized.definitions || {};
|
|
15639
|
+
const ctx = {
|
|
15640
|
+
version: version2,
|
|
15641
|
+
defs,
|
|
15642
|
+
refs: new Map,
|
|
15643
|
+
processing: new Set,
|
|
15644
|
+
rootSchema: normalized,
|
|
15645
|
+
registry: params?.registry ?? globalRegistry
|
|
15700
15646
|
};
|
|
15647
|
+
return convertSchema(normalized, ctx);
|
|
15701
15648
|
}
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
15707
|
-
|
|
15708
|
-
|
|
15709
|
-
|
|
15710
|
-
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
name: commandName,
|
|
15714
|
-
file: entry.path,
|
|
15715
|
-
category: entry.category
|
|
15716
|
-
};
|
|
15717
|
-
});
|
|
15649
|
+
// node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/coerce.js
|
|
15650
|
+
var exports_coerce = {};
|
|
15651
|
+
__export(exports_coerce, {
|
|
15652
|
+
string: () => string3,
|
|
15653
|
+
number: () => number3,
|
|
15654
|
+
date: () => date4,
|
|
15655
|
+
boolean: () => boolean3,
|
|
15656
|
+
bigint: () => bigint3
|
|
15657
|
+
});
|
|
15658
|
+
function string3(params) {
|
|
15659
|
+
return _coercedString(ZodString, params);
|
|
15718
15660
|
}
|
|
15719
|
-
function
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
|
|
15730
|
-
}
|
|
15731
|
-
const argumentHintRaw = extractString(data, "argument-hint");
|
|
15732
|
-
return {
|
|
15733
|
-
name: extractString(data, "name"),
|
|
15734
|
-
description: extractString(data, "description"),
|
|
15735
|
-
argumentHint: argumentHintRaw.replace(/^["']|["']$/g, ""),
|
|
15736
|
-
agent: extractNonEmptyString(data, "agent"),
|
|
15737
|
-
model: extractNonEmptyString(data, "model"),
|
|
15738
|
-
subtask: extractBoolean(data, "subtask")
|
|
15739
|
-
};
|
|
15661
|
+
function number3(params) {
|
|
15662
|
+
return _coercedNumber(ZodNumber, params);
|
|
15663
|
+
}
|
|
15664
|
+
function boolean3(params) {
|
|
15665
|
+
return _coercedBoolean(ZodBoolean, params);
|
|
15666
|
+
}
|
|
15667
|
+
function bigint3(params) {
|
|
15668
|
+
return _coercedBigint(ZodBigInt, params);
|
|
15669
|
+
}
|
|
15670
|
+
function date4(params) {
|
|
15671
|
+
return _coercedDate(ZodDate, params);
|
|
15740
15672
|
}
|
|
15741
15673
|
|
|
15742
|
-
//
|
|
15743
|
-
|
|
15744
|
-
|
|
15745
|
-
var
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
15753
|
-
[/\bAskUserQuestion\b/g, "question"],
|
|
15754
|
-
[/\bWebSearch\b/g, "google_search"],
|
|
15755
|
-
[/\bRead\b(?=\s+tool|\s+to\s+|\()/g, "read"],
|
|
15756
|
-
[/\bWrite\b(?=\s+tool|\s+to\s+|\()/g, "write"],
|
|
15757
|
-
[/\bEdit\b(?=\s+tool|\s+to\s+|\()/g, "edit"],
|
|
15758
|
-
[/\bBash\b(?=\s+tool|\s+to\s+|\()/g, "bash"],
|
|
15759
|
-
[/\bGrep\b(?=\s+tool|\s+to\s+|\()/g, "grep"],
|
|
15760
|
-
[/\bGlob\b(?=\s+tool|\s+to\s+|\()/g, "glob"],
|
|
15761
|
-
[/\bWebFetch\b/g, "webfetch"],
|
|
15762
|
-
[/\bSkill\b(?=\s+tool|\s*\()/g, "skill"]
|
|
15674
|
+
// node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
15675
|
+
config(en_default());
|
|
15676
|
+
// src/lib/agent-colors.ts
|
|
15677
|
+
var OPENCODE_AGENT_COLOR_TOKENS = [
|
|
15678
|
+
"primary",
|
|
15679
|
+
"secondary",
|
|
15680
|
+
"accent",
|
|
15681
|
+
"success",
|
|
15682
|
+
"warning",
|
|
15683
|
+
"error",
|
|
15684
|
+
"info"
|
|
15763
15685
|
];
|
|
15764
|
-
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
15770
|
-
|
|
15771
|
-
|
|
15686
|
+
|
|
15687
|
+
// src/lib/config-schema.ts
|
|
15688
|
+
var permissionSettingSchema = exports_external.enum(["ask", "allow", "deny"]);
|
|
15689
|
+
var permissionRuleSchema = exports_external.union([
|
|
15690
|
+
permissionSettingSchema,
|
|
15691
|
+
exports_external.record(exports_external.string(), permissionSettingSchema)
|
|
15692
|
+
]);
|
|
15693
|
+
var permissionSchema = exports_external.record(exports_external.string(), permissionRuleSchema).meta({
|
|
15694
|
+
description: "Permission overrides per tool",
|
|
15695
|
+
examples: [{ edit: "allow", bash: { curl: "allow", rm: "deny" } }]
|
|
15696
|
+
});
|
|
15697
|
+
var MODEL_FORMAT_MESSAGE = 'must be in provider/model format (e.g., "anthropic/claude-sonnet-4")';
|
|
15698
|
+
var MODEL_FORMAT_REGEX = /^[^\s/]+\/\S+$/;
|
|
15699
|
+
var modelSchema = exports_external.string().min(1).regex(MODEL_FORMAT_REGEX, MODEL_FORMAT_MESSAGE).nullable().meta({
|
|
15700
|
+
description: "Model identifier in provider/model format, or null to inherit parent model",
|
|
15701
|
+
examples: ["anthropic/claude-sonnet-4", null]
|
|
15702
|
+
});
|
|
15703
|
+
var variantSchema = exports_external.string().min(1).max(128, "variant must be at most 128 characters").regex(/^\S+$/, "must be a non-empty string without whitespace").meta({
|
|
15704
|
+
description: "Model variant identifier",
|
|
15705
|
+
examples: ["v2", "extended"]
|
|
15706
|
+
});
|
|
15707
|
+
var temperatureSchema = exports_external.number().min(0).meta({
|
|
15708
|
+
description: "Sampling temperature (\u22650; 0 = deterministic)",
|
|
15709
|
+
examples: [0.1, 0.7, 0]
|
|
15710
|
+
});
|
|
15711
|
+
var topPSchema = exports_external.number().min(0).max(1).meta({
|
|
15712
|
+
description: "Nucleus sampling parameter (0 to 1)",
|
|
15713
|
+
examples: [0.9, 0.1, 1]
|
|
15714
|
+
});
|
|
15715
|
+
var modeSchema = exports_external.enum(["subagent", "primary", "all"]).meta({
|
|
15716
|
+
description: "Agent execution mode",
|
|
15717
|
+
examples: ["subagent", "primary", "all"]
|
|
15718
|
+
});
|
|
15719
|
+
var colorSchema = exports_external.union([
|
|
15720
|
+
exports_external.enum(OPENCODE_AGENT_COLOR_TOKENS),
|
|
15721
|
+
exports_external.string().regex(/^#[0-9a-fA-F]{6}$/, "must be a 6-digit hex color (#RRGGBB)")
|
|
15722
|
+
]).meta({
|
|
15723
|
+
description: "Agent color \u2014 named token from OpenCode or 6-digit hex color (#RRGGBB)",
|
|
15724
|
+
examples: ["primary", "#ff6600"]
|
|
15725
|
+
});
|
|
15726
|
+
var stepsSchema = exports_external.number().int().positive().meta({
|
|
15727
|
+
description: "Maximum execution steps (positive integer)",
|
|
15728
|
+
examples: [10, 50]
|
|
15729
|
+
});
|
|
15730
|
+
var hiddenSchema = exports_external.boolean().meta({
|
|
15731
|
+
description: "Hide agent from UI",
|
|
15732
|
+
examples: [true, false]
|
|
15733
|
+
});
|
|
15734
|
+
var disableSchema = exports_external.boolean().meta({
|
|
15735
|
+
description: "Disable this agent overlay",
|
|
15736
|
+
examples: [true, false]
|
|
15737
|
+
});
|
|
15738
|
+
var skillsSchema = exports_external.array(exports_external.string().min(1)).meta({
|
|
15739
|
+
description: "Skills enabled for this agent",
|
|
15740
|
+
examples: [["ce:plan", "ce:review"]]
|
|
15741
|
+
});
|
|
15742
|
+
function trustAny(schema) {
|
|
15743
|
+
return schema.meta({ trust: "any" });
|
|
15744
|
+
}
|
|
15745
|
+
function trustProtected(schema) {
|
|
15746
|
+
return schema.meta({ trust: "project-or-higher" });
|
|
15747
|
+
}
|
|
15748
|
+
function enforceVariantHasExplicitModel(overlay, ctx) {
|
|
15749
|
+
if (overlay.variant === undefined)
|
|
15750
|
+
return;
|
|
15751
|
+
if (typeof overlay.model === "string")
|
|
15752
|
+
return;
|
|
15753
|
+
ctx.addIssue({
|
|
15754
|
+
code: "custom",
|
|
15755
|
+
path: ["variant"],
|
|
15756
|
+
message: "variant requires a non-null model in the same overlay; remove variant or set model explicitly"
|
|
15757
|
+
});
|
|
15758
|
+
}
|
|
15759
|
+
var AgentOverlaySchema = exports_external.object({
|
|
15760
|
+
model: trustProtected(modelSchema).optional(),
|
|
15761
|
+
variant: trustProtected(variantSchema).optional(),
|
|
15762
|
+
temperature: trustAny(temperatureSchema).optional(),
|
|
15763
|
+
top_p: trustAny(topPSchema).optional(),
|
|
15764
|
+
mode: modeSchema.optional(),
|
|
15765
|
+
color: colorSchema.optional(),
|
|
15766
|
+
steps: stepsSchema.optional(),
|
|
15767
|
+
hidden: hiddenSchema.optional(),
|
|
15768
|
+
disable: disableSchema.optional(),
|
|
15769
|
+
skills: trustProtected(skillsSchema).optional(),
|
|
15770
|
+
permission: trustProtected(permissionSchema).optional()
|
|
15771
|
+
}).strict().superRefine(enforceVariantHasExplicitModel).meta({
|
|
15772
|
+
description: "Per-agent configuration overlay",
|
|
15773
|
+
examples: [
|
|
15774
|
+
{
|
|
15775
|
+
model: "anthropic/claude-opus-4-7",
|
|
15776
|
+
temperature: 0.1,
|
|
15777
|
+
mode: "subagent"
|
|
15778
|
+
}
|
|
15779
|
+
]
|
|
15780
|
+
});
|
|
15781
|
+
var CategoryOverlaySchema = exports_external.object({
|
|
15782
|
+
model: trustProtected(modelSchema).optional(),
|
|
15783
|
+
variant: trustProtected(variantSchema).optional(),
|
|
15784
|
+
temperature: trustAny(temperatureSchema).optional(),
|
|
15785
|
+
top_p: trustAny(topPSchema).optional(),
|
|
15786
|
+
mode: modeSchema.optional(),
|
|
15787
|
+
color: colorSchema.optional(),
|
|
15788
|
+
steps: stepsSchema.optional(),
|
|
15789
|
+
hidden: hiddenSchema.optional(),
|
|
15790
|
+
skills: trustProtected(skillsSchema).optional(),
|
|
15791
|
+
permission: trustProtected(permissionSchema).optional()
|
|
15792
|
+
}).strict().superRefine(enforceVariantHasExplicitModel).meta({
|
|
15793
|
+
description: "Per-category configuration overlay (same fields as agent minus disable)",
|
|
15794
|
+
examples: [{ model: "anthropic/claude-opus-4-7", temperature: 0.1 }]
|
|
15795
|
+
});
|
|
15796
|
+
var BootstrapSchema = exports_external.object({
|
|
15797
|
+
enabled: exports_external.boolean().default(true).meta({
|
|
15798
|
+
description: "Enable bootstrap prompt injection into every conversation",
|
|
15799
|
+
examples: [true, false]
|
|
15800
|
+
}),
|
|
15801
|
+
file: exports_external.string().optional().meta({
|
|
15802
|
+
description: "Path to a custom bootstrap prompt file",
|
|
15803
|
+
examples: ["~/.config/opencode/bootstrap.md"]
|
|
15804
|
+
})
|
|
15805
|
+
}).strict().meta({
|
|
15806
|
+
description: "Bootstrap prompt configuration",
|
|
15807
|
+
examples: [{ enabled: true }, { enabled: false }]
|
|
15808
|
+
});
|
|
15809
|
+
var SystematicConfigSchema = exports_external.object({
|
|
15810
|
+
$schema: exports_external.string().url().optional().meta({
|
|
15811
|
+
description: "JSON Schema URL for IDE autocomplete. The value is informational only \u2014 the loader does not fetch or validate against it. Add this to enable IDE schema activation and field-level autocomplete in editors that support JSON Schema (VSCode, Zed, IntelliJ).",
|
|
15812
|
+
examples: [
|
|
15813
|
+
"https://fro.bot/systematic/schemas/v2/systematic-config.schema.json"
|
|
15814
|
+
]
|
|
15815
|
+
}),
|
|
15816
|
+
agents: exports_external.record(exports_external.string(), AgentOverlaySchema).default({}).meta({
|
|
15817
|
+
description: "Per-agent configuration overlays keyed by agent name",
|
|
15818
|
+
examples: [{ "correctness-reviewer": { temperature: 0.1 } }, {}]
|
|
15819
|
+
}),
|
|
15820
|
+
categories: exports_external.record(exports_external.string(), CategoryOverlaySchema).default({}).meta({
|
|
15821
|
+
description: "Per-category configuration overlays keyed by category name",
|
|
15822
|
+
examples: [{ review: { model: "anthropic/claude-opus-4-7" } }, {}]
|
|
15823
|
+
}),
|
|
15824
|
+
disabled_skills: exports_external.array(exports_external.string()).default([]).meta({
|
|
15825
|
+
description: "Array of skill names to disable globally",
|
|
15826
|
+
examples: [["ce:plan", "ce:review"]]
|
|
15827
|
+
}),
|
|
15828
|
+
disabled_agents: exports_external.array(exports_external.string()).default([]).meta({
|
|
15829
|
+
description: "Array of agent names to disable globally",
|
|
15830
|
+
examples: [["previous-comments-reviewer", "cli-readiness-reviewer"]]
|
|
15831
|
+
}),
|
|
15832
|
+
disabled_commands: exports_external.array(exports_external.string()).default([]).meta({
|
|
15833
|
+
description: "Array of command names to disable globally",
|
|
15834
|
+
examples: [["deprecated-migration-helper"]]
|
|
15835
|
+
}),
|
|
15836
|
+
bootstrap: BootstrapSchema.default({ enabled: true }).meta({
|
|
15837
|
+
description: "Bootstrap prompt configuration",
|
|
15838
|
+
examples: [
|
|
15839
|
+
{ enabled: true },
|
|
15840
|
+
{ enabled: false, file: ".opencode/custom-prompt.md" }
|
|
15841
|
+
]
|
|
15842
|
+
})
|
|
15843
|
+
}).strict().meta({
|
|
15844
|
+
description: "Systematic user configuration file (systematic.json / systematic.jsonc)",
|
|
15845
|
+
examples: [{ disabled_skills: ["ce:plan"], bootstrap: { enabled: false } }]
|
|
15846
|
+
});
|
|
15847
|
+
var SECURITY_OVERLAY_FIELDS = [
|
|
15848
|
+
"model",
|
|
15849
|
+
"variant",
|
|
15850
|
+
"skills",
|
|
15851
|
+
"permission"
|
|
15772
15852
|
];
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
write: "write",
|
|
15782
|
-
edit: "edit",
|
|
15783
|
-
bash: "bash",
|
|
15784
|
-
grep: "grep",
|
|
15785
|
-
glob: "glob"
|
|
15786
|
-
};
|
|
15787
|
-
var PERMISSION_MODE_MAP = {
|
|
15788
|
-
full: {
|
|
15789
|
-
edit: "allow",
|
|
15790
|
-
bash: "allow",
|
|
15791
|
-
webfetch: "allow"
|
|
15792
|
-
},
|
|
15793
|
-
default: {
|
|
15794
|
-
edit: "ask",
|
|
15795
|
-
bash: "ask",
|
|
15796
|
-
webfetch: "ask"
|
|
15797
|
-
},
|
|
15798
|
-
plan: {
|
|
15799
|
-
edit: "deny",
|
|
15800
|
-
bash: "deny",
|
|
15801
|
-
webfetch: "ask"
|
|
15853
|
+
|
|
15854
|
+
// src/lib/config.ts
|
|
15855
|
+
var DEFAULT_CONFIG = {
|
|
15856
|
+
disabled_skills: [],
|
|
15857
|
+
disabled_agents: [],
|
|
15858
|
+
disabled_commands: [],
|
|
15859
|
+
bootstrap: {
|
|
15860
|
+
enabled: true
|
|
15802
15861
|
},
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
bash: "allow",
|
|
15806
|
-
webfetch: "allow"
|
|
15807
|
-
}
|
|
15862
|
+
agents: {},
|
|
15863
|
+
categories: {}
|
|
15808
15864
|
};
|
|
15809
|
-
|
|
15810
|
-
|
|
15811
|
-
|
|
15812
|
-
|
|
15813
|
-
|
|
15814
|
-
|
|
15815
|
-
|
|
15865
|
+
var SECURITY_OVERLAY_FIELDS2 = new Set(SECURITY_OVERLAY_FIELDS);
|
|
15866
|
+
function resolveConfigPath(dir, basename) {
|
|
15867
|
+
const jsoncPath = path3.join(dir, `${basename}.jsonc`);
|
|
15868
|
+
if (fs4.existsSync(jsoncPath))
|
|
15869
|
+
return jsoncPath;
|
|
15870
|
+
return path3.join(dir, `${basename}.json`);
|
|
15871
|
+
}
|
|
15872
|
+
function isErrorWithCode(error51) {
|
|
15873
|
+
return error51 instanceof Error && "code" in error51;
|
|
15874
|
+
}
|
|
15875
|
+
function loadJsoncFile(filePath) {
|
|
15876
|
+
let content;
|
|
15877
|
+
try {
|
|
15878
|
+
content = fs4.readFileSync(filePath, "utf-8");
|
|
15879
|
+
} catch (error51) {
|
|
15880
|
+
if (isErrorWithCode(error51) && error51.code === "ENOENT")
|
|
15881
|
+
return null;
|
|
15882
|
+
throw new Error(`Invalid Systematic config in ${filePath}: unable to read file`, { cause: error51 });
|
|
15816
15883
|
}
|
|
15817
|
-
|
|
15818
|
-
|
|
15884
|
+
const errors3 = [];
|
|
15885
|
+
const parsed = parse2(content, errors3);
|
|
15886
|
+
if (errors3.length > 0) {
|
|
15887
|
+
const error51 = errors3[0];
|
|
15888
|
+
const message = error51 ? `${printParseErrorCode(error51.error)} at offset ${error51.offset}` : "unknown parse error";
|
|
15889
|
+
throw new Error(`Invalid Systematic config in ${filePath}: JSONC parse error: ${message}`);
|
|
15819
15890
|
}
|
|
15820
|
-
if (
|
|
15821
|
-
|
|
15891
|
+
if (!isRecord2(parsed)) {
|
|
15892
|
+
throw new Error(`Invalid Systematic config in ${filePath}: root must be an object`);
|
|
15822
15893
|
}
|
|
15823
|
-
return
|
|
15894
|
+
return parsed;
|
|
15824
15895
|
}
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
const withPlaceholders = body.replace(CODE_BLOCK_PATTERN, (match) => {
|
|
15830
|
-
codeBlocks.push(match);
|
|
15831
|
-
return `__CODE_BLOCK_${placeholderIndex++}__`;
|
|
15832
|
-
});
|
|
15833
|
-
let result = withPlaceholders;
|
|
15834
|
-
for (const [pattern, replacement] of TOOL_MAPPINGS) {
|
|
15835
|
-
result = result.replace(pattern, replacement);
|
|
15836
|
-
}
|
|
15837
|
-
for (const [pattern, replacement] of PATH_REPLACEMENTS) {
|
|
15838
|
-
result = result.replace(pattern, replacement);
|
|
15839
|
-
}
|
|
15840
|
-
for (let i = 0;i < codeBlocks.length; i++) {
|
|
15841
|
-
result = result.replace(`__CODE_BLOCK_${i}__`, codeBlocks[i]);
|
|
15896
|
+
function throwTopLevelConfigSchemaError(filePath, trust, issues) {
|
|
15897
|
+
const issue2 = issues[0];
|
|
15898
|
+
if (!issue2) {
|
|
15899
|
+
throw Object.assign(new Error(`Invalid Systematic config in ${filePath}: schema validation failed`), { _tag: "ConfigSchemaError", filePath, trust, issues });
|
|
15842
15900
|
}
|
|
15843
|
-
|
|
15901
|
+
const fieldPath = issue2.code === "unrecognized_keys" ? issue2.message.match(/"([^"]+)"/)?.[1] ?? issue2.path.join(".") : issue2.path.join(".");
|
|
15902
|
+
const message = fieldPath ? `Invalid Systematic config in ${filePath}: ${fieldPath} ${issue2.message}` : `Invalid Systematic config in ${filePath}: ${issue2.message}`;
|
|
15903
|
+
throw Object.assign(new Error(message), {
|
|
15904
|
+
_tag: "ConfigSchemaError",
|
|
15905
|
+
filePath,
|
|
15906
|
+
trust,
|
|
15907
|
+
issues
|
|
15908
|
+
});
|
|
15844
15909
|
}
|
|
15845
|
-
function
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
if (
|
|
15851
|
-
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
if (/^gemini-/.test(model))
|
|
15855
|
-
return `google/${model}`;
|
|
15856
|
-
return `anthropic/${model}`;
|
|
15910
|
+
function loadConfigSource(filePath, trust) {
|
|
15911
|
+
const rawConfig = loadJsoncFile(filePath);
|
|
15912
|
+
if (!rawConfig)
|
|
15913
|
+
return null;
|
|
15914
|
+
const result = SystematicConfigSchema.safeParse(rawConfig);
|
|
15915
|
+
if (!result.success) {
|
|
15916
|
+
throwTopLevelConfigSchemaError(filePath, trust, result.error.issues);
|
|
15917
|
+
}
|
|
15918
|
+
return { path: filePath, config: rawConfig, trust };
|
|
15857
15919
|
}
|
|
15858
|
-
function
|
|
15859
|
-
|
|
15860
|
-
return TOOL_NAME_MAP[lower] ?? lower;
|
|
15920
|
+
function isRecord2(value) {
|
|
15921
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15861
15922
|
}
|
|
15862
|
-
function
|
|
15863
|
-
|
|
15923
|
+
function mergeArraysUnique(arr1, arr2) {
|
|
15924
|
+
const set2 = new Set;
|
|
15925
|
+
if (arr1)
|
|
15926
|
+
for (const item of arr1)
|
|
15927
|
+
set2.add(item);
|
|
15928
|
+
if (arr2)
|
|
15929
|
+
for (const item of arr2)
|
|
15930
|
+
set2.add(item);
|
|
15931
|
+
return Array.from(set2);
|
|
15864
15932
|
}
|
|
15865
|
-
function
|
|
15866
|
-
|
|
15867
|
-
if (isValidSteps(data.steps)) {
|
|
15868
|
-
delete data.maxTurns;
|
|
15869
|
-
delete data.maxSteps;
|
|
15870
|
-
}
|
|
15871
|
-
return;
|
|
15872
|
-
}
|
|
15873
|
-
const candidates = [];
|
|
15874
|
-
if (isValidSteps(data.maxTurns))
|
|
15875
|
-
candidates.push(data.maxTurns);
|
|
15876
|
-
if (isValidSteps(data.maxSteps))
|
|
15877
|
-
candidates.push(data.maxSteps);
|
|
15878
|
-
if (candidates.length > 0) {
|
|
15879
|
-
data.steps = Math.min(...candidates);
|
|
15880
|
-
delete data.maxTurns;
|
|
15881
|
-
delete data.maxSteps;
|
|
15882
|
-
}
|
|
15933
|
+
function loadConfig(projectDir) {
|
|
15934
|
+
return loadConfigWithSources(projectDir).config;
|
|
15883
15935
|
}
|
|
15884
|
-
function
|
|
15885
|
-
|
|
15886
|
-
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15936
|
+
function loadConfigWithSources(projectDir) {
|
|
15937
|
+
const paths = getConfigPaths(projectDir);
|
|
15938
|
+
const userSource = loadConfigSource(paths.userConfig, "user");
|
|
15939
|
+
const projectSource = loadConfigSource(paths.projectConfig, "project");
|
|
15940
|
+
const customSource = paths.customConfig ? loadConfigSource(paths.customConfig, "custom") : null;
|
|
15941
|
+
const sources = [userSource, projectSource, customSource].filter((source) => source !== null);
|
|
15942
|
+
const overlays = mergeOverlaySources(sources);
|
|
15943
|
+
const userConfig = userSource?.config;
|
|
15944
|
+
const projectConfig = projectSource?.config;
|
|
15945
|
+
const customConfig = customSource?.config;
|
|
15946
|
+
const result = {
|
|
15947
|
+
disabled_skills: mergeArraysUnique(mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_skills, userConfig?.disabled_skills), projectConfig?.disabled_skills), customConfig?.disabled_skills),
|
|
15948
|
+
disabled_agents: mergeArraysUnique(mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_agents, userConfig?.disabled_agents), projectConfig?.disabled_agents), customConfig?.disabled_agents),
|
|
15949
|
+
disabled_commands: mergeArraysUnique(mergeArraysUnique(mergeArraysUnique(DEFAULT_CONFIG.disabled_commands, userConfig?.disabled_commands), projectConfig?.disabled_commands), customConfig?.disabled_commands),
|
|
15950
|
+
bootstrap: {
|
|
15951
|
+
...DEFAULT_CONFIG.bootstrap,
|
|
15952
|
+
...userConfig?.bootstrap,
|
|
15953
|
+
...projectConfig?.bootstrap,
|
|
15954
|
+
...customConfig?.bootstrap
|
|
15955
|
+
},
|
|
15956
|
+
agents: overlayValues(overlays.agents),
|
|
15957
|
+
categories: overlayValues(overlays.categories)
|
|
15958
|
+
};
|
|
15959
|
+
return { config: result, overlays };
|
|
15905
15960
|
}
|
|
15906
|
-
function
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
|
|
15913
|
-
|
|
15914
|
-
}
|
|
15915
|
-
if (Object.keys(existing).length > 0) {
|
|
15916
|
-
data.tools = existing;
|
|
15961
|
+
function mergeOverlaySources(sources) {
|
|
15962
|
+
const result = {
|
|
15963
|
+
agents: {},
|
|
15964
|
+
categories: {}
|
|
15965
|
+
};
|
|
15966
|
+
for (const source of sources) {
|
|
15967
|
+
mergeOverlayMap(result.agents, source, "agents");
|
|
15968
|
+
mergeOverlayMap(result.categories, source, "categories");
|
|
15917
15969
|
}
|
|
15918
|
-
|
|
15970
|
+
return result;
|
|
15919
15971
|
}
|
|
15920
|
-
function
|
|
15921
|
-
|
|
15922
|
-
|
|
15923
|
-
if (normalized) {
|
|
15924
|
-
data.permission = normalized;
|
|
15925
|
-
delete data.permissionMode;
|
|
15926
|
-
return;
|
|
15927
|
-
}
|
|
15928
|
-
}
|
|
15929
|
-
if (typeof data.permissionMode !== "string")
|
|
15972
|
+
function mergeOverlayMap(target, source, mapKey) {
|
|
15973
|
+
const overlayMap = source.config[mapKey];
|
|
15974
|
+
if (overlayMap === undefined)
|
|
15930
15975
|
return;
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
delete data.permissionMode;
|
|
15934
|
-
}
|
|
15935
|
-
function mapHiddenField(data) {
|
|
15936
|
-
if (data["disable-model-invocation"] === true || data.disableModelInvocation === true) {
|
|
15937
|
-
data.hidden = true;
|
|
15938
|
-
delete data["disable-model-invocation"];
|
|
15939
|
-
delete data.disableModelInvocation;
|
|
15976
|
+
if (!isRecord2(overlayMap)) {
|
|
15977
|
+
throwInvalidOverlay(source.path, mapKey);
|
|
15940
15978
|
}
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15979
|
+
for (const [key, value] of Object.entries(overlayMap)) {
|
|
15980
|
+
const keyPath = `${mapKey}.${key}`;
|
|
15981
|
+
if (!isRecord2(value)) {
|
|
15982
|
+
throwInvalidOverlay(source.path, keyPath);
|
|
15983
|
+
}
|
|
15984
|
+
if (source.trust === "project") {
|
|
15985
|
+
rejectProjectSecurityOverlay(source.path, keyPath, value);
|
|
15986
|
+
}
|
|
15987
|
+
const previous = target[key];
|
|
15988
|
+
const nextValue = source.trust === "project" && previous ? preserveSecurityFields(previous.value, value) : value;
|
|
15989
|
+
target[key] = {
|
|
15990
|
+
value: nextValue,
|
|
15991
|
+
sourcePath: source.path,
|
|
15992
|
+
keyPath
|
|
15993
|
+
};
|
|
15947
15994
|
}
|
|
15948
15995
|
}
|
|
15949
|
-
function
|
|
15950
|
-
const
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
if (description) {
|
|
15955
|
-
result.description = description;
|
|
15956
|
-
} else if (name) {
|
|
15957
|
-
result.description = `${name} agent`;
|
|
15996
|
+
function rejectProjectSecurityOverlay(sourcePath, keyPath, value) {
|
|
15997
|
+
for (const field of SECURITY_OVERLAY_FIELDS2) {
|
|
15998
|
+
if (Object.hasOwn(value, field)) {
|
|
15999
|
+
throw new Error(`Invalid Systematic config in ${sourcePath}: ${keyPath}.${field} is only valid in user config or OPENCODE_CONFIG_DIR config`);
|
|
16000
|
+
}
|
|
15958
16001
|
}
|
|
15959
|
-
normalizeModelField(result);
|
|
15960
|
-
result.temperature = typeof data.temperature === "number" ? data.temperature : inferTemperature(name, description);
|
|
15961
|
-
mapStepsField(result);
|
|
15962
|
-
mapToolsField(result);
|
|
15963
|
-
mapPermissionMode(result);
|
|
15964
|
-
mapHiddenField(result);
|
|
15965
|
-
return result;
|
|
15966
16002
|
}
|
|
15967
|
-
function
|
|
15968
|
-
const result = { ...
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
16003
|
+
function preserveSecurityFields(previous, next) {
|
|
16004
|
+
const result = { ...next };
|
|
16005
|
+
for (const field of SECURITY_OVERLAY_FIELDS2) {
|
|
16006
|
+
if (Object.hasOwn(previous, field)) {
|
|
16007
|
+
result[field] = previous[field];
|
|
16008
|
+
}
|
|
15972
16009
|
}
|
|
15973
16010
|
return result;
|
|
15974
16011
|
}
|
|
15975
|
-
function
|
|
15976
|
-
const result = {
|
|
15977
|
-
|
|
16012
|
+
function overlayValues(overlays) {
|
|
16013
|
+
const result = {};
|
|
16014
|
+
for (const [key, overlay] of Object.entries(overlays)) {
|
|
16015
|
+
result[key] = overlay.value;
|
|
16016
|
+
}
|
|
15978
16017
|
return result;
|
|
15979
16018
|
}
|
|
15980
|
-
function
|
|
15981
|
-
|
|
15982
|
-
return "";
|
|
15983
|
-
const { data, body, hadFrontmatter, parseError } = parseFrontmatter(content);
|
|
15984
|
-
if (!hadFrontmatter) {
|
|
15985
|
-
return options.skipBodyTransform ? content : transformBody(content);
|
|
15986
|
-
}
|
|
15987
|
-
if (parseError) {
|
|
15988
|
-
return options.skipBodyTransform ? content : transformBody(content);
|
|
15989
|
-
}
|
|
15990
|
-
const shouldTransformBody = !options.skipBodyTransform;
|
|
15991
|
-
const transformedBody = shouldTransformBody ? transformBody(body) : body;
|
|
15992
|
-
if (type === "agent") {
|
|
15993
|
-
const agentMode = options.agentMode ?? "subagent";
|
|
15994
|
-
const transformedData = transformAgentFrontmatter(data, agentMode);
|
|
15995
|
-
return `${formatFrontmatter(transformedData)}
|
|
15996
|
-
${transformedBody}`;
|
|
15997
|
-
}
|
|
15998
|
-
if (type === "skill") {
|
|
15999
|
-
const transformedData = transformSkillFrontmatter(data);
|
|
16000
|
-
return `${formatFrontmatter(transformedData)}
|
|
16001
|
-
${transformedBody}`;
|
|
16002
|
-
}
|
|
16003
|
-
if (type === "command") {
|
|
16004
|
-
const transformedData = transformCommandFrontmatter(data);
|
|
16005
|
-
return `${formatFrontmatter(transformedData)}
|
|
16006
|
-
${transformedBody}`;
|
|
16007
|
-
}
|
|
16008
|
-
return content;
|
|
16019
|
+
function throwInvalidOverlay(sourcePath, keyPath) {
|
|
16020
|
+
throw new Error(`Invalid Systematic config in ${sourcePath}: ${keyPath} must be an object`);
|
|
16009
16021
|
}
|
|
16010
|
-
function
|
|
16011
|
-
const
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
|
|
16016
|
-
|
|
16017
|
-
|
|
16022
|
+
function getConfigPaths(projectDir) {
|
|
16023
|
+
const homeDir = os.homedir();
|
|
16024
|
+
const customConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
16025
|
+
const result = {
|
|
16026
|
+
userConfig: resolveConfigPath(path3.join(homeDir, ".config/opencode"), "systematic"),
|
|
16027
|
+
projectConfig: resolveConfigPath(path3.join(projectDir, ".opencode"), "systematic"),
|
|
16028
|
+
userDir: path3.join(homeDir, ".config/opencode/systematic"),
|
|
16029
|
+
projectDir: path3.join(projectDir, ".opencode/systematic"),
|
|
16030
|
+
...customConfigDir && {
|
|
16031
|
+
customConfig: resolveConfigPath(customConfigDir, "systematic"),
|
|
16032
|
+
customDir: path3.join(customConfigDir, "systematic")
|
|
16018
16033
|
}
|
|
16019
|
-
|
|
16020
|
-
|
|
16021
|
-
cache.set(cacheKey, { mtimeMs: stats.mtimeMs, converted });
|
|
16022
|
-
return converted;
|
|
16023
|
-
} finally {
|
|
16024
|
-
fs3.closeSync(fd);
|
|
16025
|
-
}
|
|
16034
|
+
};
|
|
16035
|
+
return result;
|
|
16026
16036
|
}
|
|
16027
16037
|
|
|
16028
|
-
// src/lib/
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
}
|
|
16045
|
-
}
|
|
16046
|
-
const argumentHintRaw = extractNonEmptyString(data, "argument-hint");
|
|
16047
|
-
const argumentHint = argumentHintRaw?.replace(/^["']|["']$/g, "") || undefined;
|
|
16048
|
-
return {
|
|
16049
|
-
name: extractString(data, "name"),
|
|
16050
|
-
description: extractString(data, "description"),
|
|
16051
|
-
license: extractNonEmptyString(data, "license"),
|
|
16052
|
-
compatibility: extractNonEmptyString(data, "compatibility"),
|
|
16053
|
-
metadata,
|
|
16054
|
-
disableModelInvocation: extractBoolean(data, "disable-model-invocation"),
|
|
16055
|
-
userInvocable: extractBoolean(data, "user-invocable"),
|
|
16056
|
-
subtask: data.context === "fork" ? true : extractBoolean(data, "subtask") ?? undefined,
|
|
16057
|
-
agent: extractNonEmptyString(data, "agent"),
|
|
16058
|
-
model: extractNonEmptyString(data, "model"),
|
|
16059
|
-
argumentHint: argumentHint !== "" ? argumentHint : undefined,
|
|
16060
|
-
allowedTools: extractNonEmptyString(data, "allowed-tools")
|
|
16061
|
-
};
|
|
16062
|
-
} catch {
|
|
16063
|
-
return { name: "", description: "" };
|
|
16038
|
+
// src/lib/agents.ts
|
|
16039
|
+
function findAgentsInDir(dir, maxDepth = 2) {
|
|
16040
|
+
const entries = walkDir(dir, {
|
|
16041
|
+
maxDepth,
|
|
16042
|
+
filter: (e) => !e.isDirectory && e.name.endsWith(".md")
|
|
16043
|
+
});
|
|
16044
|
+
return entries.map((entry) => ({
|
|
16045
|
+
name: entry.name.replace(/\.md$/, ""),
|
|
16046
|
+
file: entry.path,
|
|
16047
|
+
category: entry.category
|
|
16048
|
+
}));
|
|
16049
|
+
}
|
|
16050
|
+
function extractAgentFrontmatter(content) {
|
|
16051
|
+
const { data, parseError, body } = parseFrontmatter(content);
|
|
16052
|
+
if (parseError) {
|
|
16053
|
+
return { name: "", description: "", prompt: body.trim() };
|
|
16064
16054
|
}
|
|
16055
|
+
return {
|
|
16056
|
+
name: extractString(data, "name"),
|
|
16057
|
+
description: extractString(data, "description"),
|
|
16058
|
+
prompt: body.trim(),
|
|
16059
|
+
model: extractNonEmptyString(data, "model"),
|
|
16060
|
+
variant: extractNonEmptyString(data, "variant"),
|
|
16061
|
+
temperature: extractNumber(data, "temperature"),
|
|
16062
|
+
top_p: extractNumber(data, "top_p"),
|
|
16063
|
+
tools: isToolsMap(data.tools) ? data.tools : undefined,
|
|
16064
|
+
disable: extractBoolean(data, "disable"),
|
|
16065
|
+
mode: isAgentMode(data.mode) ? data.mode : undefined,
|
|
16066
|
+
color: extractNonEmptyString(data, "color"),
|
|
16067
|
+
steps: extractNumber(data, "steps"),
|
|
16068
|
+
hidden: extractBoolean(data, "hidden") ?? undefined,
|
|
16069
|
+
permission: normalizePermission(data.permission)
|
|
16070
|
+
};
|
|
16065
16071
|
}
|
|
16066
|
-
|
|
16067
|
-
|
|
16072
|
+
|
|
16073
|
+
// src/lib/commands.ts
|
|
16074
|
+
function findCommandsInDir(dir, maxDepth = 2) {
|
|
16068
16075
|
const entries = walkDir(dir, {
|
|
16069
16076
|
maxDepth,
|
|
16070
|
-
filter: (e) => e.isDirectory
|
|
16077
|
+
filter: (e) => !e.isDirectory && e.name.endsWith(".md")
|
|
16071
16078
|
});
|
|
16072
|
-
|
|
16073
|
-
const
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
}
|
|
16079
|
+
return entries.map((entry) => {
|
|
16080
|
+
const baseName = entry.name.replace(/\.md$/, "");
|
|
16081
|
+
const commandName = entry.category ? `/${entry.category}:${baseName}` : `/${baseName}`;
|
|
16082
|
+
return {
|
|
16083
|
+
name: commandName,
|
|
16084
|
+
file: entry.path,
|
|
16085
|
+
category: entry.category
|
|
16086
|
+
};
|
|
16087
|
+
});
|
|
16088
|
+
}
|
|
16089
|
+
function extractCommandFrontmatter(content) {
|
|
16090
|
+
const { data, parseError } = parseFrontmatter(content);
|
|
16091
|
+
if (parseError) {
|
|
16092
|
+
return {
|
|
16093
|
+
name: "",
|
|
16094
|
+
description: "",
|
|
16095
|
+
argumentHint: "",
|
|
16096
|
+
agent: undefined,
|
|
16097
|
+
model: undefined,
|
|
16098
|
+
subtask: undefined
|
|
16099
|
+
};
|
|
16093
16100
|
}
|
|
16094
|
-
|
|
16101
|
+
const argumentHintRaw = extractString(data, "argument-hint");
|
|
16102
|
+
return {
|
|
16103
|
+
name: extractString(data, "name"),
|
|
16104
|
+
description: extractString(data, "description"),
|
|
16105
|
+
argumentHint: argumentHintRaw.replace(/^["']|["']$/g, ""),
|
|
16106
|
+
agent: extractNonEmptyString(data, "agent"),
|
|
16107
|
+
model: extractNonEmptyString(data, "model"),
|
|
16108
|
+
subtask: extractBoolean(data, "subtask")
|
|
16109
|
+
};
|
|
16095
16110
|
}
|
|
16096
16111
|
|
|
16097
|
-
export { parseFrontmatter, exports_external, AgentOverlaySchema, CategoryOverlaySchema, loadConfig, loadConfigWithSources, getConfigPaths,
|
|
16112
|
+
export { parseFrontmatter, isRecord, convertContent, convertFileWithCache, findSkillsInDir, exports_external, AgentOverlaySchema, CategoryOverlaySchema, loadConfig, loadConfigWithSources, getConfigPaths, findAgentsInDir, extractAgentFrontmatter, findCommandsInDir, extractCommandFrontmatter };
|