tools-cc 1.0.8 → 1.0.10

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,277 +1,305 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { ProjectConfig, LegacyProjectConfig, normalizeProjectConfig, SourceSelection, ExportConfig } from '../types';
4
- import { loadManifest } from './manifest';
5
- import { getToolsccDir, getProjectConfigPath } from '../utils/path';
6
-
7
- /**
8
- * 默认选择配置 - 导入所有内容
9
- */
10
- const DEFAULT_SELECTION: SourceSelection = {
11
- skills: ['*'],
12
- commands: ['*'],
13
- agents: ['*']
14
- };
15
-
16
- export async function initProject(projectDir: string): Promise<void> {
17
- const toolsccDir = getToolsccDir(projectDir);
18
- const configFile = getProjectConfigPath(projectDir);
19
-
20
- // Create .toolscc directory structure
21
- await fs.ensureDir(path.join(toolsccDir, 'skills'));
22
- await fs.ensureDir(path.join(toolsccDir, 'commands'));
23
- await fs.ensureDir(path.join(toolsccDir, 'agents'));
24
-
25
- // Create project config if not exists
26
- if (!(await fs.pathExists(configFile))) {
27
- const config: ProjectConfig = {
28
- sources: {},
29
- links: []
30
- };
31
- await fs.writeJson(configFile, config, { spaces: 2 });
32
- }
33
- }
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
-
62
- export async function useSource(
63
- sourceName: string,
64
- sourceDir: string,
65
- projectDir: string,
66
- selection?: SourceSelection
67
- ): Promise<void> {
68
- // Input validation
69
- if (!sourceName || !sourceName.trim()) {
70
- throw new Error('Source name is required');
71
- }
72
-
73
- // Check source directory existence
74
- if (!(await fs.pathExists(sourceDir))) {
75
- throw new Error(`Source directory does not exist: ${sourceDir}`);
76
- }
77
-
78
- const toolsccDir = getToolsccDir(projectDir);
79
- const manifest = await loadManifest(sourceDir);
80
-
81
- // Ensure project is initialized
82
- await initProject(projectDir);
83
-
84
- // 使用传入的选择配置或默认配置
85
- const effectiveSelection: SourceSelection = selection ?? DEFAULT_SELECTION;
86
-
87
- // Copy/link skills (flattened with prefix)
88
- const sourceSkillsDir = path.join(sourceDir, 'skills');
89
- if (await fs.pathExists(sourceSkillsDir)) {
90
- const skills = await fs.readdir(sourceSkillsDir);
91
- for (const skill of skills) {
92
- // 检查是否应该包含此 skill
93
- if (!shouldInclude(skill, effectiveSelection.skills)) {
94
- continue;
95
- }
96
-
97
- const srcPath = path.join(sourceSkillsDir, skill);
98
- const name = `${sourceName}` == `${skill}` ? skill : `${sourceName}-${skill}`;
99
- const destPath = path.join(toolsccDir, 'skills', name);
100
-
101
- // Remove existing if exists
102
- await fs.remove(destPath);
103
-
104
- // Copy directory
105
- await fs.copy(srcPath, destPath);
106
- }
107
- }
108
-
109
- // Copy commands (in subdirectory by source name)
110
- const sourceCommandsDir = path.join(sourceDir, 'commands');
111
- if (await fs.pathExists(sourceCommandsDir)) {
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
- }
130
- }
131
-
132
- // Copy agents (in subdirectory by source name)
133
- const sourceAgentsDir = path.join(sourceDir, 'agents');
134
- if (await fs.pathExists(sourceAgentsDir)) {
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
- }
153
- }
154
-
155
- // Update project config - 保存实际使用的选择配置
156
- const configFile = getProjectConfigPath(projectDir);
157
- const config = await readProjectConfig(configFile);
158
- config.sources[sourceName] = effectiveSelection;
159
- await fs.writeJson(configFile, config, { spaces: 2 });
160
- }
161
-
162
- export async function unuseSource(sourceName: string, projectDir: string): Promise<void> {
163
- // Input validation
164
- if (!sourceName || !sourceName.trim()) {
165
- throw new Error('Source name is required');
166
- }
167
-
168
- const toolsccDir = getToolsccDir(projectDir);
169
- const configFile = getProjectConfigPath(projectDir);
170
-
171
- // Remove skills with prefix
172
- const skillsDir = path.join(toolsccDir, 'skills');
173
- if (await fs.pathExists(skillsDir)) {
174
- const skills = await fs.readdir(skillsDir);
175
- for (const skill of skills) {
176
- if (skill.startsWith(`${sourceName}-`)) {
177
- await fs.remove(path.join(skillsDir, skill));
178
- }
179
- }
180
- }
181
-
182
- // Remove commands subdirectory
183
- await fs.remove(path.join(toolsccDir, 'commands', sourceName));
184
-
185
- // Remove agents subdirectory
186
- await fs.remove(path.join(toolsccDir, 'agents', sourceName));
187
-
188
- // Update project config with error handling
189
- let config: ProjectConfig;
190
- try {
191
- config = await readProjectConfig(configFile);
192
- } catch (error) {
193
- // If config file doesn't exist or is invalid, nothing to update
194
- return;
195
- }
196
-
197
- delete config.sources[sourceName];
198
- await fs.writeJson(configFile, config, { spaces: 2 });
199
- }
200
-
201
- export async function listUsedSources(projectDir: string): Promise<string[]> {
202
- const configFile = getProjectConfigPath(projectDir);
203
-
204
- if (!(await fs.pathExists(configFile))) {
205
- return [];
206
- }
207
-
208
- const config = await readProjectConfig(configFile);
209
- return getSourceNames(config);
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
- }
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { ProjectConfig, LegacyProjectConfig, normalizeProjectConfig, SourceSelection, ExportConfig } from '../types';
4
+ import { loadManifest } from './manifest';
5
+ import { getToolsccDir, getProjectConfigPath } from '../utils/path';
6
+
7
+ /**
8
+ * 默认选择配置 - 导入所有内容
9
+ */
10
+ const DEFAULT_SELECTION: SourceSelection = {
11
+ skills: ['*'],
12
+ commands: ['*'],
13
+ agents: ['*'],
14
+ rules: ['*']
15
+ };
16
+
17
+ export async function initProject(projectDir: string): Promise<void> {
18
+ const toolsccDir = getToolsccDir(projectDir);
19
+ const configFile = getProjectConfigPath(projectDir);
20
+
21
+ // Create .toolscc directory structure
22
+ await fs.ensureDir(path.join(toolsccDir, 'skills'));
23
+ await fs.ensureDir(path.join(toolsccDir, 'commands'));
24
+ await fs.ensureDir(path.join(toolsccDir, 'agents'));
25
+ await fs.ensureDir(path.join(toolsccDir, 'rules'));
26
+
27
+ // Create project config if not exists
28
+ if (!(await fs.pathExists(configFile))) {
29
+ const config: ProjectConfig = {
30
+ sources: {},
31
+ links: []
32
+ };
33
+ await fs.writeJson(configFile, config, { spaces: 2 });
34
+ }
35
+ }
36
+
37
+ /**
38
+ * 读取项目配置,自动处理新旧格式
39
+ */
40
+ async function readProjectConfig(configFile: string): Promise<ProjectConfig> {
41
+ const rawConfig = await fs.readJson(configFile);
42
+ return normalizeProjectConfig(rawConfig);
43
+ }
44
+
45
+ /**
46
+ * 获取源名称列表(兼容新旧格式)
47
+ */
48
+ function getSourceNames(config: ProjectConfig): string[] {
49
+ return Object.keys(config.sources);
50
+ }
51
+
52
+ /**
53
+ * 检查是否应该复制某项(根据选择配置)
54
+ */
55
+ function shouldInclude(itemName: string, selection: string[]): boolean {
56
+ // 如果选择包含通配符,包含所有项
57
+ if (selection.includes('*')) {
58
+ return true;
59
+ }
60
+ // 否则检查是否在选择列表中
61
+ return selection.includes(itemName);
62
+ }
63
+
64
+ export async function useSource(
65
+ sourceName: string,
66
+ sourceDir: string,
67
+ projectDir: string,
68
+ selection?: SourceSelection
69
+ ): Promise<void> {
70
+ // Input validation
71
+ if (!sourceName || !sourceName.trim()) {
72
+ throw new Error('Source name is required');
73
+ }
74
+
75
+ // Check source directory existence
76
+ if (!(await fs.pathExists(sourceDir))) {
77
+ throw new Error(`Source directory does not exist: ${sourceDir}`);
78
+ }
79
+
80
+ const toolsccDir = getToolsccDir(projectDir);
81
+ const manifest = await loadManifest(sourceDir);
82
+
83
+ // Ensure project is initialized
84
+ await initProject(projectDir);
85
+
86
+ // 使用传入的选择配置或默认配置
87
+ const effectiveSelection: SourceSelection = selection ?? DEFAULT_SELECTION;
88
+
89
+ // Copy/link skills (flattened with prefix)
90
+ const sourceSkillsDir = path.join(sourceDir, 'skills');
91
+ if (await fs.pathExists(sourceSkillsDir)) {
92
+ const skills = await fs.readdir(sourceSkillsDir);
93
+ for (const skill of skills) {
94
+ // 检查是否应该包含此 skill
95
+ if (!shouldInclude(skill, effectiveSelection.skills)) {
96
+ continue;
97
+ }
98
+
99
+ const srcPath = path.join(sourceSkillsDir, skill);
100
+ const name = `${sourceName}` == `${skill}` ? skill : `${sourceName}-${skill}`;
101
+ const destPath = path.join(toolsccDir, 'skills', name);
102
+
103
+ // Remove existing if exists
104
+ await fs.remove(destPath);
105
+
106
+ // Copy directory
107
+ await fs.copy(srcPath, destPath);
108
+ }
109
+ }
110
+
111
+ // Copy commands (in subdirectory by source name)
112
+ const sourceCommandsDir = path.join(sourceDir, 'commands');
113
+ if (await fs.pathExists(sourceCommandsDir)) {
114
+ // 检查是否有选择 commands
115
+ if (effectiveSelection.commands.includes('*')) {
116
+ // 复制所有 commands
117
+ const destDir = path.join(toolsccDir, 'commands', sourceName);
118
+ await fs.remove(destDir);
119
+ await fs.copy(sourceCommandsDir, destDir);
120
+ } else if (effectiveSelection.commands.length > 0) {
121
+ // 只复制选中的 commands
122
+ const destDir = path.join(toolsccDir, 'commands', sourceName);
123
+ await fs.ensureDir(destDir);
124
+
125
+ for (const cmdName of effectiveSelection.commands) {
126
+ const srcFile = path.join(sourceCommandsDir, `${cmdName}.md`);
127
+ if (await fs.pathExists(srcFile)) {
128
+ await fs.copy(srcFile, path.join(destDir, `${cmdName}.md`));
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ // Copy agents (in subdirectory by source name)
135
+ const sourceAgentsDir = path.join(sourceDir, 'agents');
136
+ if (await fs.pathExists(sourceAgentsDir)) {
137
+ // 检查是否有选择 agents
138
+ if (effectiveSelection.agents.includes('*')) {
139
+ // 复制所有 agents
140
+ const destDir = path.join(toolsccDir, 'agents', sourceName);
141
+ await fs.remove(destDir);
142
+ await fs.copy(sourceAgentsDir, destDir);
143
+ } else if (effectiveSelection.agents.length > 0) {
144
+ // 只复制选中的 agents
145
+ const destDir = path.join(toolsccDir, 'agents', sourceName);
146
+ await fs.ensureDir(destDir);
147
+
148
+ for (const agentName of effectiveSelection.agents) {
149
+ const srcFile = path.join(sourceAgentsDir, `${agentName}.md`);
150
+ if (await fs.pathExists(srcFile)) {
151
+ await fs.copy(srcFile, path.join(destDir, `${agentName}.md`));
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ // Copy rules (in subdirectory by source name)
158
+ const sourceRulesDir = path.join(sourceDir, 'rules');
159
+ if (await fs.pathExists(sourceRulesDir)) {
160
+ // 检查是否有选择 rules
161
+ if (effectiveSelection.rules.includes('*')) {
162
+ // 复制所有 rules
163
+ const destDir = path.join(toolsccDir, 'rules', sourceName);
164
+ await fs.remove(destDir);
165
+ await fs.copy(sourceRulesDir, destDir);
166
+ } else if (effectiveSelection.rules.length > 0) {
167
+ // 只复制选中的 rules
168
+ const destDir = path.join(toolsccDir, 'rules', sourceName);
169
+ await fs.ensureDir(destDir);
170
+
171
+ for (const ruleName of effectiveSelection.rules) {
172
+ const srcDir = path.join(sourceRulesDir, ruleName);
173
+ if (await fs.pathExists(srcDir)) {
174
+ await fs.copy(srcDir, path.join(destDir, ruleName));
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ // Update project config - 保存实际使用的选择配置
181
+ const configFile = getProjectConfigPath(projectDir);
182
+ const config = await readProjectConfig(configFile);
183
+ config.sources[sourceName] = effectiveSelection;
184
+ await fs.writeJson(configFile, config, { spaces: 2 });
185
+ }
186
+
187
+ export async function unuseSource(sourceName: string, projectDir: string): Promise<void> {
188
+ // Input validation
189
+ if (!sourceName || !sourceName.trim()) {
190
+ throw new Error('Source name is required');
191
+ }
192
+
193
+ const toolsccDir = getToolsccDir(projectDir);
194
+ const configFile = getProjectConfigPath(projectDir);
195
+
196
+ // Remove skills with prefix
197
+ const skillsDir = path.join(toolsccDir, 'skills');
198
+ if (await fs.pathExists(skillsDir)) {
199
+ const skills = await fs.readdir(skillsDir);
200
+ for (const skill of skills) {
201
+ if (skill.startsWith(`${sourceName}-`)) {
202
+ await fs.remove(path.join(skillsDir, skill));
203
+ }
204
+ }
205
+ }
206
+
207
+ // Remove commands subdirectory
208
+ await fs.remove(path.join(toolsccDir, 'commands', sourceName));
209
+
210
+ // Remove agents subdirectory
211
+ await fs.remove(path.join(toolsccDir, 'agents', sourceName));
212
+
213
+ // Remove rules subdirectory
214
+ await fs.remove(path.join(toolsccDir, 'rules', sourceName));
215
+
216
+ // Update project config with error handling
217
+ let config: ProjectConfig;
218
+ try {
219
+ config = await readProjectConfig(configFile);
220
+ } catch (error) {
221
+ // If config file doesn't exist or is invalid, nothing to update
222
+ return;
223
+ }
224
+
225
+ delete config.sources[sourceName];
226
+ await fs.writeJson(configFile, config, { spaces: 2 });
227
+ }
228
+
229
+ export async function listUsedSources(projectDir: string): Promise<string[]> {
230
+ const configFile = getProjectConfigPath(projectDir);
231
+
232
+ if (!(await fs.pathExists(configFile))) {
233
+ return [];
234
+ }
235
+
236
+ const config = await readProjectConfig(configFile);
237
+ return getSourceNames(config);
238
+ }
239
+
240
+ /**
241
+ * 导出项目配置到 JSON 文件
242
+ */
243
+ export async function exportProjectConfig(
244
+ projectDir: string,
245
+ outputPath: string
246
+ ): Promise<void> {
247
+ const configFile = getProjectConfigPath(projectDir);
248
+
249
+ if (!(await fs.pathExists(configFile))) {
250
+ throw new Error('Project not initialized. Use `tools-cc use <source>` to get started.');
251
+ }
252
+
253
+ const config = await readProjectConfig(configFile);
254
+
255
+ const exportConfig: ExportConfig = {
256
+ version: '1.0',
257
+ type: 'project',
258
+ config,
259
+ exportedAt: new Date().toISOString()
260
+ };
261
+
262
+ await fs.writeJson(outputPath, exportConfig, { spaces: 2 });
263
+ }
264
+
265
+ /**
266
+ * JSON 文件导入项目配置
267
+ */
268
+ export async function importProjectConfig(
269
+ configPath: string,
270
+ projectDir: string,
271
+ resolveSourcePath: (sourceName: string) => Promise<string>
272
+ ): Promise<void> {
273
+ if (!(await fs.pathExists(configPath))) {
274
+ throw new Error(`Config file not found: ${configPath}`);
275
+ }
276
+
277
+ const exportConfig: ExportConfig = await fs.readJson(configPath);
278
+
279
+ // Validate version
280
+ if (exportConfig.version !== '1.0') {
281
+ throw new Error(`Unsupported config version: ${exportConfig.version}`);
282
+ }
283
+
284
+ // Validate type
285
+ if (exportConfig.type !== 'project') {
286
+ throw new Error(`Invalid config type: ${exportConfig.type}. Expected 'project'.`);
287
+ }
288
+
289
+ // Initialize project
290
+ await initProject(projectDir);
291
+
292
+ // Apply each source
293
+ for (const [sourceName, selection] of Object.entries(exportConfig.config.sources)) {
294
+ const sourceDir = await resolveSourcePath(sourceName);
295
+ await useSource(sourceName, sourceDir, projectDir, selection);
296
+ }
297
+
298
+ // Update links if present
299
+ if (exportConfig.config.links && exportConfig.config.links.length > 0) {
300
+ const configFile = getProjectConfigPath(projectDir);
301
+ const config = await readProjectConfig(configFile);
302
+ config.links = exportConfig.config.links;
303
+ await fs.writeJson(configFile, config, { spaces: 2 });
304
+ }
277
305
  }