@adversity/coding-tool-x 3.1.0 → 3.1.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/CHANGELOG.md +15 -18
- package/README.md +8 -8
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
- package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
- package/dist/web/assets/Home-Di2qsylF.css +1 -0
- package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
- package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
- package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
- package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
- package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
- package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
- package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/icons-kcfLIMBB.js +1 -0
- package/dist/web/assets/index-Ufv5rCa5.css +1 -0
- package/dist/web/assets/index-lAkrRC3h.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
- package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
- package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
- package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
- package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
- package/dist/web/index.html +8 -6
- package/package.json +4 -2
- package/src/commands/channels.js +48 -1
- package/src/commands/cli-type.js +4 -2
- package/src/commands/daemon.js +81 -12
- package/src/commands/doctor.js +10 -9
- package/src/commands/list.js +1 -1
- package/src/commands/logs.js +6 -4
- package/src/commands/port-config.js +24 -4
- package/src/commands/proxy-control.js +12 -6
- package/src/commands/search.js +1 -1
- package/src/commands/security.js +3 -2
- package/src/commands/stats.js +226 -52
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +31 -6
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +1 -1
- package/src/config/default.js +39 -2
- package/src/config/loader.js +74 -8
- package/src/config/paths.js +105 -33
- package/src/index.js +64 -3
- package/src/plugins/constants.js +3 -2
- package/src/plugins/plugin-api.js +1 -1
- package/src/reset-config.js +4 -2
- package/src/server/api/agents.js +57 -14
- package/src/server/api/channels.js +112 -33
- package/src/server/api/codex-channels.js +111 -18
- package/src/server/api/codex-proxy.js +14 -8
- package/src/server/api/commands.js +71 -18
- package/src/server/api/config-export.js +0 -6
- package/src/server/api/config-registry.js +11 -3
- package/src/server/api/config.js +376 -5
- package/src/server/api/convert.js +133 -0
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/gemini-channels.js +107 -18
- package/src/server/api/gemini-proxy.js +14 -8
- package/src/server/api/gemini-sessions.js +1 -1
- package/src/server/api/health-check.js +4 -3
- package/src/server/api/mcp.js +3 -3
- package/src/server/api/opencode-channels.js +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +198 -0
- package/src/server/api/opencode-sessions.js +403 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +66 -19
- package/src/server/api/prompts.js +2 -2
- package/src/server/api/proxy.js +7 -4
- package/src/server/api/sessions.js +3 -0
- package/src/server/api/skills.js +69 -18
- package/src/server/api/workspaces.js +78 -6
- package/src/server/codex-proxy-server.js +30 -18
- package/src/server/dev-server.js +1 -1
- package/src/server/gemini-proxy-server.js +15 -3
- package/src/server/index.js +165 -58
- package/src/server/opencode-proxy-server.js +4375 -0
- package/src/server/proxy-server.js +27 -18
- package/src/server/services/agents-service.js +61 -24
- package/src/server/services/channel-scheduler.js +9 -5
- package/src/server/services/channels.js +64 -37
- package/src/server/services/codex-channels.js +56 -43
- package/src/server/services/codex-settings-manager.js +271 -49
- package/src/server/services/codex-statistics-service.js +2 -2
- package/src/server/services/commands-service.js +84 -25
- package/src/server/services/config-export-service.js +7 -45
- package/src/server/services/config-registry-service.js +63 -17
- package/src/server/services/config-sync-manager.js +160 -7
- package/src/server/services/config-templates-service.js +204 -51
- package/src/server/services/env-checker.js +26 -12
- package/src/server/services/env-manager.js +126 -18
- package/src/server/services/favorites.js +5 -3
- package/src/server/services/gemini-channels.js +33 -44
- package/src/server/services/gemini-statistics-service.js +2 -2
- package/src/server/services/mcp-service.js +350 -9
- package/src/server/services/model-detector.js +707 -221
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +206 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +663 -0
- package/src/server/services/opencode-settings-manager.js +342 -0
- package/src/server/services/opencode-statistics-service.js +255 -0
- package/src/server/services/plugins-service.js +479 -22
- package/src/server/services/prompts-service.js +53 -11
- package/src/server/services/proxy-runtime.js +1 -1
- package/src/server/services/repo-scanner-base.js +1 -1
- package/src/server/services/security-config.js +1 -1
- package/src/server/services/session-cache.js +1 -1
- package/src/server/services/skill-service.js +300 -46
- package/src/server/services/speed-test.js +464 -186
- package/src/server/services/statistics-service.js +2 -2
- package/src/server/services/terminal-commands.js +10 -3
- package/src/server/services/terminal-config.js +1 -1
- package/src/server/services/ui-config.js +1 -1
- package/src/server/services/workspace-service.js +57 -100
- package/src/server/websocket-server.js +132 -3
- package/src/ui/menu.js +49 -40
- package/src/utils/port-helper.js +22 -8
- package/src/utils/session.js +5 -4
- package/dist/web/assets/icons-CO_2OFES.js +0 -1
- package/dist/web/assets/index-DI8QOi-E.js +0 -14
- package/dist/web/assets/index-uLHGdeZh.css +0 -41
- package/dist/web/assets/naive-ui-B1re3c-e.js +0 -1
- package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
- package/src/server/api/oauth.js +0 -294
- package/src/server/api/permissions.js +0 -385
- package/src/server/config/oauth-providers.js +0 -68
- package/src/server/services/oauth-callback-server.js +0 -284
- package/src/server/services/oauth-service.js +0 -378
- package/src/server/services/oauth-token-storage.js +0 -135
- package/src/server/services/permission-templates-service.js +0 -308
|
@@ -11,18 +11,263 @@ const { listPlugins, getPlugin, updatePlugin: updatePluginRegistry } = require('
|
|
|
11
11
|
const { installPlugin: installPluginCore, uninstallPlugin: uninstallPluginCore } = require('../../plugins/plugin-installer');
|
|
12
12
|
const { initializePlugins, shutdownPlugins } = require('../../plugins/plugin-manager');
|
|
13
13
|
const { INSTALLED_DIR, CONFIG_DIR } = require('../../plugins/constants');
|
|
14
|
+
const { NATIVE_PATHS } = require('../../config/paths');
|
|
14
15
|
|
|
15
16
|
const CLAUDE_PLUGINS_DIR = path.join(os.homedir(), '.claude', 'plugins');
|
|
16
17
|
const CLAUDE_INSTALLED_FILE = path.join(CLAUDE_PLUGINS_DIR, 'installed_plugins.json');
|
|
17
18
|
const CLAUDE_MARKETPLACES_FILE = path.join(CLAUDE_PLUGINS_DIR, 'known_marketplaces.json');
|
|
19
|
+
const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
|
|
20
|
+
const DEFAULT_REPOS_BY_PLATFORM = {
|
|
21
|
+
claude: [],
|
|
22
|
+
opencode: [
|
|
23
|
+
{
|
|
24
|
+
owner: 'Tommertom',
|
|
25
|
+
name: 'opencode-plugin-marketplace',
|
|
26
|
+
url: 'https://github.com/Tommertom/opencode-plugin-marketplace',
|
|
27
|
+
branch: 'main',
|
|
28
|
+
enabled: true,
|
|
29
|
+
source: 'opencode-default'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
owner: 'avifenesh',
|
|
33
|
+
name: 'awesome-slash',
|
|
34
|
+
url: 'https://github.com/avifenesh/awesome-slash',
|
|
35
|
+
branch: 'main',
|
|
36
|
+
enabled: true,
|
|
37
|
+
source: 'opencode-default'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
owner: 'NeoLabHQ',
|
|
41
|
+
name: 'context-engineering-kit',
|
|
42
|
+
url: 'https://github.com/NeoLabHQ/context-engineering-kit',
|
|
43
|
+
branch: 'master',
|
|
44
|
+
enabled: true,
|
|
45
|
+
source: 'opencode-default'
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function cloneRepos(repos = []) {
|
|
51
|
+
return repos.map(repo => ({ ...repo }));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function stripJsonComments(input = '') {
|
|
55
|
+
let result = '';
|
|
56
|
+
let inString = false;
|
|
57
|
+
let stringChar = '';
|
|
58
|
+
let i = 0;
|
|
59
|
+
|
|
60
|
+
while (i < input.length) {
|
|
61
|
+
const ch = input[i];
|
|
62
|
+
const next = input[i + 1];
|
|
63
|
+
|
|
64
|
+
if (inString) {
|
|
65
|
+
result += ch;
|
|
66
|
+
if (ch === '\\') {
|
|
67
|
+
if (next) {
|
|
68
|
+
result += next;
|
|
69
|
+
i += 2;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
} else if (ch === stringChar) {
|
|
73
|
+
inString = false;
|
|
74
|
+
}
|
|
75
|
+
i += 1;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (ch === '"' || ch === '\'') {
|
|
80
|
+
inString = true;
|
|
81
|
+
stringChar = ch;
|
|
82
|
+
result += ch;
|
|
83
|
+
i += 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (ch === '/' && next === '/') {
|
|
88
|
+
i += 2;
|
|
89
|
+
while (i < input.length && input[i] !== '\n') i += 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (ch === '/' && next === '*') {
|
|
94
|
+
i += 2;
|
|
95
|
+
while (i < input.length - 1 && !(input[i] === '*' && input[i + 1] === '/')) i += 1;
|
|
96
|
+
i += 2;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
result += ch;
|
|
101
|
+
i += 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
18
106
|
|
|
19
107
|
class PluginsService {
|
|
108
|
+
constructor(platform = 'claude') {
|
|
109
|
+
this.platform = ['claude', 'opencode'].includes(platform) ? platform : 'claude';
|
|
110
|
+
this.ccToolConfigDir = path.join(os.homedir(), '.cc-tool');
|
|
111
|
+
this.opencodePluginsDir = path.join(OPENCODE_CONFIG_DIR, 'plugins');
|
|
112
|
+
this.opencodeLegacyPluginsDir = path.join(OPENCODE_CONFIG_DIR, 'plugin');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_ensureDir(dirPath) {
|
|
116
|
+
if (!fs.existsSync(dirPath)) {
|
|
117
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_isOpenCode() {
|
|
122
|
+
return this.platform === 'opencode';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_getOpenCodePluginsDir() {
|
|
126
|
+
if (fs.existsSync(this.opencodeLegacyPluginsDir) && !fs.existsSync(this.opencodePluginsDir)) {
|
|
127
|
+
return this.opencodeLegacyPluginsDir;
|
|
128
|
+
}
|
|
129
|
+
return this.opencodePluginsDir;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_getOpenCodeConfigPath() {
|
|
133
|
+
const jsonc = path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc');
|
|
134
|
+
const json = path.join(OPENCODE_CONFIG_DIR, 'opencode.json');
|
|
135
|
+
const config = path.join(OPENCODE_CONFIG_DIR, 'config.json');
|
|
136
|
+
if (fs.existsSync(jsonc)) return jsonc;
|
|
137
|
+
if (fs.existsSync(json)) return json;
|
|
138
|
+
if (fs.existsSync(config)) return config;
|
|
139
|
+
return json;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_readOpenCodeConfig() {
|
|
143
|
+
const filePath = this._getOpenCodeConfigPath();
|
|
144
|
+
if (!fs.existsSync(filePath)) return { filePath, config: {} };
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
148
|
+
if (!raw.trim()) return { filePath, config: {} };
|
|
149
|
+
if (filePath.endsWith('.jsonc')) {
|
|
150
|
+
return { filePath, config: JSON.parse(stripJsonComments(raw)) };
|
|
151
|
+
}
|
|
152
|
+
return { filePath, config: JSON.parse(raw) };
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.error('[PluginsService] Failed to read opencode config:', err.message);
|
|
155
|
+
return { filePath, config: {} };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_writeOpenCodeConfig(filePath, config) {
|
|
160
|
+
this._ensureDir(path.dirname(filePath));
|
|
161
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), 'utf8');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_listOpenCodeConfiguredPlugins() {
|
|
165
|
+
const { config } = this._readOpenCodeConfig();
|
|
166
|
+
if (!Array.isArray(config.plugin)) return [];
|
|
167
|
+
return config.plugin.filter(Boolean);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
_setOpenCodeConfiguredPlugins(plugins) {
|
|
171
|
+
const { filePath, config } = this._readOpenCodeConfig();
|
|
172
|
+
const nextConfig = (config && typeof config === 'object') ? { ...config } : {};
|
|
173
|
+
nextConfig.plugin = Array.from(new Set((plugins || []).filter(Boolean)));
|
|
174
|
+
this._writeOpenCodeConfig(filePath, nextConfig);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
_listOpenCodeLocalPlugins() {
|
|
178
|
+
const pluginsDir = this._getOpenCodePluginsDir();
|
|
179
|
+
if (!fs.existsSync(pluginsDir)) return [];
|
|
180
|
+
|
|
181
|
+
const entries = fs.readdirSync(pluginsDir, { withFileTypes: true });
|
|
182
|
+
const plugins = [];
|
|
183
|
+
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
if (entry.name.startsWith('.')) continue;
|
|
186
|
+
const fullPath = path.join(pluginsDir, entry.name);
|
|
187
|
+
|
|
188
|
+
if (entry.isDirectory()) {
|
|
189
|
+
const pkgPath = path.join(fullPath, 'package.json');
|
|
190
|
+
let packageName = entry.name;
|
|
191
|
+
let description = '';
|
|
192
|
+
let version = '1.0.0';
|
|
193
|
+
if (fs.existsSync(pkgPath)) {
|
|
194
|
+
try {
|
|
195
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
196
|
+
packageName = pkg.name || packageName;
|
|
197
|
+
description = pkg.description || '';
|
|
198
|
+
version = pkg.version || version;
|
|
199
|
+
} catch (err) {
|
|
200
|
+
// ignore invalid package.json
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
plugins.push({
|
|
204
|
+
name: packageName,
|
|
205
|
+
directory: entry.name,
|
|
206
|
+
installPath: fullPath,
|
|
207
|
+
source: 'opencode-local',
|
|
208
|
+
version,
|
|
209
|
+
description,
|
|
210
|
+
installed: true,
|
|
211
|
+
enabled: true,
|
|
212
|
+
pluginType: 'local'
|
|
213
|
+
});
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
218
|
+
if (['.js', '.mjs', '.cjs', '.ts'].includes(ext)) {
|
|
219
|
+
plugins.push({
|
|
220
|
+
name: entry.name.replace(ext, ''),
|
|
221
|
+
directory: entry.name,
|
|
222
|
+
installPath: fullPath,
|
|
223
|
+
source: 'opencode-local',
|
|
224
|
+
version: '1.0.0',
|
|
225
|
+
description: '',
|
|
226
|
+
installed: true,
|
|
227
|
+
enabled: true,
|
|
228
|
+
pluginType: 'local'
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return plugins;
|
|
234
|
+
}
|
|
235
|
+
|
|
20
236
|
/**
|
|
21
237
|
* List all installed plugins with their status
|
|
22
238
|
* Reads from Claude Code's native installed_plugins.json
|
|
23
239
|
* @returns {Object} { plugins: Array }
|
|
24
240
|
*/
|
|
25
241
|
listPlugins() {
|
|
242
|
+
if (this._isOpenCode()) {
|
|
243
|
+
const plugins = [];
|
|
244
|
+
const seen = new Set();
|
|
245
|
+
|
|
246
|
+
for (const pkg of this._listOpenCodeConfiguredPlugins()) {
|
|
247
|
+
if (seen.has(pkg)) continue;
|
|
248
|
+
seen.add(pkg);
|
|
249
|
+
plugins.push({
|
|
250
|
+
name: pkg,
|
|
251
|
+
directory: pkg,
|
|
252
|
+
source: 'opencode-config',
|
|
253
|
+
version: 'latest',
|
|
254
|
+
description: '',
|
|
255
|
+
installed: true,
|
|
256
|
+
enabled: true,
|
|
257
|
+
pluginType: 'npm'
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
for (const plugin of this._listOpenCodeLocalPlugins()) {
|
|
262
|
+
if (!seen.has(plugin.name)) {
|
|
263
|
+
seen.add(plugin.name);
|
|
264
|
+
plugins.push(plugin);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return { plugins };
|
|
269
|
+
}
|
|
270
|
+
|
|
26
271
|
const plugins = [];
|
|
27
272
|
|
|
28
273
|
// Read Claude Code's installed_plugins.json
|
|
@@ -101,6 +346,12 @@ class PluginsService {
|
|
|
101
346
|
* @returns {Object|null} Plugin details or null
|
|
102
347
|
*/
|
|
103
348
|
getPlugin(name) {
|
|
349
|
+
if (this._isOpenCode()) {
|
|
350
|
+
const plugin = this.listPlugins().plugins.find(p => p.name === name || p.directory === name);
|
|
351
|
+
if (!plugin) return null;
|
|
352
|
+
return plugin;
|
|
353
|
+
}
|
|
354
|
+
|
|
104
355
|
const plugin = getPlugin(name);
|
|
105
356
|
if (!plugin) {
|
|
106
357
|
return null;
|
|
@@ -136,6 +387,36 @@ class PluginsService {
|
|
|
136
387
|
* @returns {Promise<Object>} Installation result
|
|
137
388
|
*/
|
|
138
389
|
async installPlugin(source, repoInfo = null) {
|
|
390
|
+
if (this._isOpenCode()) {
|
|
391
|
+
if (repoInfo && repoInfo.owner && repoInfo.name && repoInfo.directory) {
|
|
392
|
+
return this._installFromGitHubDirectory(repoInfo, { installRoot: this._getOpenCodePluginsDir() });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const treeMatch = source.match(/github\.com\/([^\/]+)\/([^\/]+)\/tree\/([^\/]+)\/(.+)/);
|
|
396
|
+
if (treeMatch) {
|
|
397
|
+
const [, owner, name, branch, directory] = treeMatch;
|
|
398
|
+
return this._installFromGitHubDirectory({ owner, name, branch, directory }, { installRoot: this._getOpenCodePluginsDir() });
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// OpenCode 原生支持 npm 包名,通过 opencode.json 的 plugin 数组管理
|
|
402
|
+
if (!/^https?:\/\//.test(source)) {
|
|
403
|
+
const plugins = this._listOpenCodeConfiguredPlugins();
|
|
404
|
+
if (!plugins.includes(source)) {
|
|
405
|
+
plugins.push(source);
|
|
406
|
+
this._setOpenCodeConfiguredPlugins(plugins);
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
success: true,
|
|
410
|
+
plugin: { name: source, version: 'latest', description: '' }
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
success: false,
|
|
416
|
+
error: 'OpenCode plugin install expects npm package name or GitHub tree URL'
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
139
420
|
// If repoInfo is provided, download from GitHub directly
|
|
140
421
|
if (repoInfo && repoInfo.owner && repoInfo.name && repoInfo.directory) {
|
|
141
422
|
return await this._installFromGitHubDirectory(repoInfo);
|
|
@@ -156,10 +437,11 @@ class PluginsService {
|
|
|
156
437
|
* Install plugin from GitHub directory
|
|
157
438
|
* @private
|
|
158
439
|
*/
|
|
159
|
-
async _installFromGitHubDirectory(repoInfo) {
|
|
440
|
+
async _installFromGitHubDirectory(repoInfo, options = {}) {
|
|
160
441
|
const { owner, name, branch, directory } = repoInfo;
|
|
161
442
|
const https = require('https');
|
|
162
443
|
const pluginName = directory.split('/').pop();
|
|
444
|
+
const installRoot = options.installRoot || INSTALLED_DIR;
|
|
163
445
|
|
|
164
446
|
try {
|
|
165
447
|
// Fetch plugin.json from the directory
|
|
@@ -174,7 +456,7 @@ class PluginsService {
|
|
|
174
456
|
}
|
|
175
457
|
|
|
176
458
|
// Create plugin directory
|
|
177
|
-
const pluginDir = path.join(
|
|
459
|
+
const pluginDir = path.join(installRoot, manifest.name || pluginName);
|
|
178
460
|
if (!fs.existsSync(pluginDir)) {
|
|
179
461
|
fs.mkdirSync(pluginDir, { recursive: true });
|
|
180
462
|
}
|
|
@@ -196,14 +478,16 @@ class PluginsService {
|
|
|
196
478
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
197
479
|
}
|
|
198
480
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
481
|
+
if (!this._isOpenCode()) {
|
|
482
|
+
// Register plugin for Claude plugin runtime
|
|
483
|
+
const { addPlugin } = require('../../plugins/registry');
|
|
484
|
+
addPlugin(manifest.name || pluginName, {
|
|
485
|
+
version: manifest.version || '1.0.0',
|
|
486
|
+
enabled: true,
|
|
487
|
+
installedAt: new Date().toISOString(),
|
|
488
|
+
source: `https://github.com/${owner}/${name}/tree/${branch}/${directory}`
|
|
489
|
+
});
|
|
490
|
+
}
|
|
207
491
|
|
|
208
492
|
return {
|
|
209
493
|
success: true,
|
|
@@ -250,6 +534,43 @@ class PluginsService {
|
|
|
250
534
|
* @returns {Object} Uninstallation result
|
|
251
535
|
*/
|
|
252
536
|
uninstallPlugin(name) {
|
|
537
|
+
if (this._isOpenCode()) {
|
|
538
|
+
const pluginsDir = this._getOpenCodePluginsDir();
|
|
539
|
+
let removed = false;
|
|
540
|
+
|
|
541
|
+
// Remove from opencode config.plugin (npm plugins)
|
|
542
|
+
const configured = this._listOpenCodeConfiguredPlugins();
|
|
543
|
+
const next = configured.filter(p => p !== name);
|
|
544
|
+
if (next.length !== configured.length) {
|
|
545
|
+
this._setOpenCodeConfiguredPlugins(next);
|
|
546
|
+
removed = true;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Remove local plugin directory/file
|
|
550
|
+
if (fs.existsSync(pluginsDir)) {
|
|
551
|
+
const directPath = path.join(pluginsDir, name);
|
|
552
|
+
if (fs.existsSync(directPath)) {
|
|
553
|
+
fs.rmSync(directPath, { recursive: true, force: true });
|
|
554
|
+
removed = true;
|
|
555
|
+
} else {
|
|
556
|
+
const entries = fs.readdirSync(pluginsDir, { withFileTypes: true });
|
|
557
|
+
for (const entry of entries) {
|
|
558
|
+
const baseName = entry.name.replace(path.extname(entry.name), '');
|
|
559
|
+
if (entry.name === name || baseName === name) {
|
|
560
|
+
fs.rmSync(path.join(pluginsDir, entry.name), { recursive: true, force: true });
|
|
561
|
+
removed = true;
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
success: true,
|
|
570
|
+
message: removed ? 'Plugin removed successfully' : 'Plugin not found'
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
253
574
|
return uninstallPluginCore(name);
|
|
254
575
|
}
|
|
255
576
|
|
|
@@ -260,6 +581,22 @@ class PluginsService {
|
|
|
260
581
|
* @returns {Object} Updated plugin info
|
|
261
582
|
*/
|
|
262
583
|
togglePlugin(name, enabled) {
|
|
584
|
+
if (this._isOpenCode()) {
|
|
585
|
+
const configured = this._listOpenCodeConfiguredPlugins();
|
|
586
|
+
const exists = configured.includes(name);
|
|
587
|
+
if (enabled && !exists) {
|
|
588
|
+
configured.push(name);
|
|
589
|
+
this._setOpenCodeConfiguredPlugins(configured);
|
|
590
|
+
} else if (!enabled && exists) {
|
|
591
|
+
this._setOpenCodeConfiguredPlugins(configured.filter(p => p !== name));
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return {
|
|
595
|
+
name,
|
|
596
|
+
enabled
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
263
600
|
const plugin = getPlugin(name);
|
|
264
601
|
if (!plugin) {
|
|
265
602
|
throw new Error(`Plugin "${name}" not found`);
|
|
@@ -280,6 +617,17 @@ class PluginsService {
|
|
|
280
617
|
* @returns {Object} Result
|
|
281
618
|
*/
|
|
282
619
|
updatePluginConfig(name, config) {
|
|
620
|
+
if (this._isOpenCode()) {
|
|
621
|
+
const configDir = path.join(OPENCODE_CONFIG_DIR, 'plugins-config');
|
|
622
|
+
this._ensureDir(configDir);
|
|
623
|
+
const configFile = path.join(configDir, `${name}.json`);
|
|
624
|
+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2), 'utf8');
|
|
625
|
+
return {
|
|
626
|
+
success: true,
|
|
627
|
+
message: `Configuration updated for plugin "${name}"`
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
283
631
|
const plugin = getPlugin(name);
|
|
284
632
|
if (!plugin) {
|
|
285
633
|
throw new Error(`Plugin "${name}" not found`);
|
|
@@ -305,12 +653,15 @@ class PluginsService {
|
|
|
305
653
|
* @returns {string} Config file path
|
|
306
654
|
*/
|
|
307
655
|
getReposConfigPath() {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
656
|
+
this._ensureDir(this.ccToolConfigDir);
|
|
657
|
+
if (this._isOpenCode()) {
|
|
658
|
+
return path.join(this.ccToolConfigDir, 'opencode-plugin-repos.json');
|
|
312
659
|
}
|
|
313
|
-
return path.join(
|
|
660
|
+
return path.join(this.ccToolConfigDir, 'plugin-repos.json');
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
_getDefaultRepos() {
|
|
664
|
+
return cloneRepos(DEFAULT_REPOS_BY_PLATFORM[this.platform] || DEFAULT_REPOS_BY_PLATFORM.claude);
|
|
314
665
|
}
|
|
315
666
|
|
|
316
667
|
/**
|
|
@@ -319,14 +670,19 @@ class PluginsService {
|
|
|
319
670
|
*/
|
|
320
671
|
loadReposConfig() {
|
|
321
672
|
const configPath = this.getReposConfigPath();
|
|
673
|
+
const defaultRepos = this._getDefaultRepos();
|
|
322
674
|
if (!fs.existsSync(configPath)) {
|
|
323
|
-
return { repos:
|
|
675
|
+
return { repos: defaultRepos };
|
|
324
676
|
}
|
|
325
677
|
try {
|
|
326
|
-
|
|
678
|
+
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
679
|
+
if (parsed && Array.isArray(parsed.repos)) {
|
|
680
|
+
return parsed;
|
|
681
|
+
}
|
|
682
|
+
return { repos: defaultRepos };
|
|
327
683
|
} catch (err) {
|
|
328
684
|
console.error('Failed to load repos config:', err);
|
|
329
|
-
return { repos:
|
|
685
|
+
return { repos: defaultRepos };
|
|
330
686
|
}
|
|
331
687
|
}
|
|
332
688
|
|
|
@@ -358,8 +714,8 @@ class PluginsService {
|
|
|
358
714
|
}
|
|
359
715
|
}
|
|
360
716
|
|
|
361
|
-
// 2. Load Claude Code's native marketplace config
|
|
362
|
-
if (fs.existsSync(CLAUDE_MARKETPLACES_FILE)) {
|
|
717
|
+
// 2. Load Claude Code's native marketplace config (Claude only)
|
|
718
|
+
if (!this._isOpenCode() && fs.existsSync(CLAUDE_MARKETPLACES_FILE)) {
|
|
363
719
|
try {
|
|
364
720
|
const marketplaces = JSON.parse(fs.readFileSync(CLAUDE_MARKETPLACES_FILE, 'utf8'));
|
|
365
721
|
|
|
@@ -484,6 +840,10 @@ class PluginsService {
|
|
|
484
840
|
* @returns {Promise<Object>} Sync results
|
|
485
841
|
*/
|
|
486
842
|
async syncRepos() {
|
|
843
|
+
if (this._isOpenCode()) {
|
|
844
|
+
return { success: true, results: [] };
|
|
845
|
+
}
|
|
846
|
+
|
|
487
847
|
const repos = this.getRepos();
|
|
488
848
|
const results = [];
|
|
489
849
|
const { execSync } = require('child_process');
|
|
@@ -589,6 +949,73 @@ class PluginsService {
|
|
|
589
949
|
}
|
|
590
950
|
}
|
|
591
951
|
|
|
952
|
+
_parseGitHubRepo(url = '') {
|
|
953
|
+
const match = url.match(/github\.com\/([^\/]+)\/([^\/#?]+)/i);
|
|
954
|
+
if (!match) return null;
|
|
955
|
+
return {
|
|
956
|
+
owner: match[1],
|
|
957
|
+
name: match[2].replace(/\.git$/, '')
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
async _fetchOpenCodeMarketplacePlugins(repo, branch) {
|
|
962
|
+
if (!this._isOpenCode()) return [];
|
|
963
|
+
|
|
964
|
+
let entries;
|
|
965
|
+
try {
|
|
966
|
+
const indexUrl = `https://api.github.com/repos/${repo.owner}/${repo.name}/contents/plugins?ref=${branch}`;
|
|
967
|
+
entries = await this._fetchJson(indexUrl);
|
|
968
|
+
} catch (err) {
|
|
969
|
+
return [];
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
if (!Array.isArray(entries)) return [];
|
|
973
|
+
|
|
974
|
+
const manifestFiles = entries.filter(
|
|
975
|
+
item => item.type === 'file' && item.name.endsWith('.plugin.json')
|
|
976
|
+
);
|
|
977
|
+
if (manifestFiles.length === 0) return [];
|
|
978
|
+
|
|
979
|
+
const results = await Promise.allSettled(
|
|
980
|
+
manifestFiles.map(async (file) => {
|
|
981
|
+
const fileUrl = file.download_url ||
|
|
982
|
+
`https://raw.githubusercontent.com/${repo.owner}/${repo.name}/${branch}/${file.path}`;
|
|
983
|
+
const manifest = await this._fetchJson(fileUrl);
|
|
984
|
+
|
|
985
|
+
const author = Array.isArray(manifest.authors)
|
|
986
|
+
? manifest.authors.map(item => item?.name).filter(Boolean).join(', ')
|
|
987
|
+
: '';
|
|
988
|
+
const firstCategory = Array.isArray(manifest.categories) ? manifest.categories[0] : '';
|
|
989
|
+
const repoUrl = manifest.links?.repository || `https://github.com/${repo.owner}/${repo.name}`;
|
|
990
|
+
// OpenCode supports npm package plugins via opencode.json "plugin" array.
|
|
991
|
+
// Use package name as install source so UI install button is enabled.
|
|
992
|
+
const installSource = String(manifest.name || '').trim();
|
|
993
|
+
const githubRepo = this._parseGitHubRepo(repoUrl);
|
|
994
|
+
|
|
995
|
+
return {
|
|
996
|
+
name: manifest.name || file.name.replace(/\.plugin\.json$/, ''),
|
|
997
|
+
displayName: manifest.displayName || '',
|
|
998
|
+
description: manifest.description || '',
|
|
999
|
+
author: author || repo.owner,
|
|
1000
|
+
version: manifest.version || manifest.opencode?.minimumVersion || '1.0.0',
|
|
1001
|
+
category: firstCategory ? String(firstCategory).toLowerCase() : 'general',
|
|
1002
|
+
repoUrl,
|
|
1003
|
+
repoOwner: '',
|
|
1004
|
+
repoName: '',
|
|
1005
|
+
repoBranch: githubRepo ? 'main' : branch,
|
|
1006
|
+
directory: file.path,
|
|
1007
|
+
installSource,
|
|
1008
|
+
marketplaceFormat: 'opencode-plugin-json',
|
|
1009
|
+
isInstalled: false
|
|
1010
|
+
};
|
|
1011
|
+
})
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
return results
|
|
1015
|
+
.filter(item => item.status === 'fulfilled' && item.value)
|
|
1016
|
+
.map(item => item.value);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
592
1019
|
/**
|
|
593
1020
|
* Get market plugins from configured repositories
|
|
594
1021
|
* @returns {Promise<Array>} List of available market plugins
|
|
@@ -629,7 +1056,16 @@ class PluginsService {
|
|
|
629
1056
|
// marketplace.json not found, try legacy format
|
|
630
1057
|
}
|
|
631
1058
|
|
|
632
|
-
//
|
|
1059
|
+
// OpenCode plugin marketplace format: plugins/*.plugin.json
|
|
1060
|
+
if (this._isOpenCode()) {
|
|
1061
|
+
const openCodeMarketplacePlugins = await this._fetchOpenCodeMarketplacePlugins(repo, branch);
|
|
1062
|
+
if (openCodeMarketplacePlugins.length > 0) {
|
|
1063
|
+
marketPlugins.push(...openCodeMarketplacePlugins);
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Legacy format: each directory is a plugin with plugin.json/package.json
|
|
633
1069
|
const apiUrl = `https://api.github.com/repos/${repo.owner}/${repo.name}/contents?ref=${branch}`;
|
|
634
1070
|
const contents = await this._fetchJson(apiUrl);
|
|
635
1071
|
const pluginDirs = contents.filter(item => item.type === 'dir' && !item.name.startsWith('.'));
|
|
@@ -654,7 +1090,28 @@ class PluginsService {
|
|
|
654
1090
|
isInstalled: false
|
|
655
1091
|
});
|
|
656
1092
|
} catch (e) {
|
|
657
|
-
//
|
|
1093
|
+
// OpenCode 仓库常见 package.json 格式
|
|
1094
|
+
if (this._isOpenCode()) {
|
|
1095
|
+
try {
|
|
1096
|
+
const pkgUrl = `https://raw.githubusercontent.com/${repo.owner}/${repo.name}/${branch}/${dir.name}/package.json`;
|
|
1097
|
+
const pkg = await this._fetchJson(pkgUrl);
|
|
1098
|
+
const pluginName = pkg.name || dir.name;
|
|
1099
|
+
marketPlugins.push({
|
|
1100
|
+
name: pluginName,
|
|
1101
|
+
description: pkg.description || '',
|
|
1102
|
+
author: pkg.author || repo.owner,
|
|
1103
|
+
version: pkg.version || '1.0.0',
|
|
1104
|
+
repoUrl: `https://github.com/${repo.owner}/${repo.name}`,
|
|
1105
|
+
repoOwner: repo.owner,
|
|
1106
|
+
repoName: repo.name,
|
|
1107
|
+
repoBranch: branch,
|
|
1108
|
+
directory: dir.name,
|
|
1109
|
+
isInstalled: false
|
|
1110
|
+
});
|
|
1111
|
+
} catch (pkgErr) {
|
|
1112
|
+
// neither plugin.json nor package.json
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
658
1115
|
}
|
|
659
1116
|
}
|
|
660
1117
|
} catch (err) {
|