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,3 @@
1
+ export declare function handleConfigSet(key: string, value: string): Promise<void>;
2
+ export declare function handleConfigGet(key: string): Promise<void>;
3
+ export declare function handleConfigList(): Promise<void>;
@@ -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.handleConfigSet = handleConfigSet;
7
+ exports.handleConfigGet = handleConfigGet;
8
+ exports.handleConfigList = handleConfigList;
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const config_1 = require("../core/config");
11
+ const path_1 = require("../utils/path");
12
+ async function handleConfigSet(key, value) {
13
+ const config = await (0, config_1.loadGlobalConfig)(path_1.GLOBAL_CONFIG_DIR);
14
+ if (key === 'sourcesDir') {
15
+ config.sourcesDir = value;
16
+ await (0, config_1.saveGlobalConfig)(config, path_1.GLOBAL_CONFIG_DIR);
17
+ console.log(chalk_1.default.green(`✓ Set sourcesDir to: ${value}`));
18
+ }
19
+ else {
20
+ console.log(chalk_1.default.red(`✗ Unknown config key: ${key}`));
21
+ console.log(chalk_1.default.gray('Available keys: sourcesDir'));
22
+ }
23
+ }
24
+ async function handleConfigGet(key) {
25
+ const config = await (0, config_1.loadGlobalConfig)(path_1.GLOBAL_CONFIG_DIR);
26
+ if (key === 'sourcesDir') {
27
+ console.log(config.sourcesDir);
28
+ }
29
+ else if (key === 'all') {
30
+ console.log(JSON.stringify(config, null, 2));
31
+ }
32
+ else {
33
+ console.log(chalk_1.default.red(`✗ Unknown config key: ${key}`));
34
+ }
35
+ }
36
+ async function handleConfigList() {
37
+ const config = await (0, config_1.loadGlobalConfig)(path_1.GLOBAL_CONFIG_DIR);
38
+ console.log(chalk_1.default.bold('Global Configuration:'));
39
+ console.log(chalk_1.default.gray(` Config directory: ${path_1.GLOBAL_CONFIG_DIR}`));
40
+ console.log(` sourcesDir: ${chalk_1.default.cyan(config.sourcesDir || 'not set')}`);
41
+ if (config.sources && Object.keys(config.sources).length > 0) {
42
+ console.log(` sources:`);
43
+ for (const [name, source] of Object.entries(config.sources)) {
44
+ console.log(` ${chalk_1.default.cyan(name)} (${source.type})`);
45
+ if (source.type === 'git') {
46
+ console.log(chalk_1.default.gray(` URL: ${source.url}`));
47
+ }
48
+ else {
49
+ console.log(chalk_1.default.gray(` Path: ${source.path}`));
50
+ }
51
+ }
52
+ }
53
+ else {
54
+ console.log(` sources: ${chalk_1.default.gray('none')}`);
55
+ }
56
+ }
@@ -0,0 +1 @@
1
+ export declare function showHelp(): void;
@@ -0,0 +1,84 @@
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.showHelp = showHelp;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ function showHelp() {
9
+ console.log(`
10
+ ${chalk_1.default.bold.cyan('═══════════════════════════════════════════════════════════════════════════')}
11
+ ${chalk_1.default.bold('tools-cc')} - AI Coding Tools Configuration Manager
12
+ ${chalk_1.default.bold('tools-cc')} - AI 编程工具配置管理器
13
+ ${chalk_1.default.bold.cyan('═══════════════════════════════════════════════════════════════════════════')}
14
+
15
+ ${chalk_1.default.bold('DESCRIPTION / 描述')}
16
+ A CLI tool for managing skills/commands/agents configurations across multiple
17
+ AI coding tools (iflow, claude, codebuddy, opencode, etc.) via symlinks.
18
+
19
+ 一个用于统一管理多个 AI 编程工具配置的命令行工具,通过符号链接机制避免重复配置。
20
+
21
+ ${chalk_1.default.bold('USAGE / 用法')}
22
+ tools-cc <command> [options]
23
+ tools-cc <shortcut> <subcommand> [args]
24
+
25
+ ${chalk_1.default.bold('COMMANDS / 命令')}
26
+
27
+ ${chalk_1.default.cyan('Source Management / 配置源管理')}
28
+ tools-cc sources add <name> <path-or-url> Add a source / 添加配置源
29
+ tools-cc sources list, ls List all sources / 列出所有配置源
30
+ tools-cc sources remove, rm <name> Remove a source / 移除配置源
31
+ tools-cc sources update, up [name] Update source(s) / 更新配置源
32
+
33
+ ${chalk_1.default.gray('Shortcut: -s')} e.g., tools-cc -s add my-skills https://github.com/user/skills.git
34
+
35
+ ${chalk_1.default.cyan('Config Management / 配置管理')}
36
+ tools-cc config set <key> <value> Set config value / 设置配置值
37
+ tools-cc config get <key> Get config value / 获取配置值
38
+ tools-cc config list Show full config / 显示完整配置
39
+
40
+ ${chalk_1.default.gray('Shortcut: -c')} e.g., tools-cc -c set sourcesDir D:/skills
41
+
42
+ ${chalk_1.default.cyan('Project Commands / 项目命令')}
43
+ tools-cc use [sources...] [-p tools...] Use sources in project / 在项目中启用配置源
44
+ tools-cc list List used sources / 列出已启用的配置源
45
+ tools-cc rm <source> Remove source from project / 禁用配置源
46
+ tools-cc status Show project status / 显示项目状态
47
+
48
+ ${chalk_1.default.cyan('Help / 帮助')}
49
+ tools-cc help Show this help / 显示此帮助信息
50
+ tools-cc --help, -h Show command help / 显示命令帮助
51
+ tools-cc --version, -V Show version / 显示版本号
52
+
53
+ ${chalk_1.default.bold('SUPPORTED TOOLS / 支持的工具')}
54
+ iflow → .iflow
55
+ claude → .claude
56
+ codebuddy → .codebuddy
57
+ opencode → .opencode
58
+
59
+ ${chalk_1.default.bold('EXAMPLES / 示例')}
60
+ ${chalk_1.default.gray('# Add a git source / 添加 Git 配置源')}
61
+ tools-cc sources add my-skills https://github.com/user/my-skills.git
62
+
63
+ ${chalk_1.default.gray('# Add a local source / 添加本地配置源')}
64
+ tools-cc sources add local-skills D:/path/to/local-skills
65
+
66
+ ${chalk_1.default.gray('# List all sources / 列出所有配置源')}
67
+ tools-cc sources list
68
+
69
+ ${chalk_1.default.gray('# Use sources in project / 在项目中启用配置源')}
70
+ tools-cc use my-skills -p iflow claude
71
+
72
+ ${chalk_1.default.gray('# Check project status / 检查项目状态')}
73
+ tools-cc status
74
+
75
+ ${chalk_1.default.gray('# Show full configuration / 显示完整配置')}
76
+ tools-cc config list
77
+
78
+ ${chalk_1.default.bold('MORE INFO / 更多信息')}
79
+ GitHub: https://github.com/user/tools-cc
80
+ Docs: https://github.com/user/tools-cc#readme
81
+
82
+ ${chalk_1.default.bold.cyan('═══════════════════════════════════════════════════════════════════════════')}
83
+ `);
84
+ }
@@ -0,0 +1,4 @@
1
+ export declare function handleSourceAdd(name: string, pathOrUrl: string): Promise<void>;
2
+ export declare function handleSourceList(): Promise<void>;
3
+ export declare function handleSourceRemove(name: string): Promise<void>;
4
+ export declare function handleSourceUpdate(name?: string): Promise<void>;
@@ -0,0 +1,72 @@
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.handleSourceAdd = handleSourceAdd;
7
+ exports.handleSourceList = handleSourceList;
8
+ exports.handleSourceRemove = handleSourceRemove;
9
+ exports.handleSourceUpdate = handleSourceUpdate;
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const source_1 = require("../core/source");
12
+ const path_1 = require("../utils/path");
13
+ async function handleSourceAdd(name, pathOrUrl) {
14
+ try {
15
+ const result = await (0, source_1.addSource)(name, pathOrUrl, path_1.GLOBAL_CONFIG_DIR);
16
+ console.log(chalk_1.default.green(`✓ Added source: ${name}`));
17
+ console.log(chalk_1.default.gray(` Type: ${result.type}`));
18
+ if (result.type === 'git') {
19
+ console.log(chalk_1.default.gray(` URL: ${result.url}`));
20
+ }
21
+ else {
22
+ console.log(chalk_1.default.gray(` Path: ${result.path}`));
23
+ }
24
+ }
25
+ catch (error) {
26
+ console.log(chalk_1.default.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
27
+ }
28
+ }
29
+ async function handleSourceList() {
30
+ const sources = await (0, source_1.listSources)(path_1.GLOBAL_CONFIG_DIR);
31
+ const entries = Object.entries(sources);
32
+ if (entries.length === 0) {
33
+ console.log(chalk_1.default.gray('No sources configured.'));
34
+ return;
35
+ }
36
+ console.log(chalk_1.default.bold('Configured sources:'));
37
+ for (const [name, config] of entries) {
38
+ console.log(` ${chalk_1.default.cyan(name)} (${config.type})`);
39
+ if (config.type === 'git') {
40
+ console.log(chalk_1.default.gray(` ${config.url}`));
41
+ }
42
+ else {
43
+ console.log(chalk_1.default.gray(` ${config.path}`));
44
+ }
45
+ }
46
+ }
47
+ async function handleSourceRemove(name) {
48
+ try {
49
+ await (0, source_1.removeSource)(name, path_1.GLOBAL_CONFIG_DIR);
50
+ console.log(chalk_1.default.green(`✓ Removed source: ${name}`));
51
+ }
52
+ catch (error) {
53
+ console.log(chalk_1.default.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
54
+ }
55
+ }
56
+ async function handleSourceUpdate(name) {
57
+ try {
58
+ if (name) {
59
+ await (0, source_1.updateSource)(name, path_1.GLOBAL_CONFIG_DIR);
60
+ }
61
+ else {
62
+ const sources = await (0, source_1.listSources)(path_1.GLOBAL_CONFIG_DIR);
63
+ for (const sourceName of Object.keys(sources)) {
64
+ await (0, source_1.updateSource)(sourceName, path_1.GLOBAL_CONFIG_DIR);
65
+ }
66
+ }
67
+ console.log(chalk_1.default.green(`✓ Update complete`));
68
+ }
69
+ catch (error) {
70
+ console.log(chalk_1.default.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
71
+ }
72
+ }
@@ -0,0 +1,6 @@
1
+ export declare function handleUse(sourceNames: string[], options: {
2
+ projects?: string[];
3
+ }): Promise<void>;
4
+ export declare function handleList(): Promise<void>;
5
+ export declare function handleRemove(sourceName: string): Promise<void>;
6
+ export declare function handleStatus(): Promise<void>;
@@ -0,0 +1,133 @@
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.handleUse = handleUse;
7
+ exports.handleList = handleList;
8
+ exports.handleRemove = handleRemove;
9
+ exports.handleStatus = handleStatus;
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const inquirer_1 = __importDefault(require("inquirer"));
12
+ const project_1 = require("../core/project");
13
+ const source_1 = require("../core/source");
14
+ const symlink_1 = require("../core/symlink");
15
+ const path_1 = require("../utils/path");
16
+ const fs_extra_1 = __importDefault(require("fs-extra"));
17
+ const path_2 = __importDefault(require("path"));
18
+ const SUPPORTED_TOOLS = {
19
+ iflow: '.iflow',
20
+ claude: '.claude',
21
+ codebuddy: '.codebuddy',
22
+ opencode: '.opencode'
23
+ };
24
+ async function handleUse(sourceNames, options) {
25
+ const projectDir = process.cwd();
26
+ // 如果没有指定 source,进入交互模式
27
+ if (sourceNames.length === 0) {
28
+ const sources = await (0, source_1.listSources)(path_1.GLOBAL_CONFIG_DIR);
29
+ const sourceList = Object.keys(sources);
30
+ if (sourceList.length === 0) {
31
+ console.log(chalk_1.default.yellow('No sources configured. Use `tools-cc -s add` to add one.'));
32
+ return;
33
+ }
34
+ const answers = await inquirer_1.default.prompt([
35
+ {
36
+ type: 'checkbox',
37
+ name: 'selectedSources',
38
+ message: 'Select sources to use:',
39
+ choices: sourceList
40
+ }
41
+ ]);
42
+ sourceNames = answers.selectedSources;
43
+ }
44
+ if (sourceNames.length === 0) {
45
+ console.log(chalk_1.default.gray('No sources selected.'));
46
+ return;
47
+ }
48
+ // 初始化项目
49
+ await (0, project_1.initProject)(projectDir);
50
+ // 启用每个配置源
51
+ for (const sourceName of sourceNames) {
52
+ try {
53
+ const sourcePath = await (0, source_1.getSourcePath)(sourceName, path_1.GLOBAL_CONFIG_DIR);
54
+ await (0, project_1.useSource)(sourceName, sourcePath, projectDir);
55
+ console.log(chalk_1.default.green(`✓ Using source: ${sourceName}`));
56
+ }
57
+ catch (error) {
58
+ console.log(chalk_1.default.red(`✗ Failed to use ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
59
+ }
60
+ }
61
+ // 创建符号链接
62
+ const tools = options.projects || Object.keys(SUPPORTED_TOOLS);
63
+ const toolsccDir = (0, path_1.getToolsccDir)(projectDir);
64
+ for (const tool of tools) {
65
+ const linkName = SUPPORTED_TOOLS[tool];
66
+ if (!linkName) {
67
+ console.log(chalk_1.default.yellow(`Unknown tool: ${tool}`));
68
+ continue;
69
+ }
70
+ const linkPath = path_2.default.join(projectDir, linkName);
71
+ try {
72
+ await (0, symlink_1.createSymlink)(toolsccDir, linkPath, true);
73
+ console.log(chalk_1.default.green(`✓ Linked: ${linkName} -> .toolscc`));
74
+ }
75
+ catch (error) {
76
+ console.log(chalk_1.default.red(`✗ Failed to link ${linkName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
77
+ }
78
+ }
79
+ // 更新项目配置
80
+ const configFile = path_2.default.join(projectDir, 'tools-cc.json');
81
+ const config = await fs_extra_1.default.readJson(configFile);
82
+ const existingLinks = config.links || [];
83
+ config.links = [...new Set([...existingLinks, ...tools])];
84
+ await fs_extra_1.default.writeJson(configFile, config, { spaces: 2 });
85
+ }
86
+ async function handleList() {
87
+ const projectDir = process.cwd();
88
+ const sources = await (0, project_1.listUsedSources)(projectDir);
89
+ if (sources.length === 0) {
90
+ console.log(chalk_1.default.gray('No sources in use. Run `tools-cc use <source-name>` to add one.'));
91
+ return;
92
+ }
93
+ console.log(chalk_1.default.bold('Sources in use:'));
94
+ for (const source of sources) {
95
+ console.log(` ${chalk_1.default.cyan(source)}`);
96
+ }
97
+ }
98
+ async function handleRemove(sourceName) {
99
+ const projectDir = process.cwd();
100
+ try {
101
+ await (0, project_1.unuseSource)(sourceName, projectDir);
102
+ console.log(chalk_1.default.green(`✓ Removed source: ${sourceName}`));
103
+ }
104
+ catch (error) {
105
+ console.log(chalk_1.default.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
106
+ }
107
+ }
108
+ async function handleStatus() {
109
+ const projectDir = process.cwd();
110
+ const sources = await (0, project_1.listUsedSources)(projectDir);
111
+ console.log(chalk_1.default.bold('\nProject Status:'));
112
+ console.log(chalk_1.default.gray(` Directory: ${projectDir}`));
113
+ // 检查 .toolscc
114
+ const toolsccDir = (0, path_1.getToolsccDir)(projectDir);
115
+ console.log(` .toolscc: ${await fs_extra_1.default.pathExists(toolsccDir) ? chalk_1.default.green('exists') : chalk_1.default.red('not found')}`);
116
+ // 检查 sources
117
+ console.log(` Sources: ${sources.length > 0 ? sources.map(s => chalk_1.default.cyan(s)).join(', ') : chalk_1.default.gray('none')}`);
118
+ // 检查 links
119
+ const configFile = path_2.default.join(projectDir, 'tools-cc.json');
120
+ if (await fs_extra_1.default.pathExists(configFile)) {
121
+ const config = await fs_extra_1.default.readJson(configFile);
122
+ console.log(` Links:`);
123
+ for (const tool of config.links || []) {
124
+ const linkName = SUPPORTED_TOOLS[tool];
125
+ if (!linkName)
126
+ continue;
127
+ const linkPath = path_2.default.join(projectDir, linkName);
128
+ const isLink = await (0, symlink_1.isSymlink)(linkPath);
129
+ console.log(` ${tool}: ${isLink ? chalk_1.default.green('linked') : chalk_1.default.red('not linked')}`);
130
+ }
131
+ }
132
+ console.log();
133
+ }
@@ -0,0 +1,5 @@
1
+ import { GlobalConfig, ProjectConfig } from '../types';
2
+ export declare function loadGlobalConfig(configDir: string): Promise<GlobalConfig>;
3
+ export declare function saveGlobalConfig(config: GlobalConfig, configDir: string): Promise<void>;
4
+ export declare function loadProjectConfig(projectDir: string): Promise<ProjectConfig | null>;
5
+ export declare function saveProjectConfig(config: ProjectConfig, projectDir: string): Promise<void>;
@@ -0,0 +1,37 @@
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.loadGlobalConfig = loadGlobalConfig;
7
+ exports.saveGlobalConfig = saveGlobalConfig;
8
+ exports.loadProjectConfig = loadProjectConfig;
9
+ exports.saveProjectConfig = saveProjectConfig;
10
+ const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const path_2 = require("../utils/path");
13
+ async function loadGlobalConfig(configDir) {
14
+ const configFile = path_1.default.join(configDir, 'config.json');
15
+ if (!(await fs_extra_1.default.pathExists(configFile))) {
16
+ await fs_extra_1.default.ensureDir(configDir);
17
+ await fs_extra_1.default.writeJson(configFile, path_2.DEFAULT_CONFIG, { spaces: 2 });
18
+ return path_2.DEFAULT_CONFIG;
19
+ }
20
+ return await fs_extra_1.default.readJson(configFile);
21
+ }
22
+ async function saveGlobalConfig(config, configDir) {
23
+ const configFile = path_1.default.join(configDir, 'config.json');
24
+ await fs_extra_1.default.ensureDir(configDir);
25
+ await fs_extra_1.default.writeJson(configFile, config, { spaces: 2 });
26
+ }
27
+ async function loadProjectConfig(projectDir) {
28
+ const configFile = (0, path_2.getProjectConfigPath)(projectDir);
29
+ if (!(await fs_extra_1.default.pathExists(configFile))) {
30
+ return null;
31
+ }
32
+ return await fs_extra_1.default.readJson(configFile);
33
+ }
34
+ async function saveProjectConfig(config, projectDir) {
35
+ const configFile = (0, path_2.getProjectConfigPath)(projectDir);
36
+ await fs_extra_1.default.writeJson(configFile, config, { spaces: 2 });
37
+ }
@@ -0,0 +1,3 @@
1
+ import { Manifest } from '../types';
2
+ export declare function loadManifest(sourceDir: string): Promise<Manifest>;
3
+ export declare function scanSource(sourceDir: string): Promise<Manifest>;
@@ -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.loadManifest = loadManifest;
7
+ exports.scanSource = scanSource;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const path_1 = __importDefault(require("path"));
10
+ async function loadManifest(sourceDir) {
11
+ const manifestPath = path_1.default.join(sourceDir, 'manifest.json');
12
+ if (await fs_extra_1.default.pathExists(manifestPath)) {
13
+ try {
14
+ return await fs_extra_1.default.readJson(manifestPath);
15
+ }
16
+ catch (error) {
17
+ throw new Error(`Failed to parse manifest.json: ${error instanceof Error ? error.message : 'Invalid JSON'}`);
18
+ }
19
+ }
20
+ return scanSource(sourceDir);
21
+ }
22
+ async function scanSource(sourceDir) {
23
+ const name = path_1.default.basename(sourceDir);
24
+ const manifest = {
25
+ name,
26
+ version: '0.0.0',
27
+ skills: [],
28
+ commands: [],
29
+ agents: []
30
+ };
31
+ // Scan skills
32
+ const skillsDir = path_1.default.join(sourceDir, 'skills');
33
+ if (await fs_extra_1.default.pathExists(skillsDir)) {
34
+ const entries = await fs_extra_1.default.readdir(skillsDir, { withFileTypes: true });
35
+ manifest.skills = entries
36
+ .filter(e => e.isDirectory())
37
+ .map(e => e.name);
38
+ }
39
+ // Scan commands
40
+ const commandsDir = path_1.default.join(sourceDir, 'commands');
41
+ if (await fs_extra_1.default.pathExists(commandsDir)) {
42
+ const entries = await fs_extra_1.default.readdir(commandsDir, { withFileTypes: true });
43
+ manifest.commands = entries
44
+ .filter(e => e.isFile() && e.name.endsWith('.md'))
45
+ .map(e => e.name.replace('.md', ''));
46
+ }
47
+ // Scan agents
48
+ const agentsDir = path_1.default.join(sourceDir, 'agents');
49
+ if (await fs_extra_1.default.pathExists(agentsDir)) {
50
+ const entries = await fs_extra_1.default.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
+ return manifest;
56
+ }
@@ -0,0 +1,4 @@
1
+ export declare function initProject(projectDir: string): Promise<void>;
2
+ export declare function useSource(sourceName: string, sourceDir: string, projectDir: string): Promise<void>;
3
+ export declare function unuseSource(sourceName: string, projectDir: string): Promise<void>;
4
+ export declare function listUsedSources(projectDir: string): Promise<string[]>;
@@ -0,0 +1,118 @@
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.initProject = initProject;
7
+ exports.useSource = useSource;
8
+ exports.unuseSource = unuseSource;
9
+ exports.listUsedSources = listUsedSources;
10
+ const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const manifest_1 = require("./manifest");
13
+ const path_2 = require("../utils/path");
14
+ async function initProject(projectDir) {
15
+ const toolsccDir = (0, path_2.getToolsccDir)(projectDir);
16
+ const configFile = (0, path_2.getProjectConfigPath)(projectDir);
17
+ // Create .toolscc directory structure
18
+ await fs_extra_1.default.ensureDir(path_1.default.join(toolsccDir, 'skills'));
19
+ await fs_extra_1.default.ensureDir(path_1.default.join(toolsccDir, 'commands'));
20
+ await fs_extra_1.default.ensureDir(path_1.default.join(toolsccDir, 'agents'));
21
+ // Create project config if not exists
22
+ if (!(await fs_extra_1.default.pathExists(configFile))) {
23
+ const config = {
24
+ sources: [],
25
+ links: []
26
+ };
27
+ await fs_extra_1.default.writeJson(configFile, config, { spaces: 2 });
28
+ }
29
+ }
30
+ async function useSource(sourceName, sourceDir, projectDir) {
31
+ // Input validation
32
+ if (!sourceName || !sourceName.trim()) {
33
+ throw new Error('Source name is required');
34
+ }
35
+ // Check source directory existence
36
+ if (!(await fs_extra_1.default.pathExists(sourceDir))) {
37
+ throw new Error(`Source directory does not exist: ${sourceDir}`);
38
+ }
39
+ const toolsccDir = (0, path_2.getToolsccDir)(projectDir);
40
+ const manifest = await (0, manifest_1.loadManifest)(sourceDir);
41
+ // Ensure project is initialized
42
+ await initProject(projectDir);
43
+ // Copy/link skills (flattened with prefix)
44
+ const sourceSkillsDir = path_1.default.join(sourceDir, 'skills');
45
+ if (await fs_extra_1.default.pathExists(sourceSkillsDir)) {
46
+ const skills = await fs_extra_1.default.readdir(sourceSkillsDir);
47
+ for (const skill of skills) {
48
+ const srcPath = path_1.default.join(sourceSkillsDir, skill);
49
+ const destPath = path_1.default.join(toolsccDir, 'skills', `${sourceName}-${skill}`);
50
+ // Remove existing if exists
51
+ await fs_extra_1.default.remove(destPath);
52
+ // Copy directory
53
+ await fs_extra_1.default.copy(srcPath, destPath);
54
+ }
55
+ }
56
+ // Copy commands (in subdirectory by source name)
57
+ const sourceCommandsDir = path_1.default.join(sourceDir, 'commands');
58
+ if (await fs_extra_1.default.pathExists(sourceCommandsDir)) {
59
+ const destDir = path_1.default.join(toolsccDir, 'commands', sourceName);
60
+ await fs_extra_1.default.remove(destDir);
61
+ await fs_extra_1.default.copy(sourceCommandsDir, destDir);
62
+ }
63
+ // Copy agents (in subdirectory by source name)
64
+ const sourceAgentsDir = path_1.default.join(sourceDir, 'agents');
65
+ if (await fs_extra_1.default.pathExists(sourceAgentsDir)) {
66
+ const destDir = path_1.default.join(toolsccDir, 'agents', sourceName);
67
+ await fs_extra_1.default.remove(destDir);
68
+ await fs_extra_1.default.copy(sourceAgentsDir, destDir);
69
+ }
70
+ // Update project config
71
+ const configFile = (0, path_2.getProjectConfigPath)(projectDir);
72
+ const config = await fs_extra_1.default.readJson(configFile);
73
+ if (!config.sources.includes(sourceName)) {
74
+ config.sources.push(sourceName);
75
+ }
76
+ await fs_extra_1.default.writeJson(configFile, config, { spaces: 2 });
77
+ }
78
+ async function unuseSource(sourceName, projectDir) {
79
+ // Input validation
80
+ if (!sourceName || !sourceName.trim()) {
81
+ throw new Error('Source name is required');
82
+ }
83
+ const toolsccDir = (0, path_2.getToolsccDir)(projectDir);
84
+ const configFile = (0, path_2.getProjectConfigPath)(projectDir);
85
+ // Remove skills with prefix
86
+ const skillsDir = path_1.default.join(toolsccDir, 'skills');
87
+ if (await fs_extra_1.default.pathExists(skillsDir)) {
88
+ const skills = await fs_extra_1.default.readdir(skillsDir);
89
+ for (const skill of skills) {
90
+ if (skill.startsWith(`${sourceName}-`)) {
91
+ await fs_extra_1.default.remove(path_1.default.join(skillsDir, skill));
92
+ }
93
+ }
94
+ }
95
+ // Remove commands subdirectory
96
+ await fs_extra_1.default.remove(path_1.default.join(toolsccDir, 'commands', sourceName));
97
+ // Remove agents subdirectory
98
+ await fs_extra_1.default.remove(path_1.default.join(toolsccDir, 'agents', sourceName));
99
+ // Update project config with error handling
100
+ let config;
101
+ try {
102
+ config = await fs_extra_1.default.readJson(configFile);
103
+ }
104
+ catch (error) {
105
+ // If config file doesn't exist or is invalid, nothing to update
106
+ return;
107
+ }
108
+ config.sources = config.sources.filter(s => s !== sourceName);
109
+ await fs_extra_1.default.writeJson(configFile, config, { spaces: 2 });
110
+ }
111
+ async function listUsedSources(projectDir) {
112
+ const configFile = (0, path_2.getProjectConfigPath)(projectDir);
113
+ if (!(await fs_extra_1.default.pathExists(configFile))) {
114
+ return [];
115
+ }
116
+ const config = await fs_extra_1.default.readJson(configFile);
117
+ return config.sources;
118
+ }
@@ -0,0 +1,6 @@
1
+ import { SourceConfig } from '../types';
2
+ export declare function addSource(name: string, sourcePath: string, configDir: string): Promise<SourceConfig>;
3
+ export declare function listSources(configDir: string): Promise<Record<string, SourceConfig>>;
4
+ export declare function removeSource(name: string, configDir: string): Promise<void>;
5
+ export declare function updateSource(name: string, configDir: string): Promise<void>;
6
+ export declare function getSourcePath(name: string, configDir: string): Promise<string>;