tools-cc 1.0.0
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/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.js +56 -0
- package/dist/commands/help.d.ts +1 -0
- package/dist/commands/help.js +84 -0
- package/dist/commands/source.d.ts +4 -0
- package/dist/commands/source.js +72 -0
- package/dist/commands/use.d.ts +6 -0
- package/dist/commands/use.js +133 -0
- package/dist/core/config.d.ts +5 -0
- package/dist/core/config.js +37 -0
- package/dist/core/manifest.d.ts +3 -0
- package/dist/core/manifest.js +56 -0
- package/dist/core/project.d.ts +4 -0
- package/dist/core/project.js +118 -0
- package/dist/core/source.d.ts +6 -0
- package/dist/core/source.js +86 -0
- package/dist/core/symlink.d.ts +3 -0
- package/dist/core/symlink.js +56 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +165 -0
- package/dist/types/config.d.ts +23 -0
- package/dist/types/config.js +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +17 -0
- package/dist/utils/path.d.ts +8 -0
- package/dist/utils/path.js +22 -0
- package/docs/plans/2026-02-25-tools-cc-design.md +195 -0
- package/docs/plans/2026-02-25-tools-cc-impl.md +1600 -0
- package/package.json +44 -0
- package/readme.md +182 -0
- package/src/commands/config.ts +50 -0
- package/src/commands/help.ts +79 -0
- package/src/commands/source.ts +63 -0
- package/src/commands/use.ts +147 -0
- package/src/core/config.ts +37 -0
- package/src/core/manifest.ts +57 -0
- package/src/core/project.ts +136 -0
- package/src/core/source.ts +100 -0
- package/src/core/symlink.ts +56 -0
- package/src/index.ts +186 -0
- package/src/types/config.ts +27 -0
- package/src/types/index.ts +1 -0
- package/src/utils/path.ts +18 -0
- package/tests/core/config.test.ts +37 -0
- package/tests/core/manifest.test.ts +37 -0
- package/tests/core/project.test.ts +50 -0
- package/tests/core/source.test.ts +75 -0
- package/tests/core/symlink.test.ts +39 -0
- package/tsconfig.json +17 -0
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tools-cc",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "tools-cc [options] <command> [args]",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"directories": {
|
|
7
|
+
"doc": "docs"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "ts-node src/index.ts",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"test:run": "vitest run"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"tools-cc": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/q759410559/tools-cc.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/q759410559/tools-cc/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/q759410559/tools-cc#readme",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"chalk": "^5.6.2",
|
|
32
|
+
"commander": "^14.0.3",
|
|
33
|
+
"fs-extra": "^11.3.3",
|
|
34
|
+
"inquirer": "^13.3.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/fs-extra": "^11.0.4",
|
|
38
|
+
"@types/inquirer": "^9.0.9",
|
|
39
|
+
"@types/node": "^25.3.0",
|
|
40
|
+
"ts-node": "^10.9.2",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"vitest": "^4.0.18"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# tools-cc
|
|
2
|
+
|
|
3
|
+
一个用于统一管理多个 AI 编程工具(iflow、claude、codebuddy、opencode 等)的 skills/commands/agents 配置的 CLI 工具。通过符号链接机制,避免在多个工具间重复配置。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 从 npm 安装
|
|
9
|
+
npm install -g tools-cc
|
|
10
|
+
|
|
11
|
+
# 或从源码构建
|
|
12
|
+
git clone https://github.com/q759410559/tools-cc.git
|
|
13
|
+
cd tools-cc
|
|
14
|
+
npm install
|
|
15
|
+
npm run build
|
|
16
|
+
npm link
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 快速上手
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. 设置配置源存储位置(可选,默认为 ~/.tools-cc/sources)
|
|
23
|
+
tools-cc -c set sourcesDir D:/skills-hub-sources
|
|
24
|
+
|
|
25
|
+
# 2. 添加配置源(支持 Git URL 或本地路径)
|
|
26
|
+
tools-cc -s add my-skills https://github.com/user/my-skills.git
|
|
27
|
+
tools-cc -s add local-skills D:/path/to/local-skills
|
|
28
|
+
|
|
29
|
+
# 3. 查看已添加的配置源
|
|
30
|
+
tools-cc -s list
|
|
31
|
+
|
|
32
|
+
# 4. 在项目中启用配置源并创建链接
|
|
33
|
+
cd my-project
|
|
34
|
+
tools-cc use my-skills -p iflow claude
|
|
35
|
+
|
|
36
|
+
# 5. 查看项目状态
|
|
37
|
+
tools-cc status
|
|
38
|
+
|
|
39
|
+
# 6. 查看已启用的配置源
|
|
40
|
+
tools-cc list
|
|
41
|
+
|
|
42
|
+
# 7. 移除配置源
|
|
43
|
+
tools-cc rm my-skills
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 命令列表
|
|
47
|
+
|
|
48
|
+
### Source 管理
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# 快捷方式 (-s)
|
|
52
|
+
tools-cc -s add <name> <path-or-url> # 添加配置源
|
|
53
|
+
tools-cc -s list # 列出所有配置源 (缩写: -s ls)
|
|
54
|
+
tools-cc -s remove <name> # 移除配置源 (缩写: -s rm)
|
|
55
|
+
tools-cc -s update [name] # 更新配置源 (缩写: -s up)
|
|
56
|
+
|
|
57
|
+
# 完整命令 (sources)
|
|
58
|
+
tools-cc sources add <name> <path-or-url> # 添加配置源
|
|
59
|
+
tools-cc sources list # 列出所有配置源 (缩写: sources ls)
|
|
60
|
+
tools-cc sources remove <name> # 移除配置源 (缩写: sources rm)
|
|
61
|
+
tools-cc sources update [name] # 更新配置源 (缩写: sources up)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 项目配置
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
tools-cc use [sources...] [-p tools...] # 启用配置源并创建链接
|
|
68
|
+
tools-cc list # 列出已启用的配置源
|
|
69
|
+
tools-cc rm <source> # 禁用配置源
|
|
70
|
+
tools-cc status # 查看项目状态
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Config 管理
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# 快捷方式 (-c)
|
|
77
|
+
tools-cc -c set <key> <value> # 设置配置
|
|
78
|
+
tools-cc -c get <key> # 查看配置
|
|
79
|
+
|
|
80
|
+
# 完整命令 (config)
|
|
81
|
+
tools-cc config set <key> <value> # 设置配置
|
|
82
|
+
tools-cc config get <key> # 查看配置
|
|
83
|
+
tools-cc config list # 查看完整全局配置
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 帮助
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
tools-cc help # 显示中英双语帮助信息
|
|
90
|
+
tools-cc --help # 显示命令行帮助
|
|
91
|
+
tools-cc --version # 显示版本号
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 支持的工具
|
|
95
|
+
|
|
96
|
+
| 工具 | 链接名称 |
|
|
97
|
+
|------|----------|
|
|
98
|
+
| iflow | `.iflow` |
|
|
99
|
+
| claude | `.claude` |
|
|
100
|
+
| codebuddy | `.codebuddy` |
|
|
101
|
+
| opencode | `.opencode` |
|
|
102
|
+
|
|
103
|
+
## 配置源结构
|
|
104
|
+
|
|
105
|
+
配置源目录应包含以下结构:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
my-skills/
|
|
109
|
+
├── manifest.json # 可选,描述组件信息
|
|
110
|
+
├── skills/
|
|
111
|
+
│ └── my-skill/
|
|
112
|
+
│ └── SKILL.md
|
|
113
|
+
├── commands/
|
|
114
|
+
│ └── my-command.md
|
|
115
|
+
└── agents/
|
|
116
|
+
└── my-agent.md
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### manifest.json 格式
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"name": "my-skills",
|
|
124
|
+
"version": "1.0.0",
|
|
125
|
+
"skills": ["my-skill"],
|
|
126
|
+
"commands": ["my-command"],
|
|
127
|
+
"agents": ["my-agent"]
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
如果没有 `manifest.json`,CLI 会自动扫描目录结构生成。
|
|
132
|
+
|
|
133
|
+
## 项目结构
|
|
134
|
+
|
|
135
|
+
使用 `tools-cc use` 后,项目目录结构:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
my-project/
|
|
139
|
+
├── .toolscc/ # 实际内容目录
|
|
140
|
+
│ ├── skills/ # 扁平化,带来源前缀
|
|
141
|
+
│ │ └── my-skills-my-skill/
|
|
142
|
+
│ ├── commands/
|
|
143
|
+
│ │ └── my-skills/
|
|
144
|
+
│ └── agents/
|
|
145
|
+
│ └── my-skills/
|
|
146
|
+
├── .iflow -> .toolscc # 符号链接
|
|
147
|
+
├── .claude -> .toolscc
|
|
148
|
+
└── tools-cc.json # 项目配置
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 配置文件
|
|
152
|
+
|
|
153
|
+
### 全局配置 `~/.tools-cc/config.json`
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"sourcesDir": "D:/skills-hub-sources",
|
|
158
|
+
"sources": {
|
|
159
|
+
"my-skills": {
|
|
160
|
+
"type": "git",
|
|
161
|
+
"url": "https://github.com/user/my-skills.git"
|
|
162
|
+
},
|
|
163
|
+
"local-skills": {
|
|
164
|
+
"type": "local",
|
|
165
|
+
"path": "D:/local-skills"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 项目配置 `项目/tools-cc.json`
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"sources": ["my-skills"],
|
|
176
|
+
"links": ["iflow", "claude"]
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 许可证
|
|
181
|
+
|
|
182
|
+
MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadGlobalConfig, saveGlobalConfig } from '../core/config';
|
|
3
|
+
import { GLOBAL_CONFIG_DIR } from '../utils/path';
|
|
4
|
+
|
|
5
|
+
export async function handleConfigSet(key: string, value: string): Promise<void> {
|
|
6
|
+
const config = await loadGlobalConfig(GLOBAL_CONFIG_DIR);
|
|
7
|
+
|
|
8
|
+
if (key === 'sourcesDir') {
|
|
9
|
+
config.sourcesDir = value;
|
|
10
|
+
await saveGlobalConfig(config, GLOBAL_CONFIG_DIR);
|
|
11
|
+
console.log(chalk.green(`✓ Set sourcesDir to: ${value}`));
|
|
12
|
+
} else {
|
|
13
|
+
console.log(chalk.red(`✗ Unknown config key: ${key}`));
|
|
14
|
+
console.log(chalk.gray('Available keys: sourcesDir'));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function handleConfigGet(key: string): Promise<void> {
|
|
19
|
+
const config = await loadGlobalConfig(GLOBAL_CONFIG_DIR);
|
|
20
|
+
|
|
21
|
+
if (key === 'sourcesDir') {
|
|
22
|
+
console.log(config.sourcesDir);
|
|
23
|
+
} else if (key === 'all') {
|
|
24
|
+
console.log(JSON.stringify(config, null, 2));
|
|
25
|
+
} else {
|
|
26
|
+
console.log(chalk.red(`✗ Unknown config key: ${key}`));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function handleConfigList(): Promise<void> {
|
|
31
|
+
const config = await loadGlobalConfig(GLOBAL_CONFIG_DIR);
|
|
32
|
+
|
|
33
|
+
console.log(chalk.bold('Global Configuration:'));
|
|
34
|
+
console.log(chalk.gray(` Config directory: ${GLOBAL_CONFIG_DIR}`));
|
|
35
|
+
console.log(` sourcesDir: ${chalk.cyan(config.sourcesDir || 'not set')}`);
|
|
36
|
+
|
|
37
|
+
if (config.sources && Object.keys(config.sources).length > 0) {
|
|
38
|
+
console.log(` sources:`);
|
|
39
|
+
for (const [name, source] of Object.entries(config.sources)) {
|
|
40
|
+
console.log(` ${chalk.cyan(name)} (${source.type})`);
|
|
41
|
+
if (source.type === 'git') {
|
|
42
|
+
console.log(chalk.gray(` URL: ${source.url}`));
|
|
43
|
+
} else {
|
|
44
|
+
console.log(chalk.gray(` Path: ${source.path}`));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
console.log(` sources: ${chalk.gray('none')}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export function showHelp(): void {
|
|
4
|
+
console.log(`
|
|
5
|
+
${chalk.bold.cyan('═══════════════════════════════════════════════════════════════════════════')}
|
|
6
|
+
${chalk.bold('tools-cc')} - AI Coding Tools Configuration Manager
|
|
7
|
+
${chalk.bold('tools-cc')} - AI 编程工具配置管理器
|
|
8
|
+
${chalk.bold.cyan('═══════════════════════════════════════════════════════════════════════════')}
|
|
9
|
+
|
|
10
|
+
${chalk.bold('DESCRIPTION / 描述')}
|
|
11
|
+
A CLI tool for managing skills/commands/agents configurations across multiple
|
|
12
|
+
AI coding tools (iflow, claude, codebuddy, opencode, etc.) via symlinks.
|
|
13
|
+
|
|
14
|
+
一个用于统一管理多个 AI 编程工具配置的命令行工具,通过符号链接机制避免重复配置。
|
|
15
|
+
|
|
16
|
+
${chalk.bold('USAGE / 用法')}
|
|
17
|
+
tools-cc <command> [options]
|
|
18
|
+
tools-cc <shortcut> <subcommand> [args]
|
|
19
|
+
|
|
20
|
+
${chalk.bold('COMMANDS / 命令')}
|
|
21
|
+
|
|
22
|
+
${chalk.cyan('Source Management / 配置源管理')}
|
|
23
|
+
tools-cc sources add <name> <path-or-url> Add a source / 添加配置源
|
|
24
|
+
tools-cc sources list, ls List all sources / 列出所有配置源
|
|
25
|
+
tools-cc sources remove, rm <name> Remove a source / 移除配置源
|
|
26
|
+
tools-cc sources update, up [name] Update source(s) / 更新配置源
|
|
27
|
+
|
|
28
|
+
${chalk.gray('Shortcut: -s')} e.g., tools-cc -s add my-skills https://github.com/user/skills.git
|
|
29
|
+
|
|
30
|
+
${chalk.cyan('Config Management / 配置管理')}
|
|
31
|
+
tools-cc config set <key> <value> Set config value / 设置配置值
|
|
32
|
+
tools-cc config get <key> Get config value / 获取配置值
|
|
33
|
+
tools-cc config list Show full config / 显示完整配置
|
|
34
|
+
|
|
35
|
+
${chalk.gray('Shortcut: -c')} e.g., tools-cc -c set sourcesDir D:/skills
|
|
36
|
+
|
|
37
|
+
${chalk.cyan('Project Commands / 项目命令')}
|
|
38
|
+
tools-cc use [sources...] [-p tools...] Use sources in project / 在项目中启用配置源
|
|
39
|
+
tools-cc list List used sources / 列出已启用的配置源
|
|
40
|
+
tools-cc rm <source> Remove source from project / 禁用配置源
|
|
41
|
+
tools-cc status Show project status / 显示项目状态
|
|
42
|
+
|
|
43
|
+
${chalk.cyan('Help / 帮助')}
|
|
44
|
+
tools-cc help Show this help / 显示此帮助信息
|
|
45
|
+
tools-cc --help, -h Show command help / 显示命令帮助
|
|
46
|
+
tools-cc --version, -V Show version / 显示版本号
|
|
47
|
+
|
|
48
|
+
${chalk.bold('SUPPORTED TOOLS / 支持的工具')}
|
|
49
|
+
iflow → .iflow
|
|
50
|
+
claude → .claude
|
|
51
|
+
codebuddy → .codebuddy
|
|
52
|
+
opencode → .opencode
|
|
53
|
+
|
|
54
|
+
${chalk.bold('EXAMPLES / 示例')}
|
|
55
|
+
${chalk.gray('# Add a git source / 添加 Git 配置源')}
|
|
56
|
+
tools-cc sources add my-skills https://github.com/user/my-skills.git
|
|
57
|
+
|
|
58
|
+
${chalk.gray('# Add a local source / 添加本地配置源')}
|
|
59
|
+
tools-cc sources add local-skills D:/path/to/local-skills
|
|
60
|
+
|
|
61
|
+
${chalk.gray('# List all sources / 列出所有配置源')}
|
|
62
|
+
tools-cc sources list
|
|
63
|
+
|
|
64
|
+
${chalk.gray('# Use sources in project / 在项目中启用配置源')}
|
|
65
|
+
tools-cc use my-skills -p iflow claude
|
|
66
|
+
|
|
67
|
+
${chalk.gray('# Check project status / 检查项目状态')}
|
|
68
|
+
tools-cc status
|
|
69
|
+
|
|
70
|
+
${chalk.gray('# Show full configuration / 显示完整配置')}
|
|
71
|
+
tools-cc config list
|
|
72
|
+
|
|
73
|
+
${chalk.bold('MORE INFO / 更多信息')}
|
|
74
|
+
GitHub: https://github.com/user/tools-cc
|
|
75
|
+
Docs: https://github.com/user/tools-cc#readme
|
|
76
|
+
|
|
77
|
+
${chalk.bold.cyan('═══════════════════════════════════════════════════════════════════════════')}
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { addSource, listSources, removeSource, updateSource } from '../core/source';
|
|
3
|
+
import { GLOBAL_CONFIG_DIR } from '../utils/path';
|
|
4
|
+
|
|
5
|
+
export async function handleSourceAdd(name: string, pathOrUrl: string): Promise<void> {
|
|
6
|
+
try {
|
|
7
|
+
const result = await addSource(name, pathOrUrl, GLOBAL_CONFIG_DIR);
|
|
8
|
+
console.log(chalk.green(`✓ Added source: ${name}`));
|
|
9
|
+
console.log(chalk.gray(` Type: ${result.type}`));
|
|
10
|
+
if (result.type === 'git') {
|
|
11
|
+
console.log(chalk.gray(` URL: ${result.url}`));
|
|
12
|
+
} else {
|
|
13
|
+
console.log(chalk.gray(` Path: ${result.path}`));
|
|
14
|
+
}
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function handleSourceList(): Promise<void> {
|
|
21
|
+
const sources = await listSources(GLOBAL_CONFIG_DIR);
|
|
22
|
+
const entries = Object.entries(sources);
|
|
23
|
+
|
|
24
|
+
if (entries.length === 0) {
|
|
25
|
+
console.log(chalk.gray('No sources configured.'));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(chalk.bold('Configured sources:'));
|
|
30
|
+
for (const [name, config] of entries) {
|
|
31
|
+
console.log(` ${chalk.cyan(name)} (${config.type})`);
|
|
32
|
+
if (config.type === 'git') {
|
|
33
|
+
console.log(chalk.gray(` ${config.url}`));
|
|
34
|
+
} else {
|
|
35
|
+
console.log(chalk.gray(` ${config.path}`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function handleSourceRemove(name: string): Promise<void> {
|
|
41
|
+
try {
|
|
42
|
+
await removeSource(name, GLOBAL_CONFIG_DIR);
|
|
43
|
+
console.log(chalk.green(`✓ Removed source: ${name}`));
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function handleSourceUpdate(name?: string): Promise<void> {
|
|
50
|
+
try {
|
|
51
|
+
if (name) {
|
|
52
|
+
await updateSource(name, GLOBAL_CONFIG_DIR);
|
|
53
|
+
} else {
|
|
54
|
+
const sources = await listSources(GLOBAL_CONFIG_DIR);
|
|
55
|
+
for (const sourceName of Object.keys(sources)) {
|
|
56
|
+
await updateSource(sourceName, GLOBAL_CONFIG_DIR);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
console.log(chalk.green(`✓ Update complete`));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { useSource, unuseSource, listUsedSources, initProject } from '../core/project';
|
|
4
|
+
import { getSourcePath, listSources } from '../core/source';
|
|
5
|
+
import { createSymlink, isSymlink } from '../core/symlink';
|
|
6
|
+
import { GLOBAL_CONFIG_DIR, getToolsccDir } from '../utils/path';
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
|
|
10
|
+
const SUPPORTED_TOOLS: Record<string, string> = {
|
|
11
|
+
iflow: '.iflow',
|
|
12
|
+
claude: '.claude',
|
|
13
|
+
codebuddy: '.codebuddy',
|
|
14
|
+
opencode: '.opencode'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function handleUse(
|
|
18
|
+
sourceNames: string[],
|
|
19
|
+
options: { projects?: string[] }
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const projectDir = process.cwd();
|
|
22
|
+
|
|
23
|
+
// 如果没有指定 source,进入交互模式
|
|
24
|
+
if (sourceNames.length === 0) {
|
|
25
|
+
const sources = await listSources(GLOBAL_CONFIG_DIR);
|
|
26
|
+
const sourceList = Object.keys(sources);
|
|
27
|
+
|
|
28
|
+
if (sourceList.length === 0) {
|
|
29
|
+
console.log(chalk.yellow('No sources configured. Use `tools-cc -s add` to add one.'));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const answers = await inquirer.prompt([
|
|
34
|
+
{
|
|
35
|
+
type: 'checkbox',
|
|
36
|
+
name: 'selectedSources',
|
|
37
|
+
message: 'Select sources to use:',
|
|
38
|
+
choices: sourceList
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
sourceNames = answers.selectedSources;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (sourceNames.length === 0) {
|
|
46
|
+
console.log(chalk.gray('No sources selected.'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 初始化项目
|
|
51
|
+
await initProject(projectDir);
|
|
52
|
+
|
|
53
|
+
// 启用每个配置源
|
|
54
|
+
for (const sourceName of sourceNames) {
|
|
55
|
+
try {
|
|
56
|
+
const sourcePath = await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
|
|
57
|
+
await useSource(sourceName, sourcePath, projectDir);
|
|
58
|
+
console.log(chalk.green(`✓ Using source: ${sourceName}`));
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.log(chalk.red(`✗ Failed to use ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 创建符号链接
|
|
65
|
+
const tools = options.projects || Object.keys(SUPPORTED_TOOLS);
|
|
66
|
+
const toolsccDir = getToolsccDir(projectDir);
|
|
67
|
+
|
|
68
|
+
for (const tool of tools) {
|
|
69
|
+
const linkName = SUPPORTED_TOOLS[tool];
|
|
70
|
+
if (!linkName) {
|
|
71
|
+
console.log(chalk.yellow(`Unknown tool: ${tool}`));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const linkPath = path.join(projectDir, linkName);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await createSymlink(toolsccDir, linkPath, true);
|
|
79
|
+
console.log(chalk.green(`✓ Linked: ${linkName} -> .toolscc`));
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.log(chalk.red(`✗ Failed to link ${linkName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 更新项目配置
|
|
86
|
+
const configFile = path.join(projectDir, 'tools-cc.json');
|
|
87
|
+
const config = await fs.readJson(configFile);
|
|
88
|
+
const existingLinks = config.links || [];
|
|
89
|
+
config.links = [...new Set([...existingLinks, ...tools])];
|
|
90
|
+
await fs.writeJson(configFile, config, { spaces: 2 });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function handleList(): Promise<void> {
|
|
94
|
+
const projectDir = process.cwd();
|
|
95
|
+
const sources = await listUsedSources(projectDir);
|
|
96
|
+
|
|
97
|
+
if (sources.length === 0) {
|
|
98
|
+
console.log(chalk.gray('No sources in use. Run `tools-cc use <source-name>` to add one.'));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(chalk.bold('Sources in use:'));
|
|
103
|
+
for (const source of sources) {
|
|
104
|
+
console.log(` ${chalk.cyan(source)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function handleRemove(sourceName: string): Promise<void> {
|
|
109
|
+
const projectDir = process.cwd();
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
await unuseSource(sourceName, projectDir);
|
|
113
|
+
console.log(chalk.green(`✓ Removed source: ${sourceName}`));
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function handleStatus(): Promise<void> {
|
|
120
|
+
const projectDir = process.cwd();
|
|
121
|
+
const sources = await listUsedSources(projectDir);
|
|
122
|
+
|
|
123
|
+
console.log(chalk.bold('\nProject Status:'));
|
|
124
|
+
console.log(chalk.gray(` Directory: ${projectDir}`));
|
|
125
|
+
|
|
126
|
+
// 检查 .toolscc
|
|
127
|
+
const toolsccDir = getToolsccDir(projectDir);
|
|
128
|
+
console.log(` .toolscc: ${await fs.pathExists(toolsccDir) ? chalk.green('exists') : chalk.red('not found')}`);
|
|
129
|
+
|
|
130
|
+
// 检查 sources
|
|
131
|
+
console.log(` Sources: ${sources.length > 0 ? sources.map(s => chalk.cyan(s)).join(', ') : chalk.gray('none')}`);
|
|
132
|
+
|
|
133
|
+
// 检查 links
|
|
134
|
+
const configFile = path.join(projectDir, 'tools-cc.json');
|
|
135
|
+
if (await fs.pathExists(configFile)) {
|
|
136
|
+
const config = await fs.readJson(configFile);
|
|
137
|
+
console.log(` Links:`);
|
|
138
|
+
for (const tool of config.links || []) {
|
|
139
|
+
const linkName = SUPPORTED_TOOLS[tool];
|
|
140
|
+
if (!linkName) continue;
|
|
141
|
+
const linkPath = path.join(projectDir, linkName);
|
|
142
|
+
const isLink = await isSymlink(linkPath);
|
|
143
|
+
console.log(` ${tool}: ${isLink ? chalk.green('linked') : chalk.red('not linked')}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
console.log();
|
|
147
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { GlobalConfig, ProjectConfig } from '../types';
|
|
4
|
+
import { DEFAULT_CONFIG, getProjectConfigPath } from '../utils/path';
|
|
5
|
+
|
|
6
|
+
export async function loadGlobalConfig(configDir: string): Promise<GlobalConfig> {
|
|
7
|
+
const configFile = path.join(configDir, 'config.json');
|
|
8
|
+
|
|
9
|
+
if (!(await fs.pathExists(configFile))) {
|
|
10
|
+
await fs.ensureDir(configDir);
|
|
11
|
+
await fs.writeJson(configFile, DEFAULT_CONFIG, { spaces: 2 });
|
|
12
|
+
return DEFAULT_CONFIG;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return await fs.readJson(configFile);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function saveGlobalConfig(config: GlobalConfig, configDir: string): Promise<void> {
|
|
19
|
+
const configFile = path.join(configDir, 'config.json');
|
|
20
|
+
await fs.ensureDir(configDir);
|
|
21
|
+
await fs.writeJson(configFile, config, { spaces: 2 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function loadProjectConfig(projectDir: string): Promise<ProjectConfig | null> {
|
|
25
|
+
const configFile = getProjectConfigPath(projectDir);
|
|
26
|
+
|
|
27
|
+
if (!(await fs.pathExists(configFile))) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return await fs.readJson(configFile);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function saveProjectConfig(config: ProjectConfig, projectDir: string): Promise<void> {
|
|
35
|
+
const configFile = getProjectConfigPath(projectDir);
|
|
36
|
+
await fs.writeJson(configFile, config, { spaces: 2 });
|
|
37
|
+
}
|
|
@@ -0,0 +1,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
|
+
};
|
|
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
|
+
}
|