@algochad/archcoder 2.0.2
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 +113 -0
- package/bin/cli-entry.js +55 -0
- package/bin/cli-output.js +145 -0
- package/bin/cli.js +5108 -0
- package/bin/cli.test.js +56 -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 +67 -0
- package/dist/assets/MultiRunWindow-BZp3MjJP.js +1 -0
- package/dist/assets/SettingsWindow-DoGYXpX7.js +1 -0
- package/dist/assets/TerminalView-BN7BR5Ff.js +3 -0
- package/dist/assets/TimelineDialog-ZQ33oVQR.js +1 -0
- package/dist/assets/ToolOutputDialog-Blv3pnug.js +16 -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-CtCEGYrr.css +1 -0
- package/dist/assets/index-o_d2wtWC.js +48 -0
- package/dist/assets/main-5QGBtzdq.css +1 -0
- package/dist/assets/main-B6oiMU86.js +8033 -0
- package/dist/assets/vendor--DbVqbJpV.css +1 -0
- package/dist/assets/vendor-.bun-HTKwyaEM.js +10086 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/worker-bqd4RMrj.js +155 -0
- package/dist/favicon-16.png +0 -0
- package/dist/favicon-32.png +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +67 -0
- package/dist/index.html +533 -0
- package/dist/logo-dark-192x192.png +0 -0
- package/dist/logo-dark-512x512.svg +16 -0
- package/dist/logo-light-192x192.png +0 -0
- package/dist/logo-light-512x512.svg +16 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/pwa-maskable-192.png +0 -0
- package/dist/pwa-maskable-512.png +0 -0
- package/dist/site.webmanifest +22 -0
- package/dist/sw.js +1 -0
- package/package.json +107 -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 +67 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +67 -0
- package/public/logo-dark-192x192.png +0 -0
- package/public/logo-dark-512x512.svg +16 -0
- package/public/logo-light-192x192.png +0 -0
- package/public/logo-light-512x512.svg +16 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/pwa-maskable-192.png +0 -0
- package/public/pwa-maskable-512.png +0 -0
- package/public/site.webmanifest +22 -0
- package/server/TERMINAL_INPUT_WS_PROTOCOL.md +44 -0
- package/server/index.d.ts +37 -0
- package/server/index.js +14694 -0
- package/server/lib/cloudflare-tunnel.js +650 -0
- package/server/lib/git/DOCUMENTATION.md +146 -0
- package/server/lib/git/credentials.js +74 -0
- package/server/lib/git/identity-storage.js +110 -0
- package/server/lib/git/index.js +6 -0
- package/server/lib/git/service.js +3117 -0
- package/server/lib/github/DOCUMENTATION.md +170 -0
- package/server/lib/github/auth.js +307 -0
- package/server/lib/github/device-flow.js +50 -0
- package/server/lib/github/index.js +24 -0
- package/server/lib/github/octokit.js +10 -0
- package/server/lib/github/pr-status.js +478 -0
- package/server/lib/github/repo/index.js +55 -0
- package/server/lib/installer/desktop.js +289 -0
- package/server/lib/installer/download.js +208 -0
- package/server/lib/installer/index.js +45 -0
- package/server/lib/installer/platform.js +100 -0
- package/server/lib/notifications/DOCUMENTATION.md +61 -0
- package/server/lib/notifications/index.js +1 -0
- package/server/lib/notifications/message.js +49 -0
- package/server/lib/notifications/message.test.js +59 -0
- package/server/lib/opencode/DOCUMENTATION.md +59 -0
- package/server/lib/opencode/agents.js +634 -0
- package/server/lib/opencode/auth.js +81 -0
- package/server/lib/opencode/commands.js +339 -0
- package/server/lib/opencode/index.js +66 -0
- package/server/lib/opencode/mcp.js +206 -0
- package/server/lib/opencode/providers.js +96 -0
- package/server/lib/opencode/shared.js +527 -0
- package/server/lib/opencode/skills.js +480 -0
- package/server/lib/opencode/tunnel-auth.js +591 -0
- package/server/lib/opencode/ui-auth.js +510 -0
- package/server/lib/package-manager.js +505 -0
- package/server/lib/quota/DOCUMENTATION.md +55 -0
- package/server/lib/quota/index.js +24 -0
- package/server/lib/quota/providers/claude.js +107 -0
- package/server/lib/quota/providers/codex.js +113 -0
- package/server/lib/quota/providers/copilot.js +165 -0
- package/server/lib/quota/providers/google/api.js +92 -0
- package/server/lib/quota/providers/google/auth.js +108 -0
- package/server/lib/quota/providers/google/index.js +124 -0
- package/server/lib/quota/providers/google/transforms.js +109 -0
- package/server/lib/quota/providers/index.js +152 -0
- package/server/lib/quota/providers/interface.js +55 -0
- package/server/lib/quota/providers/kimi.js +108 -0
- package/server/lib/quota/providers/minimax-cn-coding-plan.js +15 -0
- package/server/lib/quota/providers/minimax-coding-plan.js +15 -0
- package/server/lib/quota/providers/minimax-shared.js +136 -0
- package/server/lib/quota/providers/nanogpt.js +124 -0
- package/server/lib/quota/providers/ollama-cloud.js +112 -0
- package/server/lib/quota/providers/openai.js +91 -0
- package/server/lib/quota/providers/openrouter.js +92 -0
- package/server/lib/quota/providers/zai.js +91 -0
- package/server/lib/quota/utils/auth.js +46 -0
- package/server/lib/quota/utils/formatters.js +76 -0
- package/server/lib/quota/utils/index.js +10 -0
- package/server/lib/quota/utils/transformers.js +55 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +178 -0
- package/server/lib/skills-catalog/cache.js +32 -0
- package/server/lib/skills-catalog/clawdhub/api.js +158 -0
- package/server/lib/skills-catalog/clawdhub/index.js +30 -0
- package/server/lib/skills-catalog/clawdhub/install.js +238 -0
- package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
- package/server/lib/skills-catalog/curated-sources.js +21 -0
- package/server/lib/skills-catalog/git.js +77 -0
- package/server/lib/skills-catalog/index.js +42 -0
- package/server/lib/skills-catalog/install.js +294 -0
- package/server/lib/skills-catalog/scan.js +221 -0
- package/server/lib/skills-catalog/source.js +85 -0
- package/server/lib/terminal/DOCUMENTATION.md +114 -0
- package/server/lib/terminal/index.js +12 -0
- package/server/lib/terminal/input-ws-protocol.js +66 -0
- package/server/lib/terminal/input-ws-protocol.test.js +138 -0
- package/server/lib/tts/DOCUMENTATION.md +134 -0
- package/server/lib/tts/index.js +16 -0
- package/server/lib/tts/service.js +162 -0
- package/server/lib/tts/summarization.js +171 -0
- package/server/lib/tunnels/index.js +166 -0
- package/server/lib/tunnels/providers/cloudflare.js +260 -0
- package/server/lib/tunnels/registry.js +51 -0
- package/server/lib/tunnels/types.js +219 -0
- package/server/lib/utils/lru.js +107 -0
- package/server/lib/utils/sse.js +121 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import {
|
|
4
|
+
CONFIG_FILE,
|
|
5
|
+
AGENT_DIR,
|
|
6
|
+
AGENT_SCOPE,
|
|
7
|
+
ensureDirs,
|
|
8
|
+
parseMdFile,
|
|
9
|
+
writeMdFile,
|
|
10
|
+
readConfigLayers,
|
|
11
|
+
readConfigFile,
|
|
12
|
+
writeConfig,
|
|
13
|
+
getJsonEntrySource,
|
|
14
|
+
getJsonWriteTarget,
|
|
15
|
+
isPromptFileReference,
|
|
16
|
+
resolvePromptFilePath,
|
|
17
|
+
writePromptFile,
|
|
18
|
+
} from './shared.js';
|
|
19
|
+
|
|
20
|
+
// ============== AGENT SCOPE HELPERS ==============
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Ensure project-level agent directory exists
|
|
24
|
+
*/
|
|
25
|
+
function ensureProjectAgentDir(workingDirectory) {
|
|
26
|
+
const projectAgentDir = path.join(workingDirectory, '.opencode', 'agents');
|
|
27
|
+
if (!fs.existsSync(projectAgentDir)) {
|
|
28
|
+
fs.mkdirSync(projectAgentDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
const legacyProjectAgentDir = path.join(workingDirectory, '.opencode', 'agent');
|
|
31
|
+
if (!fs.existsSync(legacyProjectAgentDir)) {
|
|
32
|
+
fs.mkdirSync(legacyProjectAgentDir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
return projectAgentDir;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get project-level agent path
|
|
39
|
+
*/
|
|
40
|
+
function getProjectAgentPath(workingDirectory, agentName) {
|
|
41
|
+
const pluralPath = path.join(workingDirectory, '.opencode', 'agents', `${agentName}.md`);
|
|
42
|
+
const legacyPath = path.join(workingDirectory, '.opencode', 'agent', `${agentName}.md`);
|
|
43
|
+
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
|
|
44
|
+
return pluralPath;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a per-request lookup cache for user-level agent path resolution.
|
|
49
|
+
*/
|
|
50
|
+
function createAgentLookupCache() {
|
|
51
|
+
return {
|
|
52
|
+
userAgentIndexByName: new Map(),
|
|
53
|
+
userAgentLookupByName: new Map(),
|
|
54
|
+
userAgentIndexReady: false,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildUserAgentIndex(cache) {
|
|
59
|
+
if (cache.userAgentIndexReady) return;
|
|
60
|
+
cache.userAgentIndexReady = true;
|
|
61
|
+
|
|
62
|
+
if (!fs.existsSync(AGENT_DIR)) return;
|
|
63
|
+
|
|
64
|
+
const dirsToVisit = [AGENT_DIR];
|
|
65
|
+
while (dirsToVisit.length > 0) {
|
|
66
|
+
const dir = dirsToVisit.pop();
|
|
67
|
+
let entries;
|
|
68
|
+
try {
|
|
69
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
70
|
+
} catch {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
75
|
+
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
78
|
+
const agentName = entry.name.slice(0, -3);
|
|
79
|
+
if (!cache.userAgentIndexByName.has(agentName)) {
|
|
80
|
+
cache.userAgentIndexByName.set(agentName, path.join(dir, entry.name));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
85
|
+
const entry = entries[i];
|
|
86
|
+
if (entry.isDirectory()) {
|
|
87
|
+
dirsToVisit.push(path.join(dir, entry.name));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getIndexedUserAgentPath(agentName, cache) {
|
|
94
|
+
if (cache.userAgentLookupByName.has(agentName)) {
|
|
95
|
+
return cache.userAgentLookupByName.get(agentName);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
buildUserAgentIndex(cache);
|
|
99
|
+
const found = cache.userAgentIndexByName.get(agentName) || null;
|
|
100
|
+
cache.userAgentLookupByName.set(agentName, found);
|
|
101
|
+
return found;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get user-level agent path — walks subfolders to support grouped layouts.
|
|
106
|
+
* e.g. ~/.config/opencode/agents/business/ceo-diginno.md
|
|
107
|
+
*/
|
|
108
|
+
function getUserAgentPath(agentName, lookupCache = null) {
|
|
109
|
+
// 1. Check flat path first (legacy / newly created agents)
|
|
110
|
+
const pluralPath = path.join(AGENT_DIR, `${agentName}.md`);
|
|
111
|
+
if (fs.existsSync(pluralPath)) return pluralPath;
|
|
112
|
+
|
|
113
|
+
const legacyPath = path.join(AGENT_DIR, '..', 'agent', `${agentName}.md`);
|
|
114
|
+
if (fs.existsSync(legacyPath)) return legacyPath;
|
|
115
|
+
|
|
116
|
+
// 2. Lookup subfolders for grouped layout
|
|
117
|
+
const cache = lookupCache || createAgentLookupCache();
|
|
118
|
+
const found = getIndexedUserAgentPath(agentName, cache);
|
|
119
|
+
if (found) return found;
|
|
120
|
+
|
|
121
|
+
// 3. Return expected flat path as default (for new agent creation)
|
|
122
|
+
return pluralPath;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Determine agent scope based on where the .md file exists
|
|
127
|
+
* Priority: project level > user level > null (built-in only)
|
|
128
|
+
*/
|
|
129
|
+
function getAgentScope(agentName, workingDirectory, lookupCache = null) {
|
|
130
|
+
if (workingDirectory) {
|
|
131
|
+
const projectPath = getProjectAgentPath(workingDirectory, agentName);
|
|
132
|
+
if (fs.existsSync(projectPath)) {
|
|
133
|
+
return { scope: AGENT_SCOPE.PROJECT, path: projectPath };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const userPath = getUserAgentPath(agentName, lookupCache);
|
|
138
|
+
if (fs.existsSync(userPath)) {
|
|
139
|
+
return { scope: AGENT_SCOPE.USER, path: userPath };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { scope: null, path: null };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get the path where an agent should be written based on scope
|
|
147
|
+
*/
|
|
148
|
+
function getAgentWritePath(agentName, workingDirectory, requestedScope, lookupCache = null) {
|
|
149
|
+
// For updates: check existing location first (project takes precedence)
|
|
150
|
+
const existing = getAgentScope(agentName, workingDirectory, lookupCache);
|
|
151
|
+
if (existing.path) {
|
|
152
|
+
return existing;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// For new agents or built-in overrides: use requested scope or default to user
|
|
156
|
+
const scope = requestedScope || AGENT_SCOPE.USER;
|
|
157
|
+
if (scope === AGENT_SCOPE.PROJECT && workingDirectory) {
|
|
158
|
+
return {
|
|
159
|
+
scope: AGENT_SCOPE.PROJECT,
|
|
160
|
+
path: getProjectAgentPath(workingDirectory, agentName)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
scope: AGENT_SCOPE.USER,
|
|
166
|
+
path: getUserAgentPath(agentName, lookupCache)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Detect where an agent's permission field is currently defined
|
|
172
|
+
* Priority: project .md > user .md > project JSON > user JSON
|
|
173
|
+
* Returns: { source: 'md'|'json'|null, scope: 'project'|'user'|null, path: string|null }
|
|
174
|
+
*/
|
|
175
|
+
function getAgentPermissionSource(agentName, workingDirectory, lookupCache = null) {
|
|
176
|
+
// Check project-level .md first
|
|
177
|
+
if (workingDirectory) {
|
|
178
|
+
const projectMdPath = getProjectAgentPath(workingDirectory, agentName);
|
|
179
|
+
if (fs.existsSync(projectMdPath)) {
|
|
180
|
+
const { frontmatter } = parseMdFile(projectMdPath);
|
|
181
|
+
if (frontmatter.permission !== undefined) {
|
|
182
|
+
return { source: 'md', scope: AGENT_SCOPE.PROJECT, path: projectMdPath };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check user-level .md
|
|
188
|
+
const userMdPath = getUserAgentPath(agentName, lookupCache);
|
|
189
|
+
if (fs.existsSync(userMdPath)) {
|
|
190
|
+
const { frontmatter } = parseMdFile(userMdPath);
|
|
191
|
+
if (frontmatter.permission !== undefined) {
|
|
192
|
+
return { source: 'md', scope: AGENT_SCOPE.USER, path: userMdPath };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check JSON layers (project > user)
|
|
197
|
+
const layers = readConfigLayers(workingDirectory);
|
|
198
|
+
|
|
199
|
+
// Project opencode.json
|
|
200
|
+
const projectJsonPermission = layers.projectConfig?.agent?.[agentName]?.permission;
|
|
201
|
+
if (projectJsonPermission !== undefined && layers.paths.projectPath) {
|
|
202
|
+
return { source: 'json', scope: AGENT_SCOPE.PROJECT, path: layers.paths.projectPath };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// User opencode.json
|
|
206
|
+
const userJsonPermission = layers.userConfig?.agent?.[agentName]?.permission;
|
|
207
|
+
if (userJsonPermission !== undefined) {
|
|
208
|
+
return { source: 'json', scope: AGENT_SCOPE.USER, path: layers.paths.userPath };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Custom config (env var)
|
|
212
|
+
const customJsonPermission = layers.customConfig?.agent?.[agentName]?.permission;
|
|
213
|
+
if (customJsonPermission !== undefined && layers.paths.customPath) {
|
|
214
|
+
return { source: 'json', scope: 'custom', path: layers.paths.customPath };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { source: null, scope: null, path: null };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function mergePermissionWithNonWildcards(newPermission, permissionSource, agentName) {
|
|
221
|
+
if (!permissionSource.source || !permissionSource.path) {
|
|
222
|
+
return newPermission;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let existingPermission = null;
|
|
226
|
+
if (permissionSource.source === 'md') {
|
|
227
|
+
const { frontmatter } = parseMdFile(permissionSource.path);
|
|
228
|
+
existingPermission = frontmatter.permission;
|
|
229
|
+
} else if (permissionSource.source === 'json') {
|
|
230
|
+
const config = readConfigFile(permissionSource.path);
|
|
231
|
+
existingPermission = config?.agent?.[agentName]?.permission;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!existingPermission || typeof existingPermission === 'string') {
|
|
235
|
+
return newPermission;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (newPermission == null) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (typeof newPermission === 'string') {
|
|
243
|
+
return newPermission;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const nonWildcardPatterns = {};
|
|
247
|
+
for (const [permKey, permValue] of Object.entries(existingPermission)) {
|
|
248
|
+
if (permKey === '*') continue;
|
|
249
|
+
|
|
250
|
+
if (typeof permValue === 'object' && permValue !== null && !Array.isArray(permValue)) {
|
|
251
|
+
const nonWildcards = {};
|
|
252
|
+
for (const [pattern, action] of Object.entries(permValue)) {
|
|
253
|
+
if (pattern !== '*') {
|
|
254
|
+
nonWildcards[pattern] = action;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (Object.keys(nonWildcards).length > 0) {
|
|
258
|
+
nonWildcardPatterns[permKey] = nonWildcards;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (Object.keys(nonWildcardPatterns).length === 0) {
|
|
264
|
+
return newPermission;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const merged = { ...newPermission };
|
|
268
|
+
for (const [permKey, patterns] of Object.entries(nonWildcardPatterns)) {
|
|
269
|
+
const newValue = merged[permKey];
|
|
270
|
+
if (typeof newValue === 'string') {
|
|
271
|
+
merged[permKey] = { '*': newValue, ...patterns };
|
|
272
|
+
} else if (typeof newValue === 'object' && newValue !== null) {
|
|
273
|
+
merged[permKey] = { ...patterns, ...newValue };
|
|
274
|
+
} else {
|
|
275
|
+
const existingValue = existingPermission[permKey];
|
|
276
|
+
if (typeof existingValue === 'object' && existingValue !== null) {
|
|
277
|
+
const wildcard = existingValue['*'];
|
|
278
|
+
merged[permKey] = wildcard ? { '*': wildcard, ...patterns } : patterns;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return merged;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getAgentSources(agentName, workingDirectory, lookupCache = createAgentLookupCache()) {
|
|
287
|
+
const projectPath = workingDirectory ? getProjectAgentPath(workingDirectory, agentName) : null;
|
|
288
|
+
const projectExists = projectPath && fs.existsSync(projectPath);
|
|
289
|
+
|
|
290
|
+
const userPath = getUserAgentPath(agentName, lookupCache);
|
|
291
|
+
const userExists = fs.existsSync(userPath);
|
|
292
|
+
|
|
293
|
+
const mdPath = projectExists ? projectPath : (userExists ? userPath : null);
|
|
294
|
+
const mdExists = !!mdPath;
|
|
295
|
+
const mdScope = projectExists ? AGENT_SCOPE.PROJECT : (userExists ? AGENT_SCOPE.USER : null);
|
|
296
|
+
|
|
297
|
+
const layers = readConfigLayers(workingDirectory);
|
|
298
|
+
const jsonSource = getJsonEntrySource(layers, 'agent', agentName);
|
|
299
|
+
const jsonSection = jsonSource.section;
|
|
300
|
+
const jsonPath = jsonSource.path || layers.paths.customPath || layers.paths.projectPath || layers.paths.userPath;
|
|
301
|
+
const jsonScope = jsonSource.path === layers.paths.projectPath ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER;
|
|
302
|
+
|
|
303
|
+
const sources = {
|
|
304
|
+
md: {
|
|
305
|
+
exists: mdExists,
|
|
306
|
+
path: mdPath,
|
|
307
|
+
scope: mdScope,
|
|
308
|
+
fields: []
|
|
309
|
+
},
|
|
310
|
+
json: {
|
|
311
|
+
exists: jsonSource.exists,
|
|
312
|
+
path: jsonPath,
|
|
313
|
+
scope: jsonSource.exists ? jsonScope : null,
|
|
314
|
+
fields: []
|
|
315
|
+
},
|
|
316
|
+
projectMd: {
|
|
317
|
+
exists: projectExists,
|
|
318
|
+
path: projectPath
|
|
319
|
+
},
|
|
320
|
+
userMd: {
|
|
321
|
+
exists: userExists,
|
|
322
|
+
path: userPath
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
if (mdExists) {
|
|
327
|
+
const { frontmatter, body } = parseMdFile(mdPath);
|
|
328
|
+
sources.md.fields = Object.keys(frontmatter);
|
|
329
|
+
if (body) {
|
|
330
|
+
sources.md.fields.push('prompt');
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (jsonSection) {
|
|
335
|
+
sources.json.fields = Object.keys(jsonSection);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return sources;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function getAgentConfig(agentName, workingDirectory, lookupCache = createAgentLookupCache()) {
|
|
342
|
+
const projectPath = workingDirectory ? getProjectAgentPath(workingDirectory, agentName) : null;
|
|
343
|
+
const projectExists = projectPath && fs.existsSync(projectPath);
|
|
344
|
+
|
|
345
|
+
const userPath = getUserAgentPath(agentName, lookupCache);
|
|
346
|
+
const userExists = fs.existsSync(userPath);
|
|
347
|
+
|
|
348
|
+
if (projectExists || userExists) {
|
|
349
|
+
const mdPath = projectExists ? projectPath : userPath;
|
|
350
|
+
const { frontmatter, body } = parseMdFile(mdPath);
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
source: 'md',
|
|
354
|
+
scope: projectExists ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER,
|
|
355
|
+
config: {
|
|
356
|
+
...frontmatter,
|
|
357
|
+
...(typeof body === 'string' && body.length > 0 ? { prompt: body } : {}),
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const layers = readConfigLayers(workingDirectory);
|
|
363
|
+
const jsonSource = getJsonEntrySource(layers, 'agent', agentName);
|
|
364
|
+
|
|
365
|
+
if (jsonSource.exists && jsonSource.section) {
|
|
366
|
+
const scope = jsonSource.path === layers.paths.projectPath ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER;
|
|
367
|
+
return {
|
|
368
|
+
source: 'json',
|
|
369
|
+
scope,
|
|
370
|
+
config: { ...jsonSource.section },
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
source: 'none',
|
|
376
|
+
scope: null,
|
|
377
|
+
config: {},
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function createAgent(agentName, config, workingDirectory, scope) {
|
|
382
|
+
ensureDirs();
|
|
383
|
+
const lookupCache = createAgentLookupCache();
|
|
384
|
+
|
|
385
|
+
const projectPath = workingDirectory ? getProjectAgentPath(workingDirectory, agentName) : null;
|
|
386
|
+
const userPath = getUserAgentPath(agentName, lookupCache);
|
|
387
|
+
|
|
388
|
+
if (projectPath && fs.existsSync(projectPath)) {
|
|
389
|
+
throw new Error(`Agent ${agentName} already exists as project-level .md file`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (fs.existsSync(userPath)) {
|
|
393
|
+
throw new Error(`Agent ${agentName} already exists as user-level .md file`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const layers = readConfigLayers(workingDirectory);
|
|
397
|
+
const jsonSource = getJsonEntrySource(layers, 'agent', agentName);
|
|
398
|
+
if (jsonSource.exists) {
|
|
399
|
+
throw new Error(`Agent ${agentName} already exists in opencode.json`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let targetPath;
|
|
403
|
+
let targetScope;
|
|
404
|
+
|
|
405
|
+
if (scope === AGENT_SCOPE.PROJECT && workingDirectory) {
|
|
406
|
+
ensureProjectAgentDir(workingDirectory);
|
|
407
|
+
targetPath = projectPath;
|
|
408
|
+
targetScope = AGENT_SCOPE.PROJECT;
|
|
409
|
+
} else {
|
|
410
|
+
targetPath = userPath;
|
|
411
|
+
targetScope = AGENT_SCOPE.USER;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const { prompt, scope: _scopeFromConfig, ...frontmatter } = config;
|
|
415
|
+
|
|
416
|
+
writeMdFile(targetPath, frontmatter, prompt || '');
|
|
417
|
+
console.log(`Created new agent: ${agentName} (scope: ${targetScope}, path: ${targetPath})`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function updateAgent(agentName, updates, workingDirectory) {
|
|
421
|
+
ensureDirs();
|
|
422
|
+
const lookupCache = createAgentLookupCache();
|
|
423
|
+
|
|
424
|
+
const { scope, path: mdPath } = getAgentWritePath(agentName, workingDirectory, undefined, lookupCache);
|
|
425
|
+
const mdExists = mdPath && fs.existsSync(mdPath);
|
|
426
|
+
|
|
427
|
+
const layers = readConfigLayers(workingDirectory);
|
|
428
|
+
const jsonSource = getJsonEntrySource(layers, 'agent', agentName);
|
|
429
|
+
const jsonSection = jsonSource.section;
|
|
430
|
+
const hasJsonFields = jsonSource.exists && jsonSection && Object.keys(jsonSection).length > 0;
|
|
431
|
+
const jsonTarget = jsonSource.exists
|
|
432
|
+
? { config: jsonSource.config, path: jsonSource.path }
|
|
433
|
+
: getJsonWriteTarget(layers, AGENT_SCOPE.USER);
|
|
434
|
+
let config = jsonTarget.config || {};
|
|
435
|
+
|
|
436
|
+
const isBuiltinOverride = !mdExists && !hasJsonFields;
|
|
437
|
+
|
|
438
|
+
let targetPath = mdPath;
|
|
439
|
+
let targetScope = scope;
|
|
440
|
+
|
|
441
|
+
if (!mdExists && isBuiltinOverride) {
|
|
442
|
+
targetPath = getUserAgentPath(agentName, lookupCache);
|
|
443
|
+
targetScope = AGENT_SCOPE.USER;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
let mdData = mdExists ? parseMdFile(mdPath) : (isBuiltinOverride ? { frontmatter: {}, body: '' } : null);
|
|
447
|
+
|
|
448
|
+
let mdModified = false;
|
|
449
|
+
let jsonModified = false;
|
|
450
|
+
const creatingNewMd = isBuiltinOverride;
|
|
451
|
+
|
|
452
|
+
for (const [field, value] of Object.entries(updates)) {
|
|
453
|
+
if (field === 'prompt') {
|
|
454
|
+
const normalizedValue = typeof value === 'string' ? value : (value == null ? '' : String(value));
|
|
455
|
+
|
|
456
|
+
if (mdExists || creatingNewMd) {
|
|
457
|
+
if (mdData) {
|
|
458
|
+
mdData.body = normalizedValue;
|
|
459
|
+
mdModified = true;
|
|
460
|
+
}
|
|
461
|
+
continue;
|
|
462
|
+
} else if (isPromptFileReference(jsonSection?.prompt)) {
|
|
463
|
+
const promptFilePath = resolvePromptFilePath(jsonSection.prompt);
|
|
464
|
+
if (!promptFilePath) {
|
|
465
|
+
throw new Error(`Invalid prompt file reference for agent ${agentName}`);
|
|
466
|
+
}
|
|
467
|
+
writePromptFile(promptFilePath, normalizedValue);
|
|
468
|
+
continue;
|
|
469
|
+
} else if (isPromptFileReference(normalizedValue)) {
|
|
470
|
+
if (!config.agent) config.agent = {};
|
|
471
|
+
if (!config.agent[agentName]) config.agent[agentName] = {};
|
|
472
|
+
config.agent[agentName].prompt = normalizedValue;
|
|
473
|
+
jsonModified = true;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (!config.agent) config.agent = {};
|
|
478
|
+
if (!config.agent[agentName]) config.agent[agentName] = {};
|
|
479
|
+
config.agent[agentName].prompt = normalizedValue;
|
|
480
|
+
jsonModified = true;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (field === 'permission') {
|
|
485
|
+
const permissionSource = getAgentPermissionSource(agentName, workingDirectory, lookupCache);
|
|
486
|
+
const newPermission = mergePermissionWithNonWildcards(value, permissionSource, agentName);
|
|
487
|
+
|
|
488
|
+
if (permissionSource.source === 'md') {
|
|
489
|
+
const existingMdData = parseMdFile(permissionSource.path);
|
|
490
|
+
existingMdData.frontmatter.permission = newPermission;
|
|
491
|
+
writeMdFile(permissionSource.path, existingMdData.frontmatter, existingMdData.body);
|
|
492
|
+
console.log(`Updated permission in .md file: ${permissionSource.path}`);
|
|
493
|
+
} else if (permissionSource.source === 'json') {
|
|
494
|
+
const existingConfig = readConfigFile(permissionSource.path);
|
|
495
|
+
if (!existingConfig.agent) existingConfig.agent = {};
|
|
496
|
+
if (!existingConfig.agent[agentName]) existingConfig.agent[agentName] = {};
|
|
497
|
+
existingConfig.agent[agentName].permission = newPermission;
|
|
498
|
+
writeConfig(existingConfig, permissionSource.path);
|
|
499
|
+
console.log(`Updated permission in JSON: ${permissionSource.path}`);
|
|
500
|
+
} else {
|
|
501
|
+
if ((mdExists || creatingNewMd) && mdData) {
|
|
502
|
+
mdData.frontmatter.permission = newPermission;
|
|
503
|
+
mdModified = true;
|
|
504
|
+
} else if (hasJsonFields) {
|
|
505
|
+
if (!config.agent) config.agent = {};
|
|
506
|
+
if (!config.agent[agentName]) config.agent[agentName] = {};
|
|
507
|
+
config.agent[agentName].permission = newPermission;
|
|
508
|
+
jsonModified = true;
|
|
509
|
+
} else {
|
|
510
|
+
const writeTarget = workingDirectory
|
|
511
|
+
? { config: layers.projectConfig || {}, path: layers.paths.projectPath || layers.paths.userPath }
|
|
512
|
+
: { config: layers.userConfig || {}, path: layers.paths.userPath };
|
|
513
|
+
if (!writeTarget.config.agent) writeTarget.config.agent = {};
|
|
514
|
+
if (!writeTarget.config.agent[agentName]) writeTarget.config.agent[agentName] = {};
|
|
515
|
+
writeTarget.config.agent[agentName].permission = newPermission;
|
|
516
|
+
writeConfig(writeTarget.config, writeTarget.path);
|
|
517
|
+
console.log(`Created permission in JSON: ${writeTarget.path}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const inMd = mdData?.frontmatter?.[field] !== undefined;
|
|
524
|
+
const inJson = jsonSection?.[field] !== undefined;
|
|
525
|
+
|
|
526
|
+
if (value === null) {
|
|
527
|
+
if (mdData && inMd) {
|
|
528
|
+
delete mdData.frontmatter[field];
|
|
529
|
+
mdModified = true;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (inJson && config.agent?.[agentName]) {
|
|
533
|
+
delete config.agent[agentName][field];
|
|
534
|
+
|
|
535
|
+
if (Object.keys(config.agent[agentName]).length === 0) {
|
|
536
|
+
delete config.agent[agentName];
|
|
537
|
+
}
|
|
538
|
+
if (Object.keys(config.agent).length === 0) {
|
|
539
|
+
delete config.agent;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
jsonModified = true;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (inJson) {
|
|
549
|
+
if (!config.agent) config.agent = {};
|
|
550
|
+
if (!config.agent[agentName]) config.agent[agentName] = {};
|
|
551
|
+
config.agent[agentName][field] = value;
|
|
552
|
+
jsonModified = true;
|
|
553
|
+
} else if (inMd || creatingNewMd) {
|
|
554
|
+
if (mdData) {
|
|
555
|
+
mdData.frontmatter[field] = value;
|
|
556
|
+
mdModified = true;
|
|
557
|
+
}
|
|
558
|
+
} else {
|
|
559
|
+
if ((mdExists || creatingNewMd) && mdData) {
|
|
560
|
+
mdData.frontmatter[field] = value;
|
|
561
|
+
mdModified = true;
|
|
562
|
+
} else {
|
|
563
|
+
if (!config.agent) config.agent = {};
|
|
564
|
+
if (!config.agent[agentName]) config.agent[agentName] = {};
|
|
565
|
+
config.agent[agentName][field] = value;
|
|
566
|
+
jsonModified = true;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (mdModified && mdData) {
|
|
572
|
+
writeMdFile(targetPath, mdData.frontmatter, mdData.body);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (jsonModified) {
|
|
576
|
+
writeConfig(config, jsonTarget.path || CONFIG_FILE);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
console.log(`Updated agent: ${agentName} (scope: ${targetScope}, md: ${mdModified}, json: ${jsonModified})`);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function deleteAgent(agentName, workingDirectory) {
|
|
583
|
+
const lookupCache = createAgentLookupCache();
|
|
584
|
+
let deleted = false;
|
|
585
|
+
|
|
586
|
+
if (workingDirectory) {
|
|
587
|
+
const projectPath = getProjectAgentPath(workingDirectory, agentName);
|
|
588
|
+
if (fs.existsSync(projectPath)) {
|
|
589
|
+
fs.unlinkSync(projectPath);
|
|
590
|
+
console.log(`Deleted project-level agent .md file: ${projectPath}`);
|
|
591
|
+
deleted = true;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const userPath = getUserAgentPath(agentName, lookupCache);
|
|
596
|
+
if (fs.existsSync(userPath)) {
|
|
597
|
+
fs.unlinkSync(userPath);
|
|
598
|
+
console.log(`Deleted user-level agent .md file: ${userPath}`);
|
|
599
|
+
deleted = true;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const layers = readConfigLayers(workingDirectory);
|
|
603
|
+
const jsonSource = getJsonEntrySource(layers, 'agent', agentName);
|
|
604
|
+
if (jsonSource.exists && jsonSource.config && jsonSource.path) {
|
|
605
|
+
if (!jsonSource.config.agent) jsonSource.config.agent = {};
|
|
606
|
+
delete jsonSource.config.agent[agentName];
|
|
607
|
+
writeConfig(jsonSource.config, jsonSource.path);
|
|
608
|
+
console.log(`Removed agent from opencode.json: ${agentName}`);
|
|
609
|
+
deleted = true;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (!deleted) {
|
|
613
|
+
const jsonTarget = getJsonWriteTarget(layers, workingDirectory ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER);
|
|
614
|
+
const targetConfig = jsonTarget.config || {};
|
|
615
|
+
if (!targetConfig.agent) targetConfig.agent = {};
|
|
616
|
+
targetConfig.agent[agentName] = { disable: true };
|
|
617
|
+
writeConfig(targetConfig, jsonTarget.path || CONFIG_FILE);
|
|
618
|
+
console.log(`Disabled built-in agent: ${agentName}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export {
|
|
623
|
+
ensureProjectAgentDir,
|
|
624
|
+
getProjectAgentPath,
|
|
625
|
+
getUserAgentPath,
|
|
626
|
+
getAgentScope,
|
|
627
|
+
getAgentWritePath,
|
|
628
|
+
getAgentPermissionSource,
|
|
629
|
+
getAgentSources,
|
|
630
|
+
getAgentConfig,
|
|
631
|
+
createAgent,
|
|
632
|
+
updateAgent,
|
|
633
|
+
deleteAgent,
|
|
634
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const OPENCODE_DATA_DIR = path.join(os.homedir(), '.local', 'share', 'opencode');
|
|
6
|
+
const AUTH_FILE = path.join(OPENCODE_DATA_DIR, 'auth.json');
|
|
7
|
+
|
|
8
|
+
function readAuthFile() {
|
|
9
|
+
if (!fs.existsSync(AUTH_FILE)) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const content = fs.readFileSync(AUTH_FILE, 'utf8');
|
|
14
|
+
const trimmed = content.trim();
|
|
15
|
+
if (!trimmed) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
return JSON.parse(trimmed);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error('Failed to read auth file:', error);
|
|
21
|
+
throw new Error('Failed to read OpenCode auth configuration');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeAuthFile(auth) {
|
|
26
|
+
try {
|
|
27
|
+
if (!fs.existsSync(OPENCODE_DATA_DIR)) {
|
|
28
|
+
fs.mkdirSync(OPENCODE_DATA_DIR, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (fs.existsSync(AUTH_FILE)) {
|
|
32
|
+
const backupFile = `${AUTH_FILE}.archcoder.backup`;
|
|
33
|
+
fs.copyFileSync(AUTH_FILE, backupFile);
|
|
34
|
+
console.log(`Created auth backup: ${backupFile}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify(auth, null, 2), 'utf8');
|
|
38
|
+
console.log('Successfully wrote auth file');
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Failed to write auth file:', error);
|
|
41
|
+
throw new Error('Failed to write OpenCode auth configuration');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function removeProviderAuth(providerId) {
|
|
46
|
+
if (!providerId || typeof providerId !== 'string') {
|
|
47
|
+
throw new Error('Provider ID is required');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const auth = readAuthFile();
|
|
51
|
+
|
|
52
|
+
if (!auth[providerId]) {
|
|
53
|
+
console.log(`Provider ${providerId} not found in auth file, nothing to remove`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
delete auth[providerId];
|
|
58
|
+
writeAuthFile(auth);
|
|
59
|
+
console.log(`Removed provider auth: ${providerId}`);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getProviderAuth(providerId) {
|
|
64
|
+
const auth = readAuthFile();
|
|
65
|
+
return auth[providerId] || null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function listProviderAuths() {
|
|
69
|
+
const auth = readAuthFile();
|
|
70
|
+
return Object.keys(auth);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
readAuthFile,
|
|
75
|
+
writeAuthFile,
|
|
76
|
+
removeProviderAuth,
|
|
77
|
+
getProviderAuth,
|
|
78
|
+
listProviderAuths,
|
|
79
|
+
AUTH_FILE,
|
|
80
|
+
OPENCODE_DATA_DIR
|
|
81
|
+
};
|