tools-cc 1.0.3 → 1.0.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/CHANGELOG.md +8 -0
- package/CHANGELOG_en.md +71 -0
- package/README.md +57 -2
- package/README_en.md +218 -0
- package/dist/commands/export.d.ts +7 -0
- package/dist/commands/export.js +57 -0
- package/dist/commands/use.d.ts +18 -2
- package/dist/commands/use.js +202 -29
- package/dist/core/project.d.ts +10 -1
- package/dist/core/project.js +136 -18
- package/dist/index.js +11 -0
- package/dist/types/config.d.ts +45 -0
- package/dist/types/config.js +32 -0
- package/dist/utils/parsePath.d.ts +31 -0
- package/dist/utils/parsePath.js +86 -0
- package/package.json +6 -2
- package/src/commands/export.ts +60 -0
- package/src/commands/use.ts +414 -190
- package/src/core/project.ts +179 -38
- package/src/index.ts +217 -205
- package/src/types/config.ts +75 -0
- package/src/utils/parsePath.ts +108 -0
- package/docs/plans/2026-02-25-tools-cc-design.md +0 -195
- package/docs/plans/2026-02-25-tools-cc-impl.md +0 -1600
- package/tests/core/config.test.ts +0 -37
- package/tests/core/manifest.test.ts +0 -37
- package/tests/core/project.test.ts +0 -50
- package/tests/core/source.test.ts +0 -75
- package/tests/core/symlink.test.ts +0 -39
package/dist/commands/use.js
CHANGED
|
@@ -12,8 +12,10 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
12
12
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
13
13
|
const project_1 = require("../core/project");
|
|
14
14
|
const source_1 = require("../core/source");
|
|
15
|
+
const manifest_1 = require("../core/manifest");
|
|
15
16
|
const symlink_1 = require("../core/symlink");
|
|
16
17
|
const path_1 = require("../utils/path");
|
|
18
|
+
const parsePath_1 = require("../utils/parsePath");
|
|
17
19
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
18
20
|
const path_2 = __importDefault(require("path"));
|
|
19
21
|
const SUPPORTED_TOOLS = {
|
|
@@ -22,37 +24,64 @@ const SUPPORTED_TOOLS = {
|
|
|
22
24
|
codebuddy: '.codebuddy',
|
|
23
25
|
opencode: '.opencode'
|
|
24
26
|
};
|
|
25
|
-
|
|
27
|
+
/**
|
|
28
|
+
* 默认选择配置 - 导入所有内容
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_SELECTION = {
|
|
31
|
+
skills: ['*'],
|
|
32
|
+
commands: ['*'],
|
|
33
|
+
agents: ['*']
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* 处理 use 命令
|
|
37
|
+
*
|
|
38
|
+
* 支持多种模式:
|
|
39
|
+
* 1. 配置导入模式: tools-cc use -c config.json
|
|
40
|
+
* 2. 交互选择模式: tools-cc use my-source -ls
|
|
41
|
+
* 3. 路径语法模式: tools-cc use my-source/skills/a-skill
|
|
42
|
+
* 4. 整体导入模式: tools-cc use my-source
|
|
43
|
+
* 5. 点模式: tools-cc use . (使用已配置源)
|
|
44
|
+
*/
|
|
45
|
+
async function handleUse(sourceSpecs, options) {
|
|
26
46
|
const projectDir = process.cwd();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
47
|
+
const toolsccDir = (0, path_1.getToolsccDir)(projectDir);
|
|
48
|
+
const configFile = (0, path_1.getProjectConfigPath)(projectDir);
|
|
49
|
+
// 1. 配置导入模式
|
|
50
|
+
if (options.config) {
|
|
51
|
+
await handleConfigImportMode(options.config, projectDir, options.projects);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// 2. 点模式:使用当前项目已配置的源
|
|
55
|
+
if (sourceSpecs.length === 1 && sourceSpecs[0] === '.') {
|
|
56
|
+
await handleDotMode(projectDir, toolsccDir, configFile, options.projects);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// 3. 交互选择模式:单个源 + -ls 选项
|
|
60
|
+
if (options.ls && sourceSpecs.length === 1) {
|
|
61
|
+
const parsed = (0, parsePath_1.parseSourcePath)(sourceSpecs[0]);
|
|
62
|
+
// 只有源名称时才进入交互模式
|
|
63
|
+
if (!parsed.type && parsed.sourceName) {
|
|
64
|
+
await handleInteractiveMode(parsed.sourceName, projectDir, options.projects);
|
|
33
65
|
return;
|
|
34
66
|
}
|
|
35
|
-
const answers = await inquirer_1.default.prompt([
|
|
36
|
-
{
|
|
37
|
-
type: 'checkbox',
|
|
38
|
-
name: 'selectedSources',
|
|
39
|
-
message: 'Select sources to use:',
|
|
40
|
-
choices: sourceList
|
|
41
|
-
}
|
|
42
|
-
]);
|
|
43
|
-
sourceNames = answers.selectedSources;
|
|
44
67
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
68
|
+
// 4. 无参数时显示源选择列表
|
|
69
|
+
if (sourceSpecs.length === 0) {
|
|
70
|
+
sourceSpecs = await selectSourcesInteractively();
|
|
71
|
+
if (sourceSpecs.length === 0) {
|
|
72
|
+
console.log(chalk_1.default.gray('No sources selected.'));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
48
75
|
}
|
|
76
|
+
// 5. 解析路径语法并构建选择配置
|
|
77
|
+
const selectionMap = (0, parsePath_1.buildSelectionFromPaths)(sourceSpecs);
|
|
49
78
|
// 初始化项目
|
|
50
79
|
await (0, project_1.initProject)(projectDir);
|
|
51
|
-
//
|
|
52
|
-
for (const sourceName of
|
|
80
|
+
// 应用每个源的选择配置
|
|
81
|
+
for (const [sourceName, selection] of Object.entries(selectionMap)) {
|
|
53
82
|
try {
|
|
54
83
|
const sourcePath = await (0, source_1.getSourcePath)(sourceName, path_1.GLOBAL_CONFIG_DIR);
|
|
55
|
-
await (0, project_1.useSource)(sourceName, sourcePath, projectDir);
|
|
84
|
+
await (0, project_1.useSource)(sourceName, sourcePath, projectDir, selection);
|
|
56
85
|
console.log(chalk_1.default.green(`✓ Using source: ${sourceName}`));
|
|
57
86
|
}
|
|
58
87
|
catch (error) {
|
|
@@ -60,8 +89,150 @@ async function handleUse(sourceNames, options) {
|
|
|
60
89
|
}
|
|
61
90
|
}
|
|
62
91
|
// 创建符号链接
|
|
63
|
-
|
|
64
|
-
|
|
92
|
+
await createToolLinks(projectDir, toolsccDir, configFile, options.projects);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 配置导入模式
|
|
96
|
+
*/
|
|
97
|
+
async function handleConfigImportMode(configPath, projectDir, projects) {
|
|
98
|
+
try {
|
|
99
|
+
const toolsccDir = (0, path_1.getToolsccDir)(projectDir);
|
|
100
|
+
const configFile = (0, path_1.getProjectConfigPath)(projectDir);
|
|
101
|
+
// 解析配置文件路径
|
|
102
|
+
const resolvedPath = path_2.default.resolve(configPath);
|
|
103
|
+
// 定义源路径解析函数
|
|
104
|
+
const resolveSourcePath = async (sourceName) => {
|
|
105
|
+
return await (0, source_1.getSourcePath)(sourceName, path_1.GLOBAL_CONFIG_DIR);
|
|
106
|
+
};
|
|
107
|
+
// 导入配置
|
|
108
|
+
await (0, project_1.importProjectConfig)(resolvedPath, projectDir, resolveSourcePath);
|
|
109
|
+
console.log(chalk_1.default.green(`✓ Imported config from: ${resolvedPath}`));
|
|
110
|
+
// 创建符号链接
|
|
111
|
+
await createToolLinks(projectDir, toolsccDir, configFile, projects);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.log(chalk_1.default.red(`✗ Failed to import config: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 点模式:使用已配置源创建符号链接
|
|
119
|
+
*/
|
|
120
|
+
async function handleDotMode(projectDir, toolsccDir, configFile, projects) {
|
|
121
|
+
if (!(await fs_extra_1.default.pathExists(configFile))) {
|
|
122
|
+
console.log(chalk_1.default.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const config = await fs_extra_1.default.readJson(configFile);
|
|
126
|
+
const configuredSources = Object.keys(config.sources || {});
|
|
127
|
+
if (configuredSources.length === 0) {
|
|
128
|
+
console.log(chalk_1.default.yellow('No sources configured in this project. Run `tools-cc use <source>` to add one.'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
console.log(chalk_1.default.cyan(`Using existing sources: ${configuredSources.join(', ')}`));
|
|
132
|
+
await createToolLinks(projectDir, toolsccDir, configFile, projects);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 交互选择模式:显示技能/命令/代理选择列表
|
|
136
|
+
*/
|
|
137
|
+
async function handleInteractiveMode(sourceName, projectDir, projects) {
|
|
138
|
+
try {
|
|
139
|
+
const sourcePath = await (0, source_1.getSourcePath)(sourceName, path_1.GLOBAL_CONFIG_DIR);
|
|
140
|
+
const manifest = await (0, manifest_1.scanSource)(sourcePath);
|
|
141
|
+
// 构建选项列表
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
143
|
+
const choices = [];
|
|
144
|
+
// Skills 组
|
|
145
|
+
if (manifest.skills && manifest.skills.length > 0) {
|
|
146
|
+
choices.push(new inquirer_1.default.Separator(`--- Skills (${manifest.skills.length}) ---`));
|
|
147
|
+
for (const skill of manifest.skills) {
|
|
148
|
+
choices.push({ name: skill, value: `skills/${skill}` });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Commands 组
|
|
152
|
+
if (manifest.commands && manifest.commands.length > 0) {
|
|
153
|
+
choices.push(new inquirer_1.default.Separator(`--- Commands (${manifest.commands.length}) ---`));
|
|
154
|
+
for (const cmd of manifest.commands) {
|
|
155
|
+
choices.push({ name: cmd, value: `commands/${cmd}` });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Agents 组
|
|
159
|
+
if (manifest.agents && manifest.agents.length > 0) {
|
|
160
|
+
choices.push(new inquirer_1.default.Separator(`--- Agents (${manifest.agents.length}) ---`));
|
|
161
|
+
for (const agent of manifest.agents) {
|
|
162
|
+
choices.push({ name: agent, value: `agents/${agent}` });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (choices.length === 0) {
|
|
166
|
+
console.log(chalk_1.default.yellow(`No items found in source: ${sourceName}`));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// 显示选择列表
|
|
170
|
+
const answers = await inquirer_1.default.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: 'checkbox',
|
|
173
|
+
name: 'selectedItems',
|
|
174
|
+
message: `Select items from ${sourceName}:`,
|
|
175
|
+
choices,
|
|
176
|
+
pageSize: 15
|
|
177
|
+
}
|
|
178
|
+
]);
|
|
179
|
+
if (answers.selectedItems.length === 0) {
|
|
180
|
+
console.log(chalk_1.default.gray('No items selected.'));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// 将选择转换为 SourceSelection
|
|
184
|
+
const selection = {
|
|
185
|
+
skills: [],
|
|
186
|
+
commands: [],
|
|
187
|
+
agents: []
|
|
188
|
+
};
|
|
189
|
+
for (const item of answers.selectedItems) {
|
|
190
|
+
const [type, name] = item.split('/');
|
|
191
|
+
if (type === 'skills')
|
|
192
|
+
selection.skills.push(name);
|
|
193
|
+
else if (type === 'commands')
|
|
194
|
+
selection.commands.push(name);
|
|
195
|
+
else if (type === 'agents')
|
|
196
|
+
selection.agents.push(name);
|
|
197
|
+
}
|
|
198
|
+
// 初始化项目并应用选择
|
|
199
|
+
await (0, project_1.initProject)(projectDir);
|
|
200
|
+
await (0, project_1.useSource)(sourceName, sourcePath, projectDir, selection);
|
|
201
|
+
console.log(chalk_1.default.green(`✓ Using source: ${sourceName}`));
|
|
202
|
+
// 创建符号链接
|
|
203
|
+
const toolsccDir = (0, path_1.getToolsccDir)(projectDir);
|
|
204
|
+
const configFile = (0, path_1.getProjectConfigPath)(projectDir);
|
|
205
|
+
await createToolLinks(projectDir, toolsccDir, configFile, projects);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
console.log(chalk_1.default.red(`✗ Failed to use ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* 显示源选择列表并返回用户选择
|
|
213
|
+
*/
|
|
214
|
+
async function selectSourcesInteractively() {
|
|
215
|
+
const sources = await (0, source_1.listSources)(path_1.GLOBAL_CONFIG_DIR);
|
|
216
|
+
const sourceList = Object.keys(sources);
|
|
217
|
+
if (sourceList.length === 0) {
|
|
218
|
+
console.log(chalk_1.default.yellow('No sources configured. Use `tools-cc -s add` to add one.'));
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
const answers = await inquirer_1.default.prompt([
|
|
222
|
+
{
|
|
223
|
+
type: 'checkbox',
|
|
224
|
+
name: 'selectedSources',
|
|
225
|
+
message: 'Select sources to use:',
|
|
226
|
+
choices: sourceList
|
|
227
|
+
}
|
|
228
|
+
]);
|
|
229
|
+
return answers.selectedSources;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 创建工具符号链接
|
|
233
|
+
*/
|
|
234
|
+
async function createToolLinks(projectDir, toolsccDir, configFile, projects) {
|
|
235
|
+
const tools = projects || Object.keys(SUPPORTED_TOOLS);
|
|
65
236
|
for (const tool of tools) {
|
|
66
237
|
const linkName = SUPPORTED_TOOLS[tool];
|
|
67
238
|
if (!linkName) {
|
|
@@ -78,7 +249,6 @@ async function handleUse(sourceNames, options) {
|
|
|
78
249
|
}
|
|
79
250
|
}
|
|
80
251
|
// 更新项目配置
|
|
81
|
-
const configFile = (0, path_1.getProjectConfigPath)(projectDir);
|
|
82
252
|
const config = await fs_extra_1.default.readJson(configFile);
|
|
83
253
|
const existingLinks = config.links || [];
|
|
84
254
|
config.links = [...new Set([...existingLinks, ...tools])];
|
|
@@ -141,26 +311,29 @@ async function handleProjectUpdate(sourceNames) {
|
|
|
141
311
|
return;
|
|
142
312
|
}
|
|
143
313
|
const config = await fs_extra_1.default.readJson(configFile);
|
|
314
|
+
const configuredSources = Object.keys(config.sources || {});
|
|
144
315
|
let sourcesToUpdate = sourceNames && sourceNames.length > 0
|
|
145
316
|
? sourceNames
|
|
146
|
-
:
|
|
317
|
+
: configuredSources;
|
|
147
318
|
if (sourcesToUpdate.length === 0) {
|
|
148
319
|
console.log(chalk_1.default.gray('No sources to update.'));
|
|
149
320
|
return;
|
|
150
321
|
}
|
|
151
322
|
// 验证指定的源是否存在于项目配置中
|
|
152
323
|
if (sourceNames && sourceNames.length > 0) {
|
|
153
|
-
const invalidSources = sourceNames.filter((s) => !
|
|
324
|
+
const invalidSources = sourceNames.filter((s) => !configuredSources.includes(s));
|
|
154
325
|
if (invalidSources.length > 0) {
|
|
155
326
|
console.log(chalk_1.default.yellow(`Sources not in project: ${invalidSources.join(', ')}`));
|
|
156
327
|
}
|
|
157
|
-
sourcesToUpdate = sourcesToUpdate.filter((s) =>
|
|
328
|
+
sourcesToUpdate = sourcesToUpdate.filter((s) => configuredSources.includes(s));
|
|
158
329
|
}
|
|
159
330
|
// 更新每个配置源
|
|
160
331
|
for (const sourceName of sourcesToUpdate) {
|
|
161
332
|
try {
|
|
162
333
|
const sourcePath = await (0, source_1.getSourcePath)(sourceName, path_1.GLOBAL_CONFIG_DIR);
|
|
163
|
-
|
|
334
|
+
// 使用保存的选择配置进行更新
|
|
335
|
+
const selection = config.sources[sourceName];
|
|
336
|
+
await (0, project_1.useSource)(sourceName, sourcePath, projectDir, selection);
|
|
164
337
|
console.log(chalk_1.default.green(`✓ Updated source: ${sourceName}`));
|
|
165
338
|
}
|
|
166
339
|
catch (error) {
|
package/dist/core/project.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import { SourceSelection } from '../types';
|
|
1
2
|
export declare function initProject(projectDir: string): Promise<void>;
|
|
2
|
-
export declare function useSource(sourceName: string, sourceDir: string, projectDir: string): Promise<void>;
|
|
3
|
+
export declare function useSource(sourceName: string, sourceDir: string, projectDir: string, selection?: SourceSelection): Promise<void>;
|
|
3
4
|
export declare function unuseSource(sourceName: string, projectDir: string): Promise<void>;
|
|
4
5
|
export declare function listUsedSources(projectDir: string): Promise<string[]>;
|
|
6
|
+
/**
|
|
7
|
+
* 导出项目配置到 JSON 文件
|
|
8
|
+
*/
|
|
9
|
+
export declare function exportProjectConfig(projectDir: string, outputPath: string): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* 从 JSON 文件导入项目配置
|
|
12
|
+
*/
|
|
13
|
+
export declare function importProjectConfig(configPath: string, projectDir: string, resolveSourcePath: (sourceName: string) => Promise<string>): Promise<void>;
|
package/dist/core/project.js
CHANGED
|
@@ -7,10 +7,21 @@ exports.initProject = initProject;
|
|
|
7
7
|
exports.useSource = useSource;
|
|
8
8
|
exports.unuseSource = unuseSource;
|
|
9
9
|
exports.listUsedSources = listUsedSources;
|
|
10
|
+
exports.exportProjectConfig = exportProjectConfig;
|
|
11
|
+
exports.importProjectConfig = importProjectConfig;
|
|
10
12
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
13
|
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const types_1 = require("../types");
|
|
12
15
|
const manifest_1 = require("./manifest");
|
|
13
16
|
const path_2 = require("../utils/path");
|
|
17
|
+
/**
|
|
18
|
+
* 默认选择配置 - 导入所有内容
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_SELECTION = {
|
|
21
|
+
skills: ['*'],
|
|
22
|
+
commands: ['*'],
|
|
23
|
+
agents: ['*']
|
|
24
|
+
};
|
|
14
25
|
async function initProject(projectDir) {
|
|
15
26
|
const toolsccDir = (0, path_2.getToolsccDir)(projectDir);
|
|
16
27
|
const configFile = (0, path_2.getProjectConfigPath)(projectDir);
|
|
@@ -21,13 +32,37 @@ async function initProject(projectDir) {
|
|
|
21
32
|
// Create project config if not exists
|
|
22
33
|
if (!(await fs_extra_1.default.pathExists(configFile))) {
|
|
23
34
|
const config = {
|
|
24
|
-
sources:
|
|
35
|
+
sources: {},
|
|
25
36
|
links: []
|
|
26
37
|
};
|
|
27
38
|
await fs_extra_1.default.writeJson(configFile, config, { spaces: 2 });
|
|
28
39
|
}
|
|
29
40
|
}
|
|
30
|
-
|
|
41
|
+
/**
|
|
42
|
+
* 读取项目配置,自动处理新旧格式
|
|
43
|
+
*/
|
|
44
|
+
async function readProjectConfig(configFile) {
|
|
45
|
+
const rawConfig = await fs_extra_1.default.readJson(configFile);
|
|
46
|
+
return (0, types_1.normalizeProjectConfig)(rawConfig);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 获取源名称列表(兼容新旧格式)
|
|
50
|
+
*/
|
|
51
|
+
function getSourceNames(config) {
|
|
52
|
+
return Object.keys(config.sources);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 检查是否应该复制某项(根据选择配置)
|
|
56
|
+
*/
|
|
57
|
+
function shouldInclude(itemName, selection) {
|
|
58
|
+
// 如果选择包含通配符,包含所有项
|
|
59
|
+
if (selection.includes('*')) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
// 否则检查是否在选择列表中
|
|
63
|
+
return selection.includes(itemName);
|
|
64
|
+
}
|
|
65
|
+
async function useSource(sourceName, sourceDir, projectDir, selection) {
|
|
31
66
|
// Input validation
|
|
32
67
|
if (!sourceName || !sourceName.trim()) {
|
|
33
68
|
throw new Error('Source name is required');
|
|
@@ -40,13 +75,20 @@ async function useSource(sourceName, sourceDir, projectDir) {
|
|
|
40
75
|
const manifest = await (0, manifest_1.loadManifest)(sourceDir);
|
|
41
76
|
// Ensure project is initialized
|
|
42
77
|
await initProject(projectDir);
|
|
78
|
+
// 使用传入的选择配置或默认配置
|
|
79
|
+
const effectiveSelection = selection ?? DEFAULT_SELECTION;
|
|
43
80
|
// Copy/link skills (flattened with prefix)
|
|
44
81
|
const sourceSkillsDir = path_1.default.join(sourceDir, 'skills');
|
|
45
82
|
if (await fs_extra_1.default.pathExists(sourceSkillsDir)) {
|
|
46
83
|
const skills = await fs_extra_1.default.readdir(sourceSkillsDir);
|
|
47
84
|
for (const skill of skills) {
|
|
85
|
+
// 检查是否应该包含此 skill
|
|
86
|
+
if (!shouldInclude(skill, effectiveSelection.skills)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
48
89
|
const srcPath = path_1.default.join(sourceSkillsDir, skill);
|
|
49
|
-
const
|
|
90
|
+
const name = `${sourceName}` == `${skill}` ? skill : `${sourceName}-${skill}`;
|
|
91
|
+
const destPath = path_1.default.join(toolsccDir, 'skills', name);
|
|
50
92
|
// Remove existing if exists
|
|
51
93
|
await fs_extra_1.default.remove(destPath);
|
|
52
94
|
// Copy directory
|
|
@@ -56,23 +98,51 @@ async function useSource(sourceName, sourceDir, projectDir) {
|
|
|
56
98
|
// Copy commands (in subdirectory by source name)
|
|
57
99
|
const sourceCommandsDir = path_1.default.join(sourceDir, 'commands');
|
|
58
100
|
if (await fs_extra_1.default.pathExists(sourceCommandsDir)) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
101
|
+
// 检查是否有选择 commands
|
|
102
|
+
if (effectiveSelection.commands.includes('*')) {
|
|
103
|
+
// 复制所有 commands
|
|
104
|
+
const destDir = path_1.default.join(toolsccDir, 'commands', sourceName);
|
|
105
|
+
await fs_extra_1.default.remove(destDir);
|
|
106
|
+
await fs_extra_1.default.copy(sourceCommandsDir, destDir);
|
|
107
|
+
}
|
|
108
|
+
else if (effectiveSelection.commands.length > 0) {
|
|
109
|
+
// 只复制选中的 commands
|
|
110
|
+
const destDir = path_1.default.join(toolsccDir, 'commands', sourceName);
|
|
111
|
+
await fs_extra_1.default.ensureDir(destDir);
|
|
112
|
+
for (const cmdName of effectiveSelection.commands) {
|
|
113
|
+
const srcFile = path_1.default.join(sourceCommandsDir, `${cmdName}.md`);
|
|
114
|
+
if (await fs_extra_1.default.pathExists(srcFile)) {
|
|
115
|
+
await fs_extra_1.default.copy(srcFile, path_1.default.join(destDir, `${cmdName}.md`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
62
119
|
}
|
|
63
120
|
// Copy agents (in subdirectory by source name)
|
|
64
121
|
const sourceAgentsDir = path_1.default.join(sourceDir, 'agents');
|
|
65
122
|
if (await fs_extra_1.default.pathExists(sourceAgentsDir)) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
123
|
+
// 检查是否有选择 agents
|
|
124
|
+
if (effectiveSelection.agents.includes('*')) {
|
|
125
|
+
// 复制所有 agents
|
|
126
|
+
const destDir = path_1.default.join(toolsccDir, 'agents', sourceName);
|
|
127
|
+
await fs_extra_1.default.remove(destDir);
|
|
128
|
+
await fs_extra_1.default.copy(sourceAgentsDir, destDir);
|
|
129
|
+
}
|
|
130
|
+
else if (effectiveSelection.agents.length > 0) {
|
|
131
|
+
// 只复制选中的 agents
|
|
132
|
+
const destDir = path_1.default.join(toolsccDir, 'agents', sourceName);
|
|
133
|
+
await fs_extra_1.default.ensureDir(destDir);
|
|
134
|
+
for (const agentName of effectiveSelection.agents) {
|
|
135
|
+
const srcFile = path_1.default.join(sourceAgentsDir, `${agentName}.md`);
|
|
136
|
+
if (await fs_extra_1.default.pathExists(srcFile)) {
|
|
137
|
+
await fs_extra_1.default.copy(srcFile, path_1.default.join(destDir, `${agentName}.md`));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
69
141
|
}
|
|
70
|
-
// Update project config
|
|
142
|
+
// Update project config - 保存实际使用的选择配置
|
|
71
143
|
const configFile = (0, path_2.getProjectConfigPath)(projectDir);
|
|
72
|
-
const config = await
|
|
73
|
-
|
|
74
|
-
config.sources.push(sourceName);
|
|
75
|
-
}
|
|
144
|
+
const config = await readProjectConfig(configFile);
|
|
145
|
+
config.sources[sourceName] = effectiveSelection;
|
|
76
146
|
await fs_extra_1.default.writeJson(configFile, config, { spaces: 2 });
|
|
77
147
|
}
|
|
78
148
|
async function unuseSource(sourceName, projectDir) {
|
|
@@ -99,13 +169,13 @@ async function unuseSource(sourceName, projectDir) {
|
|
|
99
169
|
// Update project config with error handling
|
|
100
170
|
let config;
|
|
101
171
|
try {
|
|
102
|
-
config = await
|
|
172
|
+
config = await readProjectConfig(configFile);
|
|
103
173
|
}
|
|
104
174
|
catch (error) {
|
|
105
175
|
// If config file doesn't exist or is invalid, nothing to update
|
|
106
176
|
return;
|
|
107
177
|
}
|
|
108
|
-
|
|
178
|
+
delete config.sources[sourceName];
|
|
109
179
|
await fs_extra_1.default.writeJson(configFile, config, { spaces: 2 });
|
|
110
180
|
}
|
|
111
181
|
async function listUsedSources(projectDir) {
|
|
@@ -113,6 +183,54 @@ async function listUsedSources(projectDir) {
|
|
|
113
183
|
if (!(await fs_extra_1.default.pathExists(configFile))) {
|
|
114
184
|
return [];
|
|
115
185
|
}
|
|
116
|
-
const config = await
|
|
117
|
-
return config
|
|
186
|
+
const config = await readProjectConfig(configFile);
|
|
187
|
+
return getSourceNames(config);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 导出项目配置到 JSON 文件
|
|
191
|
+
*/
|
|
192
|
+
async function exportProjectConfig(projectDir, outputPath) {
|
|
193
|
+
const configFile = (0, path_2.getProjectConfigPath)(projectDir);
|
|
194
|
+
if (!(await fs_extra_1.default.pathExists(configFile))) {
|
|
195
|
+
throw new Error('Project not initialized. Use `tools-cc use <source>` to get started.');
|
|
196
|
+
}
|
|
197
|
+
const config = await readProjectConfig(configFile);
|
|
198
|
+
const exportConfig = {
|
|
199
|
+
version: '1.0',
|
|
200
|
+
type: 'project',
|
|
201
|
+
config,
|
|
202
|
+
exportedAt: new Date().toISOString()
|
|
203
|
+
};
|
|
204
|
+
await fs_extra_1.default.writeJson(outputPath, exportConfig, { spaces: 2 });
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* 从 JSON 文件导入项目配置
|
|
208
|
+
*/
|
|
209
|
+
async function importProjectConfig(configPath, projectDir, resolveSourcePath) {
|
|
210
|
+
if (!(await fs_extra_1.default.pathExists(configPath))) {
|
|
211
|
+
throw new Error(`Config file not found: ${configPath}`);
|
|
212
|
+
}
|
|
213
|
+
const exportConfig = await fs_extra_1.default.readJson(configPath);
|
|
214
|
+
// Validate version
|
|
215
|
+
if (exportConfig.version !== '1.0') {
|
|
216
|
+
throw new Error(`Unsupported config version: ${exportConfig.version}`);
|
|
217
|
+
}
|
|
218
|
+
// Validate type
|
|
219
|
+
if (exportConfig.type !== 'project') {
|
|
220
|
+
throw new Error(`Invalid config type: ${exportConfig.type}. Expected 'project'.`);
|
|
221
|
+
}
|
|
222
|
+
// Initialize project
|
|
223
|
+
await initProject(projectDir);
|
|
224
|
+
// Apply each source
|
|
225
|
+
for (const [sourceName, selection] of Object.entries(exportConfig.config.sources)) {
|
|
226
|
+
const sourceDir = await resolveSourcePath(sourceName);
|
|
227
|
+
await useSource(sourceName, sourceDir, projectDir, selection);
|
|
228
|
+
}
|
|
229
|
+
// Update links if present
|
|
230
|
+
if (exportConfig.config.links && exportConfig.config.links.length > 0) {
|
|
231
|
+
const configFile = (0, path_2.getProjectConfigPath)(projectDir);
|
|
232
|
+
const config = await readProjectConfig(configFile);
|
|
233
|
+
config.links = exportConfig.config.links;
|
|
234
|
+
await fs_extra_1.default.writeJson(configFile, config, { spaces: 2 });
|
|
235
|
+
}
|
|
118
236
|
}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const commander_1 = require("commander");
|
|
|
5
5
|
const config_1 = require("./commands/config");
|
|
6
6
|
const source_1 = require("./commands/source");
|
|
7
7
|
const use_1 = require("./commands/use");
|
|
8
|
+
const export_1 = require("./commands/export");
|
|
8
9
|
const help_1 = require("./commands/help");
|
|
9
10
|
const program = new commander_1.Command();
|
|
10
11
|
program
|
|
@@ -80,6 +81,8 @@ program
|
|
|
80
81
|
.command('use [sources...]')
|
|
81
82
|
.description('Use sources in current project')
|
|
82
83
|
.option('-p, --projects <tools...>', 'Tools to link (iflow, claude, codebuddy, opencode)')
|
|
84
|
+
.option('-ls', 'Interactive selection mode for single source')
|
|
85
|
+
.option('-c, --config <file>', 'Import from config file')
|
|
83
86
|
.action(async (sources, options) => {
|
|
84
87
|
await (0, use_1.handleUse)(sources, options);
|
|
85
88
|
});
|
|
@@ -107,6 +110,14 @@ program
|
|
|
107
110
|
.action(async (sources) => {
|
|
108
111
|
await (0, use_1.handleProjectUpdate)(sources);
|
|
109
112
|
});
|
|
113
|
+
program
|
|
114
|
+
.command('export')
|
|
115
|
+
.description('Export project or global config')
|
|
116
|
+
.option('-o, --output <file>', 'Output file path')
|
|
117
|
+
.option('--global', 'Export global config')
|
|
118
|
+
.action(async (options) => {
|
|
119
|
+
await (0, export_1.handleExport)(options);
|
|
120
|
+
});
|
|
110
121
|
// Help command
|
|
111
122
|
program
|
|
112
123
|
.command('help')
|
package/dist/types/config.d.ts
CHANGED
|
@@ -7,10 +7,37 @@ export interface GlobalConfig {
|
|
|
7
7
|
sourcesDir: string;
|
|
8
8
|
sources: Record<string, SourceConfig>;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* 源选择配置 - 指定从源中导入哪些 skills/commands/agents
|
|
12
|
+
*/
|
|
13
|
+
export interface SourceSelection {
|
|
14
|
+
skills: string[];
|
|
15
|
+
commands: string[];
|
|
16
|
+
agents: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 新版项目配置 - sources 使用 Record 格式支持部分导入
|
|
20
|
+
*/
|
|
10
21
|
export interface ProjectConfig {
|
|
22
|
+
sources: Record<string, SourceSelection>;
|
|
23
|
+
links: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 旧版项目配置 - sources 为字符串数组(向后兼容)
|
|
27
|
+
*/
|
|
28
|
+
export interface LegacyProjectConfig {
|
|
11
29
|
sources: string[];
|
|
12
30
|
links: string[];
|
|
13
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* 判断值是否为有效的 SourceSelection 对象
|
|
34
|
+
*/
|
|
35
|
+
export declare function isSourceSelection(value: unknown): value is SourceSelection;
|
|
36
|
+
/**
|
|
37
|
+
* 将旧版项目配置转换为新版格式
|
|
38
|
+
* 如果 sources 是字符串数组,转换为 Record 格式,每个源默认导入全部内容
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizeProjectConfig(config: LegacyProjectConfig | ProjectConfig): ProjectConfig;
|
|
14
41
|
export interface Manifest {
|
|
15
42
|
name: string;
|
|
16
43
|
version: string;
|
|
@@ -21,3 +48,21 @@ export interface Manifest {
|
|
|
21
48
|
export interface ToolConfig {
|
|
22
49
|
linkName: string;
|
|
23
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* 项目配置导出格式
|
|
53
|
+
*/
|
|
54
|
+
export interface ExportConfig {
|
|
55
|
+
version: string;
|
|
56
|
+
type: 'project';
|
|
57
|
+
config: ProjectConfig;
|
|
58
|
+
exportedAt: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 全局配置导出格式
|
|
62
|
+
*/
|
|
63
|
+
export interface GlobalExportConfig {
|
|
64
|
+
version: string;
|
|
65
|
+
type: 'global';
|
|
66
|
+
config: GlobalConfig;
|
|
67
|
+
exportedAt: string;
|
|
68
|
+
}
|
package/dist/types/config.js
CHANGED
|
@@ -1,2 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isSourceSelection = isSourceSelection;
|
|
4
|
+
exports.normalizeProjectConfig = normalizeProjectConfig;
|
|
5
|
+
/**
|
|
6
|
+
* 判断值是否为有效的 SourceSelection 对象
|
|
7
|
+
*/
|
|
8
|
+
function isSourceSelection(value) {
|
|
9
|
+
if (typeof value !== 'object' || value === null)
|
|
10
|
+
return false;
|
|
11
|
+
const obj = value;
|
|
12
|
+
return (Array.isArray(obj.skills) &&
|
|
13
|
+
Array.isArray(obj.commands) &&
|
|
14
|
+
Array.isArray(obj.agents));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 将旧版项目配置转换为新版格式
|
|
18
|
+
* 如果 sources 是字符串数组,转换为 Record 格式,每个源默认导入全部内容
|
|
19
|
+
*/
|
|
20
|
+
function normalizeProjectConfig(config) {
|
|
21
|
+
// If sources is an array, convert to new format
|
|
22
|
+
if (Array.isArray(config.sources)) {
|
|
23
|
+
const newSources = {};
|
|
24
|
+
for (const sourceName of config.sources) {
|
|
25
|
+
newSources[sourceName] = {
|
|
26
|
+
skills: ['*'],
|
|
27
|
+
commands: ['*'],
|
|
28
|
+
agents: ['*']
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { sources: newSources, links: config.links };
|
|
32
|
+
}
|
|
33
|
+
return config;
|
|
34
|
+
}
|