@openchamber/web 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/bin/cli.js +561 -0
- package/dist/apple-touch-icon-120x120.png +0 -0
- package/dist/apple-touch-icon-152x152.png +0 -0
- package/dist/apple-touch-icon-167x167.png +0 -0
- package/dist/apple-touch-icon-180x180.png +0 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/apple-touch-icon.svg +18 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/MonacoDiffViewer-J2AIDXvs.js +1 -0
- package/dist/assets/ToolOutputDialog-B0y5ge-3.js +5 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/assets/index-iDfKTtMQ.css +1 -0
- package/dist/assets/index-kNntYPVa.js +2 -0
- package/dist/assets/main-BEJ2XliY.css +1 -0
- package/dist/assets/main-Ba339xde.js +59 -0
- package/dist/assets/vendor--B3aGWKBE.css +32 -0
- package/dist/assets/vendor-.pnpm-B1ce5n1Z.js +3192 -0
- package/dist/favicon-16.png +0 -0
- package/dist/favicon-32.png +0 -0
- package/dist/index.html +197 -0
- package/dist/logo-dark.svg +4 -0
- package/dist/logo-light.svg +4 -0
- package/dist/site.webmanifest +36 -0
- package/dist/vite.svg +1 -0
- package/package.json +92 -0
- package/public/apple-touch-icon-120x120.png +0 -0
- package/public/apple-touch-icon-152x152.png +0 -0
- package/public/apple-touch-icon-167x167.png +0 -0
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/apple-touch-icon.svg +18 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/logo-dark.svg +4 -0
- package/public/logo-light.svg +4 -0
- package/public/site.webmanifest +36 -0
- package/public/vite.svg +1 -0
- package/server/index.d.ts +28 -0
- package/server/index.js +3038 -0
- package/server/lib/git-identity-storage.js +108 -0
- package/server/lib/git-service.js +899 -0
- package/server/lib/opencode-config.js +471 -0
- package/server/lib/opencode-config.js.d.ts +12 -0
- package/server/lib/ui-auth.js +266 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import yaml from 'yaml';
|
|
5
|
+
import stripJsonComments from 'strip-json-comments';
|
|
6
|
+
|
|
7
|
+
const OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode');
|
|
8
|
+
const AGENT_DIR = path.join(OPENCODE_CONFIG_DIR, 'agent');
|
|
9
|
+
const COMMAND_DIR = path.join(OPENCODE_CONFIG_DIR, 'command');
|
|
10
|
+
const CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, 'opencode.json');
|
|
11
|
+
const PROMPT_FILE_PATTERN = /^\{file:(.+)\}$/i;
|
|
12
|
+
|
|
13
|
+
function ensureDirs() {
|
|
14
|
+
if (!fs.existsSync(OPENCODE_CONFIG_DIR)) {
|
|
15
|
+
fs.mkdirSync(OPENCODE_CONFIG_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
if (!fs.existsSync(AGENT_DIR)) {
|
|
18
|
+
fs.mkdirSync(AGENT_DIR, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
if (!fs.existsSync(COMMAND_DIR)) {
|
|
21
|
+
fs.mkdirSync(COMMAND_DIR, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isPromptFileReference(value) {
|
|
26
|
+
if (typeof value !== 'string') {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return PROMPT_FILE_PATTERN.test(value.trim());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolvePromptFilePath(reference) {
|
|
33
|
+
const match = typeof reference === 'string' ? reference.trim().match(PROMPT_FILE_PATTERN) : null;
|
|
34
|
+
if (!match) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
let target = match[1].trim();
|
|
38
|
+
if (!target) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (target.startsWith('./')) {
|
|
43
|
+
target = target.slice(2);
|
|
44
|
+
target = path.join(OPENCODE_CONFIG_DIR, target);
|
|
45
|
+
} else if (!path.isAbsolute(target)) {
|
|
46
|
+
target = path.join(OPENCODE_CONFIG_DIR, target);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return target;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function writePromptFile(filePath, content) {
|
|
53
|
+
const dir = path.dirname(filePath);
|
|
54
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
55
|
+
fs.writeFileSync(filePath, content ?? '', 'utf8');
|
|
56
|
+
console.log(`Updated prompt file: ${filePath}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readConfig() {
|
|
60
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
65
|
+
const normalized = stripJsonComments(content).trim();
|
|
66
|
+
if (!normalized) {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
return JSON.parse(normalized);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Failed to read config file:', error);
|
|
72
|
+
throw new Error('Failed to read OpenCode configuration');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeConfig(config) {
|
|
77
|
+
try {
|
|
78
|
+
|
|
79
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
80
|
+
const backupFile = `${CONFIG_FILE}.openchamber.backup`;
|
|
81
|
+
fs.copyFileSync(CONFIG_FILE, backupFile);
|
|
82
|
+
console.log(`Created config backup: ${backupFile}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
|
86
|
+
console.log('Successfully wrote config file');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Failed to write config file:', error);
|
|
89
|
+
throw new Error('Failed to write OpenCode configuration');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseMdFile(filePath) {
|
|
94
|
+
try {
|
|
95
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
96
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
97
|
+
|
|
98
|
+
if (!match) {
|
|
99
|
+
return { frontmatter: {}, body: content.trim() };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const frontmatter = yaml.parse(match[1]) || {};
|
|
103
|
+
const body = match[2].trim();
|
|
104
|
+
|
|
105
|
+
return { frontmatter, body };
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(`Failed to parse markdown file ${filePath}:`, error);
|
|
108
|
+
throw new Error('Failed to parse agent markdown file');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function writeMdFile(filePath, frontmatter, body) {
|
|
113
|
+
try {
|
|
114
|
+
const yamlStr = yaml.stringify(frontmatter);
|
|
115
|
+
const content = `---\n${yamlStr}---\n\n${body}`;
|
|
116
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
117
|
+
console.log(`Successfully wrote markdown file: ${filePath}`);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(`Failed to write markdown file ${filePath}:`, error);
|
|
120
|
+
throw new Error('Failed to write agent markdown file');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getAgentSources(agentName) {
|
|
125
|
+
const mdPath = path.join(AGENT_DIR, `${agentName}.md`);
|
|
126
|
+
const mdExists = fs.existsSync(mdPath);
|
|
127
|
+
|
|
128
|
+
const config = readConfig();
|
|
129
|
+
const jsonSection = config.agent?.[agentName];
|
|
130
|
+
|
|
131
|
+
const sources = {
|
|
132
|
+
md: {
|
|
133
|
+
exists: mdExists,
|
|
134
|
+
path: mdExists ? mdPath : null,
|
|
135
|
+
fields: []
|
|
136
|
+
},
|
|
137
|
+
json: {
|
|
138
|
+
exists: !!jsonSection,
|
|
139
|
+
path: CONFIG_FILE,
|
|
140
|
+
fields: []
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (mdExists) {
|
|
145
|
+
const { frontmatter, body } = parseMdFile(mdPath);
|
|
146
|
+
sources.md.fields = Object.keys(frontmatter);
|
|
147
|
+
if (body) {
|
|
148
|
+
sources.md.fields.push('prompt');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (jsonSection) {
|
|
153
|
+
sources.json.fields = Object.keys(jsonSection);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return sources;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function createAgent(agentName, config) {
|
|
160
|
+
ensureDirs();
|
|
161
|
+
|
|
162
|
+
const mdPath = path.join(AGENT_DIR, `${agentName}.md`);
|
|
163
|
+
|
|
164
|
+
if (fs.existsSync(mdPath)) {
|
|
165
|
+
throw new Error(`Agent ${agentName} already exists as .md file`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const existingConfig = readConfig();
|
|
169
|
+
if (existingConfig.agent?.[agentName]) {
|
|
170
|
+
throw new Error(`Agent ${agentName} already exists in opencode.json`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const { prompt, ...frontmatter } = config;
|
|
174
|
+
|
|
175
|
+
writeMdFile(mdPath, frontmatter, prompt || '');
|
|
176
|
+
console.log(`Created new agent: ${agentName}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function updateAgent(agentName, updates) {
|
|
180
|
+
ensureDirs();
|
|
181
|
+
|
|
182
|
+
const mdPath = path.join(AGENT_DIR, `${agentName}.md`);
|
|
183
|
+
const mdExists = fs.existsSync(mdPath);
|
|
184
|
+
|
|
185
|
+
let mdData = mdExists ? parseMdFile(mdPath) : null;
|
|
186
|
+
let config = readConfig();
|
|
187
|
+
const jsonSection = config.agent?.[agentName];
|
|
188
|
+
|
|
189
|
+
let mdModified = false;
|
|
190
|
+
let jsonModified = false;
|
|
191
|
+
|
|
192
|
+
for (const [field, value] of Object.entries(updates)) {
|
|
193
|
+
|
|
194
|
+
if (field === 'prompt') {
|
|
195
|
+
const normalizedValue = typeof value === 'string' ? value : (value == null ? '' : String(value));
|
|
196
|
+
|
|
197
|
+
if (mdExists) {
|
|
198
|
+
mdData.body = normalizedValue;
|
|
199
|
+
mdModified = true;
|
|
200
|
+
} else if (isPromptFileReference(jsonSection?.prompt)) {
|
|
201
|
+
const promptFilePath = resolvePromptFilePath(jsonSection.prompt);
|
|
202
|
+
if (!promptFilePath) {
|
|
203
|
+
throw new Error(`Invalid prompt file reference for agent ${agentName}`);
|
|
204
|
+
}
|
|
205
|
+
writePromptFile(promptFilePath, normalizedValue);
|
|
206
|
+
} else if (isPromptFileReference(normalizedValue)) {
|
|
207
|
+
if (!config.agent) config.agent = {};
|
|
208
|
+
if (!config.agent[agentName]) config.agent[agentName] = {};
|
|
209
|
+
config.agent[agentName].prompt = normalizedValue;
|
|
210
|
+
jsonModified = true;
|
|
211
|
+
} else {
|
|
212
|
+
if (!config.agent) config.agent = {};
|
|
213
|
+
if (!config.agent[agentName]) config.agent[agentName] = {};
|
|
214
|
+
config.agent[agentName].prompt = normalizedValue;
|
|
215
|
+
jsonModified = true;
|
|
216
|
+
}
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const inMd = mdData?.frontmatter?.[field] !== undefined;
|
|
221
|
+
const inJson = jsonSection?.[field] !== undefined;
|
|
222
|
+
|
|
223
|
+
if (inMd) {
|
|
224
|
+
|
|
225
|
+
mdData.frontmatter[field] = value;
|
|
226
|
+
mdModified = true;
|
|
227
|
+
} else if (inJson) {
|
|
228
|
+
|
|
229
|
+
if (!config.agent) config.agent = {};
|
|
230
|
+
if (!config.agent[agentName]) config.agent[agentName] = {};
|
|
231
|
+
config.agent[agentName][field] = value;
|
|
232
|
+
jsonModified = true;
|
|
233
|
+
} else {
|
|
234
|
+
|
|
235
|
+
if (mdExists && jsonSection) {
|
|
236
|
+
|
|
237
|
+
if (!config.agent) config.agent = {};
|
|
238
|
+
if (!config.agent[agentName]) config.agent[agentName] = {};
|
|
239
|
+
config.agent[agentName][field] = value;
|
|
240
|
+
jsonModified = true;
|
|
241
|
+
} else if (mdExists) {
|
|
242
|
+
|
|
243
|
+
mdData.frontmatter[field] = value;
|
|
244
|
+
mdModified = true;
|
|
245
|
+
} else {
|
|
246
|
+
|
|
247
|
+
if (!config.agent) config.agent = {};
|
|
248
|
+
if (!config.agent[agentName]) config.agent[agentName] = {};
|
|
249
|
+
config.agent[agentName][field] = value;
|
|
250
|
+
jsonModified = true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (mdModified) {
|
|
256
|
+
writeMdFile(mdPath, mdData.frontmatter, mdData.body);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (jsonModified) {
|
|
260
|
+
writeConfig(config);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
console.log(`Updated agent: ${agentName} (md: ${mdModified}, json: ${jsonModified})`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function deleteAgent(agentName) {
|
|
267
|
+
const mdPath = path.join(AGENT_DIR, `${agentName}.md`);
|
|
268
|
+
let deleted = false;
|
|
269
|
+
|
|
270
|
+
if (fs.existsSync(mdPath)) {
|
|
271
|
+
fs.unlinkSync(mdPath);
|
|
272
|
+
console.log(`Deleted agent .md file: ${mdPath}`);
|
|
273
|
+
deleted = true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const config = readConfig();
|
|
277
|
+
if (config.agent?.[agentName]) {
|
|
278
|
+
delete config.agent[agentName];
|
|
279
|
+
writeConfig(config);
|
|
280
|
+
console.log(`Removed agent from opencode.json: ${agentName}`);
|
|
281
|
+
deleted = true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!deleted) {
|
|
285
|
+
if (!config.agent) config.agent = {};
|
|
286
|
+
config.agent[agentName] = { disable: true };
|
|
287
|
+
writeConfig(config);
|
|
288
|
+
console.log(`Disabled built-in agent: ${agentName}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function getCommandSources(commandName) {
|
|
293
|
+
const mdPath = path.join(COMMAND_DIR, `${commandName}.md`);
|
|
294
|
+
const mdExists = fs.existsSync(mdPath);
|
|
295
|
+
|
|
296
|
+
const config = readConfig();
|
|
297
|
+
const jsonSection = config.command?.[commandName];
|
|
298
|
+
|
|
299
|
+
const sources = {
|
|
300
|
+
md: {
|
|
301
|
+
exists: mdExists,
|
|
302
|
+
path: mdExists ? mdPath : null,
|
|
303
|
+
fields: []
|
|
304
|
+
},
|
|
305
|
+
json: {
|
|
306
|
+
exists: !!jsonSection,
|
|
307
|
+
path: CONFIG_FILE,
|
|
308
|
+
fields: []
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
if (mdExists) {
|
|
313
|
+
const { frontmatter, body } = parseMdFile(mdPath);
|
|
314
|
+
sources.md.fields = Object.keys(frontmatter);
|
|
315
|
+
if (body) {
|
|
316
|
+
sources.md.fields.push('template');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (jsonSection) {
|
|
321
|
+
sources.json.fields = Object.keys(jsonSection);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return sources;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function createCommand(commandName, config) {
|
|
328
|
+
ensureDirs();
|
|
329
|
+
|
|
330
|
+
const mdPath = path.join(COMMAND_DIR, `${commandName}.md`);
|
|
331
|
+
|
|
332
|
+
if (fs.existsSync(mdPath)) {
|
|
333
|
+
throw new Error(`Command ${commandName} already exists as .md file`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const existingConfig = readConfig();
|
|
337
|
+
if (existingConfig.command?.[commandName]) {
|
|
338
|
+
throw new Error(`Command ${commandName} already exists in opencode.json`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const { template, ...frontmatter } = config;
|
|
342
|
+
|
|
343
|
+
writeMdFile(mdPath, frontmatter, template || '');
|
|
344
|
+
console.log(`Created new command: ${commandName}`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function updateCommand(commandName, updates) {
|
|
348
|
+
ensureDirs();
|
|
349
|
+
|
|
350
|
+
const mdPath = path.join(COMMAND_DIR, `${commandName}.md`);
|
|
351
|
+
const mdExists = fs.existsSync(mdPath);
|
|
352
|
+
|
|
353
|
+
let mdData = mdExists ? parseMdFile(mdPath) : null;
|
|
354
|
+
let config = readConfig();
|
|
355
|
+
const jsonSection = config.command?.[commandName];
|
|
356
|
+
|
|
357
|
+
let mdModified = false;
|
|
358
|
+
let jsonModified = false;
|
|
359
|
+
|
|
360
|
+
for (const [field, value] of Object.entries(updates)) {
|
|
361
|
+
|
|
362
|
+
if (field === 'template') {
|
|
363
|
+
const normalizedValue = typeof value === 'string' ? value : (value == null ? '' : String(value));
|
|
364
|
+
|
|
365
|
+
if (mdExists) {
|
|
366
|
+
mdData.body = normalizedValue;
|
|
367
|
+
mdModified = true;
|
|
368
|
+
} else if (isPromptFileReference(jsonSection?.template)) {
|
|
369
|
+
const templateFilePath = resolvePromptFilePath(jsonSection.template);
|
|
370
|
+
if (!templateFilePath) {
|
|
371
|
+
throw new Error(`Invalid template file reference for command ${commandName}`);
|
|
372
|
+
}
|
|
373
|
+
writePromptFile(templateFilePath, normalizedValue);
|
|
374
|
+
} else if (isPromptFileReference(normalizedValue)) {
|
|
375
|
+
if (!config.command) config.command = {};
|
|
376
|
+
if (!config.command[commandName]) config.command[commandName] = {};
|
|
377
|
+
config.command[commandName].template = normalizedValue;
|
|
378
|
+
jsonModified = true;
|
|
379
|
+
} else {
|
|
380
|
+
if (!config.command) config.command = {};
|
|
381
|
+
if (!config.command[commandName]) config.command[commandName] = {};
|
|
382
|
+
config.command[commandName].template = normalizedValue;
|
|
383
|
+
jsonModified = true;
|
|
384
|
+
}
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const inMd = mdData?.frontmatter?.[field] !== undefined;
|
|
389
|
+
const inJson = jsonSection?.[field] !== undefined;
|
|
390
|
+
|
|
391
|
+
if (inMd) {
|
|
392
|
+
|
|
393
|
+
mdData.frontmatter[field] = value;
|
|
394
|
+
mdModified = true;
|
|
395
|
+
} else if (inJson) {
|
|
396
|
+
|
|
397
|
+
if (!config.command) config.command = {};
|
|
398
|
+
if (!config.command[commandName]) config.command[commandName] = {};
|
|
399
|
+
config.command[commandName][field] = value;
|
|
400
|
+
jsonModified = true;
|
|
401
|
+
} else {
|
|
402
|
+
|
|
403
|
+
if (mdExists && jsonSection) {
|
|
404
|
+
|
|
405
|
+
if (!config.command) config.command = {};
|
|
406
|
+
if (!config.command[commandName]) config.command[commandName] = {};
|
|
407
|
+
config.command[commandName][field] = value;
|
|
408
|
+
jsonModified = true;
|
|
409
|
+
} else if (mdExists) {
|
|
410
|
+
|
|
411
|
+
mdData.frontmatter[field] = value;
|
|
412
|
+
mdModified = true;
|
|
413
|
+
} else {
|
|
414
|
+
|
|
415
|
+
if (!config.command) config.command = {};
|
|
416
|
+
if (!config.command[commandName]) config.command[commandName] = {};
|
|
417
|
+
config.command[commandName][field] = value;
|
|
418
|
+
jsonModified = true;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (mdModified) {
|
|
424
|
+
writeMdFile(mdPath, mdData.frontmatter, mdData.body);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (jsonModified) {
|
|
428
|
+
writeConfig(config);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
console.log(`Updated command: ${commandName} (md: ${mdModified}, json: ${jsonModified})`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function deleteCommand(commandName) {
|
|
435
|
+
const mdPath = path.join(COMMAND_DIR, `${commandName}.md`);
|
|
436
|
+
let deleted = false;
|
|
437
|
+
|
|
438
|
+
if (fs.existsSync(mdPath)) {
|
|
439
|
+
fs.unlinkSync(mdPath);
|
|
440
|
+
console.log(`Deleted command .md file: ${mdPath}`);
|
|
441
|
+
deleted = true;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const config = readConfig();
|
|
445
|
+
if (config.command?.[commandName]) {
|
|
446
|
+
delete config.command[commandName];
|
|
447
|
+
writeConfig(config);
|
|
448
|
+
console.log(`Removed command from opencode.json: ${commandName}`);
|
|
449
|
+
deleted = true;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (!deleted) {
|
|
453
|
+
throw new Error(`Command "${commandName}" not found`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export {
|
|
458
|
+
getAgentSources,
|
|
459
|
+
createAgent,
|
|
460
|
+
updateAgent,
|
|
461
|
+
deleteAgent,
|
|
462
|
+
getCommandSources,
|
|
463
|
+
createCommand,
|
|
464
|
+
updateCommand,
|
|
465
|
+
deleteCommand,
|
|
466
|
+
readConfig,
|
|
467
|
+
writeConfig,
|
|
468
|
+
AGENT_DIR,
|
|
469
|
+
COMMAND_DIR,
|
|
470
|
+
CONFIG_FILE
|
|
471
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare module "../server/lib/opencode-config.js" {
|
|
2
|
+
export function getAgentSources(agentName: string): {
|
|
3
|
+
md: { exists: boolean; path: string | null; fields: string[] };
|
|
4
|
+
json: { exists: boolean; path: string | null; fields: string[] };
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function createAgent(agentName: string, config: Record<string, unknown>): void;
|
|
8
|
+
|
|
9
|
+
export function updateAgent(agentName: string, updates: Record<string, unknown>): void;
|
|
10
|
+
|
|
11
|
+
export function deleteAgent(agentName: string): void;
|
|
12
|
+
}
|