tools-cc 1.0.0 → 1.0.3
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/AGENTS.md +164 -0
- package/CHANGELOG.md +63 -0
- package/{readme.md → README.md} +44 -8
- package/dist/commands/source.d.ts +1 -0
- package/dist/commands/source.js +29 -0
- package/dist/commands/use.d.ts +1 -0
- package/dist/commands/use.js +40 -2
- package/dist/core/source.d.ts +6 -0
- package/dist/core/source.js +78 -0
- package/dist/index.js +18 -1
- package/dist/utils/path.d.ts +1 -1
- package/dist/utils/path.js +4 -4
- package/package.json +1 -1
- package/src/commands/source.ts +34 -1
- package/src/commands/use.ts +190 -147
- package/src/core/source.ts +184 -100
- package/src/index.ts +22 -3
- package/src/utils/path.ts +4 -4
package/AGENTS.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# tools-cc 项目上下文
|
|
2
|
+
|
|
3
|
+
## 项目概述
|
|
4
|
+
|
|
5
|
+
tools-cc 是一个 CLI 工具,用于统一管理多个 AI 编程工具(iflow、claude、codebuddy、opencode 等)的 skills/commands/agents 配置。通过符号链接机制,避免在多个工具间重复配置。
|
|
6
|
+
|
|
7
|
+
## 技术栈
|
|
8
|
+
|
|
9
|
+
- **语言**: TypeScript (ES2020, strict mode)
|
|
10
|
+
- **CLI 框架**: Commander.js
|
|
11
|
+
- **文件操作**: fs-extra
|
|
12
|
+
- **终端输出**: chalk
|
|
13
|
+
- **交互式命令**: inquirer
|
|
14
|
+
- **测试框架**: vitest
|
|
15
|
+
- **运行时**: Node.js (CommonJS)
|
|
16
|
+
|
|
17
|
+
## 项目结构
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
tools-cc/
|
|
21
|
+
├── src/
|
|
22
|
+
│ ├── index.ts # CLI 入口,命令注册
|
|
23
|
+
│ ├── commands/ # CLI 命令处理器
|
|
24
|
+
│ │ ├── config.ts # 配置管理命令
|
|
25
|
+
│ │ ├── help.ts # 帮助信息
|
|
26
|
+
│ │ ├── source.ts # 源管理命令
|
|
27
|
+
│ │ └── use.ts # 项目使用命令
|
|
28
|
+
│ ├── core/ # 核心业务逻辑
|
|
29
|
+
│ │ ├── config.ts # 配置读写
|
|
30
|
+
│ │ ├── manifest.ts # manifest.json 解析
|
|
31
|
+
│ │ ├── project.ts # 项目管理
|
|
32
|
+
│ │ ├── source.ts # 源管理
|
|
33
|
+
│ │ └── symlink.ts # 符号链接创建
|
|
34
|
+
│ ├── types/ # TypeScript 类型定义
|
|
35
|
+
│ │ └── config.ts # 核心接口定义
|
|
36
|
+
│ └── utils/
|
|
37
|
+
│ └── path.ts # 路径常量和工具函数
|
|
38
|
+
├── tests/ # 测试文件
|
|
39
|
+
│ ├── core/ # 核心模块测试
|
|
40
|
+
│ └── fixtures/ # 测试固件
|
|
41
|
+
└── dist/ # 编译输出
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 构建和运行
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 安装依赖
|
|
48
|
+
npm install
|
|
49
|
+
|
|
50
|
+
# 开发模式运行
|
|
51
|
+
npm run dev
|
|
52
|
+
|
|
53
|
+
# 编译 TypeScript
|
|
54
|
+
npm run build
|
|
55
|
+
|
|
56
|
+
# 运行编译后的代码
|
|
57
|
+
npm start
|
|
58
|
+
|
|
59
|
+
# 运行测试
|
|
60
|
+
npm test
|
|
61
|
+
|
|
62
|
+
# 运行测试(单次)
|
|
63
|
+
npm run test:run
|
|
64
|
+
|
|
65
|
+
# 全局安装(开发)
|
|
66
|
+
npm link
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 核心命令
|
|
70
|
+
|
|
71
|
+
### 全局配置源管理
|
|
72
|
+
- `tools-cc -s add <name> <path-or-url>` - 添加配置源
|
|
73
|
+
- `tools-cc -s list` - 列出所有配置源
|
|
74
|
+
- `tools-cc -s remove <name>` - 移除配置源
|
|
75
|
+
- `tools-cc -s update [name]` - git pull 更新源代码
|
|
76
|
+
- `tools-cc -s scan` - 扫描目录发现新源
|
|
77
|
+
|
|
78
|
+
### 项目配置
|
|
79
|
+
- `tools-cc use [sources...] [-p tools...]` - 启用配置源并创建符号链接
|
|
80
|
+
- `tools-cc update [sources...]` - 同步源内容到项目
|
|
81
|
+
- `tools-cc list` - 列出已启用的配置源
|
|
82
|
+
- `tools-cc rm <source>` - 禁用配置源
|
|
83
|
+
- `tools-cc status` - 查看项目状态
|
|
84
|
+
|
|
85
|
+
### 配置管理
|
|
86
|
+
- `tools-cc config set <key> <value>` - 设置配置
|
|
87
|
+
- `tools-cc config get <key>` - 获取配置
|
|
88
|
+
- `tools-cc config list` - 查看完整配置
|
|
89
|
+
|
|
90
|
+
## 支持的 AI 工具
|
|
91
|
+
|
|
92
|
+
| 工具 | 链接名称 |
|
|
93
|
+
|------|----------|
|
|
94
|
+
| iflow | `.iflow` |
|
|
95
|
+
| claude | `.claude` |
|
|
96
|
+
| codebuddy | `.codebuddy` |
|
|
97
|
+
| opencode | `.opencode` |
|
|
98
|
+
|
|
99
|
+
## 配置文件位置
|
|
100
|
+
|
|
101
|
+
- **全局配置**: `~/.tools-cc/config.json`
|
|
102
|
+
- **项目配置**: `项目/.toolscc/config.json`
|
|
103
|
+
- **源存储目录**: `~/.tools-cc/sources`(可配置)
|
|
104
|
+
|
|
105
|
+
## 核心类型
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// 源配置
|
|
109
|
+
interface SourceConfig {
|
|
110
|
+
type: 'git' | 'local';
|
|
111
|
+
url?: string;
|
|
112
|
+
path?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 全局配置
|
|
116
|
+
interface GlobalConfig {
|
|
117
|
+
sourcesDir: string;
|
|
118
|
+
sources: Record<string, SourceConfig>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 项目配置
|
|
122
|
+
interface ProjectConfig {
|
|
123
|
+
sources: string[];
|
|
124
|
+
links: string[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Manifest 文件
|
|
128
|
+
interface Manifest {
|
|
129
|
+
name: string;
|
|
130
|
+
version: string;
|
|
131
|
+
skills?: string[];
|
|
132
|
+
commands?: string[];
|
|
133
|
+
agents?: string[];
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 开发约定
|
|
138
|
+
|
|
139
|
+
### 代码风格
|
|
140
|
+
- 使用 TypeScript strict 模式
|
|
141
|
+
- 异步函数使用 async/await
|
|
142
|
+
- 文件操作使用 fs-extra
|
|
143
|
+
- 命令处理函数命名:`handle*`
|
|
144
|
+
|
|
145
|
+
### 测试
|
|
146
|
+
- 测试文件放在 `tests/` 目录,镜像 `src/` 结构
|
|
147
|
+
- 使用 vitest 框架
|
|
148
|
+
- 测试固件放在 `tests/fixtures/`
|
|
149
|
+
|
|
150
|
+
### 命令添加
|
|
151
|
+
1. 在 `src/commands/` 创建或修改命令处理文件
|
|
152
|
+
2. 在 `src/index.ts` 注册新命令
|
|
153
|
+
3. 添加相应的测试
|
|
154
|
+
|
|
155
|
+
### 文档更新 [CHANGELOG.md](CHANGELOG.md)[README.md](README.md)
|
|
156
|
+
- 更新文档时,请确保命令描述清晰、准确,并使用一致的格式
|
|
157
|
+
- 添加新命令时,请更新命令列表和描述
|
|
158
|
+
- 修改现有命令时,请更新相关命令的描述
|
|
159
|
+
|
|
160
|
+
## 注意事项
|
|
161
|
+
|
|
162
|
+
- 此项目运行在 Windows 系统,注意路径分隔符兼容性
|
|
163
|
+
- 符号链接在 Windows 上可能需要管理员权限
|
|
164
|
+
- 全局配置目录位于用户主目录下的 `.tools-cc`
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# 变更日志
|
|
2
|
+
|
|
3
|
+
本项目的所有重要变更都将记录在此文件中。
|
|
4
|
+
|
|
5
|
+
## [1.0.3] - 2026-02-26
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- 项目配置文件位置从 `tools-cc.json` 改为 `.toolscc/config.json`
|
|
9
|
+
- 优化文档命令描述,明确区分全局和项目命令
|
|
10
|
+
|
|
11
|
+
## [1.0.2] - 2026-02-26
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- 新增 `tools-cc -s scan` 命令:扫描 sourcesDir 目录,自动发现并添加配置源
|
|
15
|
+
- 新增 `tools-cc -s upgrade [name]` 命令别名:执行 git pull 更新配置源代码
|
|
16
|
+
- 新增 `tools-cc update [sources...]` 命令:同步配置源内容到项目 .toolscc 目录
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- 优化帮助文档,添加工作流说明和命令对比表
|
|
20
|
+
|
|
21
|
+
## [1.0.1] - 2026-02-25
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- 补充 package.json 仓库相关元数据(repository、bugs、homepage)
|
|
25
|
+
|
|
26
|
+
## [1.0.0] - 2026-02-25
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- 项目初始化,基于 TypeScript + Commander 构建 CLI 框架
|
|
30
|
+
- 全局配置管理模块
|
|
31
|
+
- `tools-cc -c set <key> <value>` 设置配置
|
|
32
|
+
- `tools-cc -c get <key>` 查看配置
|
|
33
|
+
- `tools-cc config list` 查看完整配置
|
|
34
|
+
- 配置源管理模块
|
|
35
|
+
- `tools-cc -s add <name> <path-or-url>` 添加配置源(支持 Git URL 和本地路径)
|
|
36
|
+
- `tools-cc -s list` 列出所有配置源
|
|
37
|
+
- `tools-cc -s remove <name>` 移除配置源
|
|
38
|
+
- `tools-cc -s update [name]` 更新配置源(git pull)
|
|
39
|
+
- 项目管理模块
|
|
40
|
+
- `tools-cc use [sources...]` 启用配置源并创建符号链接
|
|
41
|
+
- `tools-cc list` 列出已启用的配置源
|
|
42
|
+
- `tools-cc rm <source>` 禁用配置源
|
|
43
|
+
- `tools-cc status` 查看项目状态
|
|
44
|
+
- 符号链接管理模块(支持 Windows Junction)
|
|
45
|
+
- Manifest 加载和扫描模块
|
|
46
|
+
- 中英双语帮助信息 (`tools-cc help`)
|
|
47
|
+
- 单元测试覆盖(vitest)
|
|
48
|
+
|
|
49
|
+
### Supported Tools
|
|
50
|
+
| 工具 | 链接名称 |
|
|
51
|
+
|------|----------|
|
|
52
|
+
| iflow | `.iflow` |
|
|
53
|
+
| claude | `.claude` |
|
|
54
|
+
| codebuddy | `.codebuddy` |
|
|
55
|
+
| opencode | `.opencode` |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 版本说明
|
|
60
|
+
|
|
61
|
+
- **主版本号**: 不兼容的 API 变更
|
|
62
|
+
- **次版本号**: 向后兼容的功能新增
|
|
63
|
+
- **修订号**: 向后兼容的问题修复
|
package/{readme.md → README.md}
RENAMED
|
@@ -26,6 +26,9 @@ tools-cc -c set sourcesDir D:/skills-hub-sources
|
|
|
26
26
|
tools-cc -s add my-skills https://github.com/user/my-skills.git
|
|
27
27
|
tools-cc -s add local-skills D:/path/to/local-skills
|
|
28
28
|
|
|
29
|
+
# 或者扫描源目录自动发现并添加配置
|
|
30
|
+
tools-cc -s scan
|
|
31
|
+
|
|
29
32
|
# 3. 查看已添加的配置源
|
|
30
33
|
tools-cc -s list
|
|
31
34
|
|
|
@@ -39,32 +42,65 @@ tools-cc status
|
|
|
39
42
|
# 6. 查看已启用的配置源
|
|
40
43
|
tools-cc list
|
|
41
44
|
|
|
42
|
-
# 7.
|
|
45
|
+
# 7. 更新配置源(从源目录同步最新内容到项目)
|
|
46
|
+
tools-cc update my-skills
|
|
47
|
+
tools-cc update # 更新全部
|
|
48
|
+
|
|
49
|
+
# 8. 移除配置源
|
|
43
50
|
tools-cc rm my-skills
|
|
44
51
|
```
|
|
45
52
|
|
|
53
|
+
## 工作流说明
|
|
54
|
+
|
|
55
|
+
### 全局配置源管理 vs 项目配置
|
|
56
|
+
|
|
57
|
+
| 命令 | 作用域 | 说明 |
|
|
58
|
+
|------|--------|------|
|
|
59
|
+
| `tools-cc -s add/remove/list` | 全局 | 管理全局配置源 |
|
|
60
|
+
| `tools-cc -s update/upgrade` | 全局 | git pull 更新源代码 |
|
|
61
|
+
| `tools-cc -s scan` | 全局 | 扫描目录发现新源 |
|
|
62
|
+
| `tools-cc use/rm` | 项目 | 在项目中启用/禁用源 |
|
|
63
|
+
| `tools-cc update` | 项目 | 同步源内容到项目 |
|
|
64
|
+
|
|
65
|
+
### 典型工作流
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 场景1: 配置源有更新,需要同步到项目
|
|
69
|
+
tools-cc -s upgrade my-skills # 1. git pull 更新源代码
|
|
70
|
+
cd my-project
|
|
71
|
+
tools-cc update my-skills # 2. 同步到项目 .toolscc 目录
|
|
72
|
+
|
|
73
|
+
# 场景2: 批量更新所有源
|
|
74
|
+
tools-cc -s upgrade # 1. 更新所有 git 源
|
|
75
|
+
cd my-project
|
|
76
|
+
tools-cc update # 2. 同步所有源到项目
|
|
77
|
+
```
|
|
78
|
+
|
|
46
79
|
## 命令列表
|
|
47
80
|
|
|
48
81
|
### Source 管理
|
|
49
82
|
|
|
50
83
|
```bash
|
|
51
84
|
# 快捷方式 (-s)
|
|
52
|
-
tools-cc -s add <name> <path-or-url> #
|
|
85
|
+
tools-cc -s add <name> <path-or-url> # 添加配置源(Git URL 或本地路径)
|
|
53
86
|
tools-cc -s list # 列出所有配置源 (缩写: -s ls)
|
|
54
87
|
tools-cc -s remove <name> # 移除配置源 (缩写: -s rm)
|
|
55
|
-
tools-cc -s update [name] #
|
|
88
|
+
tools-cc -s update [name] # git pull 更新配置源代码 (缩写: -s up, -s upgrade)
|
|
89
|
+
tools-cc -s scan # 扫描 sourcesDir 目录,自动发现并添加配置源
|
|
56
90
|
|
|
57
91
|
# 完整命令 (sources)
|
|
58
92
|
tools-cc sources add <name> <path-or-url> # 添加配置源
|
|
59
93
|
tools-cc sources list # 列出所有配置源 (缩写: sources ls)
|
|
60
94
|
tools-cc sources remove <name> # 移除配置源 (缩写: sources rm)
|
|
61
|
-
tools-cc sources update [name] #
|
|
95
|
+
tools-cc sources update [name] # git pull 更新配置源代码 (缩写: sources up, sources upgrade)
|
|
96
|
+
tools-cc sources scan # 扫描 sourcesDir 目录,自动发现并添加配置源
|
|
62
97
|
```
|
|
63
98
|
|
|
64
99
|
### 项目配置
|
|
65
100
|
|
|
66
101
|
```bash
|
|
67
|
-
tools-cc use [sources...] [-p tools...] #
|
|
102
|
+
tools-cc use [sources...] [-p tools...] # 启用配置源并创建符号链接
|
|
103
|
+
tools-cc update [sources...] # 同步配置源内容到项目 .toolscc 目录
|
|
68
104
|
tools-cc list # 列出已启用的配置源
|
|
69
105
|
tools-cc rm <source> # 禁用配置源
|
|
70
106
|
tools-cc status # 查看项目状态
|
|
@@ -137,6 +173,7 @@ my-skills/
|
|
|
137
173
|
```
|
|
138
174
|
my-project/
|
|
139
175
|
├── .toolscc/ # 实际内容目录
|
|
176
|
+
│ ├── config.json # 项目配置
|
|
140
177
|
│ ├── skills/ # 扁平化,带来源前缀
|
|
141
178
|
│ │ └── my-skills-my-skill/
|
|
142
179
|
│ ├── commands/
|
|
@@ -144,8 +181,7 @@ my-project/
|
|
|
144
181
|
│ └── agents/
|
|
145
182
|
│ └── my-skills/
|
|
146
183
|
├── .iflow -> .toolscc # 符号链接
|
|
147
|
-
|
|
148
|
-
└── tools-cc.json # 项目配置
|
|
184
|
+
└── .claude -> .toolscc
|
|
149
185
|
```
|
|
150
186
|
|
|
151
187
|
## 配置文件
|
|
@@ -168,7 +204,7 @@ my-project/
|
|
|
168
204
|
}
|
|
169
205
|
```
|
|
170
206
|
|
|
171
|
-
### 项目配置
|
|
207
|
+
### 项目配置 `项目/.toolscc/config.json`
|
|
172
208
|
|
|
173
209
|
```json
|
|
174
210
|
{
|
|
@@ -2,3 +2,4 @@ export declare function handleSourceAdd(name: string, pathOrUrl: string): Promis
|
|
|
2
2
|
export declare function handleSourceList(): Promise<void>;
|
|
3
3
|
export declare function handleSourceRemove(name: string): Promise<void>;
|
|
4
4
|
export declare function handleSourceUpdate(name?: string): Promise<void>;
|
|
5
|
+
export declare function handleSourceScan(): Promise<void>;
|
package/dist/commands/source.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.handleSourceAdd = handleSourceAdd;
|
|
|
7
7
|
exports.handleSourceList = handleSourceList;
|
|
8
8
|
exports.handleSourceRemove = handleSourceRemove;
|
|
9
9
|
exports.handleSourceUpdate = handleSourceUpdate;
|
|
10
|
+
exports.handleSourceScan = handleSourceScan;
|
|
10
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
12
|
const source_1 = require("../core/source");
|
|
12
13
|
const path_1 = require("../utils/path");
|
|
@@ -70,3 +71,31 @@ async function handleSourceUpdate(name) {
|
|
|
70
71
|
console.log(chalk_1.default.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
71
72
|
}
|
|
72
73
|
}
|
|
74
|
+
async function handleSourceScan() {
|
|
75
|
+
try {
|
|
76
|
+
console.log(chalk_1.default.bold('Scanning sources directory...'));
|
|
77
|
+
const result = await (0, source_1.scanSources)(path_1.GLOBAL_CONFIG_DIR);
|
|
78
|
+
if (result.added.length > 0) {
|
|
79
|
+
console.log(chalk_1.default.green(`\n✓ Added ${result.added.length} source(s):`));
|
|
80
|
+
for (const name of result.added) {
|
|
81
|
+
console.log(chalk_1.default.gray(` + ${name}`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (result.updated.length > 0) {
|
|
85
|
+
console.log(chalk_1.default.yellow(`\n⚡ Updated ${result.updated.length} source(s):`));
|
|
86
|
+
for (const name of result.updated) {
|
|
87
|
+
console.log(chalk_1.default.gray(` ~ ${name}`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (result.skipped.length > 0) {
|
|
91
|
+
console.log(chalk_1.default.gray(`\n→ Skipped ${result.skipped.length} existing source(s)`));
|
|
92
|
+
}
|
|
93
|
+
if (result.added.length === 0 && result.updated.length === 0 && result.skipped.length === 0) {
|
|
94
|
+
console.log(chalk_1.default.gray('No sources found in sources directory.'));
|
|
95
|
+
}
|
|
96
|
+
console.log(chalk_1.default.green(`\n✓ Scan complete`));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.log(chalk_1.default.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
100
|
+
}
|
|
101
|
+
}
|
package/dist/commands/use.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ export declare function handleUse(sourceNames: string[], options: {
|
|
|
4
4
|
export declare function handleList(): Promise<void>;
|
|
5
5
|
export declare function handleRemove(sourceName: string): Promise<void>;
|
|
6
6
|
export declare function handleStatus(): Promise<void>;
|
|
7
|
+
export declare function handleProjectUpdate(sourceNames?: string[]): Promise<void>;
|
package/dist/commands/use.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.handleUse = handleUse;
|
|
|
7
7
|
exports.handleList = handleList;
|
|
8
8
|
exports.handleRemove = handleRemove;
|
|
9
9
|
exports.handleStatus = handleStatus;
|
|
10
|
+
exports.handleProjectUpdate = handleProjectUpdate;
|
|
10
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
12
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
13
|
const project_1 = require("../core/project");
|
|
@@ -77,7 +78,7 @@ async function handleUse(sourceNames, options) {
|
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
// 更新项目配置
|
|
80
|
-
const configFile =
|
|
81
|
+
const configFile = (0, path_1.getProjectConfigPath)(projectDir);
|
|
81
82
|
const config = await fs_extra_1.default.readJson(configFile);
|
|
82
83
|
const existingLinks = config.links || [];
|
|
83
84
|
config.links = [...new Set([...existingLinks, ...tools])];
|
|
@@ -116,7 +117,7 @@ async function handleStatus() {
|
|
|
116
117
|
// 检查 sources
|
|
117
118
|
console.log(` Sources: ${sources.length > 0 ? sources.map(s => chalk_1.default.cyan(s)).join(', ') : chalk_1.default.gray('none')}`);
|
|
118
119
|
// 检查 links
|
|
119
|
-
const configFile =
|
|
120
|
+
const configFile = (0, path_1.getProjectConfigPath)(projectDir);
|
|
120
121
|
if (await fs_extra_1.default.pathExists(configFile)) {
|
|
121
122
|
const config = await fs_extra_1.default.readJson(configFile);
|
|
122
123
|
console.log(` Links:`);
|
|
@@ -131,3 +132,40 @@ async function handleStatus() {
|
|
|
131
132
|
}
|
|
132
133
|
console.log();
|
|
133
134
|
}
|
|
135
|
+
async function handleProjectUpdate(sourceNames) {
|
|
136
|
+
const projectDir = process.cwd();
|
|
137
|
+
const configFile = (0, path_1.getProjectConfigPath)(projectDir);
|
|
138
|
+
// 检查项目是否已初始化
|
|
139
|
+
if (!(await fs_extra_1.default.pathExists(configFile))) {
|
|
140
|
+
console.log(chalk_1.default.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const config = await fs_extra_1.default.readJson(configFile);
|
|
144
|
+
let sourcesToUpdate = sourceNames && sourceNames.length > 0
|
|
145
|
+
? sourceNames
|
|
146
|
+
: config.sources || [];
|
|
147
|
+
if (sourcesToUpdate.length === 0) {
|
|
148
|
+
console.log(chalk_1.default.gray('No sources to update.'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// 验证指定的源是否存在于项目配置中
|
|
152
|
+
if (sourceNames && sourceNames.length > 0) {
|
|
153
|
+
const invalidSources = sourceNames.filter((s) => !config.sources.includes(s));
|
|
154
|
+
if (invalidSources.length > 0) {
|
|
155
|
+
console.log(chalk_1.default.yellow(`Sources not in project: ${invalidSources.join(', ')}`));
|
|
156
|
+
}
|
|
157
|
+
sourcesToUpdate = sourcesToUpdate.filter((s) => config.sources.includes(s));
|
|
158
|
+
}
|
|
159
|
+
// 更新每个配置源
|
|
160
|
+
for (const sourceName of sourcesToUpdate) {
|
|
161
|
+
try {
|
|
162
|
+
const sourcePath = await (0, source_1.getSourcePath)(sourceName, path_1.GLOBAL_CONFIG_DIR);
|
|
163
|
+
await (0, project_1.useSource)(sourceName, sourcePath, projectDir);
|
|
164
|
+
console.log(chalk_1.default.green(`✓ Updated source: ${sourceName}`));
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.log(chalk_1.default.red(`✗ Failed to update ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
console.log(chalk_1.default.green(`\n✓ Project update complete`));
|
|
171
|
+
}
|
package/dist/core/source.d.ts
CHANGED
|
@@ -4,3 +4,9 @@ export declare function listSources(configDir: string): Promise<Record<string, S
|
|
|
4
4
|
export declare function removeSource(name: string, configDir: string): Promise<void>;
|
|
5
5
|
export declare function updateSource(name: string, configDir: string): Promise<void>;
|
|
6
6
|
export declare function getSourcePath(name: string, configDir: string): Promise<string>;
|
|
7
|
+
export interface ScanResult {
|
|
8
|
+
added: string[];
|
|
9
|
+
updated: string[];
|
|
10
|
+
skipped: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function scanSources(configDir: string): Promise<ScanResult>;
|
package/dist/core/source.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.listSources = listSources;
|
|
|
8
8
|
exports.removeSource = removeSource;
|
|
9
9
|
exports.updateSource = updateSource;
|
|
10
10
|
exports.getSourcePath = getSourcePath;
|
|
11
|
+
exports.scanSources = scanSources;
|
|
11
12
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
13
|
const path_1 = __importDefault(require("path"));
|
|
13
14
|
const child_process_1 = require("child_process");
|
|
@@ -84,3 +85,80 @@ async function getSourcePath(name, configDir) {
|
|
|
84
85
|
}
|
|
85
86
|
return path_1.default.join(config.sourcesDir, name);
|
|
86
87
|
}
|
|
88
|
+
async function scanSources(configDir) {
|
|
89
|
+
const config = await (0, config_1.loadGlobalConfig)(configDir);
|
|
90
|
+
const result = { added: [], updated: [], skipped: [] };
|
|
91
|
+
// 确保 sourcesDir 存在
|
|
92
|
+
if (!(await fs_extra_1.default.pathExists(config.sourcesDir))) {
|
|
93
|
+
await fs_extra_1.default.ensureDir(config.sourcesDir);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
// 获取 sourcesDir 下的所有文件夹
|
|
97
|
+
const entries = await fs_extra_1.default.readdir(config.sourcesDir, { withFileTypes: true });
|
|
98
|
+
const directories = entries
|
|
99
|
+
.filter(entry => entry.isDirectory())
|
|
100
|
+
.map(entry => entry.name);
|
|
101
|
+
// 检查每个 git source 的克隆目录是否还存在
|
|
102
|
+
for (const [name, source] of Object.entries(config.sources)) {
|
|
103
|
+
if (source.type === 'git') {
|
|
104
|
+
const cloneDir = path_1.default.join(config.sourcesDir, name);
|
|
105
|
+
if (!(await fs_extra_1.default.pathExists(cloneDir))) {
|
|
106
|
+
// 克隆目录不存在,从配置中移除
|
|
107
|
+
delete config.sources[name];
|
|
108
|
+
console.log(`Removed missing git source: ${name}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// 扫描目录并更新配置
|
|
113
|
+
for (const dirName of directories) {
|
|
114
|
+
const dirPath = path_1.default.join(config.sourcesDir, dirName);
|
|
115
|
+
const existingSource = config.sources[dirName];
|
|
116
|
+
// 检查是否是 git 仓库
|
|
117
|
+
const isGitRepo = await fs_extra_1.default.pathExists(path_1.default.join(dirPath, '.git'));
|
|
118
|
+
if (existingSource) {
|
|
119
|
+
// 已存在配置
|
|
120
|
+
if (existingSource.type === 'git' && isGitRepo) {
|
|
121
|
+
result.skipped.push(dirName);
|
|
122
|
+
}
|
|
123
|
+
else if (existingSource.type === 'local' && existingSource.path === dirPath) {
|
|
124
|
+
result.skipped.push(dirName);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// 配置不一致,更新为当前状态
|
|
128
|
+
if (isGitRepo) {
|
|
129
|
+
// 尝试获取远程 URL
|
|
130
|
+
try {
|
|
131
|
+
const remoteUrl = (0, child_process_1.execSync)(`git -C "${dirPath}" config --get remote.origin.url`, { encoding: 'utf-8' }).trim();
|
|
132
|
+
config.sources[dirName] = { type: 'git', url: remoteUrl };
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
140
|
+
}
|
|
141
|
+
result.updated.push(dirName);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// 新发现的目录,添加到配置
|
|
146
|
+
if (isGitRepo) {
|
|
147
|
+
// 尝试获取远程 URL
|
|
148
|
+
try {
|
|
149
|
+
const remoteUrl = (0, child_process_1.execSync)(`git -C "${dirPath}" config --get remote.origin.url`, { encoding: 'utf-8' }).trim();
|
|
150
|
+
config.sources[dirName] = { type: 'git', url: remoteUrl };
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
158
|
+
}
|
|
159
|
+
result.added.push(dirName);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
await (0, config_1.saveGlobalConfig)(config, configDir);
|
|
163
|
+
return result;
|
|
164
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -42,10 +42,17 @@ sourceCmd
|
|
|
42
42
|
sourceCmd
|
|
43
43
|
.command('update [name]')
|
|
44
44
|
.alias('up')
|
|
45
|
-
.
|
|
45
|
+
.alias('upgrade')
|
|
46
|
+
.description('Update source(s) with git pull')
|
|
46
47
|
.action(async (name) => {
|
|
47
48
|
await (0, source_1.handleSourceUpdate)(name);
|
|
48
49
|
});
|
|
50
|
+
sourceCmd
|
|
51
|
+
.command('scan')
|
|
52
|
+
.description('Scan sources directory and update configuration')
|
|
53
|
+
.action(async () => {
|
|
54
|
+
await (0, source_1.handleSourceScan)();
|
|
55
|
+
});
|
|
49
56
|
// Config subcommands (full command version)
|
|
50
57
|
const configCmd = program
|
|
51
58
|
.command('config')
|
|
@@ -94,6 +101,12 @@ program
|
|
|
94
101
|
.action(async () => {
|
|
95
102
|
await (0, use_1.handleStatus)();
|
|
96
103
|
});
|
|
104
|
+
program
|
|
105
|
+
.command('update [sources...]')
|
|
106
|
+
.description('Update source(s) in current project')
|
|
107
|
+
.action(async (sources) => {
|
|
108
|
+
await (0, use_1.handleProjectUpdate)(sources);
|
|
109
|
+
});
|
|
97
110
|
// Help command
|
|
98
111
|
program
|
|
99
112
|
.command('help')
|
|
@@ -129,8 +142,12 @@ program
|
|
|
129
142
|
break;
|
|
130
143
|
case 'update':
|
|
131
144
|
case 'up':
|
|
145
|
+
case 'upgrade':
|
|
132
146
|
await (0, source_1.handleSourceUpdate)(args[0]);
|
|
133
147
|
break;
|
|
148
|
+
case 'scan':
|
|
149
|
+
await (0, source_1.handleSourceScan)();
|
|
150
|
+
break;
|
|
134
151
|
default:
|
|
135
152
|
console.log(`Unknown source command: ${cmd}`);
|
|
136
153
|
}
|
package/dist/utils/path.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export declare const DEFAULT_CONFIG: {
|
|
|
4
4
|
sourcesDir: string;
|
|
5
5
|
sources: {};
|
|
6
6
|
};
|
|
7
|
-
export declare function getProjectConfigPath(projectDir: string): string;
|
|
8
7
|
export declare function getToolsccDir(projectDir: string): string;
|
|
8
|
+
export declare function getProjectConfigPath(projectDir: string): string;
|
package/dist/utils/path.js
CHANGED
|
@@ -4,8 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.DEFAULT_CONFIG = exports.GLOBAL_CONFIG_FILE = exports.GLOBAL_CONFIG_DIR = void 0;
|
|
7
|
-
exports.getProjectConfigPath = getProjectConfigPath;
|
|
8
7
|
exports.getToolsccDir = getToolsccDir;
|
|
8
|
+
exports.getProjectConfigPath = getProjectConfigPath;
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
exports.GLOBAL_CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.tools-cc');
|
|
@@ -14,9 +14,9 @@ exports.DEFAULT_CONFIG = {
|
|
|
14
14
|
sourcesDir: path_1.default.join(exports.GLOBAL_CONFIG_DIR, 'sources'),
|
|
15
15
|
sources: {}
|
|
16
16
|
};
|
|
17
|
-
function getProjectConfigPath(projectDir) {
|
|
18
|
-
return path_1.default.join(projectDir, 'tools-cc.json');
|
|
19
|
-
}
|
|
20
17
|
function getToolsccDir(projectDir) {
|
|
21
18
|
return path_1.default.join(projectDir, '.toolscc');
|
|
22
19
|
}
|
|
20
|
+
function getProjectConfigPath(projectDir) {
|
|
21
|
+
return path_1.default.join(getToolsccDir(projectDir), 'config.json');
|
|
22
|
+
}
|
package/package.json
CHANGED
package/src/commands/source.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { addSource, listSources, removeSource, updateSource } from '../core/source';
|
|
2
|
+
import { addSource, listSources, removeSource, updateSource, scanSources } from '../core/source';
|
|
3
3
|
import { GLOBAL_CONFIG_DIR } from '../utils/path';
|
|
4
4
|
|
|
5
5
|
export async function handleSourceAdd(name: string, pathOrUrl: string): Promise<void> {
|
|
@@ -61,3 +61,36 @@ export async function handleSourceUpdate(name?: string): Promise<void> {
|
|
|
61
61
|
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
export async function handleSourceScan(): Promise<void> {
|
|
66
|
+
try {
|
|
67
|
+
console.log(chalk.bold('Scanning sources directory...'));
|
|
68
|
+
const result = await scanSources(GLOBAL_CONFIG_DIR);
|
|
69
|
+
|
|
70
|
+
if (result.added.length > 0) {
|
|
71
|
+
console.log(chalk.green(`\n✓ Added ${result.added.length} source(s):`));
|
|
72
|
+
for (const name of result.added) {
|
|
73
|
+
console.log(chalk.gray(` + ${name}`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (result.updated.length > 0) {
|
|
78
|
+
console.log(chalk.yellow(`\n⚡ Updated ${result.updated.length} source(s):`));
|
|
79
|
+
for (const name of result.updated) {
|
|
80
|
+
console.log(chalk.gray(` ~ ${name}`));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (result.skipped.length > 0) {
|
|
85
|
+
console.log(chalk.gray(`\n→ Skipped ${result.skipped.length} existing source(s)`));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (result.added.length === 0 && result.updated.length === 0 && result.skipped.length === 0) {
|
|
89
|
+
console.log(chalk.gray('No sources found in sources directory.'));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(chalk.green(`\n✓ Scan complete`));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
95
|
+
}
|
|
96
|
+
}
|