tools-cc 1.0.8 → 1.0.9

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 CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  本项目的所有重要变更都将记录在此文件中。
4
4
 
5
+ ## [1.0.9] - 2026-03-04
6
+
7
+ ### Added
8
+ - 新增 rules 支持,可管理 rules 配置
9
+ - `tools-cc use source:rules/my-rule` 只导入指定 rule
10
+ - `tools-cc use source:rules/` 导入所有 rules
11
+ - 交互模式支持 rules 选择
12
+ - Rules 存储在 `.toolscc/rules/<source>/` 目录
13
+
5
14
  ## [1.0.8] - 2026-03-04
6
15
 
7
16
  ### Added
package/CHANGELOG_en.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.0.9] - 2026-03-04
6
+
7
+ ### Added
8
+ - Added rules support for managing rules configurations
9
+ - `tools-cc use source:rules/my-rule` import only specified rule
10
+ - `tools-cc use source:rules/` import all rules
11
+ - Interactive mode supports rules selection
12
+ - Rules stored in `.toolscc/rules/<source>/` directory
13
+
5
14
  ## [1.0.8] - 2026-03-04
6
15
 
7
16
  ### Added
package/README.md CHANGED
@@ -131,6 +131,7 @@ tools-cc use my-skills -p iflow claude codex # 指定工具链接
131
131
  tools-cc use my-skills/skills/a-skill # 引入单个 skill
132
132
  tools-cc use my-skills/commands/test # 引入单个 command
133
133
  tools-cc use my-skills/agents/reviewer # 引入单个 agent
134
+ tools-cc use my-skills/rules/my-rule # 引入单个 rule
134
135
  tools-cc use my-skills/skills/a my-skills/commands/test # 引入多项
135
136
 
136
137
  # 交互式选择内容(--ls 参数)
@@ -168,6 +169,24 @@ tools-cc config get <key> # 查看配置
168
169
  tools-cc config list # 查看完整全局配置
169
170
  ```
170
171
 
172
+ ### Template 管理
173
+
174
+ ```bash
175
+ # 完整命令 (template)
176
+ tools-cc template save # 保存当前项目配置为模板 (缩写: tpl save)
177
+ tools-cc template save -n my-template # 指定模板名称保存
178
+ tools-cc template list # 列出所有保存的模板 (缩写: template ls, tpl list, tpl ls)
179
+ tools-cc template rm <name> # 删除模板 (缩写: tpl rm)
180
+ tools-cc template use [name] # 应用模板到当前项目 (缩写: tpl use)
181
+
182
+ # 快捷方式 (tpl)
183
+ tools-cc tpl save # 保存当前项目配置为模板
184
+ tools-cc tpl save -n my-template # 指定模板名称保存
185
+ tools-cc tpl list # 列出所有保存的模板
186
+ tools-cc tpl rm <name> # 删除模板
187
+ tools-cc tpl use [name] # 应用模板到当前项目
188
+ ```
189
+
171
190
  ### 帮助
172
191
 
173
192
  ```bash
@@ -198,8 +217,11 @@ my-skills/
198
217
  │ └── SKILL.md
199
218
  ├── commands/
200
219
  │ └── my-command.md
201
- └── agents/
202
- └── my-agent.md
220
+ ├── agents/
221
+ └── my-agent.md
222
+ └── rules/
223
+ └── my-rule/
224
+ └── RULE.md
203
225
  ```
204
226
 
205
227
  ### manifest.json 格式
@@ -210,7 +232,8 @@ my-skills/
210
232
  "version": "1.0.0",
211
233
  "skills": ["my-skill"],
212
234
  "commands": ["my-command"],
213
- "agents": ["my-agent"]
235
+ "agents": ["my-agent"],
236
+ "rules": ["my-rule"]
214
237
  }
215
238
  ```
216
239
 
@@ -228,7 +251,9 @@ my-project/
228
251
  │ │ └── my-skills-my-skill/
229
252
  │ ├── commands/
230
253
  │ │ └── my-skills/
231
- └── agents/
254
+ ├── agents/
255
+ │ │ └── my-skills/
256
+ │ └── rules/
232
257
  │ └── my-skills/
233
258
  ├── .iflow -> .toolscc # 符号链接
234
259
  ├── .claude -> .toolscc
@@ -263,7 +288,8 @@ my-project/
263
288
  "my-skills": {
264
289
  "skills": ["a-skill", "b-skill"],
265
290
  "commands": ["test"],
266
- "agents": ["*"]
291
+ "agents": ["*"],
292
+ "rules": []
267
293
  }
268
294
  },
269
295
  "links": ["iflow", "claude", "codex"]
package/README_en.md CHANGED
@@ -122,6 +122,7 @@ tools-cc use my-skills -p iflow claude codex # Specify tool links
122
122
  tools-cc use my-skills/skills/a-skill # Import single skill
123
123
  tools-cc use my-skills/commands/test # Import single command
124
124
  tools-cc use my-skills/agents/reviewer # Import single agent
125
+ tools-cc use my-skills/rules/my-rule # Import single rule
125
126
  tools-cc use my-skills/skills/a my-skills/commands/test # Import multiple
126
127
 
127
128
  # Interactive content selection (--ls flag)
@@ -159,6 +160,24 @@ tools-cc config get <key> # Get configuration
159
160
  tools-cc config list # View complete global configuration
160
161
  ```
161
162
 
163
+ ### Template Management
164
+
165
+ ```bash
166
+ # Full commands (template)
167
+ tools-cc template save # Save current project config as template (alias: tpl save)
168
+ tools-cc template save -n my-template # Save with specified template name
169
+ tools-cc template list # List all saved templates (alias: template ls, tpl list, tpl ls)
170
+ tools-cc template rm <name> # Remove a template (alias: tpl rm)
171
+ tools-cc template use [name] # Apply a template to current project (alias: tpl use)
172
+
173
+ # Shortcuts (tpl)
174
+ tools-cc tpl save # Save current project config as template
175
+ tools-cc tpl save -n my-template # Save with specified template name
176
+ tools-cc tpl list # List all saved templates
177
+ tools-cc tpl rm <name> # Remove a template
178
+ tools-cc tpl use [name] # Apply a template to current project
179
+ ```
180
+
162
181
  ### Help
163
182
 
164
183
  ```bash
@@ -189,8 +208,11 @@ my-skills/
189
208
  │ └── SKILL.md
190
209
  ├── commands/
191
210
  │ └── my-command.md
192
- └── agents/
193
- └── my-agent.md
211
+ ├── agents/
212
+ └── my-agent.md
213
+ └── rules/
214
+ └── my-rule/
215
+ └── RULE.md
194
216
  ```
195
217
 
196
218
  ### manifest.json Format
@@ -201,7 +223,8 @@ my-skills/
201
223
  "version": "1.0.0",
202
224
  "skills": ["my-skill"],
203
225
  "commands": ["my-command"],
204
- "agents": ["my-agent"]
226
+ "agents": ["my-agent"],
227
+ "rules": ["my-rule"]
205
228
  }
206
229
  ```
207
230
 
@@ -219,7 +242,9 @@ my-project/
219
242
  │ │ └── my-skills-my-skill/
220
243
  │ ├── commands/
221
244
  │ │ └── my-skills/
222
- └── agents/
245
+ ├── agents/
246
+ │ │ └── my-skills/
247
+ │ └── rules/
223
248
  │ └── my-skills/
224
249
  ├── .iflow -> .toolscc # Symbolic link
225
250
  └── .claude -> .toolscc
@@ -253,7 +278,8 @@ my-project/
253
278
  "my-skills": {
254
279
  "skills": ["a-skill", "b-skill"],
255
280
  "commands": ["test"],
256
- "agents": ["*"]
281
+ "agents": ["*"],
282
+ "rules": []
257
283
  }
258
284
  },
259
285
  "links": ["iflow", "claude", "codex"]
@@ -28,7 +28,7 @@ ${chalk_1.default.bold('COMMANDS / 命令')}
28
28
  tools-cc sources add <name> <path-or-url> Add a source / 添加配置源
29
29
  tools-cc sources list, ls List all sources / 列出所有配置源
30
30
  tools-cc sources remove, rm <name> Remove a source / 移除配置源
31
- tools-cc sources update, up [name] git pull update / 更新源代码
31
+ tools-cc sources update, up, upgrade [name] git pull update / 更新源代码
32
32
  tools-cc sources scan Scan dir for sources / 扫描发现新源
33
33
 
34
34
  ${chalk_1.default.gray('Shortcut: -s')} e.g., tools-cc -s add my-skills https://github.com/user/skills.git
@@ -48,6 +48,14 @@ ${chalk_1.default.bold('COMMANDS / 命令')}
48
48
  tools-cc status Show project status / 显示项目状态
49
49
  tools-cc export [options] Export config / 导出配置
50
50
 
51
+ ${chalk_1.default.cyan('Template Management / 模板管理')}
52
+ tools-cc template save [-n <name>] Save project config as template / 保存项目配置为模板
53
+ tools-cc template list, ls List all templates / 列出所有模板
54
+ tools-cc template rm <name> Delete template / 删除模板
55
+ tools-cc template use [name] Apply template to project / 应用模板到项目
56
+
57
+ ${chalk_1.default.gray('Shortcut: tpl')} e.g., tools-cc tpl save, tools-cc tpl list
58
+
51
59
  ${chalk_1.default.cyan('Help / 帮助')}
52
60
  tools-cc help Show this help / 显示此帮助信息
53
61
  tools-cc --help, -h Show command help / 显示命令帮助
@@ -100,6 +108,15 @@ ${chalk_1.default.bold('EXAMPLES / 示例')}
100
108
  ${chalk_1.default.gray('# Export project config / 导出项目配置')}
101
109
  tools-cc export -o my-config.json
102
110
 
111
+ ${chalk_1.default.gray('# Save project config as template / 保存项目配置为模板')}
112
+ tools-cc template save -n my-template
113
+
114
+ ${chalk_1.default.gray('# List all templates / 列出所有模板')}
115
+ tools-cc template list
116
+
117
+ ${chalk_1.default.gray('# Apply template to project / 应用模板到项目')}
118
+ tools-cc template use my-template
119
+
103
120
  ${chalk_1.default.gray('# Show full configuration / 显示完整配置')}
104
121
  tools-cc config list
105
122
 
@@ -156,6 +156,13 @@ async function handleInteractiveMode(sourceName, projectDir, projects) {
156
156
  choices.push({ name: agent, value: `agents/${agent}` });
157
157
  }
158
158
  }
159
+ // Rules 区
160
+ if (manifest.rules && manifest.rules.length > 0) {
161
+ choices.push(new inquirer_1.default.Separator(`--- Rules (${manifest.rules.length}) ---`));
162
+ for (const rule of manifest.rules) {
163
+ choices.push({ name: rule, value: `rules/${rule}` });
164
+ }
165
+ }
159
166
  if (choices.length === 0) {
160
167
  console.log(chalk_1.default.yellow(`No items found in source: ${sourceName}`));
161
168
  return;
@@ -178,7 +185,8 @@ async function handleInteractiveMode(sourceName, projectDir, projects) {
178
185
  const selection = {
179
186
  skills: [],
180
187
  commands: [],
181
- agents: []
188
+ agents: [],
189
+ rules: []
182
190
  };
183
191
  for (const item of answers.selectedItems) {
184
192
  const [type, name] = item.split('/');
@@ -188,6 +196,8 @@ async function handleInteractiveMode(sourceName, projectDir, projects) {
188
196
  selection.commands.push(name);
189
197
  else if (type === 'agents')
190
198
  selection.agents.push(name);
199
+ else if (type === 'rules')
200
+ selection.rules.push(name);
191
201
  }
192
202
  // 初始化项目并应用选择
193
203
  await (0, project_1.initProject)(projectDir);
@@ -26,7 +26,8 @@ async function scanSource(sourceDir) {
26
26
  version: '0.0.0',
27
27
  skills: [],
28
28
  commands: [],
29
- agents: []
29
+ agents: [],
30
+ rules: []
30
31
  };
31
32
  // Scan skills
32
33
  const skillsDir = path_1.default.join(sourceDir, 'skills');
@@ -52,5 +53,13 @@ async function scanSource(sourceDir) {
52
53
  .filter(e => e.isFile() && e.name.endsWith('.md'))
53
54
  .map(e => e.name.replace('.md', ''));
54
55
  }
56
+ // Scan rules
57
+ const rulesDir = path_1.default.join(sourceDir, 'rules');
58
+ if (await fs_extra_1.default.pathExists(rulesDir)) {
59
+ const entries = await fs_extra_1.default.readdir(rulesDir, { withFileTypes: true });
60
+ manifest.rules = entries
61
+ .filter(e => e.isDirectory())
62
+ .map(e => e.name);
63
+ }
55
64
  return manifest;
56
65
  }
@@ -20,7 +20,8 @@ const path_2 = require("../utils/path");
20
20
  const DEFAULT_SELECTION = {
21
21
  skills: ['*'],
22
22
  commands: ['*'],
23
- agents: ['*']
23
+ agents: ['*'],
24
+ rules: ['*']
24
25
  };
25
26
  async function initProject(projectDir) {
26
27
  const toolsccDir = (0, path_2.getToolsccDir)(projectDir);
@@ -29,6 +30,7 @@ async function initProject(projectDir) {
29
30
  await fs_extra_1.default.ensureDir(path_1.default.join(toolsccDir, 'skills'));
30
31
  await fs_extra_1.default.ensureDir(path_1.default.join(toolsccDir, 'commands'));
31
32
  await fs_extra_1.default.ensureDir(path_1.default.join(toolsccDir, 'agents'));
33
+ await fs_extra_1.default.ensureDir(path_1.default.join(toolsccDir, 'rules'));
32
34
  // Create project config if not exists
33
35
  if (!(await fs_extra_1.default.pathExists(configFile))) {
34
36
  const config = {
@@ -139,6 +141,28 @@ async function useSource(sourceName, sourceDir, projectDir, selection) {
139
141
  }
140
142
  }
141
143
  }
144
+ // Copy rules (in subdirectory by source name)
145
+ const sourceRulesDir = path_1.default.join(sourceDir, 'rules');
146
+ if (await fs_extra_1.default.pathExists(sourceRulesDir)) {
147
+ // 检查是否有选择 rules
148
+ if (effectiveSelection.rules.includes('*')) {
149
+ // 复制所有 rules
150
+ const destDir = path_1.default.join(toolsccDir, 'rules', sourceName);
151
+ await fs_extra_1.default.remove(destDir);
152
+ await fs_extra_1.default.copy(sourceRulesDir, destDir);
153
+ }
154
+ else if (effectiveSelection.rules.length > 0) {
155
+ // 只复制选中的 rules
156
+ const destDir = path_1.default.join(toolsccDir, 'rules', sourceName);
157
+ await fs_extra_1.default.ensureDir(destDir);
158
+ for (const ruleName of effectiveSelection.rules) {
159
+ const srcDir = path_1.default.join(sourceRulesDir, ruleName);
160
+ if (await fs_extra_1.default.pathExists(srcDir)) {
161
+ await fs_extra_1.default.copy(srcDir, path_1.default.join(destDir, ruleName));
162
+ }
163
+ }
164
+ }
165
+ }
142
166
  // Update project config - 保存实际使用的选择配置
143
167
  const configFile = (0, path_2.getProjectConfigPath)(projectDir);
144
168
  const config = await readProjectConfig(configFile);
@@ -166,6 +190,8 @@ async function unuseSource(sourceName, projectDir) {
166
190
  await fs_extra_1.default.remove(path_1.default.join(toolsccDir, 'commands', sourceName));
167
191
  // Remove agents subdirectory
168
192
  await fs_extra_1.default.remove(path_1.default.join(toolsccDir, 'agents', sourceName));
193
+ // Remove rules subdirectory
194
+ await fs_extra_1.default.remove(path_1.default.join(toolsccDir, 'rules', sourceName));
169
195
  // Update project config with error handling
170
196
  let config;
171
197
  try {
@@ -8,12 +8,13 @@ export interface GlobalConfig {
8
8
  sources: Record<string, SourceConfig>;
9
9
  }
10
10
  /**
11
- * 源选择配置 - 指定从源中导入哪些 skills/commands/agents
11
+ * 源选择配置 - 指定从源中导入哪些 skills/commands/agents/rules
12
12
  */
13
13
  export interface SourceSelection {
14
14
  skills: string[];
15
15
  commands: string[];
16
16
  agents: string[];
17
+ rules: string[];
17
18
  }
18
19
  /**
19
20
  * 新版项目配置 - sources 使用 Record 格式支持部分导入
@@ -44,6 +45,7 @@ export interface Manifest {
44
45
  skills?: string[];
45
46
  commands?: string[];
46
47
  agents?: string[];
48
+ rules?: string[];
47
49
  }
48
50
  export interface ToolConfig {
49
51
  linkName: string;
@@ -11,7 +11,8 @@ function isSourceSelection(value) {
11
11
  const obj = value;
12
12
  return (Array.isArray(obj.skills) &&
13
13
  Array.isArray(obj.commands) &&
14
- Array.isArray(obj.agents));
14
+ Array.isArray(obj.agents) &&
15
+ Array.isArray(obj.rules));
15
16
  }
16
17
  /**
17
18
  * 将旧版项目配置转换为新版格式
@@ -25,7 +26,8 @@ function normalizeProjectConfig(config) {
25
26
  newSources[sourceName] = {
26
27
  skills: ['*'],
27
28
  commands: ['*'],
28
- agents: ['*']
29
+ agents: ['*'],
30
+ rules: ['*']
29
31
  };
30
32
  }
31
33
  return { sources: newSources, links: config.links };
@@ -4,7 +4,7 @@ import { SourceSelection } from '../types/config';
4
4
  */
5
5
  export interface ParsedSourcePath {
6
6
  sourceName: string;
7
- type?: 'skills' | 'commands' | 'agents';
7
+ type?: 'skills' | 'commands' | 'agents' | 'rules';
8
8
  itemName?: string;
9
9
  }
10
10
  /**
@@ -25,7 +25,7 @@ function parseSourcePath(input) {
25
25
  return { sourceName };
26
26
  }
27
27
  // 检查第二部分是否为有效类型
28
- const validTypes = ['skills', 'commands', 'agents'];
28
+ const validTypes = ['skills', 'commands', 'agents', 'rules'];
29
29
  const type = parts[1];
30
30
  if (!validTypes.includes(type)) {
31
31
  return { sourceName };
@@ -63,7 +63,8 @@ function buildSelectionFromPaths(paths) {
63
63
  result[sourceName] = {
64
64
  skills: [],
65
65
  commands: [],
66
- agents: []
66
+ agents: [],
67
+ rules: []
67
68
  };
68
69
  }
69
70
  // 如果没有指定类型和项目名称,表示整个源
@@ -71,7 +72,8 @@ function buildSelectionFromPaths(paths) {
71
72
  result[sourceName] = {
72
73
  skills: ['*'],
73
74
  commands: ['*'],
74
- agents: ['*']
75
+ agents: ['*'],
76
+ rules: ['*']
75
77
  };
76
78
  continue;
77
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tools-cc",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "tools-cc [options] <command> [args]",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -23,7 +23,7 @@ ${chalk.bold('COMMANDS / 命令')}
23
23
  tools-cc sources add <name> <path-or-url> Add a source / 添加配置源
24
24
  tools-cc sources list, ls List all sources / 列出所有配置源
25
25
  tools-cc sources remove, rm <name> Remove a source / 移除配置源
26
- tools-cc sources update, up [name] git pull update / 更新源代码
26
+ tools-cc sources update, up, upgrade [name] git pull update / 更新源代码
27
27
  tools-cc sources scan Scan dir for sources / 扫描发现新源
28
28
 
29
29
  ${chalk.gray('Shortcut: -s')} e.g., tools-cc -s add my-skills https://github.com/user/skills.git
@@ -43,6 +43,14 @@ ${chalk.bold('COMMANDS / 命令')}
43
43
  tools-cc status Show project status / 显示项目状态
44
44
  tools-cc export [options] Export config / 导出配置
45
45
 
46
+ ${chalk.cyan('Template Management / 模板管理')}
47
+ tools-cc template save [-n <name>] Save project config as template / 保存项目配置为模板
48
+ tools-cc template list, ls List all templates / 列出所有模板
49
+ tools-cc template rm <name> Delete template / 删除模板
50
+ tools-cc template use [name] Apply template to project / 应用模板到项目
51
+
52
+ ${chalk.gray('Shortcut: tpl')} e.g., tools-cc tpl save, tools-cc tpl list
53
+
46
54
  ${chalk.cyan('Help / 帮助')}
47
55
  tools-cc help Show this help / 显示此帮助信息
48
56
  tools-cc --help, -h Show command help / 显示命令帮助
@@ -95,6 +103,15 @@ ${chalk.bold('EXAMPLES / 示例')}
95
103
  ${chalk.gray('# Export project config / 导出项目配置')}
96
104
  tools-cc export -o my-config.json
97
105
 
106
+ ${chalk.gray('# Save project config as template / 保存项目配置为模板')}
107
+ tools-cc template save -n my-template
108
+
109
+ ${chalk.gray('# List all templates / 列出所有模板')}
110
+ tools-cc template list
111
+
112
+ ${chalk.gray('# Apply template to project / 应用模板到项目')}
113
+ tools-cc template use my-template
114
+
98
115
  ${chalk.gray('# Show full configuration / 显示完整配置')}
99
116
  tools-cc config list
100
117
 
@@ -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
+ }