tools-cc 1.0.4 → 1.0.6

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,216 @@
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';
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
6
  import { handleUse, handleList, handleRemove, handleStatus, handleProjectUpdate } from './commands/use';
7
+ import { handleExport } from './commands/export';
7
8
  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
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
94
  .command('use [sources...]')
95
95
  .description('Use sources in current project')
96
- .option('-p, --projects <tools...>', 'Tools to link (iflow, claude, codebuddy, opencode)')
96
+ .option('-p, --projects <tools...>', 'Tools to link (iflow, claude, codebuddy, opencode, codex)')
97
+ .option('-l, --ls', 'Interactive selection mode for single source')
98
+ .option('-c, --config <file>', 'Import from config file')
97
99
  .action(async (sources: string[], options) => {
98
100
  await handleUse(sources, options);
99
101
  });
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();
102
+
103
+ program
104
+ .command('list')
105
+ .description('List sources in use')
106
+ .action(async () => {
107
+ await handleList();
108
+ });
109
+
110
+ program
111
+ .command('rm <source>')
112
+ .description('Remove a source from project')
113
+ .action(async (source: string) => {
114
+ await handleRemove(source);
115
+ });
116
+
117
+ program
118
+ .command('status')
119
+ .description('Show project status')
120
+ .action(async () => {
121
+ await handleStatus();
122
+ });
123
+
124
+ program
125
+ .command('update [sources...]')
126
+ .description('Update source(s) in current project')
127
+ .action(async (sources: string[]) => {
128
+ await handleProjectUpdate(sources);
129
+ });
130
+
131
+ program
132
+ .command('export')
133
+ .description('Export project or global config')
134
+ .option('-o, --output <file>', 'Output file path')
135
+ .option('--global', 'Export global config')
136
+ .action(async (options) => {
137
+ await handleExport(options);
138
+ });
139
+
140
+ // Help command
141
+ program
142
+ .command('help')
143
+ .description('Show bilingual help information')
144
+ .action(() => {
145
+ showHelp();
146
+ });
147
+
148
+ // Main action handler for -s and -c options
149
+ program
150
+ .action(async (options) => {
151
+ // Handle -s/--source
152
+ if (options.source) {
153
+ const [cmd, ...args] = options.source;
154
+ switch (cmd) {
155
+ case 'add':
156
+ if (args.length < 2) {
157
+ console.log('Usage: tools-cc -s add <name> <path-or-url>');
158
+ return;
159
+ }
160
+ await handleSourceAdd(args[0], args[1]);
161
+ break;
162
+ case 'list':
163
+ case 'ls':
164
+ await handleSourceList();
165
+ break;
166
+ case 'remove':
167
+ case 'rm':
168
+ if (args.length < 1) {
169
+ console.log('Usage: tools-cc -s remove <name>');
170
+ return;
171
+ }
172
+ await handleSourceRemove(args[0]);
173
+ break;
174
+ case 'update':
175
+ case 'up':
176
+ case 'upgrade':
177
+ await handleSourceUpdate(args[0]);
178
+ break;
179
+ case 'scan':
180
+ await handleSourceScan();
181
+ break;
182
+ default:
183
+ console.log(`Unknown source command: ${cmd}`);
184
+ }
185
+ return;
186
+ }
187
+
188
+ // Handle -c/--config
189
+ if (options.config) {
190
+ const [cmd, ...args] = options.config;
191
+ switch (cmd) {
192
+ case 'set':
193
+ if (args.length < 2) {
194
+ console.log('Usage: tools-cc -c set <key> <value>');
195
+ return;
196
+ }
197
+ await handleConfigSet(args[0], args[1]);
198
+ break;
199
+ case 'get':
200
+ if (args.length < 1) {
201
+ console.log('Usage: tools-cc -c get <key>');
202
+ return;
203
+ }
204
+ await handleConfigGet(args[0]);
205
+ break;
206
+ default:
207
+ console.log(`Unknown config command: ${cmd}`);
208
+ }
209
+ return;
210
+ }
211
+
212
+ // No options provided, show help
213
+ program.outputHelp();
214
+ });
215
+
216
+ 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
+ }