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.
@@ -1,9 +1,18 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
- import { ProjectConfig } from '../types';
3
+ import { ProjectConfig, LegacyProjectConfig, normalizeProjectConfig, SourceSelection, ExportConfig } from '../types';
4
4
  import { loadManifest } from './manifest';
5
5
  import { getToolsccDir, getProjectConfigPath } from '../utils/path';
6
6
 
7
+ /**
8
+ * 默认选择配置 - 导入所有内容
9
+ */
10
+ const DEFAULT_SELECTION: SourceSelection = {
11
+ skills: ['*'],
12
+ commands: ['*'],
13
+ agents: ['*']
14
+ };
15
+
7
16
  export async function initProject(projectDir: string): Promise<void> {
8
17
  const toolsccDir = getToolsccDir(projectDir);
9
18
  const configFile = getProjectConfigPath(projectDir);
@@ -16,17 +25,45 @@ export async function initProject(projectDir: string): Promise<void> {
16
25
  // Create project config if not exists
17
26
  if (!(await fs.pathExists(configFile))) {
18
27
  const config: ProjectConfig = {
19
- sources: [],
28
+ sources: {},
20
29
  links: []
21
30
  };
22
31
  await fs.writeJson(configFile, config, { spaces: 2 });
23
32
  }
24
33
  }
25
34
 
35
+ /**
36
+ * 读取项目配置,自动处理新旧格式
37
+ */
38
+ async function readProjectConfig(configFile: string): Promise<ProjectConfig> {
39
+ const rawConfig = await fs.readJson(configFile);
40
+ return normalizeProjectConfig(rawConfig);
41
+ }
42
+
43
+ /**
44
+ * 获取源名称列表(兼容新旧格式)
45
+ */
46
+ function getSourceNames(config: ProjectConfig): string[] {
47
+ return Object.keys(config.sources);
48
+ }
49
+
50
+ /**
51
+ * 检查是否应该复制某项(根据选择配置)
52
+ */
53
+ function shouldInclude(itemName: string, selection: string[]): boolean {
54
+ // 如果选择包含通配符,包含所有项
55
+ if (selection.includes('*')) {
56
+ return true;
57
+ }
58
+ // 否则检查是否在选择列表中
59
+ return selection.includes(itemName);
60
+ }
61
+
26
62
  export async function useSource(
27
63
  sourceName: string,
28
64
  sourceDir: string,
29
- projectDir: string
65
+ projectDir: string,
66
+ selection?: SourceSelection
30
67
  ): Promise<void> {
31
68
  // Input validation
32
69
  if (!sourceName || !sourceName.trim()) {
@@ -44,11 +81,19 @@ export async function useSource(
44
81
  // Ensure project is initialized
45
82
  await initProject(projectDir);
46
83
 
84
+ // 使用传入的选择配置或默认配置
85
+ const effectiveSelection: SourceSelection = selection ?? DEFAULT_SELECTION;
86
+
47
87
  // Copy/link skills (flattened with prefix)
48
88
  const sourceSkillsDir = path.join(sourceDir, 'skills');
49
89
  if (await fs.pathExists(sourceSkillsDir)) {
50
90
  const skills = await fs.readdir(sourceSkillsDir);
51
91
  for (const skill of skills) {
92
+ // 检查是否应该包含此 skill
93
+ if (!shouldInclude(skill, effectiveSelection.skills)) {
94
+ continue;
95
+ }
96
+
52
97
  const srcPath = path.join(sourceSkillsDir, skill);
53
98
  const name = `${sourceName}` == `${skill}` ? skill : `${sourceName}-${skill}`;
54
99
  const destPath = path.join(toolsccDir, 'skills', name);
@@ -64,25 +109,53 @@ export async function useSource(
64
109
  // Copy commands (in subdirectory by source name)
65
110
  const sourceCommandsDir = path.join(sourceDir, 'commands');
66
111
  if (await fs.pathExists(sourceCommandsDir)) {
67
- const destDir = path.join(toolsccDir, 'commands', sourceName);
68
- await fs.remove(destDir);
69
- await fs.copy(sourceCommandsDir, destDir);
112
+ // 检查是否有选择 commands
113
+ if (effectiveSelection.commands.includes('*')) {
114
+ // 复制所有 commands
115
+ const destDir = path.join(toolsccDir, 'commands', sourceName);
116
+ await fs.remove(destDir);
117
+ await fs.copy(sourceCommandsDir, destDir);
118
+ } else if (effectiveSelection.commands.length > 0) {
119
+ // 只复制选中的 commands
120
+ const destDir = path.join(toolsccDir, 'commands', sourceName);
121
+ await fs.ensureDir(destDir);
122
+
123
+ for (const cmdName of effectiveSelection.commands) {
124
+ const srcFile = path.join(sourceCommandsDir, `${cmdName}.md`);
125
+ if (await fs.pathExists(srcFile)) {
126
+ await fs.copy(srcFile, path.join(destDir, `${cmdName}.md`));
127
+ }
128
+ }
129
+ }
70
130
  }
71
131
 
72
132
  // Copy agents (in subdirectory by source name)
73
133
  const sourceAgentsDir = path.join(sourceDir, 'agents');
74
134
  if (await fs.pathExists(sourceAgentsDir)) {
75
- const destDir = path.join(toolsccDir, 'agents', sourceName);
76
- await fs.remove(destDir);
77
- await fs.copy(sourceAgentsDir, destDir);
135
+ // 检查是否有选择 agents
136
+ if (effectiveSelection.agents.includes('*')) {
137
+ // 复制所有 agents
138
+ const destDir = path.join(toolsccDir, 'agents', sourceName);
139
+ await fs.remove(destDir);
140
+ await fs.copy(sourceAgentsDir, destDir);
141
+ } else if (effectiveSelection.agents.length > 0) {
142
+ // 只复制选中的 agents
143
+ const destDir = path.join(toolsccDir, 'agents', sourceName);
144
+ await fs.ensureDir(destDir);
145
+
146
+ for (const agentName of effectiveSelection.agents) {
147
+ const srcFile = path.join(sourceAgentsDir, `${agentName}.md`);
148
+ if (await fs.pathExists(srcFile)) {
149
+ await fs.copy(srcFile, path.join(destDir, `${agentName}.md`));
150
+ }
151
+ }
152
+ }
78
153
  }
79
154
 
80
- // Update project config
155
+ // Update project config - 保存实际使用的选择配置
81
156
  const configFile = getProjectConfigPath(projectDir);
82
- const config: ProjectConfig = await fs.readJson(configFile);
83
- if (!config.sources.includes(sourceName)) {
84
- config.sources.push(sourceName);
85
- }
157
+ const config = await readProjectConfig(configFile);
158
+ config.sources[sourceName] = effectiveSelection;
86
159
  await fs.writeJson(configFile, config, { spaces: 2 });
87
160
  }
88
161
 
@@ -115,13 +188,13 @@ export async function unuseSource(sourceName: string, projectDir: string): Promi
115
188
  // Update project config with error handling
116
189
  let config: ProjectConfig;
117
190
  try {
118
- config = await fs.readJson(configFile);
191
+ config = await readProjectConfig(configFile);
119
192
  } catch (error) {
120
193
  // If config file doesn't exist or is invalid, nothing to update
121
194
  return;
122
195
  }
123
196
 
124
- config.sources = config.sources.filter(s => s !== sourceName);
197
+ delete config.sources[sourceName];
125
198
  await fs.writeJson(configFile, config, { spaces: 2 });
126
199
  }
127
200
 
@@ -132,6 +205,73 @@ export async function listUsedSources(projectDir: string): Promise<string[]> {
132
205
  return [];
133
206
  }
134
207
 
135
- const config: ProjectConfig = await fs.readJson(configFile);
136
- return config.sources;
208
+ const config = await readProjectConfig(configFile);
209
+ return getSourceNames(config);
137
210
  }
211
+
212
+ /**
213
+ * 导出项目配置到 JSON 文件
214
+ */
215
+ export async function exportProjectConfig(
216
+ projectDir: string,
217
+ outputPath: string
218
+ ): Promise<void> {
219
+ const configFile = getProjectConfigPath(projectDir);
220
+
221
+ if (!(await fs.pathExists(configFile))) {
222
+ throw new Error('Project not initialized. Use `tools-cc use <source>` to get started.');
223
+ }
224
+
225
+ const config = await readProjectConfig(configFile);
226
+
227
+ const exportConfig: ExportConfig = {
228
+ version: '1.0',
229
+ type: 'project',
230
+ config,
231
+ exportedAt: new Date().toISOString()
232
+ };
233
+
234
+ await fs.writeJson(outputPath, exportConfig, { spaces: 2 });
235
+ }
236
+
237
+ /**
238
+ * 从 JSON 文件导入项目配置
239
+ */
240
+ export async function importProjectConfig(
241
+ configPath: string,
242
+ projectDir: string,
243
+ resolveSourcePath: (sourceName: string) => Promise<string>
244
+ ): Promise<void> {
245
+ if (!(await fs.pathExists(configPath))) {
246
+ throw new Error(`Config file not found: ${configPath}`);
247
+ }
248
+
249
+ const exportConfig: ExportConfig = await fs.readJson(configPath);
250
+
251
+ // Validate version
252
+ if (exportConfig.version !== '1.0') {
253
+ throw new Error(`Unsupported config version: ${exportConfig.version}`);
254
+ }
255
+
256
+ // Validate type
257
+ if (exportConfig.type !== 'project') {
258
+ throw new Error(`Invalid config type: ${exportConfig.type}. Expected 'project'.`);
259
+ }
260
+
261
+ // Initialize project
262
+ await initProject(projectDir);
263
+
264
+ // Apply each source
265
+ for (const [sourceName, selection] of Object.entries(exportConfig.config.sources)) {
266
+ const sourceDir = await resolveSourcePath(sourceName);
267
+ await useSource(sourceName, sourceDir, projectDir, selection);
268
+ }
269
+
270
+ // Update links if present
271
+ if (exportConfig.config.links && exportConfig.config.links.length > 0) {
272
+ const configFile = getProjectConfigPath(projectDir);
273
+ const config = await readProjectConfig(configFile);
274
+ config.links = exportConfig.config.links;
275
+ await fs.writeJson(configFile, config, { spaces: 2 });
276
+ }
277
+ }
package/src/index.ts CHANGED
@@ -1,205 +1,217 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import { handleConfigSet, handleConfigGet, handleConfigList } from './commands/config';
5
- import { handleSourceAdd, handleSourceList, handleSourceRemove, handleSourceUpdate, handleSourceScan } from './commands/source';
6
- import { handleUse, handleList, handleRemove, handleStatus, handleProjectUpdate } from './commands/use';
7
- import { showHelp } from './commands/help';
8
- import { GLOBAL_CONFIG_DIR } from './utils/path';
9
-
10
- const program = new Command();
11
-
12
- program
13
- .name('tools-cc')
14
- .description('CLI tool for managing skills/commands/agents across multiple AI coding tools')
15
- .version('0.0.1');
16
-
17
- // Source management (shortcut options)
18
- program
19
- .option('-s, --source <command> [args...]', 'Source management (shortcut)')
20
- .option('-c, --config <command> [args...]', 'Config management (shortcut)');
21
-
22
- // Source subcommands (full command version)
23
- const sourceCmd = program
24
- .command('sources')
25
- .description('Source management');
26
-
27
- sourceCmd
28
- .command('add <name> <path-or-url>')
29
- .description('Add a source')
30
- .action(async (name: string, pathOrUrl: string) => {
31
- await handleSourceAdd(name, pathOrUrl);
32
- });
33
-
34
- sourceCmd
35
- .command('list')
36
- .alias('ls')
37
- .description('List all sources')
38
- .action(async () => {
39
- await handleSourceList();
40
- });
41
-
42
- sourceCmd
43
- .command('remove <name>')
44
- .alias('rm')
45
- .description('Remove a source')
46
- .action(async (name: string) => {
47
- await handleSourceRemove(name);
48
- });
49
-
50
- sourceCmd
51
- .command('update [name]')
52
- .alias('up')
53
- .alias('upgrade')
54
- .description('Update source(s) with git pull')
55
- .action(async (name?: string) => {
56
- await handleSourceUpdate(name);
57
- });
58
-
59
- sourceCmd
60
- .command('scan')
61
- .description('Scan sources directory and update configuration')
62
- .action(async () => {
63
- await handleSourceScan();
64
- });
65
-
66
- // Config subcommands (full command version)
67
- const configCmd = program
68
- .command('config')
69
- .description('Config management');
70
-
71
- configCmd
72
- .command('set <key> <value>')
73
- .description('Set a config value')
74
- .action(async (key: string, value: string) => {
75
- await handleConfigSet(key, value);
76
- });
77
-
78
- configCmd
79
- .command('get <key>')
80
- .description('Get a config value')
81
- .action(async (key: string) => {
82
- await handleConfigGet(key);
83
- });
84
-
85
- configCmd
86
- .command('list')
87
- .description('Show full configuration')
88
- .action(async () => {
89
- await handleConfigList();
90
- });
91
-
92
- // Project commands
93
- program
94
- .command('use [sources...]')
95
- .description('Use sources in current project')
96
- .option('-p, --projects <tools...>', 'Tools to link (iflow, claude, codebuddy, opencode)')
97
- .action(async (sources: string[], options) => {
98
- await handleUse(sources, options);
99
- });
100
-
101
- program
102
- .command('list')
103
- .description('List sources in use')
104
- .action(async () => {
105
- await handleList();
106
- });
107
-
108
- program
109
- .command('rm <source>')
110
- .description('Remove a source from project')
111
- .action(async (source: string) => {
112
- await handleRemove(source);
113
- });
114
-
115
- program
116
- .command('status')
117
- .description('Show project status')
118
- .action(async () => {
119
- await handleStatus();
120
- });
121
-
122
- program
123
- .command('update [sources...]')
124
- .description('Update source(s) in current project')
125
- .action(async (sources: string[]) => {
126
- await handleProjectUpdate(sources);
127
- });
128
-
129
- // Help command
130
- program
131
- .command('help')
132
- .description('Show bilingual help information')
133
- .action(() => {
134
- showHelp();
135
- });
136
-
137
- // Main action handler for -s and -c options
138
- program
139
- .action(async (options) => {
140
- // Handle -s/--source
141
- if (options.source) {
142
- const [cmd, ...args] = options.source;
143
- switch (cmd) {
144
- case 'add':
145
- if (args.length < 2) {
146
- console.log('Usage: tools-cc -s add <name> <path-or-url>');
147
- return;
148
- }
149
- await handleSourceAdd(args[0], args[1]);
150
- break;
151
- case 'list':
152
- case 'ls':
153
- await handleSourceList();
154
- break;
155
- case 'remove':
156
- case 'rm':
157
- if (args.length < 1) {
158
- console.log('Usage: tools-cc -s remove <name>');
159
- return;
160
- }
161
- await handleSourceRemove(args[0]);
162
- break;
163
- case 'update':
164
- case 'up':
165
- case 'upgrade':
166
- await handleSourceUpdate(args[0]);
167
- break;
168
- case 'scan':
169
- await handleSourceScan();
170
- break;
171
- default:
172
- console.log(`Unknown source command: ${cmd}`);
173
- }
174
- return;
175
- }
176
-
177
- // Handle -c/--config
178
- if (options.config) {
179
- const [cmd, ...args] = options.config;
180
- switch (cmd) {
181
- case 'set':
182
- if (args.length < 2) {
183
- console.log('Usage: tools-cc -c set <key> <value>');
184
- return;
185
- }
186
- await handleConfigSet(args[0], args[1]);
187
- break;
188
- case 'get':
189
- if (args.length < 1) {
190
- console.log('Usage: tools-cc -c get <key>');
191
- return;
192
- }
193
- await handleConfigGet(args[0]);
194
- break;
195
- default:
196
- console.log(`Unknown config command: ${cmd}`);
197
- }
198
- return;
199
- }
200
-
201
- // No options provided, show help
202
- program.outputHelp();
203
- });
204
-
205
- program.parseAsync();
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { handleConfigSet, handleConfigGet, handleConfigList } from './commands/config';
5
+ import { handleSourceAdd, handleSourceList, handleSourceRemove, handleSourceUpdate, handleSourceScan } from './commands/source';
6
+ import { handleUse, handleList, handleRemove, handleStatus, handleProjectUpdate } from './commands/use';
7
+ import { handleExport } from './commands/export';
8
+ import { showHelp } from './commands/help';
9
+ import { GLOBAL_CONFIG_DIR } from './utils/path';
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name('tools-cc')
15
+ .description('CLI tool for managing skills/commands/agents across multiple AI coding tools')
16
+ .version('0.0.1');
17
+
18
+ // Source management (shortcut options)
19
+ program
20
+ .option('-s, --source <command> [args...]', 'Source management (shortcut)')
21
+ .option('-c, --config <command> [args...]', 'Config management (shortcut)');
22
+
23
+ // Source subcommands (full command version)
24
+ const sourceCmd = program
25
+ .command('sources')
26
+ .description('Source management');
27
+
28
+ sourceCmd
29
+ .command('add <name> <path-or-url>')
30
+ .description('Add a source')
31
+ .action(async (name: string, pathOrUrl: string) => {
32
+ await handleSourceAdd(name, pathOrUrl);
33
+ });
34
+
35
+ sourceCmd
36
+ .command('list')
37
+ .alias('ls')
38
+ .description('List all sources')
39
+ .action(async () => {
40
+ await handleSourceList();
41
+ });
42
+
43
+ sourceCmd
44
+ .command('remove <name>')
45
+ .alias('rm')
46
+ .description('Remove a source')
47
+ .action(async (name: string) => {
48
+ await handleSourceRemove(name);
49
+ });
50
+
51
+ sourceCmd
52
+ .command('update [name]')
53
+ .alias('up')
54
+ .alias('upgrade')
55
+ .description('Update source(s) with git pull')
56
+ .action(async (name?: string) => {
57
+ await handleSourceUpdate(name);
58
+ });
59
+
60
+ sourceCmd
61
+ .command('scan')
62
+ .description('Scan sources directory and update configuration')
63
+ .action(async () => {
64
+ await handleSourceScan();
65
+ });
66
+
67
+ // Config subcommands (full command version)
68
+ const configCmd = program
69
+ .command('config')
70
+ .description('Config management');
71
+
72
+ configCmd
73
+ .command('set <key> <value>')
74
+ .description('Set a config value')
75
+ .action(async (key: string, value: string) => {
76
+ await handleConfigSet(key, value);
77
+ });
78
+
79
+ configCmd
80
+ .command('get <key>')
81
+ .description('Get a config value')
82
+ .action(async (key: string) => {
83
+ await handleConfigGet(key);
84
+ });
85
+
86
+ configCmd
87
+ .command('list')
88
+ .description('Show full configuration')
89
+ .action(async () => {
90
+ await handleConfigList();
91
+ });
92
+
93
+ // Project commands
94
+ program
95
+ .command('use [sources...]')
96
+ .description('Use sources in current project')
97
+ .option('-p, --projects <tools...>', 'Tools to link (iflow, claude, codebuddy, opencode)')
98
+ .option('-ls', 'Interactive selection mode for single source')
99
+ .option('-c, --config <file>', 'Import from config file')
100
+ .action(async (sources: string[], options) => {
101
+ await handleUse(sources, options);
102
+ });
103
+
104
+ program
105
+ .command('list')
106
+ .description('List sources in use')
107
+ .action(async () => {
108
+ await handleList();
109
+ });
110
+
111
+ program
112
+ .command('rm <source>')
113
+ .description('Remove a source from project')
114
+ .action(async (source: string) => {
115
+ await handleRemove(source);
116
+ });
117
+
118
+ program
119
+ .command('status')
120
+ .description('Show project status')
121
+ .action(async () => {
122
+ await handleStatus();
123
+ });
124
+
125
+ program
126
+ .command('update [sources...]')
127
+ .description('Update source(s) in current project')
128
+ .action(async (sources: string[]) => {
129
+ await handleProjectUpdate(sources);
130
+ });
131
+
132
+ program
133
+ .command('export')
134
+ .description('Export project or global config')
135
+ .option('-o, --output <file>', 'Output file path')
136
+ .option('--global', 'Export global config')
137
+ .action(async (options) => {
138
+ await handleExport(options);
139
+ });
140
+
141
+ // Help command
142
+ program
143
+ .command('help')
144
+ .description('Show bilingual help information')
145
+ .action(() => {
146
+ showHelp();
147
+ });
148
+
149
+ // Main action handler for -s and -c options
150
+ program
151
+ .action(async (options) => {
152
+ // Handle -s/--source
153
+ if (options.source) {
154
+ const [cmd, ...args] = options.source;
155
+ switch (cmd) {
156
+ case 'add':
157
+ if (args.length < 2) {
158
+ console.log('Usage: tools-cc -s add <name> <path-or-url>');
159
+ return;
160
+ }
161
+ await handleSourceAdd(args[0], args[1]);
162
+ break;
163
+ case 'list':
164
+ case 'ls':
165
+ await handleSourceList();
166
+ break;
167
+ case 'remove':
168
+ case 'rm':
169
+ if (args.length < 1) {
170
+ console.log('Usage: tools-cc -s remove <name>');
171
+ return;
172
+ }
173
+ await handleSourceRemove(args[0]);
174
+ break;
175
+ case 'update':
176
+ case 'up':
177
+ case 'upgrade':
178
+ await handleSourceUpdate(args[0]);
179
+ break;
180
+ case 'scan':
181
+ await handleSourceScan();
182
+ break;
183
+ default:
184
+ console.log(`Unknown source command: ${cmd}`);
185
+ }
186
+ return;
187
+ }
188
+
189
+ // Handle -c/--config
190
+ if (options.config) {
191
+ const [cmd, ...args] = options.config;
192
+ switch (cmd) {
193
+ case 'set':
194
+ if (args.length < 2) {
195
+ console.log('Usage: tools-cc -c set <key> <value>');
196
+ return;
197
+ }
198
+ await handleConfigSet(args[0], args[1]);
199
+ break;
200
+ case 'get':
201
+ if (args.length < 1) {
202
+ console.log('Usage: tools-cc -c get <key>');
203
+ return;
204
+ }
205
+ await handleConfigGet(args[0]);
206
+ break;
207
+ default:
208
+ console.log(`Unknown config command: ${cmd}`);
209
+ }
210
+ return;
211
+ }
212
+
213
+ // No options provided, show help
214
+ program.outputHelp();
215
+ });
216
+
217
+ program.parseAsync();