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/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();
@@ -9,11 +9,66 @@ export interface GlobalConfig {
9
9
  sources: Record<string, SourceConfig>;
10
10
  }
11
11
 
12
+ /**
13
+ * 源选择配置 - 指定从源中导入哪些 skills/commands/agents
14
+ */
15
+ export interface SourceSelection {
16
+ skills: string[];
17
+ commands: string[];
18
+ agents: string[];
19
+ }
20
+
21
+ /**
22
+ * 新版项目配置 - sources 使用 Record 格式支持部分导入
23
+ */
12
24
  export interface ProjectConfig {
25
+ sources: Record<string, SourceSelection>;
26
+ links: string[];
27
+ }
28
+
29
+ /**
30
+ * 旧版项目配置 - sources 为字符串数组(向后兼容)
31
+ */
32
+ export interface LegacyProjectConfig {
13
33
  sources: string[];
14
34
  links: string[];
15
35
  }
16
36
 
37
+ /**
38
+ * 判断值是否为有效的 SourceSelection 对象
39
+ */
40
+ export function isSourceSelection(value: unknown): value is SourceSelection {
41
+ if (typeof value !== 'object' || value === null) return false;
42
+ const obj = value as Record<string, unknown>;
43
+ return (
44
+ Array.isArray(obj.skills) &&
45
+ Array.isArray(obj.commands) &&
46
+ Array.isArray(obj.agents)
47
+ );
48
+ }
49
+
50
+ /**
51
+ * 将旧版项目配置转换为新版格式
52
+ * 如果 sources 是字符串数组,转换为 Record 格式,每个源默认导入全部内容
53
+ */
54
+ export function normalizeProjectConfig(
55
+ config: LegacyProjectConfig | ProjectConfig
56
+ ): ProjectConfig {
57
+ // If sources is an array, convert to new format
58
+ if (Array.isArray(config.sources)) {
59
+ const newSources: Record<string, SourceSelection> = {};
60
+ for (const sourceName of config.sources) {
61
+ newSources[sourceName] = {
62
+ skills: ['*'],
63
+ commands: ['*'],
64
+ agents: ['*']
65
+ };
66
+ }
67
+ return { sources: newSources, links: config.links };
68
+ }
69
+ return config as ProjectConfig;
70
+ }
71
+
17
72
  export interface Manifest {
18
73
  name: string;
19
74
  version: string;
@@ -25,3 +80,23 @@ export interface Manifest {
25
80
  export interface ToolConfig {
26
81
  linkName: string;
27
82
  }
83
+
84
+ /**
85
+ * 项目配置导出格式
86
+ */
87
+ export interface ExportConfig {
88
+ version: string;
89
+ type: 'project';
90
+ config: ProjectConfig;
91
+ exportedAt: string;
92
+ }
93
+
94
+ /**
95
+ * 全局配置导出格式
96
+ */
97
+ export interface GlobalExportConfig {
98
+ version: string;
99
+ type: 'global';
100
+ config: GlobalConfig;
101
+ exportedAt: string;
102
+ }
@@ -0,0 +1,108 @@
1
+ import { SourceSelection } from '../types/config';
2
+
3
+ /**
4
+ * 解析后的源路径
5
+ */
6
+ export interface ParsedSourcePath {
7
+ sourceName: string;
8
+ type?: 'skills' | 'commands' | 'agents';
9
+ itemName?: string;
10
+ }
11
+
12
+ /**
13
+ * 解析源路径字符串
14
+ *
15
+ * 支持的格式:
16
+ * - 'my-skills' → { sourceName: 'my-skills' } (整个源)
17
+ * - 'my-skills/skills/a-skill' → { sourceName: 'my-skills', type: 'skills', itemName: 'a-skill' }
18
+ * - 'my-skills/commands/test' → { sourceName: 'my-skills', type: 'commands', itemName: 'test' }
19
+ * - 'other/agents/reviewer' → { sourceName: 'other', type: 'agents', itemName: 'reviewer' }
20
+ *
21
+ * 无效路径只返回 sourceName
22
+ */
23
+ export function parseSourcePath(input: string): ParsedSourcePath {
24
+ const parts = input.split('/');
25
+
26
+ // 至少需要源名称
27
+ if (parts.length === 0 || input === '') {
28
+ return { sourceName: '' };
29
+ }
30
+
31
+ const sourceName = parts[0];
32
+
33
+ // 只有源名称,返回整个源
34
+ if (parts.length === 1) {
35
+ return { sourceName };
36
+ }
37
+
38
+ // 检查第二部分是否为有效类型
39
+ const validTypes = ['skills', 'commands', 'agents'] as const;
40
+ const type = parts[1] as typeof validTypes[number];
41
+
42
+ if (!validTypes.includes(type)) {
43
+ return { sourceName };
44
+ }
45
+
46
+ // 检查第三部分(项目名称)
47
+ if (parts.length < 3 || !parts[2]) {
48
+ return { sourceName };
49
+ }
50
+
51
+ return {
52
+ sourceName,
53
+ type,
54
+ itemName: parts[2]
55
+ };
56
+ }
57
+
58
+ /**
59
+ * 从路径数组构建选择配置
60
+ *
61
+ * 将多个路径转换为 Record<string, SourceSelection> 格式
62
+ *
63
+ * 示例:
64
+ * ['my-skills/skills/a', 'my-skills/skills/b', 'my-skills/commands/test']
65
+ * → { 'my-skills': { skills: ['a', 'b'], commands: ['test'], agents: [] } }
66
+ */
67
+ export function buildSelectionFromPaths(paths: string[]): Record<string, SourceSelection> {
68
+ const result: Record<string, SourceSelection> = {};
69
+
70
+ for (const path of paths) {
71
+ const parsed = parseSourcePath(path);
72
+ const { sourceName, type, itemName } = parsed;
73
+
74
+ // 跳过空源名称
75
+ if (!sourceName) {
76
+ continue;
77
+ }
78
+
79
+ // 初始化源选择(如果不存在)
80
+ if (!result[sourceName]) {
81
+ result[sourceName] = {
82
+ skills: [],
83
+ commands: [],
84
+ agents: []
85
+ };
86
+ }
87
+
88
+ // 如果没有指定类型和项目名称,表示整个源
89
+ if (!type || !itemName) {
90
+ result[sourceName] = {
91
+ skills: ['*'],
92
+ commands: ['*'],
93
+ agents: ['*']
94
+ };
95
+ continue;
96
+ }
97
+
98
+ // 添加项目到对应的类型数组(去重)
99
+ const selection = result[sourceName];
100
+ const targetArray = selection[type];
101
+
102
+ if (!targetArray.includes(itemName)) {
103
+ targetArray.push(itemName);
104
+ }
105
+ }
106
+
107
+ return result;
108
+ }