coding-tool-x 3.8.8 → 3.8.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/README.md +56 -388
- package/dist/web/assets/AgentsDrawer-Bam7kBjG.js +1 -0
- package/dist/web/assets/Analytics-D5dUWXEa.js +1 -0
- package/dist/web/assets/AssetPathField-CLqalYv-.js +1 -0
- package/dist/web/assets/{ChatHistoryDrawer-CrfS0KYV.js → ChatHistoryDrawer-C1At65O3.js} +1 -1
- package/dist/web/assets/{CollapseItem-ClMsJuUu.js → CollapseItem-CVWojShK.js} +1 -1
- package/dist/web/assets/CommandsDrawer-BEoypGBf.js +1 -0
- package/dist/web/assets/ConfigExportDrawer-DBvFDDWK.js +1 -0
- package/dist/web/assets/ConfigTemplates-BpaWG-iC.js +1 -0
- package/dist/web/assets/{ConfigTemplatesDrawer-BqM32Im7.js → ConfigTemplatesDrawer-C2lvyKMf.js} +1 -1
- package/dist/web/assets/ConfigTemplatesPanel-CorOY7kY.js +1 -0
- package/dist/web/assets/{Divider-DsdbkHrG.js → Divider-BLLhMTSt.js} +1 -1
- package/dist/web/assets/FavoritesDrawer-uW88N6te.js +1 -0
- package/dist/web/assets/GatewayConvertDrawer-i5Wt9Dd9.js +1 -0
- package/dist/web/assets/Home-BIxWvm29.js +1 -0
- package/dist/web/assets/{MarkdownEditor-BMMUufSc.js → MarkdownEditor-Ul0bSGj-.js} +1 -1
- package/dist/web/assets/McpDrawer-Cc2xq77I.js +1 -0
- package/dist/web/assets/McpFormDrawer-DLHX_nrI.js +1 -0
- package/dist/web/assets/{PluginManager-C4kJlpVX.js → PluginManager-BGQ0xF2F.js} +1 -1
- package/dist/web/assets/{PluginsDrawer-05R4k15x.js → PluginsDrawer-STwHEqiF.js} +1 -1
- package/dist/web/assets/PluginsPanel-1zuNP7da.js +1 -0
- package/dist/web/assets/ProjectList-VbIYSHbH.css +1 -0
- package/dist/web/assets/ProjectList-_pe2nk9c.js +1 -0
- package/dist/web/assets/PromptsDrawer-CJN1xZk7.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-DX1HI8Nm.js +1 -0
- package/dist/web/assets/SessionCard-Op6PHvi0.js +1 -0
- package/dist/web/assets/SessionList-Da_TAEYr.js +1 -0
- package/dist/web/assets/SettingsDrawer-DKLd4Sjy.js +1 -0
- package/dist/web/assets/{SkillManager-D9jkNuRM.js → SkillManager-PcAa5EbF.js} +1 -1
- package/dist/web/assets/{SkillsDrawer-0Z4IefxC.js → SkillsDrawer-DPdU4p6C.js} +1 -1
- package/dist/web/assets/SkillsPanel-B9qzRqjd.js +1 -0
- package/dist/web/assets/Upload-e1M9Yp5z.js +1 -0
- package/dist/web/assets/{WorkspaceDrawer-eujtWpoq.js → WorkspaceDrawer-oS6mB1VZ.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CQ-8Ej68.js → WorkspaceManager-6VvRWe3r.js} +1 -1
- package/dist/web/assets/WorkspacePanel-CYq0Z8iY.js +1 -0
- package/dist/web/assets/client-BgcNg4Bt.js +1 -0
- package/dist/web/assets/headers-DbowkmZJ.js +1 -0
- package/dist/web/assets/{icons-DmFe4b5j.js → icons-B-AzZoeE.js} +1 -1
- package/dist/web/assets/index-BlUXw84U.js +2 -0
- package/dist/web/assets/index-Bn8BUnwP.css +1 -0
- package/dist/web/assets/{light-0eavxNvt.js → light-C2X7hegj.js} +1 -1
- package/dist/web/assets/platforms-DrzAtnjY.js +1 -0
- package/dist/web/assets/platforms-mqqe_82d.js +1 -0
- package/dist/web/assets/project-config-BVt5s-pH.js +1 -0
- package/dist/web/assets/sessions-53-Fr68H.js +1 -0
- package/dist/web/assets/{sessions-D4yQtfKb.js → sessions-CaWUaysM.js} +1 -1
- package/dist/web/assets/use-message-DBsCm7FJ.js +1 -0
- package/dist/web/assets/{useResponsiveDrawer-DZd5rNk5.js → useResponsiveDrawer-ChHZrTfl.js} +1 -1
- package/dist/web/assets/{workspaces-mjyPVGS8.js → workspaces-DQuZd4qw.js} +1 -1
- package/dist/web/index.html +8 -8
- package/package.json +4 -2
- package/src/commands/daemon.js +27 -4
- package/src/commands/export-config.js +17 -211
- package/src/config/model-metadata.json +180 -16
- package/src/platforms/channel-auth-registry.js +36 -6
- package/src/platforms/channel-auth-service.js +13 -3
- package/src/platforms/commands-service.js +28 -0
- package/src/platforms/driver-registry.js +1 -0
- package/src/platforms/drivers/claude/channels-implementation.js +79 -90
- package/src/platforms/drivers/claude/native-config-implementation.js +3 -4
- package/src/platforms/drivers/codex/channels-implementation.js +34 -49
- package/src/platforms/drivers/codex/native-config-implementation.js +3 -51
- package/src/platforms/drivers/dsh/api-operations.js +1 -1
- package/src/platforms/drivers/dsh/channels-implementation.js +28 -8
- package/src/platforms/drivers/dsh/channels.js +0 -18
- package/src/platforms/drivers/dsh/common.js +7 -0
- package/src/platforms/drivers/dsh/native-config.js +7 -2
- package/src/platforms/drivers/gemini/channels-implementation.js +47 -55
- package/src/platforms/drivers/gemini/native-config-implementation.js +8 -20
- package/src/platforms/drivers/generic-native-config.js +20 -0
- package/src/platforms/drivers/native-config-snapshot.js +30 -4
- package/src/platforms/drivers/omp/channels-implementation.js +51 -17
- package/src/platforms/drivers/omp/native-config-implementation.js +10 -17
- package/src/platforms/drivers/omp/prompts.js +58 -2
- package/src/platforms/drivers/omp/proxy-implementation.js +25 -1
- package/src/platforms/drivers/opencode/channels-implementation.js +31 -22
- package/src/platforms/drivers/opencode/channels.js +6 -1
- package/src/platforms/drivers/opencode/native-config-implementation.js +3 -67
- package/src/platforms/manifest-schema.js +35 -2
- package/src/platforms/manifests/claude.json +0 -8
- package/src/platforms/manifests/codex.json +0 -8
- package/src/platforms/manifests/dsh.json +6 -9
- package/src/platforms/manifests/gemini.json +0 -8
- package/src/platforms/manifests/omp.json +2 -9
- package/src/platforms/manifests/opencode.json +28 -8
- package/src/platforms/native-oauth-adapters.js +351 -51
- package/src/platforms/oauth-credentials-service.js +344 -13
- package/src/server/index.js +37 -1
- package/src/server/services/bounded-content.js +60 -0
- package/src/server/services/channel-balance.js +10 -4
- package/src/server/services/config-export-service.js +2 -2
- package/src/server/services/config-templates-service.js +5 -1
- package/src/server/services/plugins-service.js +162 -85
- package/src/server/services/session-history-maintenance-worker.js +71 -0
- package/src/server/services/session-history-maintenance.js +166 -0
- package/src/server/services/skill-service.js +23 -3
- package/src/server/services/speed-test.js +0 -72
- package/src/shared/channel-driver.js +16 -0
- package/src/shared/driver-factories/api.js +1 -0
- package/src/utils/native-config-patcher.js +474 -0
- package/dist/web/assets/AgentsDrawer-DRQuwi0m.js +0 -1
- package/dist/web/assets/Analytics-Cf5KwEdj.js +0 -1
- package/dist/web/assets/AssetPathField-DyniM0pF.js +0 -1
- package/dist/web/assets/CommandsDrawer-CzHelf56.js +0 -1
- package/dist/web/assets/ConfigExportDrawer-BEZ8wTby.js +0 -1
- package/dist/web/assets/ConfigTemplates-Crg3xWe5.js +0 -1
- package/dist/web/assets/ConfigTemplatesPanel-lTdutC8H.js +0 -1
- package/dist/web/assets/FavoritesDrawer-DDyWUBJR.js +0 -1
- package/dist/web/assets/GatewayConvertDrawer-Rigtt_5J.js +0 -1
- package/dist/web/assets/Home-tCSKmSHV.js +0 -1
- package/dist/web/assets/McpDrawer-CmE-d7VW.js +0 -1
- package/dist/web/assets/McpFormDrawer-ByvS18od.js +0 -1
- package/dist/web/assets/PluginsPanel-BCoEs7Og.js +0 -1
- package/dist/web/assets/ProjectList-BJt1OIaI.js +0 -1
- package/dist/web/assets/ProjectList-DG3yu8xZ.css +0 -1
- package/dist/web/assets/PromptsDrawer-DnwBf_oW.js +0 -1
- package/dist/web/assets/RecentSessionsDrawer-B7d-38wH.js +0 -1
- package/dist/web/assets/SessionCard-BUgjHQwX.js +0 -1
- package/dist/web/assets/SessionList-B-bXZRFk.js +0 -1
- package/dist/web/assets/SettingsDrawer-BITzjQLl.js +0 -1
- package/dist/web/assets/SkillsPanel-BKIVOj7n.js +0 -1
- package/dist/web/assets/SpeedTestDrawer-Bx1bYf5i.js +0 -1
- package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +0 -1
- package/dist/web/assets/Upload-CzC4rGI4.js +0 -1
- package/dist/web/assets/WorkspacePanel-Cql5qE2R.js +0 -1
- package/dist/web/assets/client-UDwXBP9i.js +0 -1
- package/dist/web/assets/headers-BqDmkMjy.js +0 -1
- package/dist/web/assets/index-Cmmwj3Fx.js +0 -2
- package/dist/web/assets/index-Cnbe696C.css +0 -1
- package/dist/web/assets/platforms-BmzzwnSw.js +0 -1
- package/dist/web/assets/platforms-DWXgmy2w.js +0 -1
- package/dist/web/assets/project-config-B2baPzYL.js +0 -1
- package/dist/web/assets/sessions-DqlbocLJ.js +0 -1
- package/dist/web/assets/use-message-DqCwefG6.js +0 -1
|
@@ -429,7 +429,11 @@ function getAvailableConfigs(options = {}) {
|
|
|
429
429
|
const { commands } = service.listCommands();
|
|
430
430
|
for (const command of commands || []) {
|
|
431
431
|
if (command.scope !== 'user') continue;
|
|
432
|
-
const detail = typeof service.
|
|
432
|
+
const detail = typeof service.getCommandByPath === 'function' && command.path
|
|
433
|
+
? (service.getCommandByPath(command.path, 'user') || command)
|
|
434
|
+
: (typeof service.getCommand === 'function'
|
|
435
|
+
? (service.getCommand(command.name, 'user', null, command.namespace) || command)
|
|
436
|
+
: command);
|
|
433
437
|
const normalizedCommand = { name: command.name, namespace: command.namespace, description: command.description, allowedTools: command.allowedTools, argumentHint: command.argumentHint, body: detail.body };
|
|
434
438
|
commandsByPlatform[platform] = commandsByPlatform[platform] || [];
|
|
435
439
|
commandsByPlatform[platform].push(normalizedCommand);
|
|
@@ -36,6 +36,7 @@ const {
|
|
|
36
36
|
normalizeSafeRelativePath,
|
|
37
37
|
resolveInsideRoot
|
|
38
38
|
} = require('../../shared/config-artifact-paths');
|
|
39
|
+
const { readTextFileLimited, truncateUtf8 } = require('./bounded-content');
|
|
39
40
|
|
|
40
41
|
const CLAUDE_PLUGINS_DIR = NATIVE_PATHS.claude.plugins
|
|
41
42
|
|| path.join(NATIVE_PATHS.claude.dir || path.dirname(NATIVE_PATHS.claude.settings), 'plugins');
|
|
@@ -49,6 +50,13 @@ const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
|
|
|
49
50
|
const REPO_SOURCE_META_FILE = '.cc-tool-plugin-source.json';
|
|
50
51
|
const SUPPORTED_REPO_PROVIDERS = ['github', 'gitlab', 'local'];
|
|
51
52
|
const PLUGIN_LIST_TTL_MS = 1000;
|
|
53
|
+
const MAX_PLUGIN_DETAIL_BYTES = 128 * 1024;
|
|
54
|
+
const MAX_PLUGIN_MANIFEST_BYTES = 256 * 1024;
|
|
55
|
+
const MAX_PLUGIN_STATE_BYTES = 4 * 1024 * 1024;
|
|
56
|
+
const MAX_PLUGIN_LIST_CACHE_ENTRIES = 32;
|
|
57
|
+
const MAX_MARKET_CACHE_ENTRIES = 32;
|
|
58
|
+
const MAX_MARKET_README_CACHE_ENTRIES = 32;
|
|
59
|
+
const MAX_MARKET_CACHE_FILE_BYTES = 8 * 1024 * 1024;
|
|
52
60
|
const DEFAULT_GITHUB_HOST = 'https://github.com';
|
|
53
61
|
const DEFAULT_GITLAB_HOST = 'https://gitlab.com';
|
|
54
62
|
const DEFAULT_REPOS_BY_PLATFORM = {
|
|
@@ -522,6 +530,7 @@ class PluginsService {
|
|
|
522
530
|
this._marketReadmeCache = new Map();
|
|
523
531
|
this._marketReadmeCacheLoaded = false;
|
|
524
532
|
this._lastFetchedRepoTrees = new Map();
|
|
533
|
+
this._claudeInstalledPluginIndex = null;
|
|
525
534
|
this.ompNativeAdapter = this.platform === 'omp'
|
|
526
535
|
? new OmpNativePluginAdapter({ pathContext: this.pathContext })
|
|
527
536
|
: null;
|
|
@@ -542,6 +551,47 @@ class PluginsService {
|
|
|
542
551
|
}
|
|
543
552
|
}
|
|
544
553
|
|
|
554
|
+
_rememberPluginList(key, value, cachedAt = Date.now()) {
|
|
555
|
+
this._pluginListCache.delete(key);
|
|
556
|
+
this._pluginListCache.set(key, {
|
|
557
|
+
value: this._clone(value),
|
|
558
|
+
cachedAt,
|
|
559
|
+
generation: this._pluginListGeneration
|
|
560
|
+
});
|
|
561
|
+
while (this._pluginListCache.size > MAX_PLUGIN_LIST_CACHE_ENTRIES) {
|
|
562
|
+
this._pluginListCache.delete(this._pluginListCache.keys().next().value);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
_rememberMarketPlugins(key, plugins, fetchedAt, reposFingerprint) {
|
|
567
|
+
this._marketCacheByKey.delete(key);
|
|
568
|
+
this._marketCacheByKey.set(key, { plugins, fetchedAt, reposFingerprint });
|
|
569
|
+
while (this._marketCacheByKey.size > MAX_MARKET_CACHE_ENTRIES) {
|
|
570
|
+
this._marketCacheByKey.delete(this._marketCacheByKey.keys().next().value);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
_setMarketReadme(key, value) {
|
|
575
|
+
if (typeof value !== 'string') return '';
|
|
576
|
+
const bounded = truncateUtf8(value, MAX_PLUGIN_DETAIL_BYTES);
|
|
577
|
+
this._marketReadmeCache.delete(key);
|
|
578
|
+
this._marketReadmeCache.set(key, bounded.text);
|
|
579
|
+
while (this._marketReadmeCache.size > MAX_MARKET_README_CACHE_ENTRIES) {
|
|
580
|
+
this._marketReadmeCache.delete(this._marketReadmeCache.keys().next().value);
|
|
581
|
+
}
|
|
582
|
+
return bounded.text;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
_loadMarketReadmes(readmes = {}) {
|
|
586
|
+
this._marketReadmeCache.clear();
|
|
587
|
+
for (const [key, value] of Object.entries(readmes || {})) {
|
|
588
|
+
this._setMarketReadme(key, value);
|
|
589
|
+
if (this._marketReadmeCache.size >= MAX_MARKET_README_CACHE_ENTRIES) break;
|
|
590
|
+
}
|
|
591
|
+
this._marketReadmeCacheLoaded = true;
|
|
592
|
+
return this._marketReadmeCache;
|
|
593
|
+
}
|
|
594
|
+
|
|
545
595
|
_invalidatePluginList(options = null) {
|
|
546
596
|
this._pluginListGeneration++;
|
|
547
597
|
if (!options) {
|
|
@@ -751,7 +801,9 @@ class PluginsService {
|
|
|
751
801
|
loadMarketCacheEnvelope() {
|
|
752
802
|
try {
|
|
753
803
|
if (!fs.existsSync(this.marketCachePath)) return null;
|
|
754
|
-
const
|
|
804
|
+
const boundedCache = readTextFileLimited(this.marketCachePath, MAX_MARKET_CACHE_FILE_BYTES);
|
|
805
|
+
if (boundedCache.truncated) return null;
|
|
806
|
+
const data = JSON.parse(boundedCache.text);
|
|
755
807
|
if (Array.isArray(data)) {
|
|
756
808
|
return { plugins: data, fetchedAt: 0, reposFingerprint: null, readmes: {} };
|
|
757
809
|
}
|
|
@@ -793,20 +845,25 @@ class PluginsService {
|
|
|
793
845
|
_loadMarketReadmeCache() {
|
|
794
846
|
if (this._marketReadmeCacheLoaded) return this._marketReadmeCache;
|
|
795
847
|
const envelope = this.loadMarketCacheEnvelope();
|
|
796
|
-
this.
|
|
797
|
-
this._marketReadmeCacheLoaded = true;
|
|
798
|
-
return this._marketReadmeCache;
|
|
848
|
+
return this._loadMarketReadmes(envelope?.readmes || {});
|
|
799
849
|
}
|
|
800
850
|
|
|
801
851
|
saveMarketCacheToFile(plugins, fetchedAt = Date.now(), reposFingerprint = null, readmes = null) {
|
|
802
852
|
try {
|
|
803
853
|
this._ensureDir(path.dirname(this.marketCachePath));
|
|
854
|
+
const readmeSource = readmes || Object.fromEntries(this._loadMarketReadmeCache());
|
|
855
|
+
const boundedReadmes = {};
|
|
856
|
+
for (const [key, value] of Object.entries(readmeSource || {})) {
|
|
857
|
+
if (Object.keys(boundedReadmes).length >= MAX_MARKET_README_CACHE_ENTRIES) break;
|
|
858
|
+
if (typeof value !== 'string') continue;
|
|
859
|
+
boundedReadmes[key] = truncateUtf8(value, MAX_PLUGIN_DETAIL_BYTES).text;
|
|
860
|
+
}
|
|
804
861
|
const payload = {
|
|
805
862
|
fetchedAt,
|
|
806
863
|
time: fetchedAt,
|
|
807
864
|
reposFingerprint,
|
|
808
865
|
plugins,
|
|
809
|
-
readmes:
|
|
866
|
+
readmes: boundedReadmes
|
|
810
867
|
};
|
|
811
868
|
const tempPath = `${this.marketCachePath}.tmp-${process.pid}-${Date.now()}`;
|
|
812
869
|
try {
|
|
@@ -881,6 +938,25 @@ class PluginsService {
|
|
|
881
938
|
return (Array.isArray(plugins) ? plugins : []).map(plugin => this.sanitizePluginListItem(plugin));
|
|
882
939
|
}
|
|
883
940
|
|
|
941
|
+
sanitizePluginDetail(plugin = {}) {
|
|
942
|
+
const sanitized = { ...plugin };
|
|
943
|
+
for (const key of Object.keys(sanitized)) {
|
|
944
|
+
const normalizedKey = key.toLowerCase();
|
|
945
|
+
if (
|
|
946
|
+
normalizedKey === 'content'
|
|
947
|
+
|| normalizedKey === 'fullcontent'
|
|
948
|
+
|| normalizedKey === 'manifest'
|
|
949
|
+
|| normalizedKey === 'rawmanifest'
|
|
950
|
+
|| normalizedKey === 'files'
|
|
951
|
+
|| normalizedKey === 'filecontents'
|
|
952
|
+
|| (normalizedKey.includes('readme') && normalizedKey !== 'readmeurl')
|
|
953
|
+
) {
|
|
954
|
+
delete sanitized[key];
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return sanitized;
|
|
958
|
+
}
|
|
959
|
+
|
|
884
960
|
_ensureDir(dirPath) {
|
|
885
961
|
if (!fs.existsSync(dirPath)) {
|
|
886
962
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
@@ -931,7 +1007,7 @@ class PluginsService {
|
|
|
931
1007
|
{ allowHiddenSegments: true }
|
|
932
1008
|
);
|
|
933
1009
|
if (!fs.existsSync(cachedPath)) return null;
|
|
934
|
-
return
|
|
1010
|
+
return readTextFileLimited(cachedPath, MAX_PLUGIN_DETAIL_BYTES).text;
|
|
935
1011
|
} catch {
|
|
936
1012
|
return null;
|
|
937
1013
|
}
|
|
@@ -1137,7 +1213,7 @@ class PluginsService {
|
|
|
1137
1213
|
_readFirstMarkdownParagraph(filePath) {
|
|
1138
1214
|
if (!fs.existsSync(filePath)) return '';
|
|
1139
1215
|
try {
|
|
1140
|
-
const content =
|
|
1216
|
+
const content = readTextFileLimited(filePath, MAX_PLUGIN_DETAIL_BYTES).text;
|
|
1141
1217
|
const lines = content
|
|
1142
1218
|
.split(/\r?\n/)
|
|
1143
1219
|
.map(line => line.trim())
|
|
@@ -1207,7 +1283,9 @@ class PluginsService {
|
|
|
1207
1283
|
const manifestPath = path.join(rootDir, candidate);
|
|
1208
1284
|
if (!fs.existsSync(manifestPath)) continue;
|
|
1209
1285
|
try {
|
|
1210
|
-
|
|
1286
|
+
const bounded = readTextFileLimited(manifestPath, MAX_PLUGIN_MANIFEST_BYTES);
|
|
1287
|
+
if (bounded.truncated) continue;
|
|
1288
|
+
return JSON.parse(stripJsonComments(bounded.text));
|
|
1211
1289
|
} catch {
|
|
1212
1290
|
// try next candidate
|
|
1213
1291
|
}
|
|
@@ -1254,6 +1332,32 @@ class PluginsService {
|
|
|
1254
1332
|
}
|
|
1255
1333
|
}
|
|
1256
1334
|
|
|
1335
|
+
_readClaudeInstalledPluginIndex() {
|
|
1336
|
+
if (!fs.existsSync(this.claudeInstalledFile)) return [];
|
|
1337
|
+
try {
|
|
1338
|
+
const stat = fs.statSync(this.claudeInstalledFile);
|
|
1339
|
+
const signature = `${stat.size}:${stat.mtimeMs}`;
|
|
1340
|
+
if (this._claudeInstalledPluginIndex?.signature === signature) {
|
|
1341
|
+
return this._claudeInstalledPluginIndex.entries;
|
|
1342
|
+
}
|
|
1343
|
+
const boundedState = readTextFileLimited(this.claudeInstalledFile, MAX_PLUGIN_STATE_BYTES);
|
|
1344
|
+
if (boundedState.truncated) return [];
|
|
1345
|
+
const data = JSON.parse(boundedState.text);
|
|
1346
|
+
const entries = Object.keys(data.plugins || {}).map(key => {
|
|
1347
|
+
const parsed = splitPluginMarketplaceKey(key);
|
|
1348
|
+
return { key, name: parsed.name, marketplace: parsed.marketplace || '' };
|
|
1349
|
+
});
|
|
1350
|
+
this._claudeInstalledPluginIndex = { signature, entries };
|
|
1351
|
+
return entries;
|
|
1352
|
+
} catch {
|
|
1353
|
+
return [];
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
_clearClaudeInstalledPluginIndex() {
|
|
1358
|
+
this._claudeInstalledPluginIndex = null;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1257
1361
|
_writeClaudeSettings(settings) {
|
|
1258
1362
|
const filePath = this.pathContext?.customized
|
|
1259
1363
|
? (this.pathContext.native?.settings || NATIVE_PATHS.claude.settings)
|
|
@@ -1455,6 +1559,7 @@ class PluginsService {
|
|
|
1455
1559
|
...(installData.repoSourceMeta || {})
|
|
1456
1560
|
}];
|
|
1457
1561
|
fs.writeFileSync(this.claudeInstalledFile, JSON.stringify(nativeData, null, 2), 'utf8');
|
|
1562
|
+
this._clearClaudeInstalledPluginIndex();
|
|
1458
1563
|
this._invalidatePluginList();
|
|
1459
1564
|
this._setClaudePluginEnabled(name, marketplace, true);
|
|
1460
1565
|
}
|
|
@@ -1533,11 +1638,7 @@ class PluginsService {
|
|
|
1533
1638
|
}
|
|
1534
1639
|
|
|
1535
1640
|
const value = this._listPluginsUncached(options);
|
|
1536
|
-
this.
|
|
1537
|
-
value: this._clone(value),
|
|
1538
|
-
cachedAt: now,
|
|
1539
|
-
generation: this._pluginListGeneration
|
|
1540
|
-
});
|
|
1641
|
+
this._rememberPluginList(key, value, now);
|
|
1541
1642
|
return this._clone(value);
|
|
1542
1643
|
}
|
|
1543
1644
|
|
|
@@ -1675,15 +1776,12 @@ class PluginsService {
|
|
|
1675
1776
|
let repoId = install.repoId || '';
|
|
1676
1777
|
|
|
1677
1778
|
if (install.installPath && fs.existsSync(install.installPath)) {
|
|
1678
|
-
const
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
// Ignore parse errors
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1779
|
+
const manifest = this._readLocalManifest(install.installPath, [
|
|
1780
|
+
'plugin.json',
|
|
1781
|
+
'.claude-plugin/plugin.json',
|
|
1782
|
+
'package.json'
|
|
1783
|
+
]);
|
|
1784
|
+
description = manifest?.description || '';
|
|
1687
1785
|
|
|
1688
1786
|
const repoSourceMeta = this.readRepoSourceMeta(install.installPath) || {};
|
|
1689
1787
|
repoUrl = repoUrl || repoSourceMeta.repoUrl || '';
|
|
@@ -1762,9 +1860,10 @@ class PluginsService {
|
|
|
1762
1860
|
*/
|
|
1763
1861
|
getPlugin(name, options = {}) {
|
|
1764
1862
|
if (this._isDsh()) {
|
|
1765
|
-
|
|
1863
|
+
const plugin = this.listPlugins(options).plugins.find(plugin => (
|
|
1766
1864
|
plugin.key === name || plugin.pluginId === name || plugin.name === name
|
|
1767
|
-
))
|
|
1865
|
+
));
|
|
1866
|
+
return plugin ? this.sanitizePluginDetail(plugin) : null;
|
|
1768
1867
|
}
|
|
1769
1868
|
if (this._isCodex()) {
|
|
1770
1869
|
const plugin = this.listPlugins().plugins.find(p => p.name === name || `${p.name}@${p.marketplace}` === name);
|
|
@@ -1785,7 +1884,7 @@ class PluginsService {
|
|
|
1785
1884
|
if (this._isOpenCode()) {
|
|
1786
1885
|
const plugin = this.listPlugins().plugins.find(p => p.name === name || p.directory === name);
|
|
1787
1886
|
if (!plugin) return null;
|
|
1788
|
-
return plugin;
|
|
1887
|
+
return this.sanitizePluginDetail(plugin);
|
|
1789
1888
|
}
|
|
1790
1889
|
|
|
1791
1890
|
if (this._isOmp()) {
|
|
@@ -1793,7 +1892,7 @@ class PluginsService {
|
|
|
1793
1892
|
p.pluginId === name || p.id === name || p.name === name
|
|
1794
1893
|
);
|
|
1795
1894
|
if (!plugin) return null;
|
|
1796
|
-
return plugin;
|
|
1895
|
+
return this.sanitizePluginDetail(plugin);
|
|
1797
1896
|
}
|
|
1798
1897
|
|
|
1799
1898
|
const plugin = getPlugin(name);
|
|
@@ -1806,16 +1905,12 @@ class PluginsService {
|
|
|
1806
1905
|
|
|
1807
1906
|
let manifest = null;
|
|
1808
1907
|
if (fs.existsSync(manifestPath)) {
|
|
1809
|
-
|
|
1810
|
-
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
1811
|
-
} catch (err) {
|
|
1812
|
-
// Ignore parse errors
|
|
1813
|
-
}
|
|
1908
|
+
manifest = this._readLocalManifest(pluginDir, ['plugin.json']);
|
|
1814
1909
|
}
|
|
1815
1910
|
|
|
1816
1911
|
return {
|
|
1817
1912
|
name,
|
|
1818
|
-
...plugin,
|
|
1913
|
+
...this.sanitizePluginDetail(plugin),
|
|
1819
1914
|
description: manifest?.description || '',
|
|
1820
1915
|
author: manifest?.author || '',
|
|
1821
1916
|
commands: manifest?.commands || [],
|
|
@@ -2374,6 +2469,7 @@ class PluginsService {
|
|
|
2374
2469
|
delete data.plugins[key];
|
|
2375
2470
|
}
|
|
2376
2471
|
fs.writeFileSync(this.claudeInstalledFile, JSON.stringify(data, null, 2), 'utf8');
|
|
2472
|
+
this._clearClaudeInstalledPluginIndex();
|
|
2377
2473
|
this._invalidatePluginList();
|
|
2378
2474
|
removed = true;
|
|
2379
2475
|
}
|
|
@@ -2457,22 +2553,12 @@ class PluginsService {
|
|
|
2457
2553
|
|
|
2458
2554
|
// Claude: store enabled state in CTX registry
|
|
2459
2555
|
// First check if plugin exists in native installed_plugins.json
|
|
2460
|
-
let pluginExists = false;
|
|
2461
2556
|
const baseName = name.split('/').pop();
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
const { name: pluginName } = splitPluginMarketplaceKey(key);
|
|
2468
|
-
if (pluginName === name || key === name || pluginName === baseName) {
|
|
2469
|
-
pluginExists = true;
|
|
2470
|
-
break;
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
|
-
} catch (e) { /* ignore */ }
|
|
2475
|
-
}
|
|
2557
|
+
const installedEntries = this._readClaudeInstalledPluginIndex();
|
|
2558
|
+
const installedMatch = installedEntries.find(entry => (
|
|
2559
|
+
entry.name === name || entry.key === name || entry.name === baseName
|
|
2560
|
+
));
|
|
2561
|
+
let pluginExists = Boolean(installedMatch);
|
|
2476
2562
|
|
|
2477
2563
|
// Also check legacy registry
|
|
2478
2564
|
const legacyPlugin = getPlugin(name);
|
|
@@ -2494,20 +2580,7 @@ class PluginsService {
|
|
|
2494
2580
|
} catch (e) {
|
|
2495
2581
|
console.warn('[PluginsService] Failed to update plugin registry:', e.message);
|
|
2496
2582
|
}
|
|
2497
|
-
|
|
2498
|
-
try {
|
|
2499
|
-
const data = JSON.parse(fs.readFileSync(this.claudeInstalledFile, 'utf8'));
|
|
2500
|
-
for (const key of Object.keys(data.plugins || {})) {
|
|
2501
|
-
const parsed = splitPluginMarketplaceKey(key);
|
|
2502
|
-
if (parsed.name === name || key === name || parsed.name === baseName) {
|
|
2503
|
-
matchedMarketplace = parsed.marketplace || '';
|
|
2504
|
-
break;
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
} catch {
|
|
2508
|
-
// ignore
|
|
2509
|
-
}
|
|
2510
|
-
}
|
|
2583
|
+
matchedMarketplace = installedMatch?.marketplace || '';
|
|
2511
2584
|
this._setClaudePluginEnabled(baseName || name, matchedMarketplace, enabled);
|
|
2512
2585
|
|
|
2513
2586
|
return {
|
|
@@ -3309,7 +3382,7 @@ class PluginsService {
|
|
|
3309
3382
|
: readmeCandidates.map(candidate => path.join(plugin.installPath, candidate));
|
|
3310
3383
|
for (const candidatePath of localCandidates) {
|
|
3311
3384
|
if (fs.existsSync(candidatePath)) {
|
|
3312
|
-
return
|
|
3385
|
+
return readTextFileLimited(candidatePath, MAX_PLUGIN_DETAIL_BYTES).text;
|
|
3313
3386
|
}
|
|
3314
3387
|
}
|
|
3315
3388
|
}
|
|
@@ -3361,7 +3434,7 @@ class PluginsService {
|
|
|
3361
3434
|
for (const candidate of readmeCandidates) {
|
|
3362
3435
|
const cachedReadme = this._readCachedRepoFile(repo, candidate, { scope, cwd });
|
|
3363
3436
|
if (cachedReadme !== null) {
|
|
3364
|
-
if (cacheRemote)
|
|
3437
|
+
if (cacheRemote) this._setMarketReadme(this._pluginReadmeCacheKey(plugin), cachedReadme);
|
|
3365
3438
|
return cachedReadme;
|
|
3366
3439
|
}
|
|
3367
3440
|
}
|
|
@@ -3371,10 +3444,11 @@ class PluginsService {
|
|
|
3371
3444
|
for (const candidate of readmeCandidates) {
|
|
3372
3445
|
try {
|
|
3373
3446
|
const readme = await this.fetchRepoFileContent(repo, candidate);
|
|
3447
|
+
const boundedReadme = truncateUtf8(readme, MAX_PLUGIN_DETAIL_BYTES).text;
|
|
3374
3448
|
if (cacheRemote && typeof readme === 'string') {
|
|
3375
|
-
|
|
3449
|
+
this._setMarketReadme(this._pluginReadmeCacheKey(plugin), boundedReadme);
|
|
3376
3450
|
}
|
|
3377
|
-
return
|
|
3451
|
+
return boundedReadme;
|
|
3378
3452
|
} catch {
|
|
3379
3453
|
// try next candidate
|
|
3380
3454
|
}
|
|
@@ -3706,16 +3780,16 @@ class PluginsService {
|
|
|
3706
3780
|
const disk = this.loadMarketCacheEnvelope();
|
|
3707
3781
|
if (!forceRefresh && Array.isArray(disk?.plugins)) {
|
|
3708
3782
|
const diskPlugins = this.prepareMarketPlugins(disk.plugins);
|
|
3709
|
-
this.
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3783
|
+
this._rememberMarketPlugins(
|
|
3784
|
+
key,
|
|
3785
|
+
diskPlugins,
|
|
3786
|
+
disk.fetchedAt,
|
|
3787
|
+
disk.reposFingerprint || reposFingerprint
|
|
3788
|
+
);
|
|
3714
3789
|
this._marketCache = diskPlugins;
|
|
3715
3790
|
this._marketFetchedAt = disk.fetchedAt;
|
|
3716
3791
|
this._marketRepoFingerprint = disk.reposFingerprint || reposFingerprint;
|
|
3717
|
-
this.
|
|
3718
|
-
this._marketReadmeCacheLoaded = true;
|
|
3792
|
+
this._loadMarketReadmes(disk.readmes || {});
|
|
3719
3793
|
return this._clone(diskPlugins);
|
|
3720
3794
|
}
|
|
3721
3795
|
|
|
@@ -3729,11 +3803,7 @@ class PluginsService {
|
|
|
3729
3803
|
this._marketCache = Array.isArray(plugins) ? plugins : [];
|
|
3730
3804
|
this._marketFetchedAt = this._marketLastUsedStale ? 0 : Date.now();
|
|
3731
3805
|
this._marketRepoFingerprint = reposFingerprint;
|
|
3732
|
-
this.
|
|
3733
|
-
plugins: this._marketCache,
|
|
3734
|
-
fetchedAt: this._marketFetchedAt,
|
|
3735
|
-
reposFingerprint
|
|
3736
|
-
});
|
|
3806
|
+
this._rememberMarketPlugins(key, this._marketCache, this._marketFetchedAt, reposFingerprint);
|
|
3737
3807
|
if (!this._marketLastUsedStale) {
|
|
3738
3808
|
this.saveMarketCacheToFile(this._marketCache, this._marketFetchedAt, reposFingerprint);
|
|
3739
3809
|
}
|
|
@@ -3809,15 +3879,15 @@ class PluginsService {
|
|
|
3809
3879
|
const previousPlugins = this.prepareMarketPlugins(previousEnvelope.plugins);
|
|
3810
3880
|
this._marketCache = previousPlugins;
|
|
3811
3881
|
this._marketCacheByKey.clear();
|
|
3812
|
-
this.
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3882
|
+
this._rememberMarketPlugins(
|
|
3883
|
+
this._marketKey(refreshOptions),
|
|
3884
|
+
previousPlugins,
|
|
3885
|
+
previousEnvelope.fetchedAt || 0,
|
|
3886
|
+
previousEnvelope.reposFingerprint || null
|
|
3887
|
+
);
|
|
3817
3888
|
this._marketFetchedAt = previousEnvelope.fetchedAt || 0;
|
|
3818
3889
|
this._marketRepoFingerprint = previousEnvelope.reposFingerprint || null;
|
|
3819
|
-
this.
|
|
3820
|
-
this._marketReadmeCacheLoaded = true;
|
|
3890
|
+
this._loadMarketReadmes(previousEnvelope.readmes || {});
|
|
3821
3891
|
this.saveMarketCacheToFile(
|
|
3822
3892
|
previousEnvelope.plugins,
|
|
3823
3893
|
previousEnvelope.fetchedAt,
|
|
@@ -4033,4 +4103,11 @@ class PluginsService {
|
|
|
4033
4103
|
}
|
|
4034
4104
|
}
|
|
4035
4105
|
|
|
4036
|
-
module.exports = {
|
|
4106
|
+
module.exports = {
|
|
4107
|
+
PluginsService,
|
|
4108
|
+
MAX_PLUGIN_DETAIL_BYTES,
|
|
4109
|
+
MAX_PLUGIN_MANIFEST_BYTES,
|
|
4110
|
+
MAX_PLUGIN_LIST_CACHE_ENTRIES,
|
|
4111
|
+
MAX_MARKET_CACHE_ENTRIES,
|
|
4112
|
+
MAX_MARKET_README_CACHE_ENTRIES
|
|
4113
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { parentPort, workerData } = require('worker_threads');
|
|
6
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
7
|
+
|
|
8
|
+
const MIN_FREE_BYTES = 256 * 1024 * 1024;
|
|
9
|
+
const MIN_FREE_RATIO = 0.2;
|
|
10
|
+
|
|
11
|
+
function readPragmaNumber(db, name) {
|
|
12
|
+
return Number(db.prepare(`PRAGMA ${name}`).get()?.[name]) || 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function inspectDatabase(dbPath, db) {
|
|
16
|
+
db.exec('PRAGMA busy_timeout = 5000; PRAGMA cache_size = -16384;');
|
|
17
|
+
const pageSize = readPragmaNumber(db, 'page_size');
|
|
18
|
+
const pageCount = readPragmaNumber(db, 'page_count');
|
|
19
|
+
const freePageCount = readPragmaNumber(db, 'freelist_count');
|
|
20
|
+
const fileBytes = fs.statSync(dbPath).size;
|
|
21
|
+
const freeBytes = pageSize * freePageCount;
|
|
22
|
+
const pageBytes = pageSize * pageCount;
|
|
23
|
+
|
|
24
|
+
if (freeBytes < MIN_FREE_BYTES || freeBytes < pageBytes * MIN_FREE_RATIO) {
|
|
25
|
+
return { status: 'below-threshold', beforeBytes: fileBytes, freeBytes };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const disk = fs.statfsSync(path.dirname(dbPath));
|
|
29
|
+
const availableBytes = Number(disk.bavail) * Number(disk.bsize);
|
|
30
|
+
if (!Number.isFinite(availableBytes) || availableBytes < fileBytes) {
|
|
31
|
+
return { status: 'insufficient-space', beforeBytes: fileBytes, freeBytes };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const checkpoint = db.prepare('PRAGMA wal_checkpoint(TRUNCATE)').get();
|
|
35
|
+
if (Number(checkpoint?.busy) > 0) {
|
|
36
|
+
return { status: 'database-busy', beforeBytes: fileBytes, freeBytes };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { status: 'ready', beforeBytes: fileBytes, freeBytes };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function compactDatabase(dbPath) {
|
|
43
|
+
if (!dbPath || !fs.existsSync(dbPath)) return { status: 'missing' };
|
|
44
|
+
const db = new DatabaseSync(dbPath, { timeout: 5000 });
|
|
45
|
+
try {
|
|
46
|
+
const inspection = inspectDatabase(dbPath, db);
|
|
47
|
+
if (inspection.status !== 'ready') return inspection;
|
|
48
|
+
|
|
49
|
+
db.exec('VACUUM;');
|
|
50
|
+
const checkpoint = db.prepare('PRAGMA wal_checkpoint(TRUNCATE)').get();
|
|
51
|
+
const afterBytes = fs.statSync(dbPath).size;
|
|
52
|
+
return {
|
|
53
|
+
status: 'compacted',
|
|
54
|
+
beforeBytes: inspection.beforeBytes,
|
|
55
|
+
afterBytes,
|
|
56
|
+
reclaimedBytes: Math.max(0, inspection.beforeBytes - afterBytes),
|
|
57
|
+
checkpointBusy: Number(checkpoint?.busy) > 0
|
|
58
|
+
};
|
|
59
|
+
} finally {
|
|
60
|
+
db.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const result = compactDatabase(workerData?.dbPath);
|
|
66
|
+
parentPort.postMessage({ type: 'done', result });
|
|
67
|
+
parentPort.close();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
parentPort.postMessage({ type: 'error', error: error?.message || String(error) });
|
|
70
|
+
parentPort.close();
|
|
71
|
+
}
|