tools-cc 1.0.4 → 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/README.md CHANGED
@@ -36,6 +36,13 @@ tools-cc -s list
36
36
  cd my-project
37
37
  tools-cc use my-skills -p iflow claude
38
38
 
39
+ # 4a. 或使用部分引入(只引入需要的 skills/commands/agents)
40
+ tools-cc use my-skills/skills/a-skill
41
+ tools-cc use my-skills -ls # 交互式选择
42
+
43
+ # 4b. 或从配置文件快速导入
44
+ tools-cc use -c project-config.json
45
+
39
46
  # 5. 查看项目状态
40
47
  tools-cc status
41
48
 
@@ -99,11 +106,47 @@ tools-cc sources scan # 扫描 sourcesDir 目录,自动发
99
106
  ### 项目配置
100
107
 
101
108
  ```bash
102
- tools-cc use [sources...] [-p tools...] # 启用配置源并创建符号链接
109
+ tools-cc use [sources...] [options] # 启用配置源并创建符号链接
103
110
  tools-cc update [sources...] # 同步配置源内容到项目 .toolscc 目录
104
111
  tools-cc list # 列出已启用的配置源
105
112
  tools-cc rm <source> # 禁用配置源
106
113
  tools-cc status # 查看项目状态
114
+ tools-cc export [options] # 导出项目或全局配置
115
+ ```
116
+
117
+ #### `use` 命令详解
118
+
119
+ ```bash
120
+ # 整源引入(全部 skills/commands/agents)
121
+ tools-cc use my-skills
122
+ tools-cc use my-skills -p iflow claude # 指定工具链接
123
+
124
+ # 部分引入(路径语法)
125
+ tools-cc use my-skills/skills/a-skill # 引入单个 skill
126
+ tools-cc use my-skills/commands/test # 引入单个 command
127
+ tools-cc use my-skills/agents/reviewer # 引入单个 agent
128
+ tools-cc use my-skills/skills/a my-skills/commands/test # 引入多项
129
+
130
+ # 交互式选择(-ls 参数)
131
+ tools-cc use my-skills -ls # 分组展示,勾选引入
132
+
133
+ # 从配置文件导入
134
+ tools-cc use -c project-config.json # 快速恢复项目配置
135
+
136
+ # 使用 "." 表示当前项目已配置的源(只创建链接,不复制内容)
137
+ tools-cc use . -p iflow claude
138
+ ```
139
+
140
+ #### `export` 命令详解
141
+
142
+ ```bash
143
+ # 导出项目配置(包含具体选择)
144
+ tools-cc export # 导出到 .toolscc-export.json
145
+ tools-cc export -o my-config.json # 指定输出路径
146
+
147
+ # 导出全局配置
148
+ tools-cc export --global # 导出到 .toolscc-global-export.json
149
+ tools-cc export --global -o global.json # 指定输出路径
107
150
  ```
108
151
 
109
152
  ### Config 管理
@@ -208,11 +251,23 @@ my-project/
208
251
 
209
252
  ```json
210
253
  {
211
- "sources": ["my-skills"],
254
+ "sources": {
255
+ "my-skills": {
256
+ "skills": ["a-skill", "b-skill"],
257
+ "commands": ["test"],
258
+ "agents": ["*"]
259
+ }
260
+ },
212
261
  "links": ["iflow", "claude"]
213
262
  }
214
263
  ```
215
264
 
265
+ **说明:**
266
+ - `sources` 记录每个源具体引入了哪些内容
267
+ - `["*"]` 表示引入该类型全部内容
268
+ - `[]` 表示不引入该类型任何内容
269
+ ```
270
+
216
271
  ## 许可证
217
272
 
218
273
  MIT
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 导出项目或全局配置
3
+ */
4
+ export declare function handleExport(options: {
5
+ output?: string;
6
+ global?: boolean;
7
+ }): Promise<void>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.handleExport = handleExport;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const project_1 = require("../core/project");
11
+ const config_1 = require("../core/config");
12
+ const path_2 = require("../utils/path");
13
+ /**
14
+ * 导出项目或全局配置
15
+ */
16
+ async function handleExport(options) {
17
+ try {
18
+ if (options.global) {
19
+ // 导出全局配置
20
+ await exportGlobalConfig(options.output);
21
+ }
22
+ else {
23
+ // 导出项目配置
24
+ await exportProjectConfigCmd(options.output);
25
+ }
26
+ }
27
+ catch (error) {
28
+ const message = error instanceof Error ? error.message : String(error);
29
+ console.log(chalk_1.default.red(`✗ Export failed: ${message}`));
30
+ throw error;
31
+ }
32
+ }
33
+ /**
34
+ * 导出项目配置
35
+ */
36
+ async function exportProjectConfigCmd(outputPath) {
37
+ const projectDir = process.cwd();
38
+ const outputFile = outputPath ?? path_1.default.join(projectDir, '.toolscc-export.json');
39
+ await (0, project_1.exportProjectConfig)(projectDir, outputFile);
40
+ console.log(chalk_1.default.green(`✓ Project config exported to: ${outputFile}`));
41
+ }
42
+ /**
43
+ * 导出全局配置
44
+ */
45
+ async function exportGlobalConfig(outputPath) {
46
+ const projectDir = process.cwd();
47
+ const outputFile = outputPath ?? path_1.default.join(projectDir, '.toolscc-global-export.json');
48
+ const config = await (0, config_1.loadGlobalConfig)(path_2.GLOBAL_CONFIG_DIR);
49
+ const exportConfig = {
50
+ version: '1.0',
51
+ type: 'global',
52
+ config,
53
+ exportedAt: new Date().toISOString()
54
+ };
55
+ await fs_extra_1.default.writeJson(outputFile, exportConfig, { spaces: 2 });
56
+ console.log(chalk_1.default.green(`✓ Global config exported to: ${outputFile}`));
57
+ }
@@ -1,6 +1,22 @@
1
- export declare function handleUse(sourceNames: string[], options: {
1
+ /**
2
+ * use 命令选项
3
+ */
4
+ export interface UseOptions {
2
5
  projects?: string[];
3
- }): Promise<void>;
6
+ ls?: boolean;
7
+ config?: string;
8
+ }
9
+ /**
10
+ * 处理 use 命令
11
+ *
12
+ * 支持多种模式:
13
+ * 1. 配置导入模式: tools-cc use -c config.json
14
+ * 2. 交互选择模式: tools-cc use my-source -ls
15
+ * 3. 路径语法模式: tools-cc use my-source/skills/a-skill
16
+ * 4. 整体导入模式: tools-cc use my-source
17
+ * 5. 点模式: tools-cc use . (使用已配置源)
18
+ */
19
+ export declare function handleUse(sourceSpecs: string[], options: UseOptions): Promise<void>;
4
20
  export declare function handleList(): Promise<void>;
5
21
  export declare function handleRemove(sourceName: string): Promise<void>;
6
22
  export declare function handleStatus(): Promise<void>;
@@ -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
- async function handleUse(sourceNames, options) {
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
- // 如果没有指定 source,进入交互模式
28
- if (sourceNames.length === 0) {
29
- const sources = await (0, source_1.listSources)(path_1.GLOBAL_CONFIG_DIR);
30
- const sourceList = Object.keys(sources);
31
- if (sourceList.length === 0) {
32
- console.log(chalk_1.default.yellow('No sources configured. Use `tools-cc -s add` to add one.'));
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
- if (sourceNames.length === 0) {
46
- console.log(chalk_1.default.gray('No sources selected.'));
47
- return;
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 sourceNames) {
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
- const tools = options.projects || Object.keys(SUPPORTED_TOOLS);
64
- const toolsccDir = (0, path_1.getToolsccDir)(projectDir);
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
- : config.sources || [];
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) => !config.sources.includes(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) => config.sources.includes(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
- await (0, project_1.useSource)(sourceName, sourcePath, projectDir);
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) {
@@ -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>;