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,160 +1,202 @@
1
- import chalk from 'chalk';
2
- import inquirer from 'inquirer';
3
- import path from 'path';
4
- import { saveTemplate, listTemplates, removeTemplate, getTemplate } from '../core/template';
5
- import { loadProjectConfig } from '../core/config';
6
- import { importProjectConfig } from '../core/project';
7
- import { getSourcePath } from '../core/source';
8
- import { TEMPLATES_DIR, getProjectConfigPath, GLOBAL_CONFIG_DIR } from '../utils/path';
9
- import fs from 'fs-extra';
10
-
11
- /**
12
- * 处理 template save 命令
13
- */
14
- export async function handleTemplateSave(options: { name?: string }): Promise<void> {
15
- const projectDir = process.cwd();
16
- const configFile = getProjectConfigPath(projectDir);
17
-
18
- // 检查项目配置是否存在
19
- if (!(await fs.pathExists(configFile))) {
20
- console.log(chalk.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
21
- return;
22
- }
23
-
24
- try {
25
- // 读取项目配置
26
- const config = await loadProjectConfig(projectDir);
27
- if (!config) {
28
- console.log(chalk.yellow('No project configuration found.'));
29
- return;
30
- }
31
-
32
- // 确定模板名称
33
- let templateName = options.name;
34
- if (!templateName) {
35
- templateName = path.basename(projectDir);
36
- }
37
-
38
- // 检查是否已存在
39
- const existing = await getTemplate(templateName, TEMPLATES_DIR);
40
- if (existing) {
41
- const answers = await inquirer.prompt([
42
- {
43
- type: 'confirm',
44
- name: 'overwrite',
45
- message: `Template "${templateName}" already exists. Overwrite?`,
46
- default: false
47
- }
48
- ]);
49
- if (!answers.overwrite) {
50
- console.log(chalk.gray('Cancelled.'));
51
- return;
52
- }
53
- }
54
-
55
- // 保存模板
56
- const template = await saveTemplate(templateName, projectDir, config, TEMPLATES_DIR);
57
- console.log(chalk.green(`✓ Template saved: ${template.name}`));
58
- } catch (error) {
59
- console.log(chalk.red(`✗ Failed to save template: ${error instanceof Error ? error.message : 'Unknown error'}`));
60
- }
61
- }
62
-
63
- /**
64
- * 处理 template list 命令
65
- */
66
- export async function handleTemplateList(): Promise<void> {
67
- const templates = await listTemplates(TEMPLATES_DIR);
68
-
69
- if (templates.length === 0) {
70
- console.log(chalk.gray('No templates saved.'));
71
- return;
72
- }
73
-
74
- console.log(chalk.bold('Saved templates:'));
75
- for (const template of templates) {
76
- const date = new Date(template.savedAt).toLocaleDateString();
77
- console.log(` ${chalk.cyan(template.name.padEnd(20))} (saved: ${date})`);
78
- }
79
- }
80
-
81
- /**
82
- * 处理 template rm 命令
83
- */
84
- export async function handleTemplateRemove(name: string): Promise<void> {
85
- try {
86
- await removeTemplate(name, TEMPLATES_DIR);
87
- console.log(chalk.green(`✓ Template removed: ${name}`));
88
- } catch (error) {
89
- console.log(chalk.red(`✗ Failed to remove template: ${error instanceof Error ? error.message : 'Unknown error'}`));
90
- }
91
- }
92
-
93
- /**
94
- * 处理 template use 命令
95
- */
96
- export async function handleTemplateUse(name?: string): Promise<void> {
97
- const projectDir = process.cwd();
98
- const configFile = getProjectConfigPath(projectDir);
99
-
100
- // 检查项目是否已初始化
101
- if (!(await fs.pathExists(configFile))) {
102
- console.log(chalk.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
103
- return;
104
- }
105
-
106
- // 如果没有指定名称,显示选择列表
107
- if (!name) {
108
- const templates = await listTemplates(TEMPLATES_DIR);
109
-
110
- if (templates.length === 0) {
111
- console.log(chalk.gray('No templates saved.'));
112
- return;
113
- }
114
-
115
- const answers = await inquirer.prompt([
116
- {
117
- type: 'list',
118
- name: 'selectedTemplate',
119
- message: 'Select a template to use:',
120
- choices: templates.map(t => ({
121
- name: `${t.name} (from: ${path.basename(t.sourceProject)})`,
122
- value: t.name
123
- }))
124
- }
125
- ]);
126
- name = answers.selectedTemplate as string;
127
- }
128
-
129
- // 获取模板
130
- const template = await getTemplate(name as string, TEMPLATES_DIR);
131
- if (!template) {
132
- console.log(chalk.red(`✗ Template not found: ${name}`));
133
- return;
134
- }
135
-
136
- // 定义源路径解析函数
137
- const resolveSourcePath = async (sourceName: string): Promise<string> => {
138
- return await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
139
- };
140
-
141
- // 创建临时配置文件
142
- const tempConfigPath = path.join(projectDir, '.toolscc-template-temp.json');
143
- const exportConfig = {
144
- version: '1.0',
145
- type: 'project' as const,
146
- config: template.config,
147
- exportedAt: new Date().toISOString()
148
- };
149
-
150
- // 导入配置
151
- try {
152
- await fs.writeJson(tempConfigPath, exportConfig, { spaces: 2 });
153
- await importProjectConfig(tempConfigPath, projectDir, resolveSourcePath);
154
- console.log(chalk.green(`✓ Applied template: ${name}`));
155
- } catch (error) {
156
- console.log(chalk.red(`✗ Failed to apply template: ${error instanceof Error ? error.message : 'Unknown error'}`));
157
- } finally {
158
- await fs.remove(tempConfigPath);
159
- }
160
- }
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
3
+ import path from 'path';
4
+ import { saveTemplate, listTemplates, removeTemplate, getTemplate } from '../core/template';
5
+ import { loadProjectConfig } from '../core/config';
6
+ import { importProjectConfig } from '../core/project';
7
+ import { getSourcePath } from '../core/source';
8
+ import { createSymlink } from '../core/symlink';
9
+ import { TEMPLATES_DIR, getProjectConfigPath, getToolsccDir, GLOBAL_CONFIG_DIR } from '../utils/path';
10
+ import fs from 'fs-extra';
11
+
12
+ const SUPPORTED_TOOLS: Record<string, string> = {
13
+ iflow: '.iflow',
14
+ claude: '.claude',
15
+ codebuddy: '.codebuddy',
16
+ opencode: '.opencode',
17
+ codex: '.codex'
18
+ };
19
+
20
+ /**
21
+ * 处理 template save 命令
22
+ */
23
+ export async function handleTemplateSave(options: { name?: string }): Promise<void> {
24
+ const projectDir = process.cwd();
25
+ const configFile = getProjectConfigPath(projectDir);
26
+
27
+ // 检查项目配置是否存在
28
+ if (!(await fs.pathExists(configFile))) {
29
+ console.log(chalk.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
30
+ return;
31
+ }
32
+
33
+ try {
34
+ // 读取项目配置
35
+ const config = await loadProjectConfig(projectDir);
36
+ if (!config) {
37
+ console.log(chalk.yellow('No project configuration found.'));
38
+ return;
39
+ }
40
+
41
+ // 确定模板名称
42
+ let templateName = options.name;
43
+ if (!templateName) {
44
+ templateName = path.basename(projectDir);
45
+ }
46
+
47
+ // 检查是否已存在
48
+ const existing = await getTemplate(templateName, TEMPLATES_DIR);
49
+ if (existing) {
50
+ const answers = await inquirer.prompt([
51
+ {
52
+ type: 'confirm',
53
+ name: 'overwrite',
54
+ message: `Template "${templateName}" already exists. Overwrite?`,
55
+ default: false
56
+ }
57
+ ]);
58
+ if (!answers.overwrite) {
59
+ console.log(chalk.gray('Cancelled.'));
60
+ return;
61
+ }
62
+ }
63
+
64
+ // 保存模板
65
+ const template = await saveTemplate(templateName, projectDir, config, TEMPLATES_DIR);
66
+ console.log(chalk.green(`✓ Template saved: ${template.name}`));
67
+ } catch (error) {
68
+ console.log(chalk.red(`✗ Failed to save template: ${error instanceof Error ? error.message : 'Unknown error'}`));
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 处理 template list 命令
74
+ */
75
+ export async function handleTemplateList(): Promise<void> {
76
+ const templates = await listTemplates(TEMPLATES_DIR);
77
+
78
+ if (templates.length === 0) {
79
+ console.log(chalk.gray('No templates saved.'));
80
+ return;
81
+ }
82
+
83
+ console.log(chalk.bold('Saved templates:'));
84
+ for (const template of templates) {
85
+ const date = new Date(template.savedAt).toLocaleDateString();
86
+ console.log(` ${chalk.cyan(template.name.padEnd(20))} (saved: ${date})`);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * 处理 template rm 命令
92
+ */
93
+ export async function handleTemplateRemove(name: string): Promise<void> {
94
+ try {
95
+ await removeTemplate(name, TEMPLATES_DIR);
96
+ console.log(chalk.green(`✓ Template removed: ${name}`));
97
+ } catch (error) {
98
+ console.log(chalk.red(`✗ Failed to remove template: ${error instanceof Error ? error.message : 'Unknown error'}`));
99
+ }
100
+ }
101
+
102
+ /**
103
+ * 处理 template use 命令
104
+ */
105
+ export async function handleTemplateUse(name?: string, options?: { projects?: string[] }): Promise<void> {
106
+ const projectDir = process.cwd();
107
+ const toolsccDir = getToolsccDir(projectDir);
108
+ const configFile = getProjectConfigPath(projectDir);
109
+
110
+ // 如果没有指定名称,显示选择列表
111
+ if (!name) {
112
+ const templates = await listTemplates(TEMPLATES_DIR);
113
+
114
+ if (templates.length === 0) {
115
+ console.log(chalk.gray('No templates saved.'));
116
+ return;
117
+ }
118
+
119
+ const answers = await inquirer.prompt([
120
+ {
121
+ type: 'list',
122
+ name: 'selectedTemplate',
123
+ message: 'Select a template to use:',
124
+ choices: templates.map(t => ({
125
+ name: `${t.name} (from: ${path.basename(t.sourceProject)})`,
126
+ value: t.name
127
+ }))
128
+ }
129
+ ]);
130
+ name = answers.selectedTemplate as string;
131
+ }
132
+
133
+ // 获取模板
134
+ const template = await getTemplate(name as string, TEMPLATES_DIR);
135
+ if (!template) {
136
+ console.log(chalk.red(`✗ Template not found: ${name}`));
137
+ return;
138
+ }
139
+
140
+ // 定义源路径解析函数
141
+ const resolveSourcePath = async (sourceName: string): Promise<string> => {
142
+ return await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
143
+ };
144
+
145
+ // 创建临时配置文件
146
+ const tempConfigPath = path.join(projectDir, '.toolscc-template-temp.json');
147
+ const exportConfig = {
148
+ version: '1.0',
149
+ type: 'project' as const,
150
+ config: template.config,
151
+ exportedAt: new Date().toISOString()
152
+ };
153
+
154
+ // 导入配置
155
+ try {
156
+ await fs.writeJson(tempConfigPath, exportConfig, { spaces: 2 });
157
+ await importProjectConfig(tempConfigPath, projectDir, resolveSourcePath);
158
+ console.log(chalk.green(`✓ Applied template: ${name}`));
159
+
160
+ // 创建符号链接
161
+ await createToolLinks(projectDir, toolsccDir, configFile, options?.projects);
162
+ } catch (error) {
163
+ console.log(chalk.red(`✗ Failed to apply template: ${error instanceof Error ? error.message : 'Unknown error'}`));
164
+ } finally {
165
+ await fs.remove(tempConfigPath);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * 创建工具符号链接
171
+ */
172
+ async function createToolLinks(
173
+ projectDir: string,
174
+ toolsccDir: string,
175
+ configFile: string,
176
+ projects?: string[]
177
+ ): Promise<void> {
178
+ const tools = projects || Object.keys(SUPPORTED_TOOLS);
179
+
180
+ for (const tool of tools) {
181
+ const linkName = SUPPORTED_TOOLS[tool];
182
+ if (!linkName) {
183
+ console.log(chalk.yellow(`Unknown tool: ${tool}`));
184
+ continue;
185
+ }
186
+
187
+ const linkPath = path.join(projectDir, linkName);
188
+
189
+ try {
190
+ await createSymlink(toolsccDir, linkPath, true);
191
+ console.log(chalk.green(`✓ Linked: ${linkName} -> .toolscc`));
192
+ } catch (error) {
193
+ console.log(chalk.red(`✗ Failed to link ${linkName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
194
+ }
195
+ }
196
+
197
+ // 更新项目配置
198
+ const config = await fs.readJson(configFile);
199
+ const existingLinks = config.links || [];
200
+ config.links = [...new Set([...existingLinks, ...tools])];
201
+ await fs.writeJson(configFile, config, { spaces: 2 });
202
+ }
@@ -195,6 +195,14 @@ async function handleInteractiveMode(
195
195
  }
196
196
  }
197
197
 
198
+ // Rules 区
199
+ if (manifest.rules && manifest.rules.length > 0) {
200
+ choices.push(new inquirer.Separator(`--- Rules (${manifest.rules.length}) ---`));
201
+ for (const rule of manifest.rules) {
202
+ choices.push({ name: rule, value: `rules/${rule}` });
203
+ }
204
+ }
205
+
198
206
  if (choices.length === 0) {
199
207
  console.log(chalk.yellow(`No items found in source: ${sourceName}`));
200
208
  return;
@@ -220,7 +228,8 @@ async function handleInteractiveMode(
220
228
  const selection: SourceSelection = {
221
229
  skills: [],
222
230
  commands: [],
223
- agents: []
231
+ agents: [],
232
+ rules: []
224
233
  };
225
234
 
226
235
  for (const item of answers.selectedItems) {
@@ -228,6 +237,7 @@ async function handleInteractiveMode(
228
237
  if (type === 'skills') selection.skills.push(name);
229
238
  else if (type === 'commands') selection.commands.push(name);
230
239
  else if (type === 'agents') selection.agents.push(name);
240
+ else if (type === 'rules') selection.rules.push(name);
231
241
  }
232
242
 
233
243
  // 初始化项目并应用选择
@@ -1,57 +1,67 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { Manifest } from '../types';
4
-
5
- export async function loadManifest(sourceDir: string): Promise<Manifest> {
6
- const manifestPath = path.join(sourceDir, 'manifest.json');
7
-
8
- if (await fs.pathExists(manifestPath)) {
9
- try {
10
- return await fs.readJson(manifestPath);
11
- } catch (error) {
12
- throw new Error(`Failed to parse manifest.json: ${error instanceof Error ? error.message : 'Invalid JSON'}`);
13
- }
14
- }
15
-
16
- return scanSource(sourceDir);
17
- }
18
-
19
- export async function scanSource(sourceDir: string): Promise<Manifest> {
20
- const name = path.basename(sourceDir);
21
- const manifest: Manifest = {
22
- name,
23
- version: '0.0.0',
24
- skills: [],
25
- commands: [],
26
- agents: []
27
- };
28
-
29
- // Scan skills
30
- const skillsDir = path.join(sourceDir, 'skills');
31
- if (await fs.pathExists(skillsDir)) {
32
- const entries = await fs.readdir(skillsDir, { withFileTypes: true });
33
- manifest.skills = entries
34
- .filter(e => e.isDirectory())
35
- .map(e => e.name);
36
- }
37
-
38
- // Scan commands
39
- const commandsDir = path.join(sourceDir, 'commands');
40
- if (await fs.pathExists(commandsDir)) {
41
- const entries = await fs.readdir(commandsDir, { withFileTypes: true });
42
- manifest.commands = entries
43
- .filter(e => e.isFile() && e.name.endsWith('.md'))
44
- .map(e => e.name.replace('.md', ''));
45
- }
46
-
47
- // Scan agents
48
- const agentsDir = path.join(sourceDir, 'agents');
49
- if (await fs.pathExists(agentsDir)) {
50
- const entries = await fs.readdir(agentsDir, { withFileTypes: true });
51
- manifest.agents = entries
52
- .filter(e => e.isFile() && e.name.endsWith('.md'))
53
- .map(e => e.name.replace('.md', ''));
54
- }
55
-
56
- return manifest;
57
- }
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { Manifest } from '../types';
4
+
5
+ export async function loadManifest(sourceDir: string): Promise<Manifest> {
6
+ const manifestPath = path.join(sourceDir, 'manifest.json');
7
+
8
+ if (await fs.pathExists(manifestPath)) {
9
+ try {
10
+ return await fs.readJson(manifestPath);
11
+ } catch (error) {
12
+ throw new Error(`Failed to parse manifest.json: ${error instanceof Error ? error.message : 'Invalid JSON'}`);
13
+ }
14
+ }
15
+
16
+ return scanSource(sourceDir);
17
+ }
18
+
19
+ export async function scanSource(sourceDir: string): Promise<Manifest> {
20
+ const name = path.basename(sourceDir);
21
+ const manifest: Manifest = {
22
+ name,
23
+ version: '0.0.0',
24
+ skills: [],
25
+ commands: [],
26
+ agents: [],
27
+ rules: []
28
+ };
29
+
30
+ // Scan skills
31
+ const skillsDir = path.join(sourceDir, 'skills');
32
+ if (await fs.pathExists(skillsDir)) {
33
+ const entries = await fs.readdir(skillsDir, { withFileTypes: true });
34
+ manifest.skills = entries
35
+ .filter(e => e.isDirectory())
36
+ .map(e => e.name);
37
+ }
38
+
39
+ // Scan commands
40
+ const commandsDir = path.join(sourceDir, 'commands');
41
+ if (await fs.pathExists(commandsDir)) {
42
+ const entries = await fs.readdir(commandsDir, { withFileTypes: true });
43
+ manifest.commands = entries
44
+ .filter(e => e.isFile() && e.name.endsWith('.md'))
45
+ .map(e => e.name.replace('.md', ''));
46
+ }
47
+
48
+ // Scan agents
49
+ const agentsDir = path.join(sourceDir, 'agents');
50
+ if (await fs.pathExists(agentsDir)) {
51
+ const entries = await fs.readdir(agentsDir, { withFileTypes: true });
52
+ manifest.agents = entries
53
+ .filter(e => e.isFile() && e.name.endsWith('.md'))
54
+ .map(e => e.name.replace('.md', ''));
55
+ }
56
+
57
+ // Scan rules
58
+ const rulesDir = path.join(sourceDir, 'rules');
59
+ if (await fs.pathExists(rulesDir)) {
60
+ const entries = await fs.readdir(rulesDir, { withFileTypes: true });
61
+ manifest.rules = entries
62
+ .filter(e => e.isDirectory())
63
+ .map(e => e.name);
64
+ }
65
+
66
+ return manifest;
67
+ }