@dboio/cli 0.6.4 → 0.6.5
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/package.json
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbo",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "DBO.io CLI integration for Claude Code",
|
|
5
|
-
"author":
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"path": "skills/cli/SKILL.md",
|
|
9
|
-
"name": "cli",
|
|
10
|
-
"description": "Execute DBO.io CLI commands"
|
|
11
|
-
}
|
|
12
|
-
]
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "DBO.io"
|
|
7
|
+
}
|
|
13
8
|
}
|
package/src/commands/install.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { readdir, readFile, writeFile, mkdir, access, copyFile, cp } from 'fs/promises';
|
|
2
|
+
import { readdir, readFile, writeFile, mkdir, access, copyFile, cp, rm } from 'fs/promises';
|
|
3
3
|
import { join, dirname, resolve } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { execSync } from 'child_process';
|
|
@@ -9,6 +9,10 @@ import { homedir } from 'os';
|
|
|
9
9
|
import { log } from '../lib/logger.js';
|
|
10
10
|
import { getPluginScope, setPluginScope, isInitialized, removeFromGitignore } from '../lib/config.js';
|
|
11
11
|
|
|
12
|
+
const CLAUDE_PLUGINS_DIR = join(homedir(), '.claude', 'plugins');
|
|
13
|
+
const PLUGIN_REGISTRY_PATH = join(CLAUDE_PLUGINS_DIR, 'installed_plugins.json');
|
|
14
|
+
const PLUGIN_MARKETPLACE = 'dboio';
|
|
15
|
+
|
|
12
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
17
|
const CLI_ROOT = join(__dirname, '..', '..');
|
|
14
18
|
const LEGACY_PLUGINS_DIR = join(__dirname, '..', 'plugins', 'claudecommands');
|
|
@@ -36,16 +40,82 @@ function getCommandsDir(scope) {
|
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
42
|
* Get the target plugins directory based on scope (new directory-based format).
|
|
43
|
+
* For global scope, returns the cache path that Claude Code expects.
|
|
39
44
|
* @param {'project' | 'global'} scope
|
|
40
45
|
* @returns {string} Absolute path to plugins directory
|
|
41
46
|
*/
|
|
42
47
|
function getPluginsDir(scope) {
|
|
43
48
|
if (scope === 'global') {
|
|
44
|
-
return join(
|
|
49
|
+
return join(CLAUDE_PLUGINS_DIR, 'cache', PLUGIN_MARKETPLACE);
|
|
45
50
|
}
|
|
46
51
|
return join(process.cwd(), '.claude', 'plugins');
|
|
47
52
|
}
|
|
48
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Get the install path for a global plugin in the cache directory.
|
|
56
|
+
* @param {string} pluginName - Plugin name
|
|
57
|
+
* @param {string} version - Plugin version
|
|
58
|
+
* @returns {string} Absolute path like ~/.claude/plugins/cache/dboio/<name>/<version>/
|
|
59
|
+
*/
|
|
60
|
+
function getGlobalPluginCachePath(pluginName, version) {
|
|
61
|
+
return join(CLAUDE_PLUGINS_DIR, 'cache', PLUGIN_MARKETPLACE, pluginName, version);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Read the Claude Code plugin registry (installed_plugins.json).
|
|
66
|
+
* @returns {Promise<object>} The registry object
|
|
67
|
+
*/
|
|
68
|
+
async function readPluginRegistry() {
|
|
69
|
+
try {
|
|
70
|
+
const content = await readFile(PLUGIN_REGISTRY_PATH, 'utf8');
|
|
71
|
+
return JSON.parse(content);
|
|
72
|
+
} catch {
|
|
73
|
+
return { version: 2, plugins: {} };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Register a plugin in Claude Code's installed_plugins.json.
|
|
79
|
+
* @param {string} pluginName - Plugin name
|
|
80
|
+
* @param {string} version - Plugin version
|
|
81
|
+
* @param {string} installPath - Absolute path to installed plugin
|
|
82
|
+
*/
|
|
83
|
+
async function registerPlugin(pluginName, version, installPath) {
|
|
84
|
+
const registry = await readPluginRegistry();
|
|
85
|
+
const key = `${pluginName}@${PLUGIN_MARKETPLACE}`;
|
|
86
|
+
const now = new Date().toISOString();
|
|
87
|
+
|
|
88
|
+
const existing = registry.plugins[key]?.[0];
|
|
89
|
+
if (existing) {
|
|
90
|
+
existing.installPath = installPath;
|
|
91
|
+
existing.version = version;
|
|
92
|
+
existing.lastUpdated = now;
|
|
93
|
+
} else {
|
|
94
|
+
registry.plugins[key] = [{
|
|
95
|
+
scope: 'user',
|
|
96
|
+
installPath,
|
|
97
|
+
version,
|
|
98
|
+
installedAt: now,
|
|
99
|
+
lastUpdated: now,
|
|
100
|
+
}];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await writeFile(PLUGIN_REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Unregister a plugin from Claude Code's installed_plugins.json.
|
|
108
|
+
* @param {string} pluginName - Plugin name
|
|
109
|
+
*/
|
|
110
|
+
async function unregisterPlugin(pluginName) {
|
|
111
|
+
const registry = await readPluginRegistry();
|
|
112
|
+
const key = `${pluginName}@${PLUGIN_MARKETPLACE}`;
|
|
113
|
+
if (registry.plugins[key]) {
|
|
114
|
+
delete registry.plugins[key];
|
|
115
|
+
await writeFile(PLUGIN_REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
49
119
|
/**
|
|
50
120
|
* Check if a plugin source is a directory-based plugin (has .claude-plugin/).
|
|
51
121
|
* @param {string} pluginPath - Path to the plugin directory
|
|
@@ -147,16 +217,23 @@ async function resolvePluginScope(pluginName, options) {
|
|
|
147
217
|
|
|
148
218
|
/**
|
|
149
219
|
* Check if plugin exists in both project and global locations.
|
|
150
|
-
* Checks
|
|
220
|
+
* Checks cache path, old direct path, and legacy (.claude/commands/) paths.
|
|
151
221
|
* @param {string} pluginName - Plugin name (without extension)
|
|
152
222
|
* @param {string} [legacyFileName] - Legacy filename (with .md) for backward compat
|
|
153
223
|
* @returns {Promise<{project: boolean, global: boolean}>}
|
|
154
224
|
*/
|
|
155
225
|
async function checkPluginLocations(pluginName, legacyFileName) {
|
|
156
226
|
const projectPlugin = join(getPluginsDir('project'), pluginName);
|
|
157
|
-
const globalPlugin = join(getPluginsDir('global'), pluginName);
|
|
158
227
|
let project = await fileExists(projectPlugin);
|
|
159
|
-
|
|
228
|
+
|
|
229
|
+
// Check global: cache path first, then old direct path
|
|
230
|
+
const registry = await readPluginRegistry();
|
|
231
|
+
const key = `${pluginName}@${PLUGIN_MARKETPLACE}`;
|
|
232
|
+
let global = !!registry.plugins[key];
|
|
233
|
+
if (!global) {
|
|
234
|
+
// Check old direct path (~/.claude/plugins/<name>/)
|
|
235
|
+
global = await fileExists(join(CLAUDE_PLUGINS_DIR, pluginName, '.claude-plugin', 'plugin.json'));
|
|
236
|
+
}
|
|
160
237
|
|
|
161
238
|
// Also check legacy locations
|
|
162
239
|
if (legacyFileName) {
|
|
@@ -533,25 +610,29 @@ export async function installOrUpdateClaudeCommands(options = {}) {
|
|
|
533
610
|
}
|
|
534
611
|
}
|
|
535
612
|
|
|
536
|
-
// Persist the scope preference
|
|
537
|
-
if (options.global || options.local || !await getPluginScope(plugin.name)) {
|
|
538
|
-
if (hasProject) {
|
|
539
|
-
await setPluginScope(plugin.name, targetScope);
|
|
540
|
-
} else if (targetScope === 'global') {
|
|
541
|
-
log.warn(`Cannot persist scope preference (no .dbo/ directory). Run "dbo init" first.`);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
613
|
if (plugin.type === 'directory') {
|
|
546
614
|
// New directory-based plugin installation
|
|
547
|
-
|
|
615
|
+
// Read version from plugin.json
|
|
616
|
+
const pluginMeta = JSON.parse(await readFile(join(plugin.path, '.claude-plugin', 'plugin.json'), 'utf8'));
|
|
617
|
+
const pluginVersion = pluginMeta.version || '0.0.0';
|
|
618
|
+
|
|
619
|
+
// For global scope, use the cache path and register in installed_plugins.json
|
|
620
|
+
const destDir = targetScope === 'global'
|
|
621
|
+
? getGlobalPluginCachePath(plugin.name, pluginVersion)
|
|
622
|
+
: join(getPluginsDir('project'), plugin.name);
|
|
548
623
|
const scopeLabel = targetScope === 'global' ? '~/.claude/plugins/' : '.claude/plugins/';
|
|
549
624
|
|
|
550
|
-
|
|
625
|
+
// Check for existing installation (cache path or old direct path)
|
|
626
|
+
const oldDirectPath = join(CLAUDE_PLUGINS_DIR, plugin.name);
|
|
627
|
+
const existingPath = await fileExists(destDir) ? destDir
|
|
628
|
+
: (targetScope === 'global' && await fileExists(oldDirectPath)) ? oldDirectPath
|
|
629
|
+
: null;
|
|
630
|
+
|
|
631
|
+
if (existingPath) {
|
|
551
632
|
// Check if upgrade needed via directory hash
|
|
552
633
|
const srcHash = await directoryHash(plugin.path);
|
|
553
|
-
const destHash = await directoryHash(
|
|
554
|
-
if (srcHash === destHash) {
|
|
634
|
+
const destHash = await directoryHash(existingPath);
|
|
635
|
+
if (srcHash === destHash && existingPath === destDir) {
|
|
555
636
|
upToDate++;
|
|
556
637
|
continue;
|
|
557
638
|
}
|
|
@@ -568,12 +649,29 @@ export async function installOrUpdateClaudeCommands(options = {}) {
|
|
|
568
649
|
continue;
|
|
569
650
|
}
|
|
570
651
|
|
|
652
|
+
await mkdir(destDir, { recursive: true });
|
|
571
653
|
await cp(plugin.path, destDir, { recursive: true, force: true });
|
|
654
|
+
|
|
655
|
+
// Clean up old direct path if migrating to cache path
|
|
656
|
+
if (targetScope === 'global' && existingPath === oldDirectPath && existingPath !== destDir) {
|
|
657
|
+
await rm(oldDirectPath, { recursive: true, force: true });
|
|
658
|
+
log.dim(` Migrated from ${oldDirectPath} to cache path`);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (targetScope === 'global') {
|
|
662
|
+
await registerPlugin(plugin.name, pluginVersion, destDir);
|
|
663
|
+
}
|
|
664
|
+
|
|
572
665
|
log.success(`Upgraded ${scopeLabel}${plugin.name}/`);
|
|
573
666
|
updated++;
|
|
574
667
|
} else {
|
|
575
668
|
await mkdir(destDir, { recursive: true });
|
|
576
669
|
await cp(plugin.path, destDir, { recursive: true });
|
|
670
|
+
|
|
671
|
+
if (targetScope === 'global') {
|
|
672
|
+
await registerPlugin(plugin.name, pluginVersion, destDir);
|
|
673
|
+
}
|
|
674
|
+
|
|
577
675
|
log.success(`Installed ${scopeLabel}${plugin.name}/`);
|
|
578
676
|
installed++;
|
|
579
677
|
|
|
@@ -582,6 +680,17 @@ export async function installOrUpdateClaudeCommands(options = {}) {
|
|
|
582
680
|
}
|
|
583
681
|
}
|
|
584
682
|
|
|
683
|
+
// Persist scope + metadata to config.local.json
|
|
684
|
+
if (hasProject && (options.global || options.local || !await getPluginScope(plugin.name))) {
|
|
685
|
+
await setPluginScope(plugin.name, {
|
|
686
|
+
scope: targetScope === 'global' ? 'user' : 'project',
|
|
687
|
+
installPath: destDir,
|
|
688
|
+
version: pluginVersion,
|
|
689
|
+
});
|
|
690
|
+
} else if (targetScope === 'global' && !hasProject) {
|
|
691
|
+
log.warn(`Cannot persist scope preference (no .dbo/ directory). Run "dbo init" first.`);
|
|
692
|
+
}
|
|
693
|
+
|
|
585
694
|
// Clean up legacy file if it exists
|
|
586
695
|
const legacyProjectPath = join(getCommandsDir('project'), legacyFileName);
|
|
587
696
|
const legacyGlobalPath = join(getCommandsDir('global'), legacyFileName);
|
|
@@ -644,6 +753,11 @@ export async function installOrUpdateClaudeCommands(options = {}) {
|
|
|
644
753
|
if (targetScope === 'global' && locations.project) {
|
|
645
754
|
await removeFromGitignore(`.claude/commands/${legacyFileName}`);
|
|
646
755
|
}
|
|
756
|
+
|
|
757
|
+
// Persist scope for legacy plugins
|
|
758
|
+
if (hasProject && (options.global || options.local || !await getPluginScope(plugin.name))) {
|
|
759
|
+
await setPluginScope(plugin.name, targetScope);
|
|
760
|
+
}
|
|
647
761
|
}
|
|
648
762
|
}
|
|
649
763
|
|
|
@@ -697,24 +811,26 @@ async function installOrUpdateSpecificCommand(name, options = {}) {
|
|
|
697
811
|
targetScope = await resolvePluginScope(pluginName, options);
|
|
698
812
|
}
|
|
699
813
|
|
|
700
|
-
// Persist scope
|
|
701
|
-
if (options.global || options.local || !await getPluginScope(pluginName)) {
|
|
702
|
-
if (hasProject) {
|
|
703
|
-
await setPluginScope(pluginName, targetScope);
|
|
704
|
-
} else if (targetScope === 'global') {
|
|
705
|
-
log.warn(`Cannot persist scope preference (no .dbo/ directory). Run "dbo init" first.`);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
814
|
if (plugin.type === 'directory') {
|
|
710
815
|
// Directory-based plugin installation
|
|
711
|
-
const
|
|
816
|
+
const pluginMeta = JSON.parse(await readFile(join(plugin.path, '.claude-plugin', 'plugin.json'), 'utf8'));
|
|
817
|
+
const pluginVersion = pluginMeta.version || '0.0.0';
|
|
818
|
+
|
|
819
|
+
const destDir = targetScope === 'global'
|
|
820
|
+
? getGlobalPluginCachePath(pluginName, pluginVersion)
|
|
821
|
+
: join(getPluginsDir('project'), pluginName);
|
|
712
822
|
const scopeLabel = targetScope === 'global' ? '~/.claude/plugins/' : '.claude/plugins/';
|
|
713
823
|
|
|
714
|
-
|
|
824
|
+
// Check for existing installation (cache path or old direct path)
|
|
825
|
+
const oldDirectPath = join(CLAUDE_PLUGINS_DIR, pluginName);
|
|
826
|
+
const existingPath = await fileExists(destDir) ? destDir
|
|
827
|
+
: (targetScope === 'global' && await fileExists(oldDirectPath)) ? oldDirectPath
|
|
828
|
+
: null;
|
|
829
|
+
|
|
830
|
+
if (existingPath) {
|
|
715
831
|
const srcHash = await directoryHash(plugin.path);
|
|
716
|
-
const destHash = await directoryHash(
|
|
717
|
-
if (srcHash === destHash) {
|
|
832
|
+
const destHash = await directoryHash(existingPath);
|
|
833
|
+
if (srcHash === destHash && existingPath === destDir) {
|
|
718
834
|
log.success(`${pluginName} is already up to date in ${scopeLabel}`);
|
|
719
835
|
return;
|
|
720
836
|
}
|
|
@@ -727,11 +843,28 @@ async function installOrUpdateSpecificCommand(name, options = {}) {
|
|
|
727
843
|
}]);
|
|
728
844
|
if (!upgrade) return;
|
|
729
845
|
|
|
846
|
+
await mkdir(destDir, { recursive: true });
|
|
730
847
|
await cp(plugin.path, destDir, { recursive: true, force: true });
|
|
848
|
+
|
|
849
|
+
// Clean up old direct path if migrating to cache path
|
|
850
|
+
if (targetScope === 'global' && existingPath === oldDirectPath && existingPath !== destDir) {
|
|
851
|
+
await rm(oldDirectPath, { recursive: true, force: true });
|
|
852
|
+
log.dim(` Migrated from ${oldDirectPath} to cache path`);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (targetScope === 'global') {
|
|
856
|
+
await registerPlugin(pluginName, pluginVersion, destDir);
|
|
857
|
+
}
|
|
858
|
+
|
|
731
859
|
log.success(`Upgraded ${scopeLabel}${pluginName}/`);
|
|
732
860
|
} else {
|
|
733
861
|
await mkdir(destDir, { recursive: true });
|
|
734
862
|
await cp(plugin.path, destDir, { recursive: true });
|
|
863
|
+
|
|
864
|
+
if (targetScope === 'global') {
|
|
865
|
+
await registerPlugin(pluginName, pluginVersion, destDir);
|
|
866
|
+
}
|
|
867
|
+
|
|
735
868
|
log.success(`Installed ${scopeLabel}${pluginName}/`);
|
|
736
869
|
|
|
737
870
|
if (targetScope === 'project') {
|
|
@@ -739,6 +872,17 @@ async function installOrUpdateSpecificCommand(name, options = {}) {
|
|
|
739
872
|
}
|
|
740
873
|
}
|
|
741
874
|
|
|
875
|
+
// Persist scope + metadata to config.local.json
|
|
876
|
+
if (hasProject && (options.global || options.local || !await getPluginScope(pluginName))) {
|
|
877
|
+
await setPluginScope(pluginName, {
|
|
878
|
+
scope: targetScope === 'global' ? 'user' : 'project',
|
|
879
|
+
installPath: destDir,
|
|
880
|
+
version: pluginVersion,
|
|
881
|
+
});
|
|
882
|
+
} else if (targetScope === 'global' && !hasProject) {
|
|
883
|
+
log.warn(`Cannot persist scope preference (no .dbo/ directory). Run "dbo init" first.`);
|
|
884
|
+
}
|
|
885
|
+
|
|
742
886
|
// Clean up legacy files
|
|
743
887
|
const legacyProjectPath = join(getCommandsDir('project'), legacyFileName);
|
|
744
888
|
const legacyGlobalPath = join(getCommandsDir('global'), legacyFileName);
|
|
@@ -795,6 +939,11 @@ async function installOrUpdateSpecificCommand(name, options = {}) {
|
|
|
795
939
|
if (targetScope === 'global' && locations.project) {
|
|
796
940
|
await removeFromGitignore(`.claude/commands/${legacyFileName}`);
|
|
797
941
|
}
|
|
942
|
+
|
|
943
|
+
// Persist scope for legacy plugins
|
|
944
|
+
if (hasProject && (options.global || options.local || !await getPluginScope(pluginName))) {
|
|
945
|
+
await setPluginScope(pluginName, targetScope);
|
|
946
|
+
}
|
|
798
947
|
}
|
|
799
948
|
|
|
800
949
|
log.warn('Note: Plugins will be available in new Claude Code sessions (restart any active session).');
|
package/src/lib/config.js
CHANGED
|
@@ -420,38 +420,81 @@ export async function saveLocalConfig(data) {
|
|
|
420
420
|
}
|
|
421
421
|
|
|
422
422
|
/**
|
|
423
|
-
* Get the stored scope for a plugin
|
|
424
|
-
*
|
|
425
|
-
*
|
|
423
|
+
* Get the stored scope for a plugin.
|
|
424
|
+
* Reads from the registry-style format: plugins["name@marketplace"][0].scope
|
|
425
|
+
* Falls back to legacy format: plugins.claudecommands.name
|
|
426
|
+
* @param {string} pluginName - Plugin name (without extension)
|
|
427
|
+
* @param {string} [marketplace='dboio'] - Marketplace identifier
|
|
426
428
|
* @returns {Promise<'project' | 'global' | null>}
|
|
427
429
|
*/
|
|
428
|
-
export async function getPluginScope(pluginName,
|
|
430
|
+
export async function getPluginScope(pluginName, marketplace = 'dboio') {
|
|
429
431
|
const config = await loadLocalConfig();
|
|
430
|
-
|
|
432
|
+
const key = `${pluginName}@${marketplace}`;
|
|
433
|
+
const entry = config.plugins?.[key]?.[0];
|
|
434
|
+
if (entry) {
|
|
435
|
+
return entry.scope === 'user' ? 'global' : entry.scope || null;
|
|
436
|
+
}
|
|
437
|
+
// Legacy fallback: plugins.claudecommands.dbo = "global"
|
|
438
|
+
const legacy = config.plugins?.claudecommands?.[pluginName];
|
|
439
|
+
return legacy || null;
|
|
431
440
|
}
|
|
432
441
|
|
|
433
442
|
/**
|
|
434
|
-
* Set
|
|
435
|
-
* @param {string} pluginName - Plugin name
|
|
436
|
-
* @param {
|
|
437
|
-
* @param {string} [
|
|
443
|
+
* Set plugin metadata in registry-style format.
|
|
444
|
+
* @param {string} pluginName - Plugin name
|
|
445
|
+
* @param {object} meta - Plugin metadata { scope, installPath, version }
|
|
446
|
+
* @param {string} [marketplace='dboio'] - Marketplace identifier
|
|
438
447
|
*/
|
|
439
|
-
export async function setPluginScope(pluginName,
|
|
448
|
+
export async function setPluginScope(pluginName, meta, marketplace = 'dboio') {
|
|
440
449
|
const config = await loadLocalConfig();
|
|
441
450
|
if (!config.plugins) config.plugins = {};
|
|
442
|
-
|
|
443
|
-
|
|
451
|
+
const key = `${pluginName}@${marketplace}`;
|
|
452
|
+
const now = new Date().toISOString();
|
|
453
|
+
|
|
454
|
+
// Support legacy callers passing just a scope string
|
|
455
|
+
if (typeof meta === 'string') {
|
|
456
|
+
meta = { scope: meta === 'global' ? 'user' : meta };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const existing = config.plugins[key]?.[0];
|
|
460
|
+
if (existing) {
|
|
461
|
+
Object.assign(existing, meta);
|
|
462
|
+
existing.lastUpdated = now;
|
|
463
|
+
} else {
|
|
464
|
+
config.plugins[key] = [{
|
|
465
|
+
scope: meta.scope || 'user',
|
|
466
|
+
installPath: meta.installPath || null,
|
|
467
|
+
version: meta.version || null,
|
|
468
|
+
installedAt: now,
|
|
469
|
+
lastUpdated: now,
|
|
470
|
+
}];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Remove legacy entry if present
|
|
474
|
+
if (config.plugins.claudecommands?.[pluginName]) {
|
|
475
|
+
delete config.plugins.claudecommands[pluginName];
|
|
476
|
+
if (Object.keys(config.plugins.claudecommands).length === 0) {
|
|
477
|
+
delete config.plugins.claudecommands;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
444
481
|
await saveLocalConfig(config);
|
|
445
482
|
}
|
|
446
483
|
|
|
447
484
|
/**
|
|
448
|
-
* Get all stored plugin
|
|
449
|
-
* Returns object mapping
|
|
450
|
-
* @param {string} [category='claudecommands'] - Plugin category
|
|
485
|
+
* Get all stored plugin entries.
|
|
486
|
+
* Returns object mapping registry keys to their entry arrays.
|
|
451
487
|
*/
|
|
452
|
-
export async function getAllPluginScopes(
|
|
488
|
+
export async function getAllPluginScopes() {
|
|
453
489
|
const config = await loadLocalConfig();
|
|
454
|
-
|
|
490
|
+
const result = {};
|
|
491
|
+
if (!config.plugins) return result;
|
|
492
|
+
for (const [key, value] of Object.entries(config.plugins)) {
|
|
493
|
+
// Skip legacy category keys
|
|
494
|
+
if (typeof value === 'object' && !Array.isArray(value) && !value.scope) continue;
|
|
495
|
+
result[key] = value;
|
|
496
|
+
}
|
|
497
|
+
return result;
|
|
455
498
|
}
|
|
456
499
|
|
|
457
500
|
// ─── Gitignore ────────────────────────────────────────────────────────────
|