coding-tool-x 3.5.9 → 3.5.10
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 +11 -0
- package/README.md +4 -25
- package/bin/ctx.js +1 -6
- package/dist/web/assets/{Analytics-C5W3axXs.js → Analytics-DPb1zErn.js} +2 -2
- package/dist/web/assets/Analytics-RNn1BUbG.css +1 -0
- package/dist/web/assets/{ConfigTemplates-DzyVFDx9.js → ConfigTemplates-Bki2k4Lo.js} +1 -1
- package/dist/web/assets/{Home-C9TQNB6f.js → Home-BKYHKSax.js} +1 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
- package/dist/web/assets/{PluginManager-9B_brLWT.js → PluginManager-jgeQOE24.js} +1 -1
- package/dist/web/assets/ProjectList-CF67RzP_.js +1 -0
- package/dist/web/assets/ProjectList-DL4JK6ci.css +1 -0
- package/dist/web/assets/SessionList-DhHsY5q9.js +1 -0
- package/dist/web/assets/SessionList-lz23U6ZJ.css +1 -0
- package/dist/web/assets/{SkillManager-vST8DRRg.js → SkillManager-CtEz5gGp.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-ov1KgRXR.js → WorkspaceManager-D3B0Y5XE.js} +1 -1
- package/dist/web/assets/icons-lKCTySl7.js +1 -0
- package/dist/web/assets/index-BS7aplYp.css +1 -0
- package/dist/web/assets/index-BnyNm74G.js +2 -0
- package/dist/web/assets/{markdown-DyTJGI4N.js → markdown-BNJfEgCA.js} +1 -1
- package/dist/web/assets/{naive-ui-Cg4_ZeoT.js → naive-ui-DM897mIP.js} +1 -1
- package/dist/web/assets/vendors-DQA4Hw9v.js +7 -0
- package/dist/web/assets/vue-vendor-B5RqjEjQ.js +45 -0
- package/dist/web/index.html +8 -8
- package/package.json +1 -2
- package/src/commands/daemon.js +5 -33
- package/src/commands/export-config.js +6 -6
- package/src/commands/stats.js +4 -41
- package/src/commands/ui.js +3 -12
- package/src/config/channel-preset-websites.js +114 -0
- package/src/config/default.js +9 -5
- package/src/config/loader.js +2 -2
- package/src/config/model-metadata.json +16 -20
- package/src/config/paths.js +33 -166
- package/src/index.js +34 -125
- package/src/server/api/agents.js +2 -52
- package/src/server/api/codex-proxy.js +4 -2
- package/src/server/api/codex-sessions.js +89 -12
- package/src/server/api/commands.js +2 -38
- package/src/server/api/dashboard.js +1 -25
- package/src/server/api/gemini-proxy.js +4 -2
- package/src/server/api/gemini-sessions.js +86 -9
- package/src/server/api/hooks.js +1 -17
- package/src/server/api/opencode-proxy.js +7 -2
- package/src/server/api/opencode-sessions.js +83 -10
- package/src/server/api/plugins.js +34 -128
- package/src/server/api/proxy.js +5 -3
- package/src/server/api/sessions.js +181 -27
- package/src/server/api/workspaces.js +0 -14
- package/src/server/index.js +9 -31
- package/src/server/services/agents-service.js +62 -269
- package/src/server/services/base/base-channel-service.js +3 -1
- package/src/server/services/channels.js +5 -0
- package/src/server/services/codex-channels.js +2 -0
- package/src/server/services/codex-sessions.js +78 -108
- package/src/server/services/commands-service.js +81 -281
- package/src/server/services/config-export-service.js +7 -7
- package/src/server/services/config-registry-service.js +9 -8
- package/src/server/services/config-sync-manager.js +41 -61
- package/src/server/services/config-sync-service.js +3 -3
- package/src/server/services/gemini-channels.js +17 -6
- package/src/server/services/gemini-config.js +4 -3
- package/src/server/services/gemini-sessions.js +51 -26
- package/src/server/services/gemini-settings-manager.js +3 -2
- package/src/server/services/mcp-service.js +14 -9
- package/src/server/services/native-oauth-adapters.js +3 -3
- package/src/server/services/network-access.js +2 -61
- package/src/server/services/notification-hooks.js +19 -184
- package/src/server/services/opencode-channels.js +9 -0
- package/src/server/services/opencode-sessions.js +6 -16
- package/src/server/services/opencode-settings-manager.js +3 -3
- package/src/server/services/plugins-service.js +62 -996
- package/src/server/services/prompts-service.js +9 -5
- package/src/server/services/sessions.js +161 -183
- package/src/server/services/skill-service.js +18 -155
- package/src/server/websocket-server.js +4 -35
- package/dist/web/assets/Analytics-vQS5IWvs.css +0 -1
- package/dist/web/assets/Home-qzk118Of.css +0 -1
- package/dist/web/assets/ProjectList-Bjt6mrsV.js +0 -1
- package/dist/web/assets/ProjectList-GCC2QOmq.css +0 -1
- package/dist/web/assets/SessionList-BsHPgmUR.css +0 -1
- package/dist/web/assets/SessionList-DcBH13uA.js +0 -1
- package/dist/web/assets/icons-CEq2hYB-.js +0 -1
- package/dist/web/assets/index-Dih_bOsv.css +0 -1
- package/dist/web/assets/index-Duc7QP4e.js +0 -2
- package/dist/web/assets/vendors-Bsp-dq2d.js +0 -7
- package/dist/web/assets/vue-vendor-BxIT0uQq.js +0 -45
- package/docs/Caddyfile.example +0 -19
- package/docs/reverse-proxy-https.md +0 -57
- package/src/server/services/https-cert.js +0 -171
- package/src/server/services/session-launch-command.js +0 -58
- package/src/server/services/web-ui-runtime.js +0 -54
|
@@ -18,15 +18,10 @@ const { INSTALLED_DIR, CONFIG_DIR } = require('../../plugins/constants');
|
|
|
18
18
|
const { NATIVE_PATHS, PATHS } = require('../../config/paths');
|
|
19
19
|
const { maskToken } = require('./oauth-utils');
|
|
20
20
|
|
|
21
|
-
const CLAUDE_PLUGINS_DIR = NATIVE_PATHS.claude.plugins;
|
|
22
|
-
const CLAUDE_INSTALLED_FILE =
|
|
23
|
-
const CLAUDE_MARKETPLACES_FILE =
|
|
24
|
-
const
|
|
25
|
-
const OPENCODE_LEGACY_PLUGINS_DIR = NATIVE_PATHS.opencode.pluginsLegacy;
|
|
26
|
-
const OPENCODE_CONFIG_JSONC = NATIVE_PATHS.opencode.configJsonc;
|
|
27
|
-
const OPENCODE_CONFIG_JSON = NATIVE_PATHS.opencode.configJson;
|
|
28
|
-
const OPENCODE_CONFIG_LEGACY = NATIVE_PATHS.opencode.configLegacy;
|
|
29
|
-
const OPENCODE_PLUGINS_CONFIG_DIR = NATIVE_PATHS.opencode.pluginsConfig;
|
|
21
|
+
const CLAUDE_PLUGINS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'plugins');
|
|
22
|
+
const CLAUDE_INSTALLED_FILE = path.join(CLAUDE_PLUGINS_DIR, 'installed_plugins.json');
|
|
23
|
+
const CLAUDE_MARKETPLACES_FILE = path.join(CLAUDE_PLUGINS_DIR, 'known_marketplaces.json');
|
|
24
|
+
const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
|
|
30
25
|
const REPO_SOURCE_META_FILE = '.cc-tool-plugin-source.json';
|
|
31
26
|
const SUPPORTED_REPO_PROVIDERS = ['github', 'gitlab', 'local'];
|
|
32
27
|
const DEFAULT_GITHUB_HOST = 'https://github.com';
|
|
@@ -35,15 +30,6 @@ const DEFAULT_REPOS_BY_PLATFORM = {
|
|
|
35
30
|
claude: [],
|
|
36
31
|
opencode: []
|
|
37
32
|
};
|
|
38
|
-
const MAX_PLUGIN_FILE_PREVIEW_BYTES = 256 * 1024;
|
|
39
|
-
const TEXT_PREVIEW_EXTENSIONS = new Set([
|
|
40
|
-
'.md', '.txt', '.json', '.jsonc', '.js', '.cjs', '.mjs', '.ts', '.jsx', '.tsx', '.vue',
|
|
41
|
-
'.css', '.scss', '.sass', '.less', '.html', '.xml', '.svg', '.py', '.sh', '.bash', '.zsh',
|
|
42
|
-
'.yaml', '.yml', '.toml', '.ini', '.conf', '.cfg', '.env', '.sample', '.gitignore', '.npmignore'
|
|
43
|
-
]);
|
|
44
|
-
const TEXT_PREVIEW_BASENAMES = new Set([
|
|
45
|
-
'README', 'README.md', 'readme.md', 'LICENSE', 'Dockerfile', 'Makefile', 'SKILL.md'
|
|
46
|
-
]);
|
|
47
33
|
|
|
48
34
|
function cloneRepos(repos = []) {
|
|
49
35
|
return repos.map(repo => ({ ...repo }));
|
|
@@ -222,538 +208,17 @@ function stripJsonComments(input = '') {
|
|
|
222
208
|
return result;
|
|
223
209
|
}
|
|
224
210
|
|
|
225
|
-
function isPlainObject(value) {
|
|
226
|
-
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function getNestedValue(source, segments = []) {
|
|
230
|
-
return segments.reduce((acc, segment) => (acc && acc[segment] !== undefined ? acc[segment] : undefined), source);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function normalizeContentText(value = '') {
|
|
234
|
-
return String(value || '').trim();
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function normalizeContentEntry(raw, fallback = {}) {
|
|
238
|
-
if (typeof raw === 'string') {
|
|
239
|
-
const name = normalizeContentText(raw);
|
|
240
|
-
if (!name) return null;
|
|
241
|
-
return {
|
|
242
|
-
name,
|
|
243
|
-
path: normalizeRepoPath(fallback.path || ''),
|
|
244
|
-
description: normalizeContentText(fallback.description || '')
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (!isPlainObject(raw)) {
|
|
249
|
-
return null;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const candidateName = [
|
|
253
|
-
raw.name,
|
|
254
|
-
raw.displayName,
|
|
255
|
-
raw.id,
|
|
256
|
-
raw.title,
|
|
257
|
-
raw.fileName,
|
|
258
|
-
raw.directory,
|
|
259
|
-
raw.path,
|
|
260
|
-
fallback.name
|
|
261
|
-
].map(normalizeContentText).find(Boolean) || '';
|
|
262
|
-
const itemPath = normalizeRepoPath(raw.path || raw.file || raw.fileName || raw.directory || fallback.path || '');
|
|
263
|
-
const description = normalizeContentText(raw.description || raw.prompt || raw.usage || fallback.description || '');
|
|
264
|
-
const derivedName = candidateName || (itemPath ? path.posix.basename(itemPath, path.posix.extname(itemPath)) : '');
|
|
265
|
-
|
|
266
|
-
if (!derivedName && !itemPath) {
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
name: derivedName,
|
|
272
|
-
path: itemPath,
|
|
273
|
-
description
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function normalizeContentCollection(value, fallback = {}) {
|
|
278
|
-
if (Array.isArray(value)) {
|
|
279
|
-
return value.map(item => normalizeContentEntry(item, fallback)).filter(Boolean);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (isPlainObject(value)) {
|
|
283
|
-
return Object.entries(value)
|
|
284
|
-
.map(([key, item]) => {
|
|
285
|
-
if (isPlainObject(item)) {
|
|
286
|
-
return normalizeContentEntry({
|
|
287
|
-
...item,
|
|
288
|
-
name: item.name || key
|
|
289
|
-
}, fallback);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (typeof item === 'string') {
|
|
293
|
-
return normalizeContentEntry({
|
|
294
|
-
name: key,
|
|
295
|
-
description: item
|
|
296
|
-
}, fallback);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return normalizeContentEntry({ name: key }, fallback);
|
|
300
|
-
})
|
|
301
|
-
.filter(Boolean);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return [];
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function mergeContentCollections(...collections) {
|
|
308
|
-
const merged = [];
|
|
309
|
-
const seen = new Set();
|
|
310
|
-
|
|
311
|
-
for (const collection of collections) {
|
|
312
|
-
for (const rawEntry of collection || []) {
|
|
313
|
-
const entry = normalizeContentEntry(rawEntry);
|
|
314
|
-
if (!entry) continue;
|
|
315
|
-
|
|
316
|
-
const normalizedPath = normalizeRepoPath(entry.path || '');
|
|
317
|
-
const normalizedName = normalizeContentText(entry.name || '');
|
|
318
|
-
const key = normalizedPath
|
|
319
|
-
? `path:${normalizedPath.toLowerCase()}`
|
|
320
|
-
: (normalizedName ? `name:${normalizedName.toLowerCase()}` : '');
|
|
321
|
-
|
|
322
|
-
if (!key || seen.has(key)) {
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
seen.add(key);
|
|
327
|
-
merged.push({
|
|
328
|
-
name: normalizedName || path.posix.basename(normalizedPath, path.posix.extname(normalizedPath)),
|
|
329
|
-
path: normalizedPath,
|
|
330
|
-
description: normalizeContentText(entry.description || '')
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return merged.sort((a, b) => {
|
|
336
|
-
const left = a.name || a.path;
|
|
337
|
-
const right = b.name || b.path;
|
|
338
|
-
return left.localeCompare(right);
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
211
|
class PluginsService {
|
|
343
212
|
constructor(platform = 'claude') {
|
|
344
213
|
this.platform = ['claude', 'opencode'].includes(platform) ? platform : 'claude';
|
|
345
214
|
this.configDir = PATHS.config || path.join((PATHS.base || process.env.HOME || os.homedir()), 'config');
|
|
346
215
|
this.ccToolConfigDir = path.dirname(PATHS.pluginRepos.claude);
|
|
347
|
-
this.
|
|
348
|
-
|
|
349
|
-
: PATHS.localPlugins.claude;
|
|
350
|
-
this.opencodePluginsDir = OPENCODE_PLUGINS_DIR;
|
|
351
|
-
this.opencodeLegacyPluginsDir = OPENCODE_LEGACY_PLUGINS_DIR;
|
|
216
|
+
this.opencodePluginsDir = path.join(OPENCODE_CONFIG_DIR, 'plugins');
|
|
217
|
+
this.opencodeLegacyPluginsDir = path.join(OPENCODE_CONFIG_DIR, 'plugin');
|
|
352
218
|
this.marketCachePath = this.platform === 'opencode'
|
|
353
219
|
? PATHS.pluginMarketCache.opencode
|
|
354
220
|
: PATHS.pluginMarketCache.claude;
|
|
355
221
|
this._marketCache = null;
|
|
356
|
-
this._ensureDir(this.storageDir);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
_createEmptyContentSummary() {
|
|
360
|
-
return {
|
|
361
|
-
skills: [],
|
|
362
|
-
agents: [],
|
|
363
|
-
commands: [],
|
|
364
|
-
hooks: [],
|
|
365
|
-
files: [],
|
|
366
|
-
totalFiles: 0,
|
|
367
|
-
additionalFileCount: 0
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
_getManifestCollection(manifest = null, paths = []) {
|
|
372
|
-
if (!isPlainObject(manifest)) {
|
|
373
|
-
return [];
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return mergeContentCollections(
|
|
377
|
-
...paths.map(segments => normalizeContentCollection(getNestedValue(manifest, segments)))
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
_summarizeManifestContent(manifest = null) {
|
|
382
|
-
const emptySummary = this._createEmptyContentSummary();
|
|
383
|
-
if (!isPlainObject(manifest)) {
|
|
384
|
-
return emptySummary;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return {
|
|
388
|
-
...emptySummary,
|
|
389
|
-
skills: this._getManifestCollection(manifest, [
|
|
390
|
-
['skills'],
|
|
391
|
-
['ctx', 'skills'],
|
|
392
|
-
['contributes', 'skills']
|
|
393
|
-
]),
|
|
394
|
-
agents: this._getManifestCollection(manifest, [
|
|
395
|
-
['agents'],
|
|
396
|
-
['ctx', 'agents'],
|
|
397
|
-
['contributes', 'agents']
|
|
398
|
-
]),
|
|
399
|
-
commands: this._getManifestCollection(manifest, [
|
|
400
|
-
['commands'],
|
|
401
|
-
['ctx', 'commands'],
|
|
402
|
-
['contributes', 'commands']
|
|
403
|
-
]),
|
|
404
|
-
hooks: this._getManifestCollection(manifest, [
|
|
405
|
-
['hooks'],
|
|
406
|
-
['ctx', 'hooks'],
|
|
407
|
-
['contributes', 'hooks']
|
|
408
|
-
])
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
_getScopedFileContext(files = [], directory = '') {
|
|
413
|
-
const normalizedDirectory = normalizeRepoPath(directory);
|
|
414
|
-
const directoryLooksLikeFile = /\.[a-z0-9]+$/i.test(path.posix.basename(normalizedDirectory || ''));
|
|
415
|
-
const allFiles = (Array.isArray(files) ? files : [])
|
|
416
|
-
.filter(item => item && item.type === 'blob' && item.path)
|
|
417
|
-
.map(item => ({
|
|
418
|
-
...item,
|
|
419
|
-
path: normalizeRepoPath(item.path)
|
|
420
|
-
}))
|
|
421
|
-
.sort((a, b) => a.path.localeCompare(b.path));
|
|
422
|
-
|
|
423
|
-
let scopedFiles = allFiles;
|
|
424
|
-
if (normalizedDirectory) {
|
|
425
|
-
if (directoryLooksLikeFile) {
|
|
426
|
-
scopedFiles = allFiles.filter(item => item.path === normalizedDirectory);
|
|
427
|
-
} else {
|
|
428
|
-
const directoryPrefix = `${normalizedDirectory}/`;
|
|
429
|
-
scopedFiles = allFiles.filter(item => item.path.startsWith(directoryPrefix));
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const toScopedPath = (filePath = '') => {
|
|
434
|
-
const normalizedFilePath = normalizeRepoPath(filePath);
|
|
435
|
-
if (!normalizedDirectory || directoryLooksLikeFile) {
|
|
436
|
-
return normalizedFilePath;
|
|
437
|
-
}
|
|
438
|
-
const prefix = `${normalizedDirectory}/`;
|
|
439
|
-
return normalizedFilePath.startsWith(prefix) ? normalizedFilePath.slice(prefix.length) : normalizedFilePath;
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
return {
|
|
443
|
-
normalizedDirectory,
|
|
444
|
-
directoryLooksLikeFile,
|
|
445
|
-
allFiles,
|
|
446
|
-
scopedFiles,
|
|
447
|
-
toScopedPath
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
_summarizeContentFromFiles(files = [], directory = '') {
|
|
452
|
-
const emptySummary = this._createEmptyContentSummary();
|
|
453
|
-
const { scopedFiles, toScopedPath } = this._getScopedFileContext(files, directory);
|
|
454
|
-
|
|
455
|
-
const skillEntries = [];
|
|
456
|
-
const agentEntries = [];
|
|
457
|
-
const commandEntries = [];
|
|
458
|
-
|
|
459
|
-
for (const file of scopedFiles) {
|
|
460
|
-
const scopedPath = toScopedPath(file.path);
|
|
461
|
-
const skillMatch = scopedPath.match(/^skills\/([^/]+)\/SKILL\.md$/i);
|
|
462
|
-
if (skillMatch) {
|
|
463
|
-
skillEntries.push({ name: skillMatch[1], path: scopedPath });
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const agentMatch = scopedPath.match(/^agents\/(.+)\.md$/i);
|
|
467
|
-
if (agentMatch) {
|
|
468
|
-
agentEntries.push({
|
|
469
|
-
name: agentMatch[1].split('/').pop(),
|
|
470
|
-
path: scopedPath
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const commandMatch = scopedPath.match(/^(?:commands|prompts)\/(.+)\.md$/i);
|
|
475
|
-
if (commandMatch) {
|
|
476
|
-
commandEntries.push({
|
|
477
|
-
name: commandMatch[1].split('/').pop(),
|
|
478
|
-
path: scopedPath
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const filePreview = scopedFiles.slice(0, 8).map(file => {
|
|
484
|
-
const scopedPath = toScopedPath(file.path);
|
|
485
|
-
return {
|
|
486
|
-
name: path.posix.basename(scopedPath),
|
|
487
|
-
path: scopedPath
|
|
488
|
-
};
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
return {
|
|
492
|
-
...emptySummary,
|
|
493
|
-
skills: mergeContentCollections(skillEntries),
|
|
494
|
-
agents: mergeContentCollections(agentEntries),
|
|
495
|
-
commands: mergeContentCollections(commandEntries),
|
|
496
|
-
files: filePreview,
|
|
497
|
-
totalFiles: scopedFiles.length,
|
|
498
|
-
additionalFileCount: Math.max(0, scopedFiles.length - filePreview.length)
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
_mergeContentSummary(...summaries) {
|
|
503
|
-
const emptySummary = this._createEmptyContentSummary();
|
|
504
|
-
const validSummaries = summaries.filter(Boolean);
|
|
505
|
-
const fileSummary = validSummaries.find(summary => Array.isArray(summary.files) && summary.files.length > 0) || emptySummary;
|
|
506
|
-
|
|
507
|
-
return {
|
|
508
|
-
...emptySummary,
|
|
509
|
-
skills: mergeContentCollections(...validSummaries.map(summary => summary.skills)),
|
|
510
|
-
agents: mergeContentCollections(...validSummaries.map(summary => summary.agents)),
|
|
511
|
-
commands: mergeContentCollections(...validSummaries.map(summary => summary.commands)),
|
|
512
|
-
hooks: mergeContentCollections(...validSummaries.map(summary => summary.hooks)),
|
|
513
|
-
files: fileSummary.files || [],
|
|
514
|
-
totalFiles: fileSummary.totalFiles || 0,
|
|
515
|
-
additionalFileCount: fileSummary.additionalFileCount || 0
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
_scanInstallPath(installPath = '') {
|
|
520
|
-
const resolvedPath = String(installPath || '').trim();
|
|
521
|
-
if (!resolvedPath || !fs.existsSync(resolvedPath)) {
|
|
522
|
-
return [];
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
try {
|
|
526
|
-
const stat = fs.statSync(resolvedPath);
|
|
527
|
-
if (stat.isFile()) {
|
|
528
|
-
const fileName = path.basename(resolvedPath);
|
|
529
|
-
return [{ path: fileName, type: 'blob', name: fileName, size: stat.size }];
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const tree = [];
|
|
533
|
-
this.scanLocalRepoTree(resolvedPath, resolvedPath, tree);
|
|
534
|
-
return tree;
|
|
535
|
-
} catch {
|
|
536
|
-
return [];
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
_isTextPreviewFile(filePath = '') {
|
|
541
|
-
const normalizedPath = normalizeRepoPath(filePath);
|
|
542
|
-
const baseName = path.posix.basename(normalizedPath);
|
|
543
|
-
const extension = path.posix.extname(baseName).toLowerCase();
|
|
544
|
-
if (TEXT_PREVIEW_EXTENSIONS.has(extension)) {
|
|
545
|
-
return true;
|
|
546
|
-
}
|
|
547
|
-
if (TEXT_PREVIEW_BASENAMES.has(baseName)) {
|
|
548
|
-
return true;
|
|
549
|
-
}
|
|
550
|
-
return /^\.env(\..+)?$/i.test(baseName);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
_bufferLooksBinary(buffer) {
|
|
554
|
-
if (!Buffer.isBuffer(buffer)) return false;
|
|
555
|
-
const sampleLength = Math.min(buffer.length, 1024);
|
|
556
|
-
for (let index = 0; index < sampleLength; index += 1) {
|
|
557
|
-
if (buffer[index] === 0) {
|
|
558
|
-
return true;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
return false;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
async _resolvePluginFileSource(pluginInput = {}) {
|
|
565
|
-
const detail = await this.getPluginDetail(pluginInput);
|
|
566
|
-
if (!detail) {
|
|
567
|
-
throw new Error('插件不存在');
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
if (detail.installPath && fs.existsSync(detail.installPath)) {
|
|
571
|
-
const files = this._scanInstallPath(detail.installPath);
|
|
572
|
-
return {
|
|
573
|
-
detail,
|
|
574
|
-
installPath: detail.installPath,
|
|
575
|
-
managedPath: detail.managedPath || '',
|
|
576
|
-
repo: null,
|
|
577
|
-
fileContext: this._getScopedFileContext(files)
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
if (detail.managedPath && fs.existsSync(detail.managedPath)) {
|
|
582
|
-
const files = this._scanInstallPath(detail.managedPath);
|
|
583
|
-
return {
|
|
584
|
-
detail,
|
|
585
|
-
installPath: '',
|
|
586
|
-
managedPath: detail.managedPath,
|
|
587
|
-
repo: null,
|
|
588
|
-
fileContext: this._getScopedFileContext(files)
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
const repo = this.resolvePluginRepo(detail);
|
|
593
|
-
if (!repo) {
|
|
594
|
-
return {
|
|
595
|
-
detail,
|
|
596
|
-
installPath: '',
|
|
597
|
-
managedPath: detail.managedPath || '',
|
|
598
|
-
repo: null,
|
|
599
|
-
fileContext: this._getScopedFileContext([], detail.directory || '')
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
const repoTree = await this.fetchRepoTree(repo);
|
|
604
|
-
return {
|
|
605
|
-
detail,
|
|
606
|
-
installPath: '',
|
|
607
|
-
repo,
|
|
608
|
-
fileContext: this._getScopedFileContext(repoTree, detail.directory || '')
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
async getPluginFiles(pluginInput = {}) {
|
|
613
|
-
const source = await this._resolvePluginFileSource(pluginInput);
|
|
614
|
-
return {
|
|
615
|
-
files: source.fileContext.scopedFiles.map(file => {
|
|
616
|
-
const scopedPath = source.fileContext.toScopedPath(file.path);
|
|
617
|
-
return {
|
|
618
|
-
name: path.posix.basename(scopedPath),
|
|
619
|
-
path: scopedPath,
|
|
620
|
-
size: Number.isFinite(file.size) ? file.size : null,
|
|
621
|
-
isText: this._isTextPreviewFile(scopedPath)
|
|
622
|
-
};
|
|
623
|
-
})
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
async getPluginFileContent(pluginInput = {}, filePath = '') {
|
|
628
|
-
const normalizedPath = normalizeRepoPath(filePath);
|
|
629
|
-
if (!normalizedPath) {
|
|
630
|
-
throw new Error('请指定文件路径');
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
const source = await this._resolvePluginFileSource(pluginInput);
|
|
634
|
-
const targetFile = source.fileContext.scopedFiles.find(file =>
|
|
635
|
-
source.fileContext.toScopedPath(file.path) === normalizedPath
|
|
636
|
-
);
|
|
637
|
-
|
|
638
|
-
if (!targetFile) {
|
|
639
|
-
throw new Error(`文件 "${normalizedPath}" 不存在`);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const scopedPath = source.fileContext.toScopedPath(targetFile.path);
|
|
643
|
-
const declaredSize = Number.isFinite(targetFile.size) ? targetFile.size : null;
|
|
644
|
-
|
|
645
|
-
if (!this._isTextPreviewFile(scopedPath)) {
|
|
646
|
-
return {
|
|
647
|
-
path: scopedPath,
|
|
648
|
-
size: declaredSize,
|
|
649
|
-
content: '',
|
|
650
|
-
isBinary: true,
|
|
651
|
-
isBase64: false,
|
|
652
|
-
tooLarge: false
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (declaredSize && declaredSize > MAX_PLUGIN_FILE_PREVIEW_BYTES) {
|
|
657
|
-
return {
|
|
658
|
-
path: scopedPath,
|
|
659
|
-
size: declaredSize,
|
|
660
|
-
content: '',
|
|
661
|
-
isBinary: false,
|
|
662
|
-
isBase64: false,
|
|
663
|
-
tooLarge: true
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
let content = '';
|
|
668
|
-
let size = declaredSize;
|
|
669
|
-
|
|
670
|
-
if (source.installPath || source.managedPath) {
|
|
671
|
-
const fileRoot = source.installPath || source.managedPath;
|
|
672
|
-
const resolvedRoot = path.resolve(fileRoot);
|
|
673
|
-
const resolvedPath = path.resolve(fileRoot, targetFile.path);
|
|
674
|
-
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${path.sep}`)) {
|
|
675
|
-
throw new Error('文件路径无效');
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
const stats = fs.statSync(resolvedPath);
|
|
679
|
-
if (stats.size > MAX_PLUGIN_FILE_PREVIEW_BYTES) {
|
|
680
|
-
return {
|
|
681
|
-
path: scopedPath,
|
|
682
|
-
size: stats.size,
|
|
683
|
-
content: '',
|
|
684
|
-
isBinary: false,
|
|
685
|
-
isBase64: false,
|
|
686
|
-
tooLarge: true
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
const buffer = fs.readFileSync(resolvedPath);
|
|
691
|
-
if (this._bufferLooksBinary(buffer)) {
|
|
692
|
-
return {
|
|
693
|
-
path: scopedPath,
|
|
694
|
-
size: stats.size,
|
|
695
|
-
content: '',
|
|
696
|
-
isBinary: true,
|
|
697
|
-
isBase64: false,
|
|
698
|
-
tooLarge: false
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
content = buffer.toString('utf8');
|
|
703
|
-
size = stats.size;
|
|
704
|
-
} else if (source.repo) {
|
|
705
|
-
content = await this.fetchRepoFileContent(source.repo, targetFile.path, targetFile);
|
|
706
|
-
size = Buffer.byteLength(content, 'utf8');
|
|
707
|
-
if (size > MAX_PLUGIN_FILE_PREVIEW_BYTES) {
|
|
708
|
-
return {
|
|
709
|
-
path: scopedPath,
|
|
710
|
-
size,
|
|
711
|
-
content: '',
|
|
712
|
-
isBinary: false,
|
|
713
|
-
isBase64: false,
|
|
714
|
-
tooLarge: true
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
} else {
|
|
718
|
-
throw new Error('当前插件没有可读取的文件源');
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
return {
|
|
722
|
-
path: scopedPath,
|
|
723
|
-
size,
|
|
724
|
-
content,
|
|
725
|
-
isBinary: false,
|
|
726
|
-
isBase64: false,
|
|
727
|
-
tooLarge: false
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
_readPluginManifestFromInstallPath(installPath = '') {
|
|
732
|
-
const resolvedPath = String(installPath || '').trim();
|
|
733
|
-
if (!resolvedPath || !fs.existsSync(resolvedPath)) {
|
|
734
|
-
return null;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
try {
|
|
738
|
-
const stat = fs.statSync(resolvedPath);
|
|
739
|
-
if (stat.isFile()) {
|
|
740
|
-
if (!resolvedPath.endsWith('.json')) {
|
|
741
|
-
return null;
|
|
742
|
-
}
|
|
743
|
-
return JSON.parse(stripJsonComments(fs.readFileSync(resolvedPath, 'utf8')));
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
const candidates = ['plugin.json', 'package.json'];
|
|
747
|
-
for (const candidate of candidates) {
|
|
748
|
-
const manifestPath = path.join(resolvedPath, candidate);
|
|
749
|
-
if (!fs.existsSync(manifestPath)) continue;
|
|
750
|
-
return JSON.parse(stripJsonComments(fs.readFileSync(manifestPath, 'utf8')));
|
|
751
|
-
}
|
|
752
|
-
} catch {
|
|
753
|
-
return null;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
return null;
|
|
757
222
|
}
|
|
758
223
|
|
|
759
224
|
normalizeRepoConfig(repo = {}) {
|
|
@@ -969,10 +434,13 @@ class PluginsService {
|
|
|
969
434
|
}
|
|
970
435
|
|
|
971
436
|
_getOpenCodeConfigPath() {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
return
|
|
437
|
+
const jsonc = path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc');
|
|
438
|
+
const json = path.join(OPENCODE_CONFIG_DIR, 'opencode.json');
|
|
439
|
+
const config = path.join(OPENCODE_CONFIG_DIR, 'config.json');
|
|
440
|
+
if (fs.existsSync(jsonc)) return jsonc;
|
|
441
|
+
if (fs.existsSync(json)) return json;
|
|
442
|
+
if (fs.existsSync(config)) return config;
|
|
443
|
+
return json;
|
|
976
444
|
}
|
|
977
445
|
|
|
978
446
|
_readOpenCodeConfig() {
|
|
@@ -1071,137 +539,12 @@ class PluginsService {
|
|
|
1071
539
|
return plugins;
|
|
1072
540
|
}
|
|
1073
541
|
|
|
1074
|
-
_normalizeManagedPluginStorageName(plugin = {}) {
|
|
1075
|
-
const rawName = String(plugin.name || path.basename(plugin.installPath || plugin.directory || '') || '').trim();
|
|
1076
|
-
if (!rawName) return '';
|
|
1077
|
-
return rawName.replace(/[\\/]+/g, '__');
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
_getManagedPluginPath(plugin = {}) {
|
|
1081
|
-
const storageName = this._normalizeManagedPluginStorageName(plugin);
|
|
1082
|
-
return storageName ? path.join(this.storageDir, storageName) : '';
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
_ensureManagedPluginCopy(plugin = {}, options = {}) {
|
|
1086
|
-
const overwrite = options.overwrite === true;
|
|
1087
|
-
const sourcePath = String(plugin.installPath || '').trim();
|
|
1088
|
-
if (!sourcePath || !fs.existsSync(sourcePath)) {
|
|
1089
|
-
return false;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
const managedPath = this._getManagedPluginPath(plugin);
|
|
1093
|
-
if (!managedPath) {
|
|
1094
|
-
return false;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
if (fs.existsSync(managedPath)) {
|
|
1098
|
-
if (!overwrite) {
|
|
1099
|
-
return false;
|
|
1100
|
-
}
|
|
1101
|
-
fs.rmSync(managedPath, { recursive: true, force: true });
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
this._ensureDir(path.dirname(managedPath));
|
|
1105
|
-
const stat = fs.statSync(sourcePath);
|
|
1106
|
-
if (stat.isDirectory()) {
|
|
1107
|
-
fs.mkdirSync(managedPath, { recursive: true });
|
|
1108
|
-
this.copyDirRecursive(sourcePath, managedPath);
|
|
1109
|
-
const repoSourceMeta = {
|
|
1110
|
-
repoId: plugin.repoId || '',
|
|
1111
|
-
repoProvider: plugin.repoProvider || '',
|
|
1112
|
-
repoOwner: plugin.repoOwner || '',
|
|
1113
|
-
repoName: plugin.repoName || '',
|
|
1114
|
-
repoBranch: plugin.repoBranch || '',
|
|
1115
|
-
repoDirectory: plugin.repoDirectory || plugin.directory || '',
|
|
1116
|
-
repoHost: plugin.repoHost || '',
|
|
1117
|
-
repoProjectPath: plugin.repoProjectPath || '',
|
|
1118
|
-
repoLocalPath: plugin.repoLocalPath || '',
|
|
1119
|
-
repoUrl: plugin.repoUrl || '',
|
|
1120
|
-
source: plugin.source || ''
|
|
1121
|
-
};
|
|
1122
|
-
this.writeRepoSourceMeta(managedPath, repoSourceMeta);
|
|
1123
|
-
} else {
|
|
1124
|
-
fs.copyFileSync(sourcePath, managedPath);
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
return true;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
_listManagedClaudePlugins() {
|
|
1131
|
-
if (!fs.existsSync(this.storageDir)) return [];
|
|
1132
|
-
|
|
1133
|
-
const entries = fs.readdirSync(this.storageDir, { withFileTypes: true });
|
|
1134
|
-
const plugins = [];
|
|
1135
|
-
|
|
1136
|
-
for (const entry of entries) {
|
|
1137
|
-
if (!entry.isDirectory() || entry.name.startsWith('.')) {
|
|
1138
|
-
continue;
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
const managedPath = path.join(this.storageDir, entry.name);
|
|
1142
|
-
const manifest = this._readPluginManifestFromInstallPath(managedPath) || {};
|
|
1143
|
-
const repoSourceMeta = this.readRepoSourceMeta(managedPath) || {};
|
|
1144
|
-
const pluginName = manifest.name || entry.name;
|
|
1145
|
-
|
|
1146
|
-
plugins.push({
|
|
1147
|
-
name: pluginName,
|
|
1148
|
-
version: manifest.version || '1.0.0',
|
|
1149
|
-
description: manifest.description || '',
|
|
1150
|
-
author: manifest.author || '',
|
|
1151
|
-
directory: repoSourceMeta.repoDirectory || pluginName,
|
|
1152
|
-
repoDirectory: repoSourceMeta.repoDirectory || '',
|
|
1153
|
-
source: 'local-managed',
|
|
1154
|
-
installPath: '',
|
|
1155
|
-
managedPath,
|
|
1156
|
-
installed: false,
|
|
1157
|
-
enabled: true,
|
|
1158
|
-
isLocal: true,
|
|
1159
|
-
repoUrl: repoSourceMeta.repoUrl || '',
|
|
1160
|
-
repoProvider: repoSourceMeta.repoProvider || '',
|
|
1161
|
-
repoOwner: repoSourceMeta.repoOwner || '',
|
|
1162
|
-
repoName: repoSourceMeta.repoName || '',
|
|
1163
|
-
repoBranch: repoSourceMeta.repoBranch || 'main',
|
|
1164
|
-
repoHost: repoSourceMeta.repoHost || '',
|
|
1165
|
-
repoProjectPath: repoSourceMeta.repoProjectPath || '',
|
|
1166
|
-
repoLocalPath: repoSourceMeta.repoLocalPath || '',
|
|
1167
|
-
repoId: repoSourceMeta.repoId || ''
|
|
1168
|
-
});
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
return plugins;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
_mergeManagedClaudePlugins(plugins = [], options = {}) {
|
|
1175
|
-
const syncManagedLocalPlugins = options.syncManagedLocalPlugins === true;
|
|
1176
|
-
|
|
1177
|
-
for (const installedPlugin of plugins) {
|
|
1178
|
-
if (!installedPlugin.installPath || !fs.existsSync(installedPlugin.installPath)) {
|
|
1179
|
-
continue;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
this._ensureManagedPluginCopy(installedPlugin, {
|
|
1183
|
-
overwrite: syncManagedLocalPlugins
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
const managedPlugins = this._listManagedClaudePlugins();
|
|
1188
|
-
for (const managedPlugin of managedPlugins) {
|
|
1189
|
-
const existing = plugins.find(plugin => plugin.name === managedPlugin.name || plugin.directory === managedPlugin.directory);
|
|
1190
|
-
if (existing) {
|
|
1191
|
-
existing.isLocal = true;
|
|
1192
|
-
existing.managedPath = managedPlugin.managedPath;
|
|
1193
|
-
continue;
|
|
1194
|
-
}
|
|
1195
|
-
plugins.push(managedPlugin);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
542
|
/**
|
|
1200
543
|
* List all installed plugins with their status
|
|
1201
544
|
* Reads from Claude Code's native installed_plugins.json
|
|
1202
545
|
* @returns {Object} { plugins: Array }
|
|
1203
546
|
*/
|
|
1204
|
-
listPlugins(
|
|
547
|
+
listPlugins() {
|
|
1205
548
|
if (this._isOpenCode()) {
|
|
1206
549
|
const plugins = [];
|
|
1207
550
|
const seen = new Set();
|
|
@@ -1232,7 +575,6 @@ class PluginsService {
|
|
|
1232
575
|
}
|
|
1233
576
|
|
|
1234
577
|
const plugins = [];
|
|
1235
|
-
const syncManagedLocalPlugins = options.syncManagedLocalPlugins === true;
|
|
1236
578
|
|
|
1237
579
|
// Read Claude Code's installed_plugins.json
|
|
1238
580
|
if (fs.existsSync(CLAUDE_INSTALLED_FILE)) {
|
|
@@ -1302,7 +644,6 @@ class PluginsService {
|
|
|
1302
644
|
marketplace,
|
|
1303
645
|
version: install.version || '1.0.0',
|
|
1304
646
|
installPath: install.installPath,
|
|
1305
|
-
installed: true,
|
|
1306
647
|
installedAt: install.installedAt,
|
|
1307
648
|
scope: install.scope,
|
|
1308
649
|
enabled: enabledState,
|
|
@@ -1340,8 +681,6 @@ class PluginsService {
|
|
|
1340
681
|
// Ignore legacy registry errors
|
|
1341
682
|
}
|
|
1342
683
|
|
|
1343
|
-
this._mergeManagedClaudePlugins(plugins, { syncManagedLocalPlugins });
|
|
1344
|
-
|
|
1345
684
|
return { plugins };
|
|
1346
685
|
}
|
|
1347
686
|
|
|
@@ -1380,183 +719,7 @@ class PluginsService {
|
|
|
1380
719
|
description: manifest?.description || '',
|
|
1381
720
|
author: manifest?.author || '',
|
|
1382
721
|
commands: manifest?.commands || [],
|
|
1383
|
-
hooks: manifest?.hooks ||
|
|
1384
|
-
manifest
|
|
1385
|
-
};
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
resolvePluginRepo(plugin = {}) {
|
|
1389
|
-
let repo = null;
|
|
1390
|
-
|
|
1391
|
-
if (plugin.repoProvider || plugin.repoLocalPath || plugin.repoProjectPath || plugin.repoOwner) {
|
|
1392
|
-
try {
|
|
1393
|
-
repo = this.normalizeRepoConfig({
|
|
1394
|
-
id: plugin.repoId,
|
|
1395
|
-
provider: plugin.repoProvider,
|
|
1396
|
-
host: plugin.repoHost,
|
|
1397
|
-
owner: plugin.repoOwner,
|
|
1398
|
-
name: plugin.repoName,
|
|
1399
|
-
branch: plugin.repoBranch || 'main',
|
|
1400
|
-
projectPath: plugin.repoProjectPath,
|
|
1401
|
-
localPath: plugin.repoLocalPath,
|
|
1402
|
-
repoUrl: plugin.repoUrl
|
|
1403
|
-
});
|
|
1404
|
-
} catch {
|
|
1405
|
-
repo = null;
|
|
1406
|
-
}
|
|
1407
|
-
} else if (plugin.source) {
|
|
1408
|
-
repo = this.parseRepoTreeSource(plugin.source);
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
if (!repo && plugin.repoUrl) {
|
|
1412
|
-
const parsedTreeSource = this.parseRepoTreeSource(plugin.repoUrl);
|
|
1413
|
-
if (parsedTreeSource) {
|
|
1414
|
-
repo = parsedTreeSource;
|
|
1415
|
-
} else if (plugin.repoUrl.includes('github.com')) {
|
|
1416
|
-
const match = plugin.repoUrl.match(/github\.com\/([^\/]+)\/([^\/\.]+)/);
|
|
1417
|
-
if (match) {
|
|
1418
|
-
try {
|
|
1419
|
-
repo = this.normalizeRepoConfig({
|
|
1420
|
-
provider: 'github',
|
|
1421
|
-
owner: match[1],
|
|
1422
|
-
name: match[2],
|
|
1423
|
-
branch: plugin.repoBranch || 'main'
|
|
1424
|
-
});
|
|
1425
|
-
} catch {
|
|
1426
|
-
repo = null;
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
return repo;
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
async _readPluginManifestFromRepo(plugin = {}, repo = null) {
|
|
1436
|
-
const resolvedRepo = repo || this.resolvePluginRepo(plugin);
|
|
1437
|
-
if (!resolvedRepo) {
|
|
1438
|
-
return null;
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
const normalizedDirectory = normalizeRepoPath(plugin.directory || '');
|
|
1442
|
-
const candidates = [];
|
|
1443
|
-
|
|
1444
|
-
if (normalizedDirectory) {
|
|
1445
|
-
if (/\.[a-z0-9]+$/i.test(path.posix.basename(normalizedDirectory))) {
|
|
1446
|
-
candidates.push(normalizedDirectory);
|
|
1447
|
-
} else {
|
|
1448
|
-
candidates.push(`${normalizedDirectory}/plugin.json`, `${normalizedDirectory}/package.json`);
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
candidates.push('plugin.json', 'package.json');
|
|
1453
|
-
|
|
1454
|
-
for (const candidate of Array.from(new Set(candidates))) {
|
|
1455
|
-
try {
|
|
1456
|
-
return await this.fetchRepoJson(resolvedRepo, candidate);
|
|
1457
|
-
} catch {
|
|
1458
|
-
// try next candidate
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
return null;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
async getPluginDetail(pluginInput = {}) {
|
|
1466
|
-
const requestedPlugin = typeof pluginInput === 'string'
|
|
1467
|
-
? { name: pluginInput }
|
|
1468
|
-
: { ...(pluginInput || {}) };
|
|
1469
|
-
const requestedName = String(requestedPlugin.name || '').trim();
|
|
1470
|
-
|
|
1471
|
-
if (!requestedName && !requestedPlugin.directory) {
|
|
1472
|
-
return null;
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
const installedPlugin = this.listPlugins().plugins.find(plugin => {
|
|
1476
|
-
if (requestedName && (plugin.name === requestedName || plugin.directory === requestedName)) {
|
|
1477
|
-
return true;
|
|
1478
|
-
}
|
|
1479
|
-
if (requestedPlugin.installPath && plugin.installPath === requestedPlugin.installPath) {
|
|
1480
|
-
return true;
|
|
1481
|
-
}
|
|
1482
|
-
return Boolean(
|
|
1483
|
-
requestedPlugin.repoId &&
|
|
1484
|
-
plugin.repoId === requestedPlugin.repoId &&
|
|
1485
|
-
plugin.directory === requestedPlugin.directory
|
|
1486
|
-
);
|
|
1487
|
-
}) || null;
|
|
1488
|
-
const legacyPlugin = (!this._isOpenCode() && requestedName) ? getPlugin(requestedName) : null;
|
|
1489
|
-
|
|
1490
|
-
const mergedPlugin = {
|
|
1491
|
-
...requestedPlugin,
|
|
1492
|
-
...(installedPlugin || {}),
|
|
1493
|
-
...(legacyPlugin || {})
|
|
1494
|
-
};
|
|
1495
|
-
mergedPlugin.name = requestedName || mergedPlugin.name || mergedPlugin.directory || '';
|
|
1496
|
-
|
|
1497
|
-
if (!mergedPlugin.name) {
|
|
1498
|
-
return null;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
const fallbackInstallPath = (!mergedPlugin.installPath && !this._isOpenCode())
|
|
1502
|
-
? path.join(INSTALLED_DIR, mergedPlugin.name)
|
|
1503
|
-
: '';
|
|
1504
|
-
const installPath = mergedPlugin.installPath || (fallbackInstallPath && fs.existsSync(fallbackInstallPath) ? fallbackInstallPath : '');
|
|
1505
|
-
const managedPath = mergedPlugin.managedPath && fs.existsSync(mergedPlugin.managedPath)
|
|
1506
|
-
? mergedPlugin.managedPath
|
|
1507
|
-
: this._getManagedPluginPath(mergedPlugin);
|
|
1508
|
-
const resolvedManagedPath = managedPath && fs.existsSync(managedPath) ? managedPath : '';
|
|
1509
|
-
|
|
1510
|
-
let manifest = this._readPluginManifestFromInstallPath(installPath);
|
|
1511
|
-
if (!manifest && resolvedManagedPath) {
|
|
1512
|
-
manifest = this._readPluginManifestFromInstallPath(resolvedManagedPath);
|
|
1513
|
-
}
|
|
1514
|
-
const repo = this.resolvePluginRepo(mergedPlugin);
|
|
1515
|
-
if (!manifest) {
|
|
1516
|
-
manifest = await this._readPluginManifestFromRepo(mergedPlugin, repo);
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
let fileSummary = this._createEmptyContentSummary();
|
|
1520
|
-
if (installPath && fs.existsSync(installPath)) {
|
|
1521
|
-
fileSummary = this._summarizeContentFromFiles(this._scanInstallPath(installPath));
|
|
1522
|
-
} else if (resolvedManagedPath) {
|
|
1523
|
-
fileSummary = this._summarizeContentFromFiles(this._scanInstallPath(resolvedManagedPath));
|
|
1524
|
-
} else if (repo) {
|
|
1525
|
-
try {
|
|
1526
|
-
const repoTree = await this.fetchRepoTree(repo);
|
|
1527
|
-
fileSummary = this._summarizeContentFromFiles(repoTree, mergedPlugin.directory || '');
|
|
1528
|
-
} catch {
|
|
1529
|
-
fileSummary = this._createEmptyContentSummary();
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
const manifestSummary = this._summarizeManifestContent(manifest);
|
|
1534
|
-
const contentSummary = this._mergeContentSummary(manifestSummary, fileSummary);
|
|
1535
|
-
const commands = mergeContentCollections(
|
|
1536
|
-
contentSummary.commands,
|
|
1537
|
-
normalizeContentCollection(mergedPlugin.commands)
|
|
1538
|
-
);
|
|
1539
|
-
const hooks = mergeContentCollections(
|
|
1540
|
-
contentSummary.hooks,
|
|
1541
|
-
normalizeContentCollection(mergedPlugin.hooks)
|
|
1542
|
-
);
|
|
1543
|
-
const installed = mergedPlugin.installed !== undefined
|
|
1544
|
-
? !!mergedPlugin.installed
|
|
1545
|
-
: Boolean(mergedPlugin.isInstalled || installPath || installedPlugin);
|
|
1546
|
-
|
|
1547
|
-
return {
|
|
1548
|
-
...mergedPlugin,
|
|
1549
|
-
installPath,
|
|
1550
|
-
managedPath: resolvedManagedPath,
|
|
1551
|
-
installed,
|
|
1552
|
-
description: mergedPlugin.description || manifest?.description || '',
|
|
1553
|
-
author: mergedPlugin.author || manifest?.author || '',
|
|
1554
|
-
version: mergedPlugin.version || manifest?.version || (this._isOpenCode() ? 'latest' : '1.0.0'),
|
|
1555
|
-
commands,
|
|
1556
|
-
hooks,
|
|
1557
|
-
skills: contentSummary.skills,
|
|
1558
|
-
agents: contentSummary.agents,
|
|
1559
|
-
contentSummary,
|
|
722
|
+
hooks: manifest?.hooks || [],
|
|
1560
723
|
manifest
|
|
1561
724
|
};
|
|
1562
725
|
}
|
|
@@ -1610,124 +773,6 @@ class PluginsService {
|
|
|
1610
773
|
return await installPluginCore(source);
|
|
1611
774
|
}
|
|
1612
775
|
|
|
1613
|
-
installLocalPlugin(name) {
|
|
1614
|
-
const plugin = this.listPlugins().plugins.find(item => item.name === name || item.directory === name);
|
|
1615
|
-
const managedPath = plugin?.managedPath || '';
|
|
1616
|
-
|
|
1617
|
-
if (!managedPath || !fs.existsSync(managedPath)) {
|
|
1618
|
-
throw new Error(`本地插件 "${name}" 不存在`);
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
if (this._isOpenCode()) {
|
|
1622
|
-
const pluginsDir = this._getOpenCodePluginsDir();
|
|
1623
|
-
this._ensureDir(pluginsDir);
|
|
1624
|
-
const targetPath = path.join(pluginsDir, path.basename(managedPath));
|
|
1625
|
-
|
|
1626
|
-
if (fs.existsSync(targetPath)) {
|
|
1627
|
-
return { success: true, plugin: { name: plugin.name }, message: 'Already installed' };
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
const stat = fs.statSync(managedPath);
|
|
1631
|
-
if (stat.isDirectory()) {
|
|
1632
|
-
fs.mkdirSync(targetPath, { recursive: true });
|
|
1633
|
-
this.copyDirRecursive(managedPath, targetPath);
|
|
1634
|
-
} else {
|
|
1635
|
-
fs.copyFileSync(managedPath, targetPath);
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
return {
|
|
1639
|
-
success: true,
|
|
1640
|
-
plugin: { name: plugin.name, version: plugin.version || '1.0.0', description: plugin.description || '' },
|
|
1641
|
-
message: 'Installed successfully'
|
|
1642
|
-
};
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
const manifest = this._readPluginManifestFromInstallPath(managedPath) || {};
|
|
1646
|
-
const pluginName = manifest.name || plugin?.name || name;
|
|
1647
|
-
const pluginDir = path.join(INSTALLED_DIR, pluginName);
|
|
1648
|
-
|
|
1649
|
-
if (fs.existsSync(pluginDir)) {
|
|
1650
|
-
return { success: true, plugin: { name: pluginName }, message: 'Already installed' };
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
fs.mkdirSync(pluginDir, { recursive: true });
|
|
1654
|
-
this.copyDirRecursive(managedPath, pluginDir);
|
|
1655
|
-
|
|
1656
|
-
const installTimestamp = new Date().toISOString();
|
|
1657
|
-
const repoSourceMeta = this.readRepoSourceMeta(managedPath) || {};
|
|
1658
|
-
const sourceUrl = plugin?.source || repoSourceMeta.source || plugin?.repoUrl || '';
|
|
1659
|
-
|
|
1660
|
-
try {
|
|
1661
|
-
const { addPlugin } = require('../../plugins/registry');
|
|
1662
|
-
addPlugin(pluginName, {
|
|
1663
|
-
version: manifest.version || plugin?.version || '1.0.0',
|
|
1664
|
-
enabled: true,
|
|
1665
|
-
installedAt: installTimestamp,
|
|
1666
|
-
source: sourceUrl
|
|
1667
|
-
});
|
|
1668
|
-
} catch (e) {
|
|
1669
|
-
console.warn('[PluginsService] Legacy registry addPlugin warning:', e.message);
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
this.writeRepoSourceMeta(pluginDir, {
|
|
1673
|
-
repoId: plugin?.repoId || repoSourceMeta.repoId || '',
|
|
1674
|
-
repoProvider: plugin?.repoProvider || repoSourceMeta.repoProvider || '',
|
|
1675
|
-
repoOwner: plugin?.repoOwner || repoSourceMeta.repoOwner || '',
|
|
1676
|
-
repoName: plugin?.repoName || repoSourceMeta.repoName || '',
|
|
1677
|
-
repoBranch: plugin?.repoBranch || repoSourceMeta.repoBranch || 'main',
|
|
1678
|
-
repoDirectory: plugin?.repoDirectory || repoSourceMeta.repoDirectory || plugin?.directory || '',
|
|
1679
|
-
repoHost: plugin?.repoHost || repoSourceMeta.repoHost || '',
|
|
1680
|
-
repoProjectPath: plugin?.repoProjectPath || repoSourceMeta.repoProjectPath || '',
|
|
1681
|
-
repoLocalPath: plugin?.repoLocalPath || repoSourceMeta.repoLocalPath || '',
|
|
1682
|
-
repoUrl: plugin?.repoUrl || repoSourceMeta.repoUrl || '',
|
|
1683
|
-
source: sourceUrl
|
|
1684
|
-
});
|
|
1685
|
-
|
|
1686
|
-
try {
|
|
1687
|
-
this._ensureDir(CLAUDE_PLUGINS_DIR);
|
|
1688
|
-
let nativeData = { plugins: {} };
|
|
1689
|
-
if (fs.existsSync(CLAUDE_INSTALLED_FILE)) {
|
|
1690
|
-
try {
|
|
1691
|
-
nativeData = JSON.parse(fs.readFileSync(CLAUDE_INSTALLED_FILE, 'utf8'));
|
|
1692
|
-
if (!nativeData.plugins) nativeData.plugins = {};
|
|
1693
|
-
} catch {
|
|
1694
|
-
nativeData = { plugins: {} };
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
nativeData.plugins[`${pluginName}@ctx`] = [{
|
|
1699
|
-
version: manifest.version || plugin?.version || '1.0.0',
|
|
1700
|
-
installPath: pluginDir,
|
|
1701
|
-
installedAt: installTimestamp,
|
|
1702
|
-
scope: 'user',
|
|
1703
|
-
source: sourceUrl,
|
|
1704
|
-
repoId: plugin?.repoId || repoSourceMeta.repoId || '',
|
|
1705
|
-
repoProvider: plugin?.repoProvider || repoSourceMeta.repoProvider || '',
|
|
1706
|
-
repoOwner: plugin?.repoOwner || repoSourceMeta.repoOwner || '',
|
|
1707
|
-
repoName: plugin?.repoName || repoSourceMeta.repoName || '',
|
|
1708
|
-
repoBranch: plugin?.repoBranch || repoSourceMeta.repoBranch || 'main',
|
|
1709
|
-
repoDirectory: plugin?.repoDirectory || repoSourceMeta.repoDirectory || plugin?.directory || '',
|
|
1710
|
-
repoHost: plugin?.repoHost || repoSourceMeta.repoHost || '',
|
|
1711
|
-
repoProjectPath: plugin?.repoProjectPath || repoSourceMeta.repoProjectPath || '',
|
|
1712
|
-
repoLocalPath: plugin?.repoLocalPath || repoSourceMeta.repoLocalPath || '',
|
|
1713
|
-
repoUrl: plugin?.repoUrl || repoSourceMeta.repoUrl || ''
|
|
1714
|
-
}];
|
|
1715
|
-
fs.writeFileSync(CLAUDE_INSTALLED_FILE, JSON.stringify(nativeData, null, 2), 'utf8');
|
|
1716
|
-
} catch (e) {
|
|
1717
|
-
console.error('[PluginsService] Failed to update native installed_plugins.json:', e.message);
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
return {
|
|
1721
|
-
success: true,
|
|
1722
|
-
plugin: {
|
|
1723
|
-
name: pluginName,
|
|
1724
|
-
version: manifest.version || plugin?.version || '1.0.0',
|
|
1725
|
-
description: manifest.description || plugin?.description || ''
|
|
1726
|
-
},
|
|
1727
|
-
message: 'Installed successfully'
|
|
1728
|
-
};
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
776
|
/**
|
|
1732
777
|
* Install plugin from repo directory
|
|
1733
778
|
* @private
|
|
@@ -2115,8 +1160,9 @@ class PluginsService {
|
|
|
2115
1160
|
*/
|
|
2116
1161
|
updatePluginConfig(name, config) {
|
|
2117
1162
|
if (this._isOpenCode()) {
|
|
2118
|
-
|
|
2119
|
-
|
|
1163
|
+
const configDir = path.join(OPENCODE_CONFIG_DIR, 'plugins-config');
|
|
1164
|
+
this._ensureDir(configDir);
|
|
1165
|
+
const configFile = path.join(configDir, `${name}.json`);
|
|
2120
1166
|
fs.writeFileSync(configFile, JSON.stringify(config, null, 2), 'utf8');
|
|
2121
1167
|
return {
|
|
2122
1168
|
success: true,
|
|
@@ -2606,8 +1652,7 @@ class PluginsService {
|
|
|
2606
1652
|
tree.push({ path: relativePath, type: 'tree', name: entry.name });
|
|
2607
1653
|
this.scanLocalRepoTree(fullPath, repoRoot, tree);
|
|
2608
1654
|
} else {
|
|
2609
|
-
|
|
2610
|
-
tree.push({ path: relativePath, type: 'blob', name: entry.name, size: stats.size });
|
|
1655
|
+
tree.push({ path: relativePath, type: 'blob', name: entry.name });
|
|
2611
1656
|
}
|
|
2612
1657
|
}
|
|
2613
1658
|
}
|
|
@@ -2847,7 +1892,44 @@ class PluginsService {
|
|
|
2847
1892
|
}
|
|
2848
1893
|
}
|
|
2849
1894
|
|
|
2850
|
-
|
|
1895
|
+
let repo = null;
|
|
1896
|
+
if (plugin.repoProvider || plugin.repoLocalPath || plugin.repoProjectPath || plugin.repoOwner) {
|
|
1897
|
+
try {
|
|
1898
|
+
repo = this.normalizeRepoConfig({
|
|
1899
|
+
id: plugin.repoId,
|
|
1900
|
+
provider: plugin.repoProvider,
|
|
1901
|
+
host: plugin.repoHost,
|
|
1902
|
+
owner: plugin.repoOwner,
|
|
1903
|
+
name: plugin.repoName,
|
|
1904
|
+
branch: plugin.repoBranch || 'main',
|
|
1905
|
+
projectPath: plugin.repoProjectPath,
|
|
1906
|
+
localPath: plugin.repoLocalPath,
|
|
1907
|
+
repoUrl: plugin.repoUrl
|
|
1908
|
+
});
|
|
1909
|
+
} catch {
|
|
1910
|
+
repo = null;
|
|
1911
|
+
}
|
|
1912
|
+
} else if (plugin.source) {
|
|
1913
|
+
repo = this.parseRepoTreeSource(plugin.source);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
if (!repo && plugin.repoUrl) {
|
|
1917
|
+
const parsedTreeSource = this.parseRepoTreeSource(plugin.repoUrl);
|
|
1918
|
+
if (parsedTreeSource) {
|
|
1919
|
+
repo = parsedTreeSource;
|
|
1920
|
+
} else if (plugin.repoUrl.includes('github.com')) {
|
|
1921
|
+
const match = plugin.repoUrl.match(/github\.com\/([^\/]+)\/([^\/\.]+)/);
|
|
1922
|
+
if (match) {
|
|
1923
|
+
repo = this.normalizeRepoConfig({
|
|
1924
|
+
provider: 'github',
|
|
1925
|
+
owner: match[1],
|
|
1926
|
+
name: match[2],
|
|
1927
|
+
branch: plugin.repoBranch || 'main'
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
2851
1933
|
if (!repo) return '';
|
|
2852
1934
|
|
|
2853
1935
|
for (const candidate of readmeCandidates) {
|
|
@@ -2898,7 +1980,6 @@ class PluginsService {
|
|
|
2898
1980
|
lspServers: data.lspServers || null,
|
|
2899
1981
|
commands: data.commands || [],
|
|
2900
1982
|
hooks: data.hooks || [],
|
|
2901
|
-
contentSummary: data.contentSummary || this._createEmptyContentSummary(),
|
|
2902
1983
|
isInstalled: false
|
|
2903
1984
|
};
|
|
2904
1985
|
}
|
|
@@ -2938,11 +2019,7 @@ class PluginsService {
|
|
|
2938
2019
|
directory: file.path,
|
|
2939
2020
|
installSource: githubRepo ? '' : installSource,
|
|
2940
2021
|
marketplaceFormat: 'opencode-plugin-json',
|
|
2941
|
-
repoUrl
|
|
2942
|
-
contentSummary: this._mergeContentSummary(
|
|
2943
|
-
this._summarizeManifestContent(manifest),
|
|
2944
|
-
this._summarizeContentFromFiles(tree, file.path)
|
|
2945
|
-
)
|
|
2022
|
+
repoUrl
|
|
2946
2023
|
});
|
|
2947
2024
|
})
|
|
2948
2025
|
);
|
|
@@ -3009,11 +2086,7 @@ class PluginsService {
|
|
|
3009
2086
|
version: plugin.version || '1.0.0',
|
|
3010
2087
|
category: plugin.category || 'general',
|
|
3011
2088
|
directory: plugin.source?.replace(/^\.\//, '') || plugin.name,
|
|
3012
|
-
lspServers: plugin.lspServers || null
|
|
3013
|
-
contentSummary: this._summarizeContentFromFiles(
|
|
3014
|
-
files,
|
|
3015
|
-
plugin.source?.replace(/^\.\//, '') || plugin.name
|
|
3016
|
-
)
|
|
2089
|
+
lspServers: plugin.lspServers || null
|
|
3017
2090
|
}));
|
|
3018
2091
|
}
|
|
3019
2092
|
continue; // Skip legacy format check
|
|
@@ -3039,8 +2112,6 @@ class PluginsService {
|
|
|
3039
2112
|
for (const dir of pluginDirs) {
|
|
3040
2113
|
try {
|
|
3041
2114
|
const manifest = await readJson(`${dir}/plugin.json`);
|
|
3042
|
-
const manifestSummary = this._summarizeManifestContent(manifest);
|
|
3043
|
-
const fileSummary = this._summarizeContentFromFiles(files, dir);
|
|
3044
2115
|
|
|
3045
2116
|
marketPlugins.push(this.buildMarketPluginItem(repo, {
|
|
3046
2117
|
name: manifest.name || dir,
|
|
@@ -3048,9 +2119,8 @@ class PluginsService {
|
|
|
3048
2119
|
author: manifest.author || repo.owner,
|
|
3049
2120
|
version: manifest.version || '1.0.0',
|
|
3050
2121
|
directory: dir,
|
|
3051
|
-
commands:
|
|
3052
|
-
hooks:
|
|
3053
|
-
contentSummary: this._mergeContentSummary(manifestSummary, fileSummary)
|
|
2122
|
+
commands: manifest.commands || [],
|
|
2123
|
+
hooks: manifest.hooks || []
|
|
3054
2124
|
}));
|
|
3055
2125
|
} catch (e) {
|
|
3056
2126
|
// OpenCode 仓库常见 package.json 格式
|
|
@@ -3062,11 +2132,7 @@ class PluginsService {
|
|
|
3062
2132
|
description: pkg.description || '',
|
|
3063
2133
|
author: pkg.author || repo.owner,
|
|
3064
2134
|
version: pkg.version || '1.0.0',
|
|
3065
|
-
directory: dir
|
|
3066
|
-
contentSummary: this._mergeContentSummary(
|
|
3067
|
-
this._summarizeManifestContent(pkg),
|
|
3068
|
-
this._summarizeContentFromFiles(files, dir)
|
|
3069
|
-
)
|
|
2135
|
+
directory: dir
|
|
3070
2136
|
}));
|
|
3071
2137
|
} catch (pkgErr) {
|
|
3072
2138
|
// neither plugin.json nor package.json
|