dantelabs-agentic-school 1.1.1 → 1.2.0
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/.claude-plugin/marketplace.json +9 -50
- package/cli/src/commands/info.js +5 -2
- package/cli/src/commands/list.js +12 -7
- package/cli/src/lib/config.js +116 -1
- package/cli/src/lib/installer.js +106 -3
- package/package.json +1 -1
- package/plugins/brand-analytics/plugin.json +9 -0
- package/plugins/campaign-orchestration/plugin.json +9 -0
- package/plugins/common/plugin.json +9 -0
- package/plugins/content-creation/plugin.json +9 -0
- package/plugins/creative-production/plugin.json +9 -0
- package/plugins/customer-segmentation/plugin.json +9 -0
- package/plugins/market-research/plugin.json +9 -0
- package/plugins/persona-builder/plugin.json +9 -0
- package/plugins/social-strategy/plugin.json +9 -0
|
@@ -16,96 +16,55 @@
|
|
|
16
16
|
"name": "common",
|
|
17
17
|
"description": "공통 유틸리티 스킬 모음. 인증 관리, 문서 제작 도구(PPTX, PDF, DOCX), 이미지/비디오 생성(Kie.ai) 등 여러 플러그인에서 공통으로 사용하는 기능을 제공합니다.",
|
|
18
18
|
"version": "1.0.0",
|
|
19
|
-
"
|
|
20
|
-
"components": {
|
|
21
|
-
"skills": ["auth-manager", "pptx", "pdf", "docx", "kie-image-generator", "kie-video-generator"]
|
|
22
|
-
}
|
|
19
|
+
"source": "./plugins/common"
|
|
23
20
|
},
|
|
24
21
|
{
|
|
25
22
|
"name": "brand-analytics",
|
|
26
23
|
"description": "브랜드 소개서를 분석하여 포지셔닝, SWOT, 경쟁사 분석을 수행합니다.",
|
|
27
24
|
"version": "1.0.0",
|
|
28
|
-
"
|
|
29
|
-
"components": {
|
|
30
|
-
"agents": ["brand-strategist", "competitive-analyst"],
|
|
31
|
-
"commands": ["analyze-brand"],
|
|
32
|
-
"skills": ["brand-positioning"]
|
|
33
|
-
}
|
|
25
|
+
"source": "./plugins/brand-analytics"
|
|
34
26
|
},
|
|
35
27
|
{
|
|
36
28
|
"name": "customer-segmentation",
|
|
37
29
|
"description": "데이터 기반 고객 세그먼트를 설계하고 정의합니다.",
|
|
38
30
|
"version": "1.0.0",
|
|
39
|
-
"
|
|
40
|
-
"components": {
|
|
41
|
-
"agents": ["segmentation-architect", "data-analyst"],
|
|
42
|
-
"commands": ["create-segments"],
|
|
43
|
-
"skills": ["segmentation-framework", "activation-map"]
|
|
44
|
-
}
|
|
31
|
+
"source": "./plugins/customer-segmentation"
|
|
45
32
|
},
|
|
46
33
|
{
|
|
47
34
|
"name": "persona-builder",
|
|
48
35
|
"description": "타겟 세그먼트의 상세 페르소나 카드를 생성합니다.",
|
|
49
36
|
"version": "1.0.0",
|
|
50
|
-
"
|
|
51
|
-
"components": {
|
|
52
|
-
"agents": ["persona-architect", "customer-insights-partner"],
|
|
53
|
-
"commands": ["build-persona"],
|
|
54
|
-
"skills": ["persona-framework"]
|
|
55
|
-
}
|
|
37
|
+
"source": "./plugins/persona-builder"
|
|
56
38
|
},
|
|
57
39
|
{
|
|
58
40
|
"name": "social-strategy",
|
|
59
41
|
"description": "페르소나 기반 채널 선정 및 콘텐츠 전략을 수립합니다.",
|
|
60
42
|
"version": "1.0.0",
|
|
61
|
-
"
|
|
62
|
-
"components": {
|
|
63
|
-
"agents": ["social-strategy-director", "channel-analyst"],
|
|
64
|
-
"commands": ["plan-channels"],
|
|
65
|
-
"skills": ["channel-roadmap", "content-pillars"]
|
|
66
|
-
}
|
|
43
|
+
"source": "./plugins/social-strategy"
|
|
67
44
|
},
|
|
68
45
|
{
|
|
69
46
|
"name": "content-creation",
|
|
70
47
|
"description": "채널별 홍보 카피 및 스크립트를 생성합니다.",
|
|
71
48
|
"version": "1.0.0",
|
|
72
|
-
"
|
|
73
|
-
"components": {
|
|
74
|
-
"agents": ["copy-strategist", "conversion-copywriter", "script-writer"],
|
|
75
|
-
"commands": ["generate-copy", "write-script"],
|
|
76
|
-
"skills": ["message-architecture", "hook-formulas"]
|
|
77
|
-
}
|
|
49
|
+
"source": "./plugins/content-creation"
|
|
78
50
|
},
|
|
79
51
|
{
|
|
80
52
|
"name": "creative-production",
|
|
81
53
|
"description": "실제 이미지와 비디오를 생성합니다. common 플러그인의 kie-image-generator, kie-video-generator 스킬을 활용합니다.",
|
|
82
54
|
"version": "1.0.0",
|
|
83
|
-
"
|
|
84
|
-
"components": {
|
|
85
|
-
"agents": ["creative-director", "production-coordinator"],
|
|
86
|
-
"commands": ["create-image", "create-video"],
|
|
87
|
-
"skills": ["image-prompt-guide", "video-production"]
|
|
88
|
-
}
|
|
55
|
+
"source": "./plugins/creative-production"
|
|
89
56
|
},
|
|
90
57
|
{
|
|
91
58
|
"name": "campaign-orchestration",
|
|
92
59
|
"description": "전체 마케팅 워크플로우를 통합하고 순차 실행합니다.",
|
|
93
60
|
"version": "1.0.0",
|
|
94
|
-
"
|
|
95
|
-
"components": {
|
|
96
|
-
"agents": ["campaign-director", "workflow-coordinator"],
|
|
97
|
-
"commands": ["run-full-pipeline", "run-phase"],
|
|
98
|
-
"skills": ["pipeline-framework"]
|
|
99
|
-
}
|
|
61
|
+
"source": "./plugins/campaign-orchestration"
|
|
100
62
|
},
|
|
101
63
|
{
|
|
102
64
|
"name": "market-research",
|
|
103
65
|
"description": "시장 분석 리포트 및 데이터 시각화를 생성합니다.",
|
|
104
66
|
"version": "1.0.0",
|
|
105
|
-
"
|
|
106
|
-
"components": {
|
|
107
|
-
"skills": ["analysis-reports", "diagram-generator"]
|
|
108
|
-
}
|
|
67
|
+
"source": "./plugins/market-research"
|
|
109
68
|
}
|
|
110
69
|
],
|
|
111
70
|
"categories": [
|
package/cli/src/commands/info.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { getMarketplaceConfig } from '../lib/config.js';
|
|
2
|
+
import { getMarketplaceConfig, enrichPluginWithComponents } from '../lib/config.js';
|
|
3
3
|
import logger from '../utils/logger.js';
|
|
4
4
|
import { t } from '../i18n/index.js';
|
|
5
5
|
|
|
@@ -11,7 +11,7 @@ export default function infoCommand(program) {
|
|
|
11
11
|
.action(async (pluginName, options) => {
|
|
12
12
|
try {
|
|
13
13
|
const config = await getMarketplaceConfig();
|
|
14
|
-
|
|
14
|
+
let plugin = config.plugins.find((p) => p.name === pluginName);
|
|
15
15
|
|
|
16
16
|
if (!plugin) {
|
|
17
17
|
logger.error(t('info.pluginNotFound', { name: pluginName }));
|
|
@@ -23,6 +23,9 @@ export default function infoCommand(program) {
|
|
|
23
23
|
process.exit(1);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// Enrich plugin with discovered components
|
|
27
|
+
plugin = await enrichPluginWithComponents(plugin);
|
|
28
|
+
|
|
26
29
|
if (options.json) {
|
|
27
30
|
console.log(JSON.stringify(plugin, null, 2));
|
|
28
31
|
return;
|
package/cli/src/commands/list.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { getMarketplaceConfig } from '../lib/config.js';
|
|
2
|
+
import { getMarketplaceConfig, enrichPluginWithComponents } from '../lib/config.js';
|
|
3
3
|
import logger from '../utils/logger.js';
|
|
4
4
|
import { t } from '../i18n/index.js';
|
|
5
5
|
|
|
@@ -14,8 +14,13 @@ export default function listCommand(program) {
|
|
|
14
14
|
try {
|
|
15
15
|
const config = await getMarketplaceConfig();
|
|
16
16
|
|
|
17
|
+
// Enrich plugins with discovered components
|
|
18
|
+
const enrichedPlugins = await Promise.all(
|
|
19
|
+
config.plugins.map(p => enrichPluginWithComponents(p))
|
|
20
|
+
);
|
|
21
|
+
|
|
17
22
|
if (options.json) {
|
|
18
|
-
console.log(JSON.stringify(
|
|
23
|
+
console.log(JSON.stringify(enrichedPlugins, null, 2));
|
|
19
24
|
return;
|
|
20
25
|
}
|
|
21
26
|
|
|
@@ -24,7 +29,7 @@ export default function listCommand(program) {
|
|
|
24
29
|
console.log(chalk.gray('━'.repeat(60)));
|
|
25
30
|
console.log();
|
|
26
31
|
|
|
27
|
-
for (const plugin of
|
|
32
|
+
for (const plugin of enrichedPlugins) {
|
|
28
33
|
console.log(
|
|
29
34
|
chalk.bold.cyan(`${plugin.name}`),
|
|
30
35
|
chalk.gray(`v${plugin.version}`)
|
|
@@ -59,15 +64,15 @@ export default function listCommand(program) {
|
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
// Summary
|
|
62
|
-
const totalAgents =
|
|
67
|
+
const totalAgents = enrichedPlugins.reduce(
|
|
63
68
|
(sum, p) => sum + (p.components?.agents?.length || 0),
|
|
64
69
|
0
|
|
65
70
|
);
|
|
66
|
-
const totalCommands =
|
|
71
|
+
const totalCommands = enrichedPlugins.reduce(
|
|
67
72
|
(sum, p) => sum + (p.components?.commands?.length || 0),
|
|
68
73
|
0
|
|
69
74
|
);
|
|
70
|
-
const totalSkills =
|
|
75
|
+
const totalSkills = enrichedPlugins.reduce(
|
|
71
76
|
(sum, p) => sum + (p.components?.skills?.length || 0),
|
|
72
77
|
0
|
|
73
78
|
);
|
|
@@ -76,7 +81,7 @@ export default function listCommand(program) {
|
|
|
76
81
|
console.log(
|
|
77
82
|
chalk.bold(`${t('common.summary')}:`),
|
|
78
83
|
t('list.summaryText', {
|
|
79
|
-
plugins:
|
|
84
|
+
plugins: enrichedPlugins.length,
|
|
80
85
|
agents: totalAgents,
|
|
81
86
|
commands: totalCommands,
|
|
82
87
|
skills: totalSkills
|
package/cli/src/lib/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile, writeFile } from 'fs/promises';
|
|
1
|
+
import { readFile, writeFile, readdir } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
@@ -7,6 +7,9 @@ import { dirname } from 'path';
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = dirname(__filename);
|
|
9
9
|
|
|
10
|
+
// Package root directory
|
|
11
|
+
const PACKAGE_ROOT = join(__dirname, '../../..');
|
|
12
|
+
|
|
10
13
|
const CONFIG_CACHE_FILE = '.dantelabs-cache.json';
|
|
11
14
|
const CACHE_TTL = 1000 * 60 * 60; // 1 hour
|
|
12
15
|
|
|
@@ -124,6 +127,118 @@ async function saveCachedConfig(config) {
|
|
|
124
127
|
}
|
|
125
128
|
}
|
|
126
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Normalize source path (handle both 'path' and 'source' fields)
|
|
132
|
+
*/
|
|
133
|
+
export function getSourcePath(plugin) {
|
|
134
|
+
const source = plugin.source || plugin.path;
|
|
135
|
+
if (!source) return `plugins/${plugin.name}`;
|
|
136
|
+
return source.replace(/^\.\//, '');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Discover components by scanning local plugin directory
|
|
141
|
+
*/
|
|
142
|
+
async function discoverLocalComponents(pluginPath) {
|
|
143
|
+
const components = { agents: [], commands: [], skills: [] };
|
|
144
|
+
|
|
145
|
+
// Scan agents directory
|
|
146
|
+
const agentsDir = join(pluginPath, 'agents');
|
|
147
|
+
if (existsSync(agentsDir)) {
|
|
148
|
+
const files = await readdir(agentsDir);
|
|
149
|
+
components.agents = files
|
|
150
|
+
.filter(f => f.endsWith('.md'))
|
|
151
|
+
.map(f => f.replace('.md', ''));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Scan commands directory
|
|
155
|
+
const commandsDir = join(pluginPath, 'commands');
|
|
156
|
+
if (existsSync(commandsDir)) {
|
|
157
|
+
const files = await readdir(commandsDir);
|
|
158
|
+
components.commands = files
|
|
159
|
+
.filter(f => f.endsWith('.md'))
|
|
160
|
+
.map(f => f.replace('.md', ''));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Scan skills directory
|
|
164
|
+
const skillsDir = join(pluginPath, 'skills');
|
|
165
|
+
if (existsSync(skillsDir)) {
|
|
166
|
+
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
167
|
+
components.skills = entries
|
|
168
|
+
.filter(e => e.isDirectory())
|
|
169
|
+
.map(e => e.name);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return components;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Discover components from remote GitHub directory
|
|
177
|
+
*/
|
|
178
|
+
async function discoverRemoteComponents(remotePath) {
|
|
179
|
+
const components = { agents: [], commands: [], skills: [] };
|
|
180
|
+
|
|
181
|
+
const fetchDir = async (path) => {
|
|
182
|
+
const url = `${GITHUB_CONFIG.apiBase}/contents/${path}?ref=${GITHUB_CONFIG.branch}`;
|
|
183
|
+
const response = await fetch(url, {
|
|
184
|
+
headers: {
|
|
185
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
186
|
+
'User-Agent': 'dantelabs-agentic-school-cli'
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
if (!response.ok) return [];
|
|
190
|
+
return response.json();
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Scan agents directory
|
|
194
|
+
try {
|
|
195
|
+
const agentsContents = await fetchDir(`${remotePath}/agents`);
|
|
196
|
+
components.agents = agentsContents
|
|
197
|
+
.filter(f => f.type === 'file' && f.name.endsWith('.md'))
|
|
198
|
+
.map(f => f.name.replace('.md', ''));
|
|
199
|
+
} catch (e) { /* no agents dir */ }
|
|
200
|
+
|
|
201
|
+
// Scan commands directory
|
|
202
|
+
try {
|
|
203
|
+
const commandsContents = await fetchDir(`${remotePath}/commands`);
|
|
204
|
+
components.commands = commandsContents
|
|
205
|
+
.filter(f => f.type === 'file' && f.name.endsWith('.md'))
|
|
206
|
+
.map(f => f.name.replace('.md', ''));
|
|
207
|
+
} catch (e) { /* no commands dir */ }
|
|
208
|
+
|
|
209
|
+
// Scan skills directory
|
|
210
|
+
try {
|
|
211
|
+
const skillsContents = await fetchDir(`${remotePath}/skills`);
|
|
212
|
+
components.skills = skillsContents
|
|
213
|
+
.filter(f => f.type === 'dir')
|
|
214
|
+
.map(f => f.name);
|
|
215
|
+
} catch (e) { /* no skills dir */ }
|
|
216
|
+
|
|
217
|
+
return components;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Enrich plugin with discovered components
|
|
222
|
+
*/
|
|
223
|
+
export async function enrichPluginWithComponents(plugin) {
|
|
224
|
+
// If components already exist, return as is
|
|
225
|
+
if (plugin.components && Object.keys(plugin.components).length > 0) {
|
|
226
|
+
return plugin;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const sourcePath = getSourcePath(plugin);
|
|
230
|
+
const localPath = join(PACKAGE_ROOT, sourcePath);
|
|
231
|
+
|
|
232
|
+
let components;
|
|
233
|
+
if (existsSync(localPath)) {
|
|
234
|
+
components = await discoverLocalComponents(localPath);
|
|
235
|
+
} else {
|
|
236
|
+
components = await discoverRemoteComponents(sourcePath);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { ...plugin, components };
|
|
240
|
+
}
|
|
241
|
+
|
|
127
242
|
/**
|
|
128
243
|
* Validate plugin name
|
|
129
244
|
*/
|
package/cli/src/lib/installer.js
CHANGED
|
@@ -46,6 +46,87 @@ async function copyDirectory(src, dest) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Discover components by scanning plugin directory
|
|
51
|
+
*/
|
|
52
|
+
async function discoverComponents(localPath) {
|
|
53
|
+
const components = { agents: [], commands: [], skills: [] };
|
|
54
|
+
|
|
55
|
+
// Scan agents directory
|
|
56
|
+
const agentsDir = join(localPath, 'agents');
|
|
57
|
+
if (existsSync(agentsDir)) {
|
|
58
|
+
const files = await readdir(agentsDir);
|
|
59
|
+
components.agents = files
|
|
60
|
+
.filter(f => f.endsWith('.md'))
|
|
61
|
+
.map(f => f.replace('.md', ''));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Scan commands directory
|
|
65
|
+
const commandsDir = join(localPath, 'commands');
|
|
66
|
+
if (existsSync(commandsDir)) {
|
|
67
|
+
const files = await readdir(commandsDir);
|
|
68
|
+
components.commands = files
|
|
69
|
+
.filter(f => f.endsWith('.md'))
|
|
70
|
+
.map(f => f.replace('.md', ''));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Scan skills directory
|
|
74
|
+
const skillsDir = join(localPath, 'skills');
|
|
75
|
+
if (existsSync(skillsDir)) {
|
|
76
|
+
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
77
|
+
components.skills = entries
|
|
78
|
+
.filter(e => e.isDirectory())
|
|
79
|
+
.map(e => e.name);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return components;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Discover components from remote GitHub directory
|
|
87
|
+
*/
|
|
88
|
+
async function discoverRemoteComponents(remotePath) {
|
|
89
|
+
const { getDirectoryContents } = await import('./downloader.js');
|
|
90
|
+
const components = { agents: [], commands: [], skills: [] };
|
|
91
|
+
|
|
92
|
+
// Scan agents directory
|
|
93
|
+
try {
|
|
94
|
+
const agentsContents = await getDirectoryContents(`${remotePath}/agents`);
|
|
95
|
+
components.agents = agentsContents
|
|
96
|
+
.filter(f => f.type === 'file' && f.name.endsWith('.md'))
|
|
97
|
+
.map(f => f.name.replace('.md', ''));
|
|
98
|
+
} catch (e) { /* no agents dir */ }
|
|
99
|
+
|
|
100
|
+
// Scan commands directory
|
|
101
|
+
try {
|
|
102
|
+
const commandsContents = await getDirectoryContents(`${remotePath}/commands`);
|
|
103
|
+
components.commands = commandsContents
|
|
104
|
+
.filter(f => f.type === 'file' && f.name.endsWith('.md'))
|
|
105
|
+
.map(f => f.name.replace('.md', ''));
|
|
106
|
+
} catch (e) { /* no commands dir */ }
|
|
107
|
+
|
|
108
|
+
// Scan skills directory
|
|
109
|
+
try {
|
|
110
|
+
const skillsContents = await getDirectoryContents(`${remotePath}/skills`);
|
|
111
|
+
components.skills = skillsContents
|
|
112
|
+
.filter(f => f.type === 'dir')
|
|
113
|
+
.map(f => f.name);
|
|
114
|
+
} catch (e) { /* no skills dir */ }
|
|
115
|
+
|
|
116
|
+
return components;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Normalize source path (handle both 'path' and 'source' fields)
|
|
121
|
+
*/
|
|
122
|
+
function getSourcePath(plugin) {
|
|
123
|
+
// Support both old 'path' format and new 'source' format
|
|
124
|
+
const source = plugin.source || plugin.path;
|
|
125
|
+
if (!source) return `plugins/${plugin.name}`;
|
|
126
|
+
// Remove leading './' if present
|
|
127
|
+
return source.replace(/^\.\//, '');
|
|
128
|
+
}
|
|
129
|
+
|
|
49
130
|
/**
|
|
50
131
|
* Install a single plugin to .claude directory
|
|
51
132
|
*
|
|
@@ -64,13 +145,22 @@ async function copyDirectory(src, dest) {
|
|
|
64
145
|
export async function installPlugin(plugin, claudeDir, options = {}) {
|
|
65
146
|
const { force = false, onProgress } = options;
|
|
66
147
|
const pluginName = plugin.name;
|
|
67
|
-
const
|
|
68
|
-
const sourcePath = plugin.path; // e.g., "plugins/brand-analytics"
|
|
148
|
+
const sourcePath = getSourcePath(plugin); // e.g., "plugins/brand-analytics"
|
|
69
149
|
|
|
70
150
|
// Determine if we should use local or remote source
|
|
71
151
|
const useLocalSource = hasLocalSource(sourcePath);
|
|
72
152
|
const localSourcePath = join(PACKAGE_ROOT, sourcePath);
|
|
73
153
|
|
|
154
|
+
// Discover components dynamically (or use provided components for backward compatibility)
|
|
155
|
+
let components = plugin.components;
|
|
156
|
+
if (!components || Object.keys(components).length === 0) {
|
|
157
|
+
if (useLocalSource) {
|
|
158
|
+
components = await discoverComponents(localSourcePath);
|
|
159
|
+
} else {
|
|
160
|
+
components = await discoverRemoteComponents(sourcePath);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
74
164
|
// Create base directories
|
|
75
165
|
await ensureDir(join(claudeDir, 'agents'));
|
|
76
166
|
await ensureDir(join(claudeDir, 'commands'));
|
|
@@ -194,7 +284,20 @@ export async function installPlugin(plugin, claudeDir, options = {}) {
|
|
|
194
284
|
*/
|
|
195
285
|
export async function uninstallPlugin(plugin, claudeDir) {
|
|
196
286
|
const pluginName = plugin.name;
|
|
197
|
-
const
|
|
287
|
+
const sourcePath = getSourcePath(plugin);
|
|
288
|
+
|
|
289
|
+
// Discover components dynamically (or use provided components for backward compatibility)
|
|
290
|
+
let components = plugin.components;
|
|
291
|
+
if (!components || Object.keys(components).length === 0) {
|
|
292
|
+
const useLocalSource = hasLocalSource(sourcePath);
|
|
293
|
+
const localSourcePath = join(PACKAGE_ROOT, sourcePath);
|
|
294
|
+
|
|
295
|
+
if (useLocalSource) {
|
|
296
|
+
components = await discoverComponents(localSourcePath);
|
|
297
|
+
} else {
|
|
298
|
+
components = await discoverRemoteComponents(sourcePath);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
198
301
|
|
|
199
302
|
const results = {
|
|
200
303
|
plugin: pluginName,
|
package/package.json
CHANGED