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
|
@@ -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,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
|
+
}
|
package/dist/index.d.ts
ADDED
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 @@
|
|
|
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)
|