coding-tool-x 3.5.5 → 3.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/web/assets/{Analytics-gvYu5sCM.js → Analytics-CRNCHeui.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-CPlH8Ehd.js → ConfigTemplates-C0erJdo2.js} +1 -1
- package/dist/web/assets/{Home-B-qbu3uk.js → Home-CL5z6Q4d.js} +1 -1
- package/dist/web/assets/{PluginManager-B2tQ_YUq.js → PluginManager-hDx0XMO_.js} +1 -1
- package/dist/web/assets/{ProjectList-kDadoXXs.js → ProjectList-BNsz96av.js} +1 -1
- package/dist/web/assets/{SessionList-eLgITwTV.js → SessionList-CG1UhFo3.js} +1 -1
- package/dist/web/assets/{SkillManager-B7zEB5Op.js → SkillManager-D6Vwpajh.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-C-RzB3ud.js → WorkspaceManager-C3TjeOPy.js} +1 -1
- package/dist/web/assets/{icons-DlxD2wZJ.js → icons-CQuif85v.js} +1 -1
- package/dist/web/assets/index-GuER-BmS.js +2 -0
- package/dist/web/assets/{index-BHeh2z0i.css → index-VGAxnLqi.css} +1 -1
- package/dist/web/index.html +3 -3
- package/package.json +1 -1
- package/src/commands/stats.js +41 -4
- package/src/index.js +1 -0
- package/src/server/api/codex-sessions.js +6 -3
- package/src/server/api/dashboard.js +25 -1
- package/src/server/api/gemini-sessions.js +6 -3
- package/src/server/api/hooks.js +17 -1
- package/src/server/api/opencode-sessions.js +6 -3
- package/src/server/api/plugins.js +24 -33
- package/src/server/api/sessions.js +6 -3
- package/src/server/index.js +6 -4
- package/src/server/services/codex-sessions.js +107 -9
- package/src/server/services/network-access.js +14 -0
- package/src/server/services/notification-hooks.js +175 -16
- package/src/server/services/plugins-service.js +502 -44
- package/src/server/services/session-launch-command.js +81 -0
- package/src/server/services/sessions.js +103 -33
- package/src/server/websocket-server.js +25 -1
- package/dist/web/assets/index-DG00t-zy.js +0 -2
|
@@ -208,6 +208,123 @@ function stripJsonComments(input = '') {
|
|
|
208
208
|
return result;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
function isPlainObject(value) {
|
|
212
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function getNestedValue(source, segments = []) {
|
|
216
|
+
return segments.reduce((acc, segment) => (acc && acc[segment] !== undefined ? acc[segment] : undefined), source);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function normalizeContentText(value = '') {
|
|
220
|
+
return String(value || '').trim();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function normalizeContentEntry(raw, fallback = {}) {
|
|
224
|
+
if (typeof raw === 'string') {
|
|
225
|
+
const name = normalizeContentText(raw);
|
|
226
|
+
if (!name) return null;
|
|
227
|
+
return {
|
|
228
|
+
name,
|
|
229
|
+
path: normalizeRepoPath(fallback.path || ''),
|
|
230
|
+
description: normalizeContentText(fallback.description || '')
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!isPlainObject(raw)) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const candidateName = [
|
|
239
|
+
raw.name,
|
|
240
|
+
raw.displayName,
|
|
241
|
+
raw.id,
|
|
242
|
+
raw.title,
|
|
243
|
+
raw.fileName,
|
|
244
|
+
raw.directory,
|
|
245
|
+
raw.path,
|
|
246
|
+
fallback.name
|
|
247
|
+
].map(normalizeContentText).find(Boolean) || '';
|
|
248
|
+
const itemPath = normalizeRepoPath(raw.path || raw.file || raw.fileName || raw.directory || fallback.path || '');
|
|
249
|
+
const description = normalizeContentText(raw.description || raw.prompt || raw.usage || fallback.description || '');
|
|
250
|
+
const derivedName = candidateName || (itemPath ? path.posix.basename(itemPath, path.posix.extname(itemPath)) : '');
|
|
251
|
+
|
|
252
|
+
if (!derivedName && !itemPath) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
name: derivedName,
|
|
258
|
+
path: itemPath,
|
|
259
|
+
description
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function normalizeContentCollection(value, fallback = {}) {
|
|
264
|
+
if (Array.isArray(value)) {
|
|
265
|
+
return value.map(item => normalizeContentEntry(item, fallback)).filter(Boolean);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (isPlainObject(value)) {
|
|
269
|
+
return Object.entries(value)
|
|
270
|
+
.map(([key, item]) => {
|
|
271
|
+
if (isPlainObject(item)) {
|
|
272
|
+
return normalizeContentEntry({
|
|
273
|
+
...item,
|
|
274
|
+
name: item.name || key
|
|
275
|
+
}, fallback);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (typeof item === 'string') {
|
|
279
|
+
return normalizeContentEntry({
|
|
280
|
+
name: key,
|
|
281
|
+
description: item
|
|
282
|
+
}, fallback);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return normalizeContentEntry({ name: key }, fallback);
|
|
286
|
+
})
|
|
287
|
+
.filter(Boolean);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function mergeContentCollections(...collections) {
|
|
294
|
+
const merged = [];
|
|
295
|
+
const seen = new Set();
|
|
296
|
+
|
|
297
|
+
for (const collection of collections) {
|
|
298
|
+
for (const rawEntry of collection || []) {
|
|
299
|
+
const entry = normalizeContentEntry(rawEntry);
|
|
300
|
+
if (!entry) continue;
|
|
301
|
+
|
|
302
|
+
const normalizedPath = normalizeRepoPath(entry.path || '');
|
|
303
|
+
const normalizedName = normalizeContentText(entry.name || '');
|
|
304
|
+
const key = normalizedPath
|
|
305
|
+
? `path:${normalizedPath.toLowerCase()}`
|
|
306
|
+
: (normalizedName ? `name:${normalizedName.toLowerCase()}` : '');
|
|
307
|
+
|
|
308
|
+
if (!key || seen.has(key)) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
seen.add(key);
|
|
313
|
+
merged.push({
|
|
314
|
+
name: normalizedName || path.posix.basename(normalizedPath, path.posix.extname(normalizedPath)),
|
|
315
|
+
path: normalizedPath,
|
|
316
|
+
description: normalizeContentText(entry.description || '')
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return merged.sort((a, b) => {
|
|
322
|
+
const left = a.name || a.path;
|
|
323
|
+
const right = b.name || b.path;
|
|
324
|
+
return left.localeCompare(right);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
211
328
|
class PluginsService {
|
|
212
329
|
constructor(platform = 'claude') {
|
|
213
330
|
this.platform = ['claude', 'opencode'].includes(platform) ? platform : 'claude';
|
|
@@ -221,6 +338,202 @@ class PluginsService {
|
|
|
221
338
|
this._marketCache = null;
|
|
222
339
|
}
|
|
223
340
|
|
|
341
|
+
_createEmptyContentSummary() {
|
|
342
|
+
return {
|
|
343
|
+
skills: [],
|
|
344
|
+
agents: [],
|
|
345
|
+
commands: [],
|
|
346
|
+
hooks: [],
|
|
347
|
+
files: [],
|
|
348
|
+
totalFiles: 0,
|
|
349
|
+
additionalFileCount: 0
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
_getManifestCollection(manifest = null, paths = []) {
|
|
354
|
+
if (!isPlainObject(manifest)) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return mergeContentCollections(
|
|
359
|
+
...paths.map(segments => normalizeContentCollection(getNestedValue(manifest, segments)))
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
_summarizeManifestContent(manifest = null) {
|
|
364
|
+
const emptySummary = this._createEmptyContentSummary();
|
|
365
|
+
if (!isPlainObject(manifest)) {
|
|
366
|
+
return emptySummary;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
...emptySummary,
|
|
371
|
+
skills: this._getManifestCollection(manifest, [
|
|
372
|
+
['skills'],
|
|
373
|
+
['ctx', 'skills'],
|
|
374
|
+
['contributes', 'skills']
|
|
375
|
+
]),
|
|
376
|
+
agents: this._getManifestCollection(manifest, [
|
|
377
|
+
['agents'],
|
|
378
|
+
['ctx', 'agents'],
|
|
379
|
+
['contributes', 'agents']
|
|
380
|
+
]),
|
|
381
|
+
commands: this._getManifestCollection(manifest, [
|
|
382
|
+
['commands'],
|
|
383
|
+
['ctx', 'commands'],
|
|
384
|
+
['contributes', 'commands']
|
|
385
|
+
]),
|
|
386
|
+
hooks: this._getManifestCollection(manifest, [
|
|
387
|
+
['hooks'],
|
|
388
|
+
['ctx', 'hooks'],
|
|
389
|
+
['contributes', 'hooks']
|
|
390
|
+
])
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
_summarizeContentFromFiles(files = [], directory = '') {
|
|
395
|
+
const emptySummary = this._createEmptyContentSummary();
|
|
396
|
+
const normalizedDirectory = normalizeRepoPath(directory);
|
|
397
|
+
const directoryLooksLikeFile = /\.[a-z0-9]+$/i.test(path.posix.basename(normalizedDirectory || ''));
|
|
398
|
+
const allFiles = (Array.isArray(files) ? files : [])
|
|
399
|
+
.filter(item => item && item.type === 'blob' && item.path)
|
|
400
|
+
.map(item => ({
|
|
401
|
+
...item,
|
|
402
|
+
path: normalizeRepoPath(item.path)
|
|
403
|
+
}))
|
|
404
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
405
|
+
|
|
406
|
+
let scopedFiles = allFiles;
|
|
407
|
+
if (normalizedDirectory) {
|
|
408
|
+
if (directoryLooksLikeFile) {
|
|
409
|
+
scopedFiles = allFiles.filter(item => item.path === normalizedDirectory);
|
|
410
|
+
} else {
|
|
411
|
+
const directoryPrefix = `${normalizedDirectory}/`;
|
|
412
|
+
scopedFiles = allFiles.filter(item => item.path.startsWith(directoryPrefix));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const toScopedPath = (filePath = '') => {
|
|
417
|
+
if (!normalizedDirectory || directoryLooksLikeFile) {
|
|
418
|
+
return normalizeRepoPath(filePath);
|
|
419
|
+
}
|
|
420
|
+
const prefix = `${normalizedDirectory}/`;
|
|
421
|
+
return filePath.startsWith(prefix) ? filePath.slice(prefix.length) : filePath;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const skillEntries = [];
|
|
425
|
+
const agentEntries = [];
|
|
426
|
+
const commandEntries = [];
|
|
427
|
+
|
|
428
|
+
for (const file of scopedFiles) {
|
|
429
|
+
const scopedPath = toScopedPath(file.path);
|
|
430
|
+
const skillMatch = scopedPath.match(/^skills\/([^/]+)\/SKILL\.md$/i);
|
|
431
|
+
if (skillMatch) {
|
|
432
|
+
skillEntries.push({ name: skillMatch[1], path: scopedPath });
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const agentMatch = scopedPath.match(/^agents\/(.+)\.md$/i);
|
|
436
|
+
if (agentMatch) {
|
|
437
|
+
agentEntries.push({
|
|
438
|
+
name: agentMatch[1].split('/').pop(),
|
|
439
|
+
path: scopedPath
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const commandMatch = scopedPath.match(/^(?:commands|prompts)\/(.+)\.md$/i);
|
|
444
|
+
if (commandMatch) {
|
|
445
|
+
commandEntries.push({
|
|
446
|
+
name: commandMatch[1].split('/').pop(),
|
|
447
|
+
path: scopedPath
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const filePreview = scopedFiles.slice(0, 8).map(file => {
|
|
453
|
+
const scopedPath = toScopedPath(file.path);
|
|
454
|
+
return {
|
|
455
|
+
name: path.posix.basename(scopedPath),
|
|
456
|
+
path: scopedPath
|
|
457
|
+
};
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
...emptySummary,
|
|
462
|
+
skills: mergeContentCollections(skillEntries),
|
|
463
|
+
agents: mergeContentCollections(agentEntries),
|
|
464
|
+
commands: mergeContentCollections(commandEntries),
|
|
465
|
+
files: filePreview,
|
|
466
|
+
totalFiles: scopedFiles.length,
|
|
467
|
+
additionalFileCount: Math.max(0, scopedFiles.length - filePreview.length)
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
_mergeContentSummary(...summaries) {
|
|
472
|
+
const emptySummary = this._createEmptyContentSummary();
|
|
473
|
+
const validSummaries = summaries.filter(Boolean);
|
|
474
|
+
const fileSummary = validSummaries.find(summary => Array.isArray(summary.files) && summary.files.length > 0) || emptySummary;
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
...emptySummary,
|
|
478
|
+
skills: mergeContentCollections(...validSummaries.map(summary => summary.skills)),
|
|
479
|
+
agents: mergeContentCollections(...validSummaries.map(summary => summary.agents)),
|
|
480
|
+
commands: mergeContentCollections(...validSummaries.map(summary => summary.commands)),
|
|
481
|
+
hooks: mergeContentCollections(...validSummaries.map(summary => summary.hooks)),
|
|
482
|
+
files: fileSummary.files || [],
|
|
483
|
+
totalFiles: fileSummary.totalFiles || 0,
|
|
484
|
+
additionalFileCount: fileSummary.additionalFileCount || 0
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
_scanInstallPath(installPath = '') {
|
|
489
|
+
const resolvedPath = String(installPath || '').trim();
|
|
490
|
+
if (!resolvedPath || !fs.existsSync(resolvedPath)) {
|
|
491
|
+
return [];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
try {
|
|
495
|
+
const stat = fs.statSync(resolvedPath);
|
|
496
|
+
if (stat.isFile()) {
|
|
497
|
+
const fileName = path.basename(resolvedPath);
|
|
498
|
+
return [{ path: fileName, type: 'blob', name: fileName }];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const tree = [];
|
|
502
|
+
this.scanLocalRepoTree(resolvedPath, resolvedPath, tree);
|
|
503
|
+
return tree;
|
|
504
|
+
} catch {
|
|
505
|
+
return [];
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
_readPluginManifestFromInstallPath(installPath = '') {
|
|
510
|
+
const resolvedPath = String(installPath || '').trim();
|
|
511
|
+
if (!resolvedPath || !fs.existsSync(resolvedPath)) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
try {
|
|
516
|
+
const stat = fs.statSync(resolvedPath);
|
|
517
|
+
if (stat.isFile()) {
|
|
518
|
+
if (!resolvedPath.endsWith('.json')) {
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
return JSON.parse(stripJsonComments(fs.readFileSync(resolvedPath, 'utf8')));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const candidates = ['plugin.json', 'package.json'];
|
|
525
|
+
for (const candidate of candidates) {
|
|
526
|
+
const manifestPath = path.join(resolvedPath, candidate);
|
|
527
|
+
if (!fs.existsSync(manifestPath)) continue;
|
|
528
|
+
return JSON.parse(stripJsonComments(fs.readFileSync(manifestPath, 'utf8')));
|
|
529
|
+
}
|
|
530
|
+
} catch {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
|
|
224
537
|
normalizeRepoConfig(repo = {}) {
|
|
225
538
|
const provider = SUPPORTED_REPO_PROVIDERS.includes(repo.provider)
|
|
226
539
|
? repo.provider
|
|
@@ -719,7 +1032,173 @@ class PluginsService {
|
|
|
719
1032
|
description: manifest?.description || '',
|
|
720
1033
|
author: manifest?.author || '',
|
|
721
1034
|
commands: manifest?.commands || [],
|
|
722
|
-
hooks: manifest?.hooks || [],
|
|
1035
|
+
hooks: manifest?.hooks || manifest?.ctx?.hooks || [],
|
|
1036
|
+
manifest
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
resolvePluginRepo(plugin = {}) {
|
|
1041
|
+
let repo = null;
|
|
1042
|
+
|
|
1043
|
+
if (plugin.repoProvider || plugin.repoLocalPath || plugin.repoProjectPath || plugin.repoOwner) {
|
|
1044
|
+
try {
|
|
1045
|
+
repo = this.normalizeRepoConfig({
|
|
1046
|
+
id: plugin.repoId,
|
|
1047
|
+
provider: plugin.repoProvider,
|
|
1048
|
+
host: plugin.repoHost,
|
|
1049
|
+
owner: plugin.repoOwner,
|
|
1050
|
+
name: plugin.repoName,
|
|
1051
|
+
branch: plugin.repoBranch || 'main',
|
|
1052
|
+
projectPath: plugin.repoProjectPath,
|
|
1053
|
+
localPath: plugin.repoLocalPath,
|
|
1054
|
+
repoUrl: plugin.repoUrl
|
|
1055
|
+
});
|
|
1056
|
+
} catch {
|
|
1057
|
+
repo = null;
|
|
1058
|
+
}
|
|
1059
|
+
} else if (plugin.source) {
|
|
1060
|
+
repo = this.parseRepoTreeSource(plugin.source);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (!repo && plugin.repoUrl) {
|
|
1064
|
+
const parsedTreeSource = this.parseRepoTreeSource(plugin.repoUrl);
|
|
1065
|
+
if (parsedTreeSource) {
|
|
1066
|
+
repo = parsedTreeSource;
|
|
1067
|
+
} else if (plugin.repoUrl.includes('github.com')) {
|
|
1068
|
+
const match = plugin.repoUrl.match(/github\.com\/([^\/]+)\/([^\/\.]+)/);
|
|
1069
|
+
if (match) {
|
|
1070
|
+
try {
|
|
1071
|
+
repo = this.normalizeRepoConfig({
|
|
1072
|
+
provider: 'github',
|
|
1073
|
+
owner: match[1],
|
|
1074
|
+
name: match[2],
|
|
1075
|
+
branch: plugin.repoBranch || 'main'
|
|
1076
|
+
});
|
|
1077
|
+
} catch {
|
|
1078
|
+
repo = null;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
return repo;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
async _readPluginManifestFromRepo(plugin = {}, repo = null) {
|
|
1088
|
+
const resolvedRepo = repo || this.resolvePluginRepo(plugin);
|
|
1089
|
+
if (!resolvedRepo) {
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const normalizedDirectory = normalizeRepoPath(plugin.directory || '');
|
|
1094
|
+
const candidates = [];
|
|
1095
|
+
|
|
1096
|
+
if (normalizedDirectory) {
|
|
1097
|
+
if (/\.[a-z0-9]+$/i.test(path.posix.basename(normalizedDirectory))) {
|
|
1098
|
+
candidates.push(normalizedDirectory);
|
|
1099
|
+
} else {
|
|
1100
|
+
candidates.push(`${normalizedDirectory}/plugin.json`, `${normalizedDirectory}/package.json`);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
candidates.push('plugin.json', 'package.json');
|
|
1105
|
+
|
|
1106
|
+
for (const candidate of Array.from(new Set(candidates))) {
|
|
1107
|
+
try {
|
|
1108
|
+
return await this.fetchRepoJson(resolvedRepo, candidate);
|
|
1109
|
+
} catch {
|
|
1110
|
+
// try next candidate
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
async getPluginDetail(pluginInput = {}) {
|
|
1118
|
+
const requestedPlugin = typeof pluginInput === 'string'
|
|
1119
|
+
? { name: pluginInput }
|
|
1120
|
+
: { ...(pluginInput || {}) };
|
|
1121
|
+
const requestedName = String(requestedPlugin.name || '').trim();
|
|
1122
|
+
|
|
1123
|
+
if (!requestedName && !requestedPlugin.directory) {
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const installedPlugin = this.listPlugins().plugins.find(plugin => {
|
|
1128
|
+
if (requestedName && (plugin.name === requestedName || plugin.directory === requestedName)) {
|
|
1129
|
+
return true;
|
|
1130
|
+
}
|
|
1131
|
+
if (requestedPlugin.installPath && plugin.installPath === requestedPlugin.installPath) {
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1134
|
+
return Boolean(
|
|
1135
|
+
requestedPlugin.repoId &&
|
|
1136
|
+
plugin.repoId === requestedPlugin.repoId &&
|
|
1137
|
+
plugin.directory === requestedPlugin.directory
|
|
1138
|
+
);
|
|
1139
|
+
}) || null;
|
|
1140
|
+
const legacyPlugin = (!this._isOpenCode() && requestedName) ? getPlugin(requestedName) : null;
|
|
1141
|
+
|
|
1142
|
+
const mergedPlugin = {
|
|
1143
|
+
...requestedPlugin,
|
|
1144
|
+
...(installedPlugin || {}),
|
|
1145
|
+
...(legacyPlugin || {})
|
|
1146
|
+
};
|
|
1147
|
+
mergedPlugin.name = requestedName || mergedPlugin.name || mergedPlugin.directory || '';
|
|
1148
|
+
|
|
1149
|
+
if (!mergedPlugin.name) {
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
const fallbackInstallPath = (!mergedPlugin.installPath && !this._isOpenCode())
|
|
1154
|
+
? path.join(INSTALLED_DIR, mergedPlugin.name)
|
|
1155
|
+
: '';
|
|
1156
|
+
const installPath = mergedPlugin.installPath || (fallbackInstallPath && fs.existsSync(fallbackInstallPath) ? fallbackInstallPath : '');
|
|
1157
|
+
|
|
1158
|
+
let manifest = this._readPluginManifestFromInstallPath(installPath);
|
|
1159
|
+
const repo = this.resolvePluginRepo(mergedPlugin);
|
|
1160
|
+
if (!manifest) {
|
|
1161
|
+
manifest = await this._readPluginManifestFromRepo(mergedPlugin, repo);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
let fileSummary = this._createEmptyContentSummary();
|
|
1165
|
+
if (installPath && fs.existsSync(installPath)) {
|
|
1166
|
+
fileSummary = this._summarizeContentFromFiles(this._scanInstallPath(installPath));
|
|
1167
|
+
} else if (repo) {
|
|
1168
|
+
try {
|
|
1169
|
+
const repoTree = await this.fetchRepoTree(repo);
|
|
1170
|
+
fileSummary = this._summarizeContentFromFiles(repoTree, mergedPlugin.directory || '');
|
|
1171
|
+
} catch {
|
|
1172
|
+
fileSummary = this._createEmptyContentSummary();
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
const manifestSummary = this._summarizeManifestContent(manifest);
|
|
1177
|
+
const contentSummary = this._mergeContentSummary(manifestSummary, fileSummary);
|
|
1178
|
+
const commands = mergeContentCollections(
|
|
1179
|
+
contentSummary.commands,
|
|
1180
|
+
normalizeContentCollection(mergedPlugin.commands)
|
|
1181
|
+
);
|
|
1182
|
+
const hooks = mergeContentCollections(
|
|
1183
|
+
contentSummary.hooks,
|
|
1184
|
+
normalizeContentCollection(mergedPlugin.hooks)
|
|
1185
|
+
);
|
|
1186
|
+
const installed = mergedPlugin.installed !== undefined
|
|
1187
|
+
? !!mergedPlugin.installed
|
|
1188
|
+
: Boolean(mergedPlugin.isInstalled || installPath || installedPlugin);
|
|
1189
|
+
|
|
1190
|
+
return {
|
|
1191
|
+
...mergedPlugin,
|
|
1192
|
+
installPath,
|
|
1193
|
+
installed,
|
|
1194
|
+
description: mergedPlugin.description || manifest?.description || '',
|
|
1195
|
+
author: mergedPlugin.author || manifest?.author || '',
|
|
1196
|
+
version: mergedPlugin.version || manifest?.version || (this._isOpenCode() ? 'latest' : '1.0.0'),
|
|
1197
|
+
commands,
|
|
1198
|
+
hooks,
|
|
1199
|
+
skills: contentSummary.skills,
|
|
1200
|
+
agents: contentSummary.agents,
|
|
1201
|
+
contentSummary,
|
|
723
1202
|
manifest
|
|
724
1203
|
};
|
|
725
1204
|
}
|
|
@@ -1892,44 +2371,7 @@ class PluginsService {
|
|
|
1892
2371
|
}
|
|
1893
2372
|
}
|
|
1894
2373
|
|
|
1895
|
-
|
|
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
|
-
|
|
2374
|
+
const repo = this.resolvePluginRepo(plugin);
|
|
1933
2375
|
if (!repo) return '';
|
|
1934
2376
|
|
|
1935
2377
|
for (const candidate of readmeCandidates) {
|
|
@@ -1980,6 +2422,7 @@ class PluginsService {
|
|
|
1980
2422
|
lspServers: data.lspServers || null,
|
|
1981
2423
|
commands: data.commands || [],
|
|
1982
2424
|
hooks: data.hooks || [],
|
|
2425
|
+
contentSummary: data.contentSummary || this._createEmptyContentSummary(),
|
|
1983
2426
|
isInstalled: false
|
|
1984
2427
|
};
|
|
1985
2428
|
}
|
|
@@ -2019,7 +2462,11 @@ class PluginsService {
|
|
|
2019
2462
|
directory: file.path,
|
|
2020
2463
|
installSource: githubRepo ? '' : installSource,
|
|
2021
2464
|
marketplaceFormat: 'opencode-plugin-json',
|
|
2022
|
-
repoUrl
|
|
2465
|
+
repoUrl,
|
|
2466
|
+
contentSummary: this._mergeContentSummary(
|
|
2467
|
+
this._summarizeManifestContent(manifest),
|
|
2468
|
+
this._summarizeContentFromFiles(tree, file.path)
|
|
2469
|
+
)
|
|
2023
2470
|
});
|
|
2024
2471
|
})
|
|
2025
2472
|
);
|
|
@@ -2086,7 +2533,11 @@ class PluginsService {
|
|
|
2086
2533
|
version: plugin.version || '1.0.0',
|
|
2087
2534
|
category: plugin.category || 'general',
|
|
2088
2535
|
directory: plugin.source?.replace(/^\.\//, '') || plugin.name,
|
|
2089
|
-
lspServers: plugin.lspServers || null
|
|
2536
|
+
lspServers: plugin.lspServers || null,
|
|
2537
|
+
contentSummary: this._summarizeContentFromFiles(
|
|
2538
|
+
files,
|
|
2539
|
+
plugin.source?.replace(/^\.\//, '') || plugin.name
|
|
2540
|
+
)
|
|
2090
2541
|
}));
|
|
2091
2542
|
}
|
|
2092
2543
|
continue; // Skip legacy format check
|
|
@@ -2112,6 +2563,8 @@ class PluginsService {
|
|
|
2112
2563
|
for (const dir of pluginDirs) {
|
|
2113
2564
|
try {
|
|
2114
2565
|
const manifest = await readJson(`${dir}/plugin.json`);
|
|
2566
|
+
const manifestSummary = this._summarizeManifestContent(manifest);
|
|
2567
|
+
const fileSummary = this._summarizeContentFromFiles(files, dir);
|
|
2115
2568
|
|
|
2116
2569
|
marketPlugins.push(this.buildMarketPluginItem(repo, {
|
|
2117
2570
|
name: manifest.name || dir,
|
|
@@ -2119,8 +2572,9 @@ class PluginsService {
|
|
|
2119
2572
|
author: manifest.author || repo.owner,
|
|
2120
2573
|
version: manifest.version || '1.0.0',
|
|
2121
2574
|
directory: dir,
|
|
2122
|
-
commands:
|
|
2123
|
-
hooks:
|
|
2575
|
+
commands: manifestSummary.commands,
|
|
2576
|
+
hooks: manifestSummary.hooks,
|
|
2577
|
+
contentSummary: this._mergeContentSummary(manifestSummary, fileSummary)
|
|
2124
2578
|
}));
|
|
2125
2579
|
} catch (e) {
|
|
2126
2580
|
// OpenCode 仓库常见 package.json 格式
|
|
@@ -2132,7 +2586,11 @@ class PluginsService {
|
|
|
2132
2586
|
description: pkg.description || '',
|
|
2133
2587
|
author: pkg.author || repo.owner,
|
|
2134
2588
|
version: pkg.version || '1.0.0',
|
|
2135
|
-
directory: dir
|
|
2589
|
+
directory: dir,
|
|
2590
|
+
contentSummary: this._mergeContentSummary(
|
|
2591
|
+
this._summarizeManifestContent(pkg),
|
|
2592
|
+
this._summarizeContentFromFiles(files, dir)
|
|
2593
|
+
)
|
|
2136
2594
|
}));
|
|
2137
2595
|
} catch (pkgErr) {
|
|
2138
2596
|
// neither plugin.json nor package.json
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const { isWindowsLikePlatform } = require('../../utils/home-dir');
|
|
2
|
+
|
|
3
|
+
function escapeForDoubleQuotes(value) {
|
|
4
|
+
return String(value).replace(/"/g, '\\"');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function escapeForPowerShellSingleQuotes(value) {
|
|
8
|
+
return String(value).replace(/'/g, "''");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function buildDisplayCommand(executable, args = []) {
|
|
12
|
+
return [String(executable || ''), ...args.map(arg => String(arg))].filter(Boolean).join(' ').trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildWindowsCopyCommand(cwd, executable, args = []) {
|
|
16
|
+
const quotedCwd = `'${escapeForPowerShellSingleQuotes(cwd)}'`;
|
|
17
|
+
const quotedExecutable = `'${escapeForPowerShellSingleQuotes(executable)}'`;
|
|
18
|
+
const quotedArgs = args.map(arg => `'${escapeForPowerShellSingleQuotes(arg)}'`).join(' ');
|
|
19
|
+
const invokeCommand = quotedArgs
|
|
20
|
+
? `& ${quotedExecutable} ${quotedArgs}`
|
|
21
|
+
: `& ${quotedExecutable}`;
|
|
22
|
+
|
|
23
|
+
return `powershell -NoProfile -ExecutionPolicy Bypass -Command "& { Set-Location -LiteralPath ${quotedCwd}; ${invokeCommand} }"`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildPosixCopyCommand(cwd, command) {
|
|
27
|
+
const quotedCwd = `"${escapeForDoubleQuotes(cwd)}"`;
|
|
28
|
+
return `cd ${quotedCwd} && ${command}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildCopyCommand({
|
|
32
|
+
cwd,
|
|
33
|
+
command,
|
|
34
|
+
executable,
|
|
35
|
+
args = [],
|
|
36
|
+
runtimePlatform = process.platform,
|
|
37
|
+
runtimeEnv = process.env
|
|
38
|
+
}) {
|
|
39
|
+
const resolvedCommand = command || buildDisplayCommand(executable, args);
|
|
40
|
+
if (!cwd) {
|
|
41
|
+
return resolvedCommand;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isWindowsLikePlatform(runtimePlatform, runtimeEnv)) {
|
|
45
|
+
return buildWindowsCopyCommand(cwd, executable, args);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return buildPosixCopyCommand(cwd, resolvedCommand);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildLaunchCommand({
|
|
52
|
+
cwd,
|
|
53
|
+
executable,
|
|
54
|
+
args = [],
|
|
55
|
+
runtimePlatform = process.platform,
|
|
56
|
+
runtimeEnv = process.env
|
|
57
|
+
}) {
|
|
58
|
+
const command = buildDisplayCommand(executable, args);
|
|
59
|
+
return {
|
|
60
|
+
command,
|
|
61
|
+
copyCommand: buildCopyCommand({
|
|
62
|
+
cwd,
|
|
63
|
+
command,
|
|
64
|
+
executable,
|
|
65
|
+
args,
|
|
66
|
+
runtimePlatform,
|
|
67
|
+
runtimeEnv
|
|
68
|
+
})
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
buildLaunchCommand,
|
|
74
|
+
_test: {
|
|
75
|
+
buildDisplayCommand,
|
|
76
|
+
buildCopyCommand,
|
|
77
|
+
buildWindowsCopyCommand,
|
|
78
|
+
buildPosixCopyCommand,
|
|
79
|
+
escapeForPowerShellSingleQuotes
|
|
80
|
+
}
|
|
81
|
+
};
|