tools-cc 1.0.5 → 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/CHANGELOG.md +20 -0
- package/CHANGELOG_en.md +18 -0
- package/README.md +11 -9
- package/dist/commands/help.js +3 -2
- package/dist/commands/use.d.ts +3 -3
- package/dist/commands/use.js +33 -40
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/src/commands/help.ts +3 -2
- package/src/commands/use.ts +405 -413
- package/src/index.ts +11 -12
package/src/commands/use.ts
CHANGED
|
@@ -1,414 +1,406 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
|
-
import { useSource, unuseSource, listUsedSources, initProject, importProjectConfig } from '../core/project';
|
|
4
|
-
import { getSourcePath, listSources } from '../core/source';
|
|
5
|
-
import { scanSource } from '../core/manifest';
|
|
6
|
-
import { createSymlink, isSymlink } from '../core/symlink';
|
|
7
|
-
import { GLOBAL_CONFIG_DIR, getToolsccDir, getProjectConfigPath } from '../utils/path';
|
|
8
|
-
import { parseSourcePath, buildSelectionFromPaths } from '../utils/parsePath';
|
|
9
|
-
import { SourceSelection } from '../types/config';
|
|
10
|
-
import fs from 'fs-extra';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
|
|
13
|
-
const SUPPORTED_TOOLS: Record<string, string> = {
|
|
14
|
-
iflow: '.iflow',
|
|
15
|
-
claude: '.claude',
|
|
16
|
-
codebuddy: '.codebuddy',
|
|
17
|
-
opencode: '.opencode'
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
configFile
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
console.log(chalk.yellow('
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
//
|
|
182
|
-
if (manifest.
|
|
183
|
-
choices.push(new inquirer.Separator(`---
|
|
184
|
-
for (const
|
|
185
|
-
choices.push({ name:
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
//
|
|
190
|
-
if (manifest.
|
|
191
|
-
choices.push(new inquirer.Separator(`---
|
|
192
|
-
for (const
|
|
193
|
-
choices.push({ name:
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
console.log(chalk.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
console.log(chalk.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
await useSource(sourceName, sourcePath, projectDir, selection);
|
|
407
|
-
console.log(chalk.green(`✓ Updated source: ${sourceName}`));
|
|
408
|
-
} catch (error) {
|
|
409
|
-
console.log(chalk.red(`✗ Failed to update ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
console.log(chalk.green(`\n✓ Project update complete`));
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { useSource, unuseSource, listUsedSources, initProject, importProjectConfig } from '../core/project';
|
|
4
|
+
import { getSourcePath, listSources } from '../core/source';
|
|
5
|
+
import { scanSource } from '../core/manifest';
|
|
6
|
+
import { createSymlink, isSymlink } from '../core/symlink';
|
|
7
|
+
import { GLOBAL_CONFIG_DIR, getToolsccDir, getProjectConfigPath } from '../utils/path';
|
|
8
|
+
import { parseSourcePath, buildSelectionFromPaths } from '../utils/parsePath';
|
|
9
|
+
import type { SourceSelection } from '../types/config';
|
|
10
|
+
import fs from 'fs-extra';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
|
|
13
|
+
const SUPPORTED_TOOLS: Record<string, string> = {
|
|
14
|
+
iflow: '.iflow',
|
|
15
|
+
claude: '.claude',
|
|
16
|
+
codebuddy: '.codebuddy',
|
|
17
|
+
opencode: '.opencode',
|
|
18
|
+
codex: '.codex'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* use 命令选项
|
|
23
|
+
*/
|
|
24
|
+
export interface UseOptions {
|
|
25
|
+
projects?: string[];
|
|
26
|
+
ls?: boolean;
|
|
27
|
+
config?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 处理 use 命令
|
|
32
|
+
*
|
|
33
|
+
* 支持多种模式:
|
|
34
|
+
* 1. 配置导入模式: tools-cc use -c config.json
|
|
35
|
+
* 2. 交互选择模式: tools-cc use my-source --ls
|
|
36
|
+
* 3. 路径语法模式: tools-cc use my-source/skills/a-skill
|
|
37
|
+
* 4. 整体导入模式: tools-cc use my-source
|
|
38
|
+
* 5. 点模式: tools-cc use . (使用已配置源)
|
|
39
|
+
*/
|
|
40
|
+
export async function handleUse(
|
|
41
|
+
sourceSpecs: string[],
|
|
42
|
+
options: UseOptions
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
const projectDir = process.cwd();
|
|
45
|
+
const toolsccDir = getToolsccDir(projectDir);
|
|
46
|
+
const configFile = getProjectConfigPath(projectDir);
|
|
47
|
+
|
|
48
|
+
// 1. 配置导入模式
|
|
49
|
+
if (options.config) {
|
|
50
|
+
await handleConfigImportMode(options.config, projectDir, options.projects);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 2. 点模式:使用当前项目已配置的源
|
|
55
|
+
if (sourceSpecs.length === 1 && sourceSpecs[0] === '.') {
|
|
56
|
+
await handleDotMode(projectDir, toolsccDir, configFile, options.projects);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 3. 交互选择模式:单个源 + --ls 选项
|
|
61
|
+
if (options.ls && sourceSpecs.length === 1) {
|
|
62
|
+
const parsed = parseSourcePath(sourceSpecs[0]);
|
|
63
|
+
// 只有源名称时才进入交互模式
|
|
64
|
+
if (!parsed.type && parsed.sourceName) {
|
|
65
|
+
await handleInteractiveMode(parsed.sourceName, projectDir, options.projects);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 4. 无参数时显示源选择列表
|
|
71
|
+
if (sourceSpecs.length === 0) {
|
|
72
|
+
sourceSpecs = await selectSourcesInteractively();
|
|
73
|
+
if (sourceSpecs.length === 0) {
|
|
74
|
+
console.log(chalk.gray('No sources selected.'));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 5. 解析路径语法并构建选择配置
|
|
80
|
+
const selectionMap = buildSelectionFromPaths(sourceSpecs);
|
|
81
|
+
|
|
82
|
+
// 初始化项目
|
|
83
|
+
await initProject(projectDir);
|
|
84
|
+
|
|
85
|
+
// 应用每个源的选择配置
|
|
86
|
+
for (const [sourceName, selection] of Object.entries(selectionMap)) {
|
|
87
|
+
try {
|
|
88
|
+
const sourcePath = await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
|
|
89
|
+
await useSource(sourceName, sourcePath, projectDir, selection);
|
|
90
|
+
console.log(chalk.green(`✓ Using source: ${sourceName}`));
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.log(chalk.red(`✗ Failed to use ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 创建符号链接
|
|
97
|
+
await createToolLinks(projectDir, toolsccDir, configFile, options.projects);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 配置导入模式
|
|
102
|
+
*/
|
|
103
|
+
async function handleConfigImportMode(
|
|
104
|
+
configPath: string,
|
|
105
|
+
projectDir: string,
|
|
106
|
+
projects?: string[]
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
try {
|
|
109
|
+
const toolsccDir = getToolsccDir(projectDir);
|
|
110
|
+
const configFile = getProjectConfigPath(projectDir);
|
|
111
|
+
|
|
112
|
+
// 解析配置文件路径
|
|
113
|
+
const resolvedPath = path.resolve(configPath);
|
|
114
|
+
|
|
115
|
+
// 定义源路径解析函数
|
|
116
|
+
const resolveSourcePath = async (sourceName: string): Promise<string> => {
|
|
117
|
+
return await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// 导入配置
|
|
121
|
+
await importProjectConfig(resolvedPath, projectDir, resolveSourcePath);
|
|
122
|
+
console.log(chalk.green(`✓ Imported config from: ${resolvedPath}`));
|
|
123
|
+
|
|
124
|
+
// 创建符号链接
|
|
125
|
+
await createToolLinks(projectDir, toolsccDir, configFile, projects);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.log(chalk.red(`✗ Failed to import config: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 点模式:使用已配置源创建符号链接
|
|
133
|
+
*/
|
|
134
|
+
async function handleDotMode(
|
|
135
|
+
projectDir: string,
|
|
136
|
+
toolsccDir: string,
|
|
137
|
+
configFile: string,
|
|
138
|
+
projects?: string[]
|
|
139
|
+
): Promise<void> {
|
|
140
|
+
if (!(await fs.pathExists(configFile))) {
|
|
141
|
+
console.log(chalk.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const config = await fs.readJson(configFile);
|
|
146
|
+
const configuredSources = Object.keys(config.sources || {});
|
|
147
|
+
|
|
148
|
+
if (configuredSources.length === 0) {
|
|
149
|
+
console.log(chalk.yellow('No sources configured in this project. Run `tools-cc use <source>` to add one.'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log(chalk.cyan(`Using existing sources: ${configuredSources.join(', ')}`));
|
|
154
|
+
await createToolLinks(projectDir, toolsccDir, configFile, projects);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 交互选择模式:显示技能/命令/代理选择列表
|
|
159
|
+
*/
|
|
160
|
+
async function handleInteractiveMode(
|
|
161
|
+
sourceName: string,
|
|
162
|
+
projectDir: string,
|
|
163
|
+
projects?: string[]
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
try {
|
|
166
|
+
const sourcePath = await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
|
|
167
|
+
const manifest = await scanSource(sourcePath);
|
|
168
|
+
|
|
169
|
+
// 构建选项列表
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
|
+
const choices: any[] = [];
|
|
172
|
+
|
|
173
|
+
// Skills 区
|
|
174
|
+
if (manifest.skills && manifest.skills.length > 0) {
|
|
175
|
+
choices.push(new inquirer.Separator(`--- Skills (${manifest.skills.length}) ---`));
|
|
176
|
+
for (const skill of manifest.skills) {
|
|
177
|
+
choices.push({ name: skill, value: `skills/${skill}` });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Commands 区
|
|
182
|
+
if (manifest.commands && manifest.commands.length > 0) {
|
|
183
|
+
choices.push(new inquirer.Separator(`--- Commands (${manifest.commands.length}) ---`));
|
|
184
|
+
for (const cmd of manifest.commands) {
|
|
185
|
+
choices.push({ name: cmd, value: `commands/${cmd}` });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Agents 区
|
|
190
|
+
if (manifest.agents && manifest.agents.length > 0) {
|
|
191
|
+
choices.push(new inquirer.Separator(`--- Agents (${manifest.agents.length}) ---`));
|
|
192
|
+
for (const agent of manifest.agents) {
|
|
193
|
+
choices.push({ name: agent, value: `agents/${agent}` });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (choices.length === 0) {
|
|
198
|
+
console.log(chalk.yellow(`No items found in source: ${sourceName}`));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 显示选择列表
|
|
203
|
+
const answers = await inquirer.prompt([
|
|
204
|
+
{
|
|
205
|
+
type: 'checkbox',
|
|
206
|
+
name: 'selectedItems',
|
|
207
|
+
message: `Select items from ${sourceName}:`,
|
|
208
|
+
choices,
|
|
209
|
+
pageSize: 15
|
|
210
|
+
}
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
if (answers.selectedItems.length === 0) {
|
|
214
|
+
console.log(chalk.gray('No items selected.'));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 将选择转换为 SourceSelection
|
|
219
|
+
const selection: SourceSelection = {
|
|
220
|
+
skills: [],
|
|
221
|
+
commands: [],
|
|
222
|
+
agents: []
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
for (const item of answers.selectedItems) {
|
|
226
|
+
const [type, name] = item.split('/');
|
|
227
|
+
if (type === 'skills') selection.skills.push(name);
|
|
228
|
+
else if (type === 'commands') selection.commands.push(name);
|
|
229
|
+
else if (type === 'agents') selection.agents.push(name);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 初始化项目并应用选择
|
|
233
|
+
await initProject(projectDir);
|
|
234
|
+
await useSource(sourceName, sourcePath, projectDir, selection);
|
|
235
|
+
console.log(chalk.green(`✓ Using source: ${sourceName}`));
|
|
236
|
+
|
|
237
|
+
// 创建符号链接
|
|
238
|
+
const toolsccDir = getToolsccDir(projectDir);
|
|
239
|
+
const configFile = getProjectConfigPath(projectDir);
|
|
240
|
+
await createToolLinks(projectDir, toolsccDir, configFile, projects);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.log(chalk.red(`✗ Failed to use ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 显示源选择列表并返回用户选择
|
|
248
|
+
*/
|
|
249
|
+
async function selectSourcesInteractively(): Promise<string[]> {
|
|
250
|
+
const sources = await listSources(GLOBAL_CONFIG_DIR);
|
|
251
|
+
const sourceList = Object.keys(sources);
|
|
252
|
+
|
|
253
|
+
if (sourceList.length === 0) {
|
|
254
|
+
console.log(chalk.yellow('No sources configured. Use `tools-cc -s add` to add one.'));
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const answers = await inquirer.prompt([
|
|
259
|
+
{
|
|
260
|
+
type: 'checkbox',
|
|
261
|
+
name: 'selectedSources',
|
|
262
|
+
message: 'Select sources to use:',
|
|
263
|
+
choices: sourceList
|
|
264
|
+
}
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
return answers.selectedSources;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 创建工具符号链接
|
|
272
|
+
*/
|
|
273
|
+
async function createToolLinks(
|
|
274
|
+
projectDir: string,
|
|
275
|
+
toolsccDir: string,
|
|
276
|
+
configFile: string,
|
|
277
|
+
projects?: string[]
|
|
278
|
+
): Promise<void> {
|
|
279
|
+
const tools = projects || Object.keys(SUPPORTED_TOOLS);
|
|
280
|
+
|
|
281
|
+
for (const tool of tools) {
|
|
282
|
+
const linkName = SUPPORTED_TOOLS[tool];
|
|
283
|
+
if (!linkName) {
|
|
284
|
+
console.log(chalk.yellow(`Unknown tool: ${tool}`));
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const linkPath = path.join(projectDir, linkName);
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
await createSymlink(toolsccDir, linkPath, true);
|
|
292
|
+
console.log(chalk.green(`✓ Linked: ${linkName} -> .toolscc`));
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.log(chalk.red(`✗ Failed to link ${linkName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// 更新项目配置
|
|
299
|
+
const config = await fs.readJson(configFile);
|
|
300
|
+
const existingLinks = config.links || [];
|
|
301
|
+
config.links = [...new Set([...existingLinks, ...tools])];
|
|
302
|
+
await fs.writeJson(configFile, config, { spaces: 2 });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export async function handleList(): Promise<void> {
|
|
306
|
+
const projectDir = process.cwd();
|
|
307
|
+
const sources = await listUsedSources(projectDir);
|
|
308
|
+
|
|
309
|
+
if (sources.length === 0) {
|
|
310
|
+
console.log(chalk.gray('No sources in use. Run `tools-cc use <source-name>` to add one.'));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log(chalk.bold('Sources in use:'));
|
|
315
|
+
for (const source of sources) {
|
|
316
|
+
console.log(` ${chalk.cyan(source)}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function handleRemove(sourceName: string): Promise<void> {
|
|
321
|
+
const projectDir = process.cwd();
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
await unuseSource(sourceName, projectDir);
|
|
325
|
+
console.log(chalk.green(`✓ Removed source: ${sourceName}`));
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export async function handleStatus(): Promise<void> {
|
|
332
|
+
const projectDir = process.cwd();
|
|
333
|
+
const sources = await listUsedSources(projectDir);
|
|
334
|
+
|
|
335
|
+
console.log(chalk.bold('\nProject Status:'));
|
|
336
|
+
console.log(chalk.gray(` Directory: ${projectDir}`));
|
|
337
|
+
|
|
338
|
+
// 检查 .toolscc
|
|
339
|
+
const toolsccDir = getToolsccDir(projectDir);
|
|
340
|
+
console.log(` .toolscc: ${await fs.pathExists(toolsccDir) ? chalk.green('exists') : chalk.red('not found')}`);
|
|
341
|
+
|
|
342
|
+
// 检查 sources
|
|
343
|
+
console.log(` Sources: ${sources.length > 0 ? sources.map(s => chalk.cyan(s)).join(', ') : chalk.gray('none')}`);
|
|
344
|
+
|
|
345
|
+
// 检查 links
|
|
346
|
+
const configFile = getProjectConfigPath(projectDir);
|
|
347
|
+
if (await fs.pathExists(configFile)) {
|
|
348
|
+
const config = await fs.readJson(configFile);
|
|
349
|
+
console.log(` Links:`);
|
|
350
|
+
for (const tool of config.links || []) {
|
|
351
|
+
const linkName = SUPPORTED_TOOLS[tool];
|
|
352
|
+
if (!linkName) continue;
|
|
353
|
+
const linkPath = path.join(projectDir, linkName);
|
|
354
|
+
const isLink = await isSymlink(linkPath);
|
|
355
|
+
console.log(` ${tool}: ${isLink ? chalk.green('linked') : chalk.red('not linked')}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
console.log();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export async function handleProjectUpdate(sourceNames?: string[]): Promise<void> {
|
|
362
|
+
const projectDir = process.cwd();
|
|
363
|
+
const configFile = getProjectConfigPath(projectDir);
|
|
364
|
+
|
|
365
|
+
// 检查项目是否已初始化
|
|
366
|
+
if (!(await fs.pathExists(configFile))) {
|
|
367
|
+
console.log(chalk.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const config = await fs.readJson(configFile);
|
|
372
|
+
const configuredSources = Object.keys(config.sources || {});
|
|
373
|
+
|
|
374
|
+
let sourcesToUpdate = sourceNames && sourceNames.length > 0
|
|
375
|
+
? sourceNames
|
|
376
|
+
: configuredSources;
|
|
377
|
+
|
|
378
|
+
if (sourcesToUpdate.length === 0) {
|
|
379
|
+
console.log(chalk.gray('No sources to update.'));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 验证指定的源是否存在于项目配置中
|
|
384
|
+
if (sourceNames && sourceNames.length > 0) {
|
|
385
|
+
const invalidSources = sourceNames.filter((s: string) => !configuredSources.includes(s));
|
|
386
|
+
if (invalidSources.length > 0) {
|
|
387
|
+
console.log(chalk.yellow(`Sources not in project: ${invalidSources.join(', ')}`));
|
|
388
|
+
}
|
|
389
|
+
sourcesToUpdate = sourcesToUpdate.filter((s: string) => configuredSources.includes(s));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// 更新每个配置源
|
|
393
|
+
for (const sourceName of sourcesToUpdate) {
|
|
394
|
+
try {
|
|
395
|
+
const sourcePath = await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
|
|
396
|
+
// 使用保存的选择配置进行更新
|
|
397
|
+
const selection = config.sources[sourceName];
|
|
398
|
+
await useSource(sourceName, sourcePath, projectDir, selection);
|
|
399
|
+
console.log(chalk.green(`✓ Updated source: ${sourceName}`));
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.log(chalk.red(`✗ Failed to update ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
console.log(chalk.green(`\n✓ Project update complete`));
|
|
414
406
|
}
|