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.
Files changed (49) hide show
  1. package/dist/commands/config.d.ts +3 -0
  2. package/dist/commands/config.js +56 -0
  3. package/dist/commands/help.d.ts +1 -0
  4. package/dist/commands/help.js +84 -0
  5. package/dist/commands/source.d.ts +4 -0
  6. package/dist/commands/source.js +72 -0
  7. package/dist/commands/use.d.ts +6 -0
  8. package/dist/commands/use.js +133 -0
  9. package/dist/core/config.d.ts +5 -0
  10. package/dist/core/config.js +37 -0
  11. package/dist/core/manifest.d.ts +3 -0
  12. package/dist/core/manifest.js +56 -0
  13. package/dist/core/project.d.ts +4 -0
  14. package/dist/core/project.js +118 -0
  15. package/dist/core/source.d.ts +6 -0
  16. package/dist/core/source.js +86 -0
  17. package/dist/core/symlink.d.ts +3 -0
  18. package/dist/core/symlink.js +56 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +165 -0
  21. package/dist/types/config.d.ts +23 -0
  22. package/dist/types/config.js +2 -0
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/index.js +17 -0
  25. package/dist/utils/path.d.ts +8 -0
  26. package/dist/utils/path.js +22 -0
  27. package/docs/plans/2026-02-25-tools-cc-design.md +195 -0
  28. package/docs/plans/2026-02-25-tools-cc-impl.md +1600 -0
  29. package/package.json +44 -0
  30. package/readme.md +182 -0
  31. package/src/commands/config.ts +50 -0
  32. package/src/commands/help.ts +79 -0
  33. package/src/commands/source.ts +63 -0
  34. package/src/commands/use.ts +147 -0
  35. package/src/core/config.ts +37 -0
  36. package/src/core/manifest.ts +57 -0
  37. package/src/core/project.ts +136 -0
  38. package/src/core/source.ts +100 -0
  39. package/src/core/symlink.ts +56 -0
  40. package/src/index.ts +186 -0
  41. package/src/types/config.ts +27 -0
  42. package/src/types/index.ts +1 -0
  43. package/src/utils/path.ts +18 -0
  44. package/tests/core/config.test.ts +37 -0
  45. package/tests/core/manifest.test.ts +37 -0
  46. package/tests/core/project.test.ts +50 -0
  47. package/tests/core/source.test.ts +75 -0
  48. package/tests/core/symlink.test.ts +39 -0
  49. package/tsconfig.json +17 -0
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.addSource = addSource;
7
+ exports.listSources = listSources;
8
+ exports.removeSource = removeSource;
9
+ exports.updateSource = updateSource;
10
+ exports.getSourcePath = getSourcePath;
11
+ const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const child_process_1 = require("child_process");
14
+ const config_1 = require("./config");
15
+ async function addSource(name, sourcePath, configDir) {
16
+ const config = await (0, config_1.loadGlobalConfig)(configDir);
17
+ // 判断是 git url 还是本地路径
18
+ const isGit = sourcePath.startsWith('http') || sourcePath.startsWith('git@');
19
+ let sourceConfig;
20
+ if (isGit) {
21
+ // Clone git repo
22
+ const cloneDir = path_1.default.join(config.sourcesDir, name);
23
+ console.log(`Cloning ${sourcePath} to ${cloneDir}...`);
24
+ await fs_extra_1.default.ensureDir(config.sourcesDir);
25
+ (0, child_process_1.execSync)(`git clone ${sourcePath} "${cloneDir}"`, { stdio: 'inherit' });
26
+ sourceConfig = { type: 'git', url: sourcePath };
27
+ }
28
+ else {
29
+ // 本地路径
30
+ const absolutePath = path_1.default.resolve(sourcePath);
31
+ if (!(await fs_extra_1.default.pathExists(absolutePath))) {
32
+ throw new Error(`Path does not exist: ${absolutePath}`);
33
+ }
34
+ sourceConfig = { type: 'local', path: absolutePath };
35
+ }
36
+ config.sources[name] = sourceConfig;
37
+ await (0, config_1.saveGlobalConfig)(config, configDir);
38
+ return sourceConfig;
39
+ }
40
+ async function listSources(configDir) {
41
+ const config = await (0, config_1.loadGlobalConfig)(configDir);
42
+ return config.sources;
43
+ }
44
+ async function removeSource(name, configDir) {
45
+ const config = await (0, config_1.loadGlobalConfig)(configDir);
46
+ if (!config.sources[name]) {
47
+ throw new Error(`Source not found: ${name}`);
48
+ }
49
+ const source = config.sources[name];
50
+ // 如果是 git 类型,清理克隆目录
51
+ if (source.type === 'git') {
52
+ const cloneDir = path_1.default.join(config.sourcesDir, name);
53
+ if (await fs_extra_1.default.pathExists(cloneDir)) {
54
+ console.log(`Removing cloned directory: ${cloneDir}`);
55
+ await fs_extra_1.default.remove(cloneDir);
56
+ }
57
+ }
58
+ delete config.sources[name];
59
+ await (0, config_1.saveGlobalConfig)(config, configDir);
60
+ }
61
+ async function updateSource(name, configDir) {
62
+ const config = await (0, config_1.loadGlobalConfig)(configDir);
63
+ const source = config.sources[name];
64
+ if (!source) {
65
+ throw new Error(`Source not found: ${name}`);
66
+ }
67
+ if (source.type === 'git') {
68
+ const cloneDir = path_1.default.join(config.sourcesDir, name);
69
+ console.log(`Updating ${name}...`);
70
+ (0, child_process_1.execSync)(`git -C "${cloneDir}" pull`, { stdio: 'inherit' });
71
+ }
72
+ else {
73
+ console.log(`Source ${name} is local, no update needed.`);
74
+ }
75
+ }
76
+ async function getSourcePath(name, configDir) {
77
+ const config = await (0, config_1.loadGlobalConfig)(configDir);
78
+ const source = config.sources[name];
79
+ if (!source) {
80
+ throw new Error(`Source not found: ${name}`);
81
+ }
82
+ if (source.type === 'local') {
83
+ return source.path;
84
+ }
85
+ return path_1.default.join(config.sourcesDir, name);
86
+ }
@@ -0,0 +1,3 @@
1
+ export declare function createSymlink(target: string, linkPath: string, force?: boolean): Promise<void>;
2
+ export declare function removeSymlink(linkPath: string): Promise<void>;
3
+ export declare function isSymlink(path: string): Promise<boolean>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createSymlink = createSymlink;
7
+ exports.removeSymlink = removeSymlink;
8
+ exports.isSymlink = isSymlink;
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const path_1 = __importDefault(require("path"));
11
+ async function createSymlink(target, linkPath, force = false) {
12
+ // 如果目标已存在
13
+ if (await fs_extra_1.default.pathExists(linkPath)) {
14
+ if (!force) {
15
+ throw new Error(`Path already exists: ${linkPath}. Use force=true to overwrite.`);
16
+ }
17
+ // 检查是否已经是符号链接
18
+ if (await isSymlink(linkPath)) {
19
+ await fs_extra_1.default.remove(linkPath);
20
+ }
21
+ else {
22
+ // 是真实目录,删除
23
+ await fs_extra_1.default.remove(linkPath);
24
+ }
25
+ }
26
+ // 确保目标存在
27
+ if (!(await fs_extra_1.default.pathExists(target))) {
28
+ throw new Error(`Target does not exist: ${target}`);
29
+ }
30
+ // 创建符号链接
31
+ // Windows: 使用 junction (不需要管理员权限)
32
+ // Linux/macOS: 使用 symlink
33
+ const targetPath = path_1.default.resolve(target);
34
+ if (process.platform === 'win32') {
35
+ // Windows: 使用 junction
36
+ await fs_extra_1.default.ensureSymlink(targetPath, linkPath, 'junction');
37
+ }
38
+ else {
39
+ // Linux/macOS: 使用 dir symlink
40
+ await fs_extra_1.default.ensureSymlink(targetPath, linkPath, 'dir');
41
+ }
42
+ }
43
+ async function removeSymlink(linkPath) {
44
+ if (await isSymlink(linkPath)) {
45
+ await fs_extra_1.default.remove(linkPath);
46
+ }
47
+ }
48
+ async function isSymlink(path) {
49
+ try {
50
+ const stats = await fs_extra_1.default.lstat(path);
51
+ return stats.isSymbolicLink();
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const config_1 = require("./commands/config");
6
+ const source_1 = require("./commands/source");
7
+ const use_1 = require("./commands/use");
8
+ const help_1 = require("./commands/help");
9
+ const program = new commander_1.Command();
10
+ program
11
+ .name('tools-cc')
12
+ .description('CLI tool for managing skills/commands/agents across multiple AI coding tools')
13
+ .version('0.0.1');
14
+ // Source management (shortcut options)
15
+ program
16
+ .option('-s, --source <command> [args...]', 'Source management (shortcut)')
17
+ .option('-c, --config <command> [args...]', 'Config management (shortcut)');
18
+ // Source subcommands (full command version)
19
+ const sourceCmd = program
20
+ .command('sources')
21
+ .description('Source management');
22
+ sourceCmd
23
+ .command('add <name> <path-or-url>')
24
+ .description('Add a source')
25
+ .action(async (name, pathOrUrl) => {
26
+ await (0, source_1.handleSourceAdd)(name, pathOrUrl);
27
+ });
28
+ sourceCmd
29
+ .command('list')
30
+ .alias('ls')
31
+ .description('List all sources')
32
+ .action(async () => {
33
+ await (0, source_1.handleSourceList)();
34
+ });
35
+ sourceCmd
36
+ .command('remove <name>')
37
+ .alias('rm')
38
+ .description('Remove a source')
39
+ .action(async (name) => {
40
+ await (0, source_1.handleSourceRemove)(name);
41
+ });
42
+ sourceCmd
43
+ .command('update [name]')
44
+ .alias('up')
45
+ .description('Update source(s)')
46
+ .action(async (name) => {
47
+ await (0, source_1.handleSourceUpdate)(name);
48
+ });
49
+ // Config subcommands (full command version)
50
+ const configCmd = program
51
+ .command('config')
52
+ .description('Config management');
53
+ configCmd
54
+ .command('set <key> <value>')
55
+ .description('Set a config value')
56
+ .action(async (key, value) => {
57
+ await (0, config_1.handleConfigSet)(key, value);
58
+ });
59
+ configCmd
60
+ .command('get <key>')
61
+ .description('Get a config value')
62
+ .action(async (key) => {
63
+ await (0, config_1.handleConfigGet)(key);
64
+ });
65
+ configCmd
66
+ .command('list')
67
+ .description('Show full configuration')
68
+ .action(async () => {
69
+ await (0, config_1.handleConfigList)();
70
+ });
71
+ // Project commands
72
+ program
73
+ .command('use [sources...]')
74
+ .description('Use sources in current project')
75
+ .option('-p, --projects <tools...>', 'Tools to link (iflow, claude, codebuddy, opencode)')
76
+ .action(async (sources, options) => {
77
+ await (0, use_1.handleUse)(sources, options);
78
+ });
79
+ program
80
+ .command('list')
81
+ .description('List sources in use')
82
+ .action(async () => {
83
+ await (0, use_1.handleList)();
84
+ });
85
+ program
86
+ .command('rm <source>')
87
+ .description('Remove a source from project')
88
+ .action(async (source) => {
89
+ await (0, use_1.handleRemove)(source);
90
+ });
91
+ program
92
+ .command('status')
93
+ .description('Show project status')
94
+ .action(async () => {
95
+ await (0, use_1.handleStatus)();
96
+ });
97
+ // Help command
98
+ program
99
+ .command('help')
100
+ .description('Show bilingual help information')
101
+ .action(() => {
102
+ (0, help_1.showHelp)();
103
+ });
104
+ // Main action handler for -s and -c options
105
+ program
106
+ .action(async (options) => {
107
+ // Handle -s/--source
108
+ if (options.source) {
109
+ const [cmd, ...args] = options.source;
110
+ switch (cmd) {
111
+ case 'add':
112
+ if (args.length < 2) {
113
+ console.log('Usage: tools-cc -s add <name> <path-or-url>');
114
+ return;
115
+ }
116
+ await (0, source_1.handleSourceAdd)(args[0], args[1]);
117
+ break;
118
+ case 'list':
119
+ case 'ls':
120
+ await (0, source_1.handleSourceList)();
121
+ break;
122
+ case 'remove':
123
+ case 'rm':
124
+ if (args.length < 1) {
125
+ console.log('Usage: tools-cc -s remove <name>');
126
+ return;
127
+ }
128
+ await (0, source_1.handleSourceRemove)(args[0]);
129
+ break;
130
+ case 'update':
131
+ case 'up':
132
+ await (0, source_1.handleSourceUpdate)(args[0]);
133
+ break;
134
+ default:
135
+ console.log(`Unknown source command: ${cmd}`);
136
+ }
137
+ return;
138
+ }
139
+ // Handle -c/--config
140
+ if (options.config) {
141
+ const [cmd, ...args] = options.config;
142
+ switch (cmd) {
143
+ case 'set':
144
+ if (args.length < 2) {
145
+ console.log('Usage: tools-cc -c set <key> <value>');
146
+ return;
147
+ }
148
+ await (0, config_1.handleConfigSet)(args[0], args[1]);
149
+ break;
150
+ case 'get':
151
+ if (args.length < 1) {
152
+ console.log('Usage: tools-cc -c get <key>');
153
+ return;
154
+ }
155
+ await (0, config_1.handleConfigGet)(args[0]);
156
+ break;
157
+ default:
158
+ console.log(`Unknown config command: ${cmd}`);
159
+ }
160
+ return;
161
+ }
162
+ // No options provided, show help
163
+ program.outputHelp();
164
+ });
165
+ program.parseAsync();
@@ -0,0 +1,23 @@
1
+ export interface SourceConfig {
2
+ type: 'git' | 'local';
3
+ url?: string;
4
+ path?: string;
5
+ }
6
+ export interface GlobalConfig {
7
+ sourcesDir: string;
8
+ sources: Record<string, SourceConfig>;
9
+ }
10
+ export interface ProjectConfig {
11
+ sources: string[];
12
+ links: string[];
13
+ }
14
+ export interface Manifest {
15
+ name: string;
16
+ version: string;
17
+ skills?: string[];
18
+ commands?: string[];
19
+ agents?: string[];
20
+ }
21
+ export interface ToolConfig {
22
+ linkName: string;
23
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export * from './config';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./config"), exports);
@@ -0,0 +1,8 @@
1
+ export declare const GLOBAL_CONFIG_DIR: string;
2
+ export declare const GLOBAL_CONFIG_FILE: string;
3
+ export declare const DEFAULT_CONFIG: {
4
+ sourcesDir: string;
5
+ sources: {};
6
+ };
7
+ export declare function getProjectConfigPath(projectDir: string): string;
8
+ export declare function getToolsccDir(projectDir: string): string;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_CONFIG = exports.GLOBAL_CONFIG_FILE = exports.GLOBAL_CONFIG_DIR = void 0;
7
+ exports.getProjectConfigPath = getProjectConfigPath;
8
+ exports.getToolsccDir = getToolsccDir;
9
+ const os_1 = __importDefault(require("os"));
10
+ const path_1 = __importDefault(require("path"));
11
+ exports.GLOBAL_CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.tools-cc');
12
+ exports.GLOBAL_CONFIG_FILE = path_1.default.join(exports.GLOBAL_CONFIG_DIR, 'config.json');
13
+ exports.DEFAULT_CONFIG = {
14
+ sourcesDir: path_1.default.join(exports.GLOBAL_CONFIG_DIR, 'sources'),
15
+ sources: {}
16
+ };
17
+ function getProjectConfigPath(projectDir) {
18
+ return path_1.default.join(projectDir, 'tools-cc.json');
19
+ }
20
+ function getToolsccDir(projectDir) {
21
+ return path_1.default.join(projectDir, '.toolscc');
22
+ }
@@ -0,0 +1,195 @@
1
+ # tools-cc CLI 设计文档
2
+
3
+ ## 概述
4
+
5
+ tools-cc 是一个用于统一管理多个 AI 编程工具(iflow、claude、codebuddy、opencode 等)的 skills、commands、agents 配置的 CLI 工具。通过符号链接机制,避免在多个工具间重复配置。
6
+
7
+ ## 核心需求
8
+
9
+ - 同时使用多个 AI 编程工具时,避免重复配置 skills/commands/agents
10
+ - 支持从 git 仓库或本地目录安装配置源
11
+ - 支持同时启用多个配置源
12
+ - 全局配置 + 项目级覆盖
13
+ - 自动创建符号链接
14
+
15
+ ## 架构设计
16
+
17
+ ### 目录结构
18
+
19
+ ```
20
+ ~/.tools-cc/ # 全局配置目录
21
+ ├── config.json # 全局配置
22
+ └── cache/ # 缓存(可选)
23
+
24
+ D:/skills-hub-sources/ # 用户自定义的 sources 存储位置
25
+ ├── my-skills/
26
+ │ ├── manifest.json
27
+ │ ├── skills/
28
+ │ ├── commands/
29
+ │ └── agents/
30
+ └── my-skills2/
31
+ ├── manifest.json
32
+ ├── skills/
33
+ ├── commands/
34
+ └── agents/
35
+
36
+ 项目目录/
37
+ ├── .toolscc/ # 实际内容目录
38
+ │ ├── skills/ # 扁平化,带来源前缀
39
+ │ │ ├── my-skills-brainstorming/
40
+ │ │ └── my-skills2-debugging/
41
+ │ ├── commands/
42
+ │ │ ├── my-skills/ # 按来源分子目录
43
+ │ │ └── my-skills2/
44
+ │ └── agents/
45
+ │ ├── my-skills/
46
+ │ └── my-skills2/
47
+ ├── .iflow -> .toolscc # 符号链接
48
+ ├── .claude -> .toolscc
49
+ └── tools-cc.json # 项目配置
50
+ ```
51
+
52
+ ### 关键设计点
53
+
54
+ 1. **skills 目录扁平化**:由于工具只能识别一级目录下的 skills,多个配置源的 skills 使用 `{source-name}-{skill-name}` 命名避免冲突
55
+ 2. **commands/agents 保持层级**:按来源分子目录,因为工具支持多级目录
56
+
57
+ ## 文件格式
58
+
59
+ ### 全局配置 `~/.tools-cc/config.json`
60
+
61
+ ```json
62
+ {
63
+ "sourcesDir": "D:/skills-hub-sources",
64
+ "sources": {
65
+ "my-skills": {
66
+ "type": "git",
67
+ "url": "https://github.com/user/my-skills.git"
68
+ },
69
+ "local-skills": {
70
+ "type": "local",
71
+ "path": "D:/local-skills"
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### 项目配置 `项目/tools-cc.json`
78
+
79
+ ```json
80
+ {
81
+ "sources": ["my-skills", "local-skills"],
82
+ "links": ["iflow", "claude"]
83
+ }
84
+ ```
85
+
86
+ ### 配置源清单 `sources/my-skills/manifest.json`
87
+
88
+ ```json
89
+ {
90
+ "name": "my-skills",
91
+ "version": "1.0.0",
92
+ "skills": ["brainstorming", "debugging"],
93
+ "commands": ["brainstorm", "review"],
94
+ "agents": ["code-reviewer"]
95
+ }
96
+ ```
97
+
98
+ 如果配置源是本地目录且没有 manifest,CLI 会自动扫描目录结构生成。
99
+
100
+ ## CLI 命令
101
+
102
+ ```
103
+ tools-cc [options] <command> [args]
104
+
105
+ # Source 管理
106
+ tools-cc -s add <name> <path-or-url> # 添加配置源
107
+ tools-cc -s list # 列出所有配置源 (-s ls)
108
+ tools-cc -s remove <name> # 移除配置源 (-s rm)
109
+ tools-cc -s update [name] # 更新配置源 (-s up)
110
+
111
+ # 项目配置
112
+ tools-cc use [source-names...] [-p tools...] # 启用配置源并可选创建链接
113
+ tools-cc list # 列出已启用的配置源
114
+ tools-cc rm <name> # 禁用配置源
115
+
116
+ # Config 管理
117
+ tools-cc -c set <key> <value> # 设置配置
118
+ tools-cc -c get <key> # 查看配置
119
+
120
+ # 信息查看
121
+ tools-cc status # 查看项目状态
122
+ ```
123
+
124
+ ## 内置支持的 Tools
125
+
126
+ ```typescript
127
+ const SUPPORTED_TOOLS = {
128
+ iflow: { linkName: '.iflow' },
129
+ claude: { linkName: '.claude' },
130
+ codebuddy: { linkName: '.codebuddy' },
131
+ opencode: { linkName: '.opencode' }
132
+ }
133
+ ```
134
+
135
+ ## 数据流
136
+
137
+ ```
138
+ 用户执行: tools-cc use my-skills -p iflow claude
139
+
140
+ 1. 检查全局配置,确认 my-skills 存在
141
+ 2. 在项目创建 .toolscc/ 目录
142
+ 3. 从 sources/my-skills/ 复制/链接组件:
143
+ - skills/* -> .toolscc/skills/my-skills-*/
144
+ - commands/* -> .toolscc/commands/my-skills/
145
+ - agents/* -> .toolscc/agents/my-skills/
146
+ 4. 创建符号链接:
147
+ - .iflow -> .toolscc
148
+ - .claude -> .toolscc
149
+ 5. 更新项目配置 tools-cc.json
150
+ ```
151
+
152
+ ## 技术栈
153
+
154
+ - **语言**: TypeScript / Node.js
155
+ - **CLI 框架**: commander 或 yargs
156
+ - **交互式 UI**: inquirer
157
+ - **符号链接**: Node.js fs.symlink / fs.symlinkSync
158
+
159
+ ## 使用示例
160
+
161
+ ```bash
162
+ # 1. 设置 sources 存储位置
163
+ tools-cc -c set sourcesDir D:/skills-hub-sources
164
+
165
+ # 2. 添加配置源
166
+ tools-cc -s add my-skills https://github.com/user/my-skills.git
167
+ tools-cc -s add local-skills D:/local-skills
168
+
169
+ # 3. 在项目中启用配置源并创建链接
170
+ cd my-project
171
+ tools-cc use my-skills local-skills -p iflow claude
172
+
173
+ # 4. 查看状态
174
+ tools-cc status
175
+ tools-cc list
176
+
177
+ # 5. 禁用某个配置源
178
+ tools-cc rm local-skills
179
+
180
+ # 6. 更新配置源
181
+ tools-cc -s update my-skills
182
+ ```
183
+
184
+ ## 符号链接处理
185
+
186
+ - **Windows**: 创建 junction 或 symlink(需要管理员权限,或开启开发者模式)
187
+ - **Linux/macOS**: 创建标准符号链接
188
+
189
+ 当目标目录已存在时,提示用户确认后强制覆盖删除。
190
+
191
+ ## 后续扩展
192
+
193
+ - 支持自定义 tool 配置(通过 config 添加新的 tool)
194
+ - 支持配置源版本管理
195
+ - 支持团队共享配置(通过 git 管理 tools-cc.json)