@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.
Files changed (157) hide show
  1. package/README.md +113 -0
  2. package/bin/cli-entry.js +55 -0
  3. package/bin/cli-output.js +145 -0
  4. package/bin/cli.js +5108 -0
  5. package/bin/cli.test.js +56 -0
  6. package/dist/apple-touch-icon-120x120.png +0 -0
  7. package/dist/apple-touch-icon-152x152.png +0 -0
  8. package/dist/apple-touch-icon-167x167.png +0 -0
  9. package/dist/apple-touch-icon-180x180.png +0 -0
  10. package/dist/apple-touch-icon.png +0 -0
  11. package/dist/apple-touch-icon.svg +67 -0
  12. package/dist/assets/MultiRunWindow-BZp3MjJP.js +1 -0
  13. package/dist/assets/SettingsWindow-DoGYXpX7.js +1 -0
  14. package/dist/assets/TerminalView-BN7BR5Ff.js +3 -0
  15. package/dist/assets/TimelineDialog-ZQ33oVQR.js +1 -0
  16. package/dist/assets/ToolOutputDialog-Blv3pnug.js +16 -0
  17. package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  18. package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  19. package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  20. package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  21. package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  22. package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  23. package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  24. package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  25. package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  26. package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  27. package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  28. package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  29. package/dist/assets/index-CtCEGYrr.css +1 -0
  30. package/dist/assets/index-o_d2wtWC.js +48 -0
  31. package/dist/assets/main-5QGBtzdq.css +1 -0
  32. package/dist/assets/main-B6oiMU86.js +8033 -0
  33. package/dist/assets/vendor--DbVqbJpV.css +1 -0
  34. package/dist/assets/vendor-.bun-HTKwyaEM.js +10086 -0
  35. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  36. package/dist/assets/worker-bqd4RMrj.js +155 -0
  37. package/dist/favicon-16.png +0 -0
  38. package/dist/favicon-32.png +0 -0
  39. package/dist/favicon.png +0 -0
  40. package/dist/favicon.svg +67 -0
  41. package/dist/index.html +533 -0
  42. package/dist/logo-dark-192x192.png +0 -0
  43. package/dist/logo-dark-512x512.svg +16 -0
  44. package/dist/logo-light-192x192.png +0 -0
  45. package/dist/logo-light-512x512.svg +16 -0
  46. package/dist/pwa-192.png +0 -0
  47. package/dist/pwa-512.png +0 -0
  48. package/dist/pwa-maskable-192.png +0 -0
  49. package/dist/pwa-maskable-512.png +0 -0
  50. package/dist/site.webmanifest +22 -0
  51. package/dist/sw.js +1 -0
  52. package/package.json +107 -0
  53. package/public/apple-touch-icon-120x120.png +0 -0
  54. package/public/apple-touch-icon-152x152.png +0 -0
  55. package/public/apple-touch-icon-167x167.png +0 -0
  56. package/public/apple-touch-icon-180x180.png +0 -0
  57. package/public/apple-touch-icon.png +0 -0
  58. package/public/apple-touch-icon.svg +67 -0
  59. package/public/favicon-16.png +0 -0
  60. package/public/favicon-32.png +0 -0
  61. package/public/favicon.png +0 -0
  62. package/public/favicon.svg +67 -0
  63. package/public/logo-dark-192x192.png +0 -0
  64. package/public/logo-dark-512x512.svg +16 -0
  65. package/public/logo-light-192x192.png +0 -0
  66. package/public/logo-light-512x512.svg +16 -0
  67. package/public/pwa-192.png +0 -0
  68. package/public/pwa-512.png +0 -0
  69. package/public/pwa-maskable-192.png +0 -0
  70. package/public/pwa-maskable-512.png +0 -0
  71. package/public/site.webmanifest +22 -0
  72. package/server/TERMINAL_INPUT_WS_PROTOCOL.md +44 -0
  73. package/server/index.d.ts +37 -0
  74. package/server/index.js +14694 -0
  75. package/server/lib/cloudflare-tunnel.js +650 -0
  76. package/server/lib/git/DOCUMENTATION.md +146 -0
  77. package/server/lib/git/credentials.js +74 -0
  78. package/server/lib/git/identity-storage.js +110 -0
  79. package/server/lib/git/index.js +6 -0
  80. package/server/lib/git/service.js +3117 -0
  81. package/server/lib/github/DOCUMENTATION.md +170 -0
  82. package/server/lib/github/auth.js +307 -0
  83. package/server/lib/github/device-flow.js +50 -0
  84. package/server/lib/github/index.js +24 -0
  85. package/server/lib/github/octokit.js +10 -0
  86. package/server/lib/github/pr-status.js +478 -0
  87. package/server/lib/github/repo/index.js +55 -0
  88. package/server/lib/installer/desktop.js +289 -0
  89. package/server/lib/installer/download.js +208 -0
  90. package/server/lib/installer/index.js +45 -0
  91. package/server/lib/installer/platform.js +100 -0
  92. package/server/lib/notifications/DOCUMENTATION.md +61 -0
  93. package/server/lib/notifications/index.js +1 -0
  94. package/server/lib/notifications/message.js +49 -0
  95. package/server/lib/notifications/message.test.js +59 -0
  96. package/server/lib/opencode/DOCUMENTATION.md +59 -0
  97. package/server/lib/opencode/agents.js +634 -0
  98. package/server/lib/opencode/auth.js +81 -0
  99. package/server/lib/opencode/commands.js +339 -0
  100. package/server/lib/opencode/index.js +66 -0
  101. package/server/lib/opencode/mcp.js +206 -0
  102. package/server/lib/opencode/providers.js +96 -0
  103. package/server/lib/opencode/shared.js +527 -0
  104. package/server/lib/opencode/skills.js +480 -0
  105. package/server/lib/opencode/tunnel-auth.js +591 -0
  106. package/server/lib/opencode/ui-auth.js +510 -0
  107. package/server/lib/package-manager.js +505 -0
  108. package/server/lib/quota/DOCUMENTATION.md +55 -0
  109. package/server/lib/quota/index.js +24 -0
  110. package/server/lib/quota/providers/claude.js +107 -0
  111. package/server/lib/quota/providers/codex.js +113 -0
  112. package/server/lib/quota/providers/copilot.js +165 -0
  113. package/server/lib/quota/providers/google/api.js +92 -0
  114. package/server/lib/quota/providers/google/auth.js +108 -0
  115. package/server/lib/quota/providers/google/index.js +124 -0
  116. package/server/lib/quota/providers/google/transforms.js +109 -0
  117. package/server/lib/quota/providers/index.js +152 -0
  118. package/server/lib/quota/providers/interface.js +55 -0
  119. package/server/lib/quota/providers/kimi.js +108 -0
  120. package/server/lib/quota/providers/minimax-cn-coding-plan.js +15 -0
  121. package/server/lib/quota/providers/minimax-coding-plan.js +15 -0
  122. package/server/lib/quota/providers/minimax-shared.js +136 -0
  123. package/server/lib/quota/providers/nanogpt.js +124 -0
  124. package/server/lib/quota/providers/ollama-cloud.js +112 -0
  125. package/server/lib/quota/providers/openai.js +91 -0
  126. package/server/lib/quota/providers/openrouter.js +92 -0
  127. package/server/lib/quota/providers/zai.js +91 -0
  128. package/server/lib/quota/utils/auth.js +46 -0
  129. package/server/lib/quota/utils/formatters.js +76 -0
  130. package/server/lib/quota/utils/index.js +10 -0
  131. package/server/lib/quota/utils/transformers.js +55 -0
  132. package/server/lib/skills-catalog/DOCUMENTATION.md +178 -0
  133. package/server/lib/skills-catalog/cache.js +32 -0
  134. package/server/lib/skills-catalog/clawdhub/api.js +158 -0
  135. package/server/lib/skills-catalog/clawdhub/index.js +30 -0
  136. package/server/lib/skills-catalog/clawdhub/install.js +238 -0
  137. package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
  138. package/server/lib/skills-catalog/curated-sources.js +21 -0
  139. package/server/lib/skills-catalog/git.js +77 -0
  140. package/server/lib/skills-catalog/index.js +42 -0
  141. package/server/lib/skills-catalog/install.js +294 -0
  142. package/server/lib/skills-catalog/scan.js +221 -0
  143. package/server/lib/skills-catalog/source.js +85 -0
  144. package/server/lib/terminal/DOCUMENTATION.md +114 -0
  145. package/server/lib/terminal/index.js +12 -0
  146. package/server/lib/terminal/input-ws-protocol.js +66 -0
  147. package/server/lib/terminal/input-ws-protocol.test.js +138 -0
  148. package/server/lib/tts/DOCUMENTATION.md +134 -0
  149. package/server/lib/tts/index.js +16 -0
  150. package/server/lib/tts/service.js +162 -0
  151. package/server/lib/tts/summarization.js +171 -0
  152. package/server/lib/tunnels/index.js +166 -0
  153. package/server/lib/tunnels/providers/cloudflare.js +260 -0
  154. package/server/lib/tunnels/registry.js +51 -0
  155. package/server/lib/tunnels/types.js +219 -0
  156. package/server/lib/utils/lru.js +107 -0
  157. package/server/lib/utils/sse.js +121 -0
@@ -0,0 +1,339 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import {
4
+ CONFIG_FILE,
5
+ OPENCODE_CONFIG_DIR,
6
+ COMMAND_DIR,
7
+ COMMAND_SCOPE,
8
+ ensureDirs,
9
+ parseMdFile,
10
+ writeMdFile,
11
+ readConfigLayers,
12
+ writeConfig,
13
+ getJsonEntrySource,
14
+ getJsonWriteTarget,
15
+ isPromptFileReference,
16
+ resolvePromptFilePath,
17
+ writePromptFile,
18
+ } from './shared.js';
19
+
20
+ // ============== COMMAND SCOPE HELPERS ==============
21
+
22
+ /**
23
+ * Ensure project-level command directory exists
24
+ */
25
+ function ensureProjectCommandDir(workingDirectory) {
26
+ const projectCommandDir = path.join(workingDirectory, '.opencode', 'commands');
27
+ if (!fs.existsSync(projectCommandDir)) {
28
+ fs.mkdirSync(projectCommandDir, { recursive: true });
29
+ }
30
+ const legacyProjectCommandDir = path.join(workingDirectory, '.opencode', 'command');
31
+ if (!fs.existsSync(legacyProjectCommandDir)) {
32
+ fs.mkdirSync(legacyProjectCommandDir, { recursive: true });
33
+ }
34
+ return projectCommandDir;
35
+ }
36
+
37
+ /**
38
+ * Get project-level command path
39
+ */
40
+ function getProjectCommandPath(workingDirectory, commandName) {
41
+ const pluralPath = path.join(workingDirectory, '.opencode', 'commands', `${commandName}.md`);
42
+ const legacyPath = path.join(workingDirectory, '.opencode', 'command', `${commandName}.md`);
43
+ if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
44
+ return pluralPath;
45
+ }
46
+
47
+ /**
48
+ * Get user-level command path
49
+ */
50
+ function getUserCommandPath(commandName) {
51
+ const pluralPath = path.join(COMMAND_DIR, `${commandName}.md`);
52
+ const legacyPath = path.join(OPENCODE_CONFIG_DIR, 'command', `${commandName}.md`);
53
+ if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
54
+ return pluralPath;
55
+ }
56
+
57
+ /**
58
+ * Determine command scope based on where the .md file exists
59
+ * Priority: project level > user level > null (built-in only)
60
+ */
61
+ function getCommandScope(commandName, workingDirectory) {
62
+ if (workingDirectory) {
63
+ const projectPath = getProjectCommandPath(workingDirectory, commandName);
64
+ if (fs.existsSync(projectPath)) {
65
+ return { scope: COMMAND_SCOPE.PROJECT, path: projectPath };
66
+ }
67
+ }
68
+
69
+ const userPath = getUserCommandPath(commandName);
70
+ if (fs.existsSync(userPath)) {
71
+ return { scope: COMMAND_SCOPE.USER, path: userPath };
72
+ }
73
+
74
+ return { scope: null, path: null };
75
+ }
76
+
77
+ /**
78
+ * Get the path where a command should be written based on scope
79
+ */
80
+ function getCommandWritePath(commandName, workingDirectory, requestedScope) {
81
+ // For updates: check existing location first (project takes precedence)
82
+ const existing = getCommandScope(commandName, workingDirectory);
83
+ if (existing.path) {
84
+ return existing;
85
+ }
86
+
87
+ // For new commands or built-in overrides: use requested scope or default to user
88
+ const scope = requestedScope || COMMAND_SCOPE.USER;
89
+ if (scope === COMMAND_SCOPE.PROJECT && workingDirectory) {
90
+ return {
91
+ scope: COMMAND_SCOPE.PROJECT,
92
+ path: getProjectCommandPath(workingDirectory, commandName)
93
+ };
94
+ }
95
+
96
+ return {
97
+ scope: COMMAND_SCOPE.USER,
98
+ path: getUserCommandPath(commandName)
99
+ };
100
+ }
101
+
102
+ function getCommandSources(commandName, workingDirectory) {
103
+ const projectPath = workingDirectory ? getProjectCommandPath(workingDirectory, commandName) : null;
104
+ const projectExists = projectPath && fs.existsSync(projectPath);
105
+
106
+ const userPath = getUserCommandPath(commandName);
107
+ const userExists = fs.existsSync(userPath);
108
+
109
+ const mdPath = projectExists ? projectPath : (userExists ? userPath : null);
110
+ const mdExists = !!mdPath;
111
+ const mdScope = projectExists ? COMMAND_SCOPE.PROJECT : (userExists ? COMMAND_SCOPE.USER : null);
112
+
113
+ const layers = readConfigLayers(workingDirectory);
114
+ const jsonSource = getJsonEntrySource(layers, 'command', commandName);
115
+ const jsonSection = jsonSource.section;
116
+ const jsonPath = jsonSource.path || layers.paths.customPath || layers.paths.projectPath || layers.paths.userPath;
117
+ const jsonScope = jsonSource.path === layers.paths.projectPath ? COMMAND_SCOPE.PROJECT : COMMAND_SCOPE.USER;
118
+
119
+ const sources = {
120
+ md: {
121
+ exists: mdExists,
122
+ path: mdPath,
123
+ scope: mdScope,
124
+ fields: []
125
+ },
126
+ json: {
127
+ exists: jsonSource.exists,
128
+ path: jsonPath,
129
+ scope: jsonSource.exists ? jsonScope : null,
130
+ fields: []
131
+ },
132
+ projectMd: {
133
+ exists: projectExists,
134
+ path: projectPath
135
+ },
136
+ userMd: {
137
+ exists: userExists,
138
+ path: userPath
139
+ }
140
+ };
141
+
142
+ if (mdExists) {
143
+ const { frontmatter, body } = parseMdFile(mdPath);
144
+ sources.md.fields = Object.keys(frontmatter);
145
+ if (body) {
146
+ sources.md.fields.push('template');
147
+ }
148
+ }
149
+
150
+ if (jsonSection) {
151
+ sources.json.fields = Object.keys(jsonSection);
152
+ }
153
+
154
+ return sources;
155
+ }
156
+
157
+ function createCommand(commandName, config, workingDirectory, scope) {
158
+ ensureDirs();
159
+
160
+ const projectPath = workingDirectory ? getProjectCommandPath(workingDirectory, commandName) : null;
161
+ const userPath = getUserCommandPath(commandName);
162
+
163
+ if (projectPath && fs.existsSync(projectPath)) {
164
+ throw new Error(`Command ${commandName} already exists as project-level .md file`);
165
+ }
166
+
167
+ if (fs.existsSync(userPath)) {
168
+ throw new Error(`Command ${commandName} already exists as user-level .md file`);
169
+ }
170
+
171
+ const layers = readConfigLayers(workingDirectory);
172
+ const jsonSource = getJsonEntrySource(layers, 'command', commandName);
173
+ if (jsonSource.exists) {
174
+ throw new Error(`Command ${commandName} already exists in opencode.json`);
175
+ }
176
+
177
+ let targetPath;
178
+ let targetScope;
179
+
180
+ if (scope === COMMAND_SCOPE.PROJECT && workingDirectory) {
181
+ ensureProjectCommandDir(workingDirectory);
182
+ targetPath = projectPath;
183
+ targetScope = COMMAND_SCOPE.PROJECT;
184
+ } else {
185
+ targetPath = userPath;
186
+ targetScope = COMMAND_SCOPE.USER;
187
+ }
188
+
189
+ const { template, scope: _scopeFromConfig, ...frontmatter } = config;
190
+
191
+ writeMdFile(targetPath, frontmatter, template || '');
192
+ console.log(`Created new command: ${commandName} (scope: ${targetScope}, path: ${targetPath})`);
193
+ }
194
+
195
+ function updateCommand(commandName, updates, workingDirectory) {
196
+ ensureDirs();
197
+
198
+ const { scope, path: mdPath } = getCommandWritePath(commandName, workingDirectory);
199
+ const mdExists = mdPath && fs.existsSync(mdPath);
200
+
201
+ const layers = readConfigLayers(workingDirectory);
202
+ const jsonSource = getJsonEntrySource(layers, 'command', commandName);
203
+ const jsonSection = jsonSource.section;
204
+ const hasJsonFields = jsonSource.exists && jsonSection && Object.keys(jsonSection).length > 0;
205
+ const jsonTarget = jsonSource.exists
206
+ ? { config: jsonSource.config, path: jsonSource.path }
207
+ : getJsonWriteTarget(layers, workingDirectory ? COMMAND_SCOPE.PROJECT : COMMAND_SCOPE.USER);
208
+ let config = jsonTarget.config || {};
209
+
210
+ const isBuiltinOverride = !mdExists && !hasJsonFields;
211
+
212
+ let targetPath = mdPath;
213
+ let targetScope = scope;
214
+
215
+ if (!mdExists && isBuiltinOverride) {
216
+ targetPath = getUserCommandPath(commandName);
217
+ targetScope = COMMAND_SCOPE.USER;
218
+ }
219
+
220
+ const mdData = mdExists ? parseMdFile(mdPath) : (isBuiltinOverride ? { frontmatter: {}, body: '' } : null);
221
+
222
+ let mdModified = false;
223
+ let jsonModified = false;
224
+ const creatingNewMd = isBuiltinOverride;
225
+
226
+ for (const [field, value] of Object.entries(updates)) {
227
+ if (field === 'template') {
228
+ const normalizedValue = typeof value === 'string' ? value : (value == null ? '' : String(value));
229
+
230
+ if (mdExists || creatingNewMd) {
231
+ if (mdData) {
232
+ mdData.body = normalizedValue;
233
+ mdModified = true;
234
+ }
235
+ continue;
236
+ } else if (isPromptFileReference(jsonSection?.template)) {
237
+ const templateFilePath = resolvePromptFilePath(jsonSection.template);
238
+ if (!templateFilePath) {
239
+ throw new Error(`Invalid template file reference for command ${commandName}`);
240
+ }
241
+ writePromptFile(templateFilePath, normalizedValue);
242
+ continue;
243
+ } else if (isPromptFileReference(normalizedValue)) {
244
+ if (!config.command) config.command = {};
245
+ if (!config.command[commandName]) config.command[commandName] = {};
246
+ config.command[commandName].template = normalizedValue;
247
+ jsonModified = true;
248
+ continue;
249
+ }
250
+
251
+ if (!config.command) config.command = {};
252
+ if (!config.command[commandName]) config.command[commandName] = {};
253
+ config.command[commandName].template = normalizedValue;
254
+ jsonModified = true;
255
+ continue;
256
+ }
257
+
258
+ const inMd = mdData?.frontmatter?.[field] !== undefined;
259
+ const inJson = jsonSection?.[field] !== undefined;
260
+
261
+ if (inJson) {
262
+ if (!config.command) config.command = {};
263
+ if (!config.command[commandName]) config.command[commandName] = {};
264
+ config.command[commandName][field] = value;
265
+ jsonModified = true;
266
+ } else if (inMd || creatingNewMd) {
267
+ if (mdData) {
268
+ mdData.frontmatter[field] = value;
269
+ mdModified = true;
270
+ }
271
+ } else {
272
+ if ((mdExists || creatingNewMd) && mdData) {
273
+ mdData.frontmatter[field] = value;
274
+ mdModified = true;
275
+ } else {
276
+ if (!config.command) config.command = {};
277
+ if (!config.command[commandName]) config.command[commandName] = {};
278
+ config.command[commandName][field] = value;
279
+ jsonModified = true;
280
+ }
281
+ }
282
+ }
283
+
284
+ if (mdModified && mdData) {
285
+ writeMdFile(targetPath, mdData.frontmatter, mdData.body);
286
+ }
287
+
288
+ if (jsonModified) {
289
+ writeConfig(config, jsonTarget.path || CONFIG_FILE);
290
+ }
291
+
292
+ console.log(`Updated command: ${commandName} (scope: ${targetScope}, md: ${mdModified}, json: ${jsonModified})`);
293
+ }
294
+
295
+ function deleteCommand(commandName, workingDirectory) {
296
+ let deleted = false;
297
+
298
+ if (workingDirectory) {
299
+ const projectPath = getProjectCommandPath(workingDirectory, commandName);
300
+ if (fs.existsSync(projectPath)) {
301
+ fs.unlinkSync(projectPath);
302
+ console.log(`Deleted project-level command .md file: ${projectPath}`);
303
+ deleted = true;
304
+ }
305
+ }
306
+
307
+ const userPath = getUserCommandPath(commandName);
308
+ if (fs.existsSync(userPath)) {
309
+ fs.unlinkSync(userPath);
310
+ console.log(`Deleted user-level command .md file: ${userPath}`);
311
+ deleted = true;
312
+ }
313
+
314
+ const layers = readConfigLayers(workingDirectory);
315
+ const jsonSource = getJsonEntrySource(layers, 'command', commandName);
316
+ if (jsonSource.exists && jsonSource.config && jsonSource.path) {
317
+ if (!jsonSource.config.command) jsonSource.config.command = {};
318
+ delete jsonSource.config.command[commandName];
319
+ writeConfig(jsonSource.config, jsonSource.path);
320
+ console.log(`Removed command from opencode.json: ${commandName}`);
321
+ deleted = true;
322
+ }
323
+
324
+ if (!deleted) {
325
+ throw new Error(`Command "${commandName}" not found`);
326
+ }
327
+ }
328
+
329
+ export {
330
+ ensureProjectCommandDir,
331
+ getProjectCommandPath,
332
+ getUserCommandPath,
333
+ getCommandScope,
334
+ getCommandWritePath,
335
+ getCommandSources,
336
+ createCommand,
337
+ updateCommand,
338
+ deleteCommand,
339
+ };
@@ -0,0 +1,66 @@
1
+ export {
2
+ AGENT_DIR,
3
+ COMMAND_DIR,
4
+ SKILL_DIR,
5
+ CONFIG_FILE,
6
+ AGENT_SCOPE,
7
+ COMMAND_SCOPE,
8
+ SKILL_SCOPE,
9
+ readConfig,
10
+ writeConfig,
11
+ readSkillSupportingFile,
12
+ writeSkillSupportingFile,
13
+ deleteSkillSupportingFile,
14
+ } from './shared.js';
15
+
16
+ export {
17
+ getAgentScope,
18
+ getAgentPermissionSource,
19
+ getAgentSources,
20
+ getAgentConfig,
21
+ createAgent,
22
+ updateAgent,
23
+ deleteAgent,
24
+ } from './agents.js';
25
+
26
+ export {
27
+ getCommandScope,
28
+ getCommandSources,
29
+ createCommand,
30
+ updateCommand,
31
+ deleteCommand,
32
+ } from './commands.js';
33
+
34
+ export {
35
+ getSkillSources,
36
+ getSkillScope,
37
+ discoverSkills,
38
+ createSkill,
39
+ updateSkill,
40
+ deleteSkill,
41
+ } from './skills.js';
42
+
43
+ export {
44
+ getProviderSources,
45
+ removeProviderConfig,
46
+ } from './providers.js';
47
+
48
+ export {
49
+ readAuthFile,
50
+ writeAuthFile,
51
+ removeProviderAuth,
52
+ getProviderAuth,
53
+ listProviderAuths,
54
+ AUTH_FILE,
55
+ OPENCODE_DATA_DIR,
56
+ } from './auth.js';
57
+
58
+ export { createUiAuth } from './ui-auth.js';
59
+
60
+ export {
61
+ listMcpConfigs,
62
+ getMcpConfig,
63
+ createMcpConfig,
64
+ updateMcpConfig,
65
+ deleteMcpConfig,
66
+ } from './mcp.js';
@@ -0,0 +1,206 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import {
4
+ CONFIG_FILE,
5
+ AGENT_SCOPE,
6
+ readConfigFile,
7
+ readConfigLayers,
8
+ getJsonEntrySource,
9
+ getJsonWriteTarget,
10
+ writeConfig,
11
+ } from './shared.js';
12
+
13
+ // ============== MCP CONFIG HELPERS ==============
14
+
15
+ /**
16
+ * Validate MCP server name
17
+ */
18
+ function validateMcpName(name) {
19
+ if (!name || typeof name !== 'string') {
20
+ throw new Error('MCP server name is required');
21
+ }
22
+ if (!/^[a-z0-9][a-z0-9_-]*[a-z0-9]$|^[a-z0-9]$/.test(name)) {
23
+ throw new Error('MCP server name must be lowercase alphanumeric with hyphens/underscores');
24
+ }
25
+ }
26
+
27
+ /**
28
+ * List all MCP server configs from user-level opencode.json
29
+ */
30
+ function resolveMcpScopeFromPath(layers, sourcePath) {
31
+ if (!sourcePath) return null;
32
+ return sourcePath === layers.paths.projectPath ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER;
33
+ }
34
+
35
+ function ensureProjectMcpConfigPath(workingDirectory) {
36
+ const configDir = path.join(workingDirectory, '.opencode');
37
+ if (!fs.existsSync(configDir)) {
38
+ fs.mkdirSync(configDir, { recursive: true });
39
+ }
40
+ return path.join(configDir, 'opencode.json');
41
+ }
42
+
43
+ function listMcpConfigs(workingDirectory) {
44
+ const layers = readConfigLayers(workingDirectory);
45
+ const mcp = layers?.mergedConfig?.mcp || {};
46
+
47
+ return Object.entries(mcp)
48
+ .filter(([, entry]) => entry && typeof entry === 'object' && !Array.isArray(entry))
49
+ .map(([name, entry]) => {
50
+ const source = getJsonEntrySource(layers, 'mcp', name);
51
+ return {
52
+ name,
53
+ ...buildMcpEntry(entry),
54
+ scope: resolveMcpScopeFromPath(layers, source.path),
55
+ };
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Get a single MCP server config by name
61
+ */
62
+ function getMcpConfig(name, workingDirectory) {
63
+ const layers = readConfigLayers(workingDirectory);
64
+ const entry = layers?.mergedConfig?.mcp?.[name];
65
+
66
+ if (!entry) {
67
+ return null;
68
+ }
69
+ const source = getJsonEntrySource(layers, 'mcp', name);
70
+ return {
71
+ name,
72
+ ...buildMcpEntry(entry),
73
+ scope: resolveMcpScopeFromPath(layers, source.path),
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Create a new MCP server config entry
79
+ */
80
+ function createMcpConfig(name, mcpConfig, workingDirectory, scope) {
81
+ validateMcpName(name);
82
+
83
+ const layers = readConfigLayers(workingDirectory);
84
+ const source = getJsonEntrySource(layers, 'mcp', name);
85
+ if (source.exists) {
86
+ throw new Error(`MCP server "${name}" already exists`);
87
+ }
88
+
89
+ let targetPath = CONFIG_FILE;
90
+ let config = {};
91
+
92
+ if (scope === AGENT_SCOPE.PROJECT) {
93
+ if (!workingDirectory) {
94
+ throw new Error('Project scope requires working directory');
95
+ }
96
+ targetPath = ensureProjectMcpConfigPath(workingDirectory);
97
+ config = fs.existsSync(targetPath) ? readConfigFile(targetPath) : {};
98
+ } else {
99
+ const jsonTarget = getJsonWriteTarget(layers, AGENT_SCOPE.USER);
100
+ targetPath = jsonTarget.path || CONFIG_FILE;
101
+ config = jsonTarget.config || {};
102
+ }
103
+
104
+ if (!config.mcp || typeof config.mcp !== 'object' || Array.isArray(config.mcp)) {
105
+ config.mcp = {};
106
+ }
107
+
108
+ const { name: _ignoredName, ...entryData } = mcpConfig;
109
+ config.mcp[name] = buildMcpEntry(entryData);
110
+
111
+ writeConfig(config, targetPath);
112
+ console.log(`Created MCP server config: ${name}`);
113
+ }
114
+
115
+ /**
116
+ * Update an existing MCP server config entry
117
+ */
118
+ function updateMcpConfig(name, updates, workingDirectory) {
119
+ const layers = readConfigLayers(workingDirectory);
120
+ const source = getJsonEntrySource(layers, 'mcp', name);
121
+ const targetPath = source.path || CONFIG_FILE;
122
+ const config = source.config || (fs.existsSync(targetPath) ? readConfigFile(targetPath) : {});
123
+
124
+ if (!config.mcp || typeof config.mcp !== 'object' || Array.isArray(config.mcp)) {
125
+ config.mcp = {};
126
+ }
127
+
128
+ const existing = config.mcp[name] ?? {};
129
+ const { name: _ignoredName, ...updateData } = updates;
130
+
131
+ config.mcp[name] = buildMcpEntry({ ...existing, ...updateData });
132
+
133
+ writeConfig(config, targetPath);
134
+ console.log(`Updated MCP server config: ${name}`);
135
+ }
136
+
137
+ /**
138
+ * Delete an MCP server config entry
139
+ */
140
+ function deleteMcpConfig(name, workingDirectory) {
141
+ const layers = readConfigLayers(workingDirectory);
142
+ const source = getJsonEntrySource(layers, 'mcp', name);
143
+ const targetPath = source.path || CONFIG_FILE;
144
+ const config = source.config || (fs.existsSync(targetPath) ? readConfigFile(targetPath) : {});
145
+
146
+ if (!config.mcp || typeof config.mcp !== 'object' || config.mcp[name] === undefined) {
147
+ throw new Error(`MCP server "${name}" not found`);
148
+ }
149
+
150
+ delete config.mcp[name];
151
+
152
+ if (Object.keys(config.mcp).length === 0) {
153
+ delete config.mcp;
154
+ }
155
+
156
+ writeConfig(config, targetPath);
157
+ console.log(`Deleted MCP server config: ${name}`);
158
+ }
159
+
160
+ /**
161
+ * Build a clean MCP entry object, omitting undefined/null values
162
+ */
163
+ function buildMcpEntry(data) {
164
+ const entry = {};
165
+
166
+ // type is required
167
+ entry.type = data.type === 'remote' ? 'remote' : 'local';
168
+
169
+ if (entry.type === 'local') {
170
+ // command must be a non-empty array of strings
171
+ if (Array.isArray(data.command) && data.command.length > 0) {
172
+ entry.command = data.command.map(String);
173
+ }
174
+ } else {
175
+ // remote: url required
176
+ if (data.url && typeof data.url === 'string') {
177
+ entry.url = data.url.trim();
178
+ }
179
+ }
180
+
181
+ // environment: flat Record<string, string>
182
+ if (data.environment && typeof data.environment === 'object' && !Array.isArray(data.environment)) {
183
+ const cleaned = {};
184
+ for (const [k, v] of Object.entries(data.environment)) {
185
+ if (k && v !== undefined && v !== null) {
186
+ cleaned[k] = String(v);
187
+ }
188
+ }
189
+ if (Object.keys(cleaned).length > 0) {
190
+ entry.environment = cleaned;
191
+ }
192
+ }
193
+
194
+ // enabled defaults to true
195
+ entry.enabled = data.enabled !== false;
196
+
197
+ return entry;
198
+ }
199
+
200
+ export {
201
+ listMcpConfigs,
202
+ getMcpConfig,
203
+ createMcpConfig,
204
+ updateMcpConfig,
205
+ deleteMcpConfig,
206
+ };
@@ -0,0 +1,96 @@
1
+ import {
2
+ CONFIG_FILE,
3
+ readConfigLayers,
4
+ isPlainObject,
5
+ getConfigForPath,
6
+ writeConfig,
7
+ } from './shared.js';
8
+
9
+ function getProviderSources(providerId, workingDirectory) {
10
+ const layers = readConfigLayers(workingDirectory);
11
+ const { userConfig, projectConfig, customConfig, paths } = layers;
12
+
13
+ const customProviders = isPlainObject(customConfig?.provider) ? customConfig.provider : {};
14
+ const customProvidersAlias = isPlainObject(customConfig?.providers) ? customConfig.providers : {};
15
+ const projectProviders = isPlainObject(projectConfig?.provider) ? projectConfig.provider : {};
16
+ const projectProvidersAlias = isPlainObject(projectConfig?.providers) ? projectConfig.providers : {};
17
+ const userProviders = isPlainObject(userConfig?.provider) ? userConfig.provider : {};
18
+ const userProvidersAlias = isPlainObject(userConfig?.providers) ? userConfig.providers : {};
19
+
20
+ const customExists =
21
+ Object.prototype.hasOwnProperty.call(customProviders, providerId) ||
22
+ Object.prototype.hasOwnProperty.call(customProvidersAlias, providerId);
23
+ const projectExists =
24
+ Object.prototype.hasOwnProperty.call(projectProviders, providerId) ||
25
+ Object.prototype.hasOwnProperty.call(projectProvidersAlias, providerId);
26
+ const userExists =
27
+ Object.prototype.hasOwnProperty.call(userProviders, providerId) ||
28
+ Object.prototype.hasOwnProperty.call(userProvidersAlias, providerId);
29
+
30
+ return {
31
+ sources: {
32
+ auth: { exists: false },
33
+ user: { exists: userExists, path: paths.userPath },
34
+ project: { exists: projectExists, path: paths.projectPath || null },
35
+ custom: { exists: customExists, path: paths.customPath }
36
+ }
37
+ };
38
+ }
39
+
40
+ function removeProviderConfig(providerId, workingDirectory, scope = 'user') {
41
+ if (!providerId || typeof providerId !== 'string') {
42
+ throw new Error('Provider ID is required');
43
+ }
44
+
45
+ const layers = readConfigLayers(workingDirectory);
46
+ let targetPath = layers.paths.userPath;
47
+
48
+ if (scope === 'project') {
49
+ if (!workingDirectory) {
50
+ throw new Error('Working directory is required for project scope');
51
+ }
52
+ targetPath = layers.paths.projectPath || targetPath;
53
+ } else if (scope === 'custom') {
54
+ if (!layers.paths.customPath) {
55
+ return false;
56
+ }
57
+ targetPath = layers.paths.customPath;
58
+ }
59
+
60
+ const targetConfig = getConfigForPath(layers, targetPath);
61
+ const providerConfig = isPlainObject(targetConfig.provider) ? targetConfig.provider : {};
62
+ const providersConfig = isPlainObject(targetConfig.providers) ? targetConfig.providers : {};
63
+ const removedProvider = Object.prototype.hasOwnProperty.call(providerConfig, providerId);
64
+ const removedProviders = Object.prototype.hasOwnProperty.call(providersConfig, providerId);
65
+
66
+ if (!removedProvider && !removedProviders) {
67
+ return false;
68
+ }
69
+
70
+ if (removedProvider) {
71
+ delete providerConfig[providerId];
72
+ if (Object.keys(providerConfig).length === 0) {
73
+ delete targetConfig.provider;
74
+ } else {
75
+ targetConfig.provider = providerConfig;
76
+ }
77
+ }
78
+
79
+ if (removedProviders) {
80
+ delete providersConfig[providerId];
81
+ if (Object.keys(providersConfig).length === 0) {
82
+ delete targetConfig.providers;
83
+ } else {
84
+ targetConfig.providers = providersConfig;
85
+ }
86
+ }
87
+
88
+ writeConfig(targetConfig, targetPath || CONFIG_FILE);
89
+ console.log(`Removed provider ${providerId} from config: ${targetPath}`);
90
+ return true;
91
+ }
92
+
93
+ export {
94
+ getProviderSources,
95
+ removeProviderConfig,
96
+ };