helloagents 1.1.0 → 2.2.8
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/LICENSE.md +51 -0
- package/README.md +444 -841
- package/bin/cli.mjs +106 -0
- package/package.json +23 -38
- package/Claude/Skills/CN/CLAUDE.md +0 -998
- package/Claude/Skills/CN/skills/helloagents/analyze/SKILL.md +0 -187
- package/Claude/Skills/CN/skills/helloagents/design/SKILL.md +0 -261
- package/Claude/Skills/CN/skills/helloagents/develop/SKILL.md +0 -352
- package/Claude/Skills/CN/skills/helloagents/kb/SKILL.md +0 -249
- package/Claude/Skills/CN/skills/helloagents/templates/SKILL.md +0 -451
- package/Claude/Skills/EN/CLAUDE.md +0 -998
- package/Claude/Skills/EN/skills/helloagents/analyze/SKILL.md +0 -187
- package/Claude/Skills/EN/skills/helloagents/design/SKILL.md +0 -261
- package/Claude/Skills/EN/skills/helloagents/develop/SKILL.md +0 -352
- package/Claude/Skills/EN/skills/helloagents/kb/SKILL.md +0 -249
- package/Claude/Skills/EN/skills/helloagents/templates/SKILL.md +0 -451
- package/Codex/Skills/CN/AGENTS.md +0 -998
- package/Codex/Skills/CN/skills/helloagents/analyze/SKILL.md +0 -187
- package/Codex/Skills/CN/skills/helloagents/design/SKILL.md +0 -261
- package/Codex/Skills/CN/skills/helloagents/develop/SKILL.md +0 -352
- package/Codex/Skills/CN/skills/helloagents/kb/SKILL.md +0 -249
- package/Codex/Skills/CN/skills/helloagents/templates/SKILL.md +0 -451
- package/Codex/Skills/EN/AGENTS.md +0 -998
- package/Codex/Skills/EN/skills/helloagents/analyze/SKILL.md +0 -187
- package/Codex/Skills/EN/skills/helloagents/design/SKILL.md +0 -261
- package/Codex/Skills/EN/skills/helloagents/develop/SKILL.md +0 -352
- package/Codex/Skills/EN/skills/helloagents/kb/SKILL.md +0 -249
- package/Codex/Skills/EN/skills/helloagents/templates/SKILL.md +0 -451
- package/bin/cli.js +0 -85
- package/lib/args.js +0 -106
- package/lib/backup.js +0 -81
- package/lib/conflict.js +0 -118
- package/lib/copy.js +0 -125
- package/lib/defaults.js +0 -47
- package/lib/index.js +0 -297
- package/lib/output.js +0 -220
- package/lib/prompts.js +0 -173
- package/lib/utils.js +0 -225
package/lib/output.js
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Output - Formatted output with ANSI colors
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
// Check if colors should be disabled
|
|
8
|
-
const NO_COLOR = process.env.NO_COLOR !== undefined || process.argv.includes('--no-color');
|
|
9
|
-
|
|
10
|
-
// ANSI color codes
|
|
11
|
-
const colors = {
|
|
12
|
-
reset: NO_COLOR ? '' : '\x1b[0m',
|
|
13
|
-
bold: NO_COLOR ? '' : '\x1b[1m',
|
|
14
|
-
dim: NO_COLOR ? '' : '\x1b[2m',
|
|
15
|
-
|
|
16
|
-
// Foreground colors
|
|
17
|
-
red: NO_COLOR ? '' : '\x1b[31m',
|
|
18
|
-
green: NO_COLOR ? '' : '\x1b[32m',
|
|
19
|
-
yellow: NO_COLOR ? '' : '\x1b[33m',
|
|
20
|
-
blue: NO_COLOR ? '' : '\x1b[34m',
|
|
21
|
-
magenta: NO_COLOR ? '' : '\x1b[35m',
|
|
22
|
-
cyan: NO_COLOR ? '' : '\x1b[36m',
|
|
23
|
-
white: NO_COLOR ? '' : '\x1b[37m',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Symbols
|
|
27
|
-
const symbols = {
|
|
28
|
-
success: NO_COLOR ? '[OK]' : '✔',
|
|
29
|
-
error: NO_COLOR ? '[ERROR]' : '✖',
|
|
30
|
-
warning: NO_COLOR ? '[WARN]' : '⚠',
|
|
31
|
-
info: NO_COLOR ? '[INFO]' : 'ℹ',
|
|
32
|
-
arrow: NO_COLOR ? '->' : '→',
|
|
33
|
-
bullet: NO_COLOR ? '*' : '•',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Create output functions with injectable stdout
|
|
38
|
-
* @param {Object} deps - Dependencies
|
|
39
|
-
* @returns {Object} Output functions
|
|
40
|
-
*/
|
|
41
|
-
function createOutput(deps = {}) {
|
|
42
|
-
const stdout = deps.stdout || process.stdout;
|
|
43
|
-
const stderr = deps.stderr || process.stderr;
|
|
44
|
-
|
|
45
|
-
function write(stream, message) {
|
|
46
|
-
stream.write(message + '\n');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
/**
|
|
51
|
-
* Print success message
|
|
52
|
-
* @param {string} message
|
|
53
|
-
*/
|
|
54
|
-
success(message) {
|
|
55
|
-
write(stdout, `${colors.green}${symbols.success}${colors.reset} ${message}`);
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Print error message
|
|
60
|
-
* @param {string} message
|
|
61
|
-
*/
|
|
62
|
-
error(message) {
|
|
63
|
-
write(stderr, `${colors.red}${symbols.error}${colors.reset} ${message}`);
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Print warning message
|
|
68
|
-
* @param {string} message
|
|
69
|
-
*/
|
|
70
|
-
warn(message) {
|
|
71
|
-
write(stdout, `${colors.yellow}${symbols.warning}${colors.reset} ${message}`);
|
|
72
|
-
},
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Print info message
|
|
76
|
-
* @param {string} message
|
|
77
|
-
*/
|
|
78
|
-
info(message) {
|
|
79
|
-
write(stdout, `${colors.cyan}${symbols.info}${colors.reset} ${message}`);
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Print plain message
|
|
84
|
-
* @param {string} message
|
|
85
|
-
*/
|
|
86
|
-
log(message) {
|
|
87
|
-
write(stdout, message);
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Print empty line
|
|
92
|
-
*/
|
|
93
|
-
newline() {
|
|
94
|
-
write(stdout, '');
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Print header
|
|
99
|
-
* @param {string} title
|
|
100
|
-
*/
|
|
101
|
-
header(title) {
|
|
102
|
-
write(stdout, `\n${colors.bold}${colors.cyan}${title}${colors.reset}\n`);
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Print divider
|
|
107
|
-
*/
|
|
108
|
-
divider() {
|
|
109
|
-
write(stdout, `${colors.dim}${'─'.repeat(40)}${colors.reset}`);
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Print operation item
|
|
114
|
-
* @param {string} source
|
|
115
|
-
* @param {string} target
|
|
116
|
-
* @param {string} action
|
|
117
|
-
*/
|
|
118
|
-
operation(source, target, action) {
|
|
119
|
-
write(stdout, `\n源: ${colors.cyan}${source}${colors.reset}`);
|
|
120
|
-
write(stdout, ` ${symbols.arrow} 目标: ${colors.cyan}${target}${colors.reset}`);
|
|
121
|
-
write(stdout, ` ${symbols.arrow} 操作: ${action}`);
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Print dry-run summary
|
|
126
|
-
* @param {Object} stats - { write, backup, newFile, skip }
|
|
127
|
-
*/
|
|
128
|
-
printDryRunSummary(stats) {
|
|
129
|
-
write(stdout, '');
|
|
130
|
-
write(stdout, `${colors.dim}${'─'.repeat(40)}${colors.reset}`);
|
|
131
|
-
write(stdout, `汇总: 写入 ${stats.write || 0} | 备份 ${stats.backup || 0} | 生成.new ${stats.newFile || 0} | 跳过 ${stats.skip || 0}`);
|
|
132
|
-
write(stdout, '');
|
|
133
|
-
write(stdout, `${colors.yellow}${symbols.warning}${colors.reset} Dry Run 模式:不会做任何写入`);
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Print final summary
|
|
138
|
-
* @param {Object} stats - { write, backup, newFile, skip }
|
|
139
|
-
* @param {Array} backups - List of backup paths
|
|
140
|
-
*/
|
|
141
|
-
printSummary(stats, backups = []) {
|
|
142
|
-
write(stdout, '');
|
|
143
|
-
write(stdout, `${colors.dim}${'─'.repeat(40)}${colors.reset}`);
|
|
144
|
-
write(stdout, `${colors.green}${symbols.success}${colors.reset} 安装完成!`);
|
|
145
|
-
write(stdout, '');
|
|
146
|
-
write(stdout, `汇总: 写入 ${stats.write || 0} | 备份 ${stats.backup || 0} | 生成.new ${stats.newFile || 0} | 跳过 ${stats.skip || 0}`);
|
|
147
|
-
|
|
148
|
-
if (backups.length > 0) {
|
|
149
|
-
write(stdout, '');
|
|
150
|
-
write(stdout, `${colors.cyan}已创建的备份:${colors.reset}`);
|
|
151
|
-
for (const backup of backups) {
|
|
152
|
-
write(stdout, ` ${symbols.bullet} ${backup}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Print welcome message
|
|
159
|
-
*/
|
|
160
|
-
printWelcome() {
|
|
161
|
-
write(stdout, '');
|
|
162
|
-
write(stdout, `${colors.bold}${colors.cyan}HelloAGENTS${colors.reset} - AI编程模块化技能系统`);
|
|
163
|
-
write(stdout, `${colors.dim}配置工具 v1.0.0${colors.reset}`);
|
|
164
|
-
write(stdout, '');
|
|
165
|
-
},
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Print recovery instructions on failure
|
|
169
|
-
* @param {string} errorMessage
|
|
170
|
-
* @param {Array} backups - List of backup paths
|
|
171
|
-
* @param {string} platform - 'claude' or 'codex'
|
|
172
|
-
*/
|
|
173
|
-
printRecoveryInstructions(errorMessage, backups, platform) {
|
|
174
|
-
const configDir = platform === 'claude' ? '.claude' : '.codex';
|
|
175
|
-
|
|
176
|
-
write(stderr, '');
|
|
177
|
-
write(stderr, `${colors.red}${symbols.error} 复制失败: ${errorMessage}${colors.reset}`);
|
|
178
|
-
|
|
179
|
-
if (backups.length > 0) {
|
|
180
|
-
write(stderr, '');
|
|
181
|
-
write(stderr, `${colors.cyan}已创建的备份:${colors.reset}`);
|
|
182
|
-
for (const backup of backups) {
|
|
183
|
-
write(stderr, ` ${backup}`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
write(stderr, '');
|
|
187
|
-
write(stderr, `${colors.cyan}恢复命令(如需回滚):${colors.reset}`);
|
|
188
|
-
write(stderr, '');
|
|
189
|
-
write(stderr, `# macOS/Linux`);
|
|
190
|
-
for (const backup of backups) {
|
|
191
|
-
const original = backup.replace(/\.backup-\d{8}-\d{6}\/?$/, '');
|
|
192
|
-
const name = original.split('/').pop() || original.split('\\').pop();
|
|
193
|
-
write(stderr, `rm -rf ${original}`);
|
|
194
|
-
write(stderr, `mv ${backup} ${original}`);
|
|
195
|
-
}
|
|
196
|
-
write(stderr, '');
|
|
197
|
-
write(stderr, `# Windows PowerShell`);
|
|
198
|
-
for (const backup of backups) {
|
|
199
|
-
const original = backup.replace(/\.backup-\d{8}-\d{6}\/?$/, '');
|
|
200
|
-
const name = original.split('/').pop() || original.split('\\').pop();
|
|
201
|
-
write(stderr, `Remove-Item "${original}" -Recurse -Force`);
|
|
202
|
-
write(stderr, `Rename-Item "${backup}" "${name}"`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
write(stderr, '');
|
|
207
|
-
write(stderr, `请修复问题后重新运行,或使用上述命令手动恢复。`);
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Default instance
|
|
213
|
-
const defaultOutput = createOutput();
|
|
214
|
-
|
|
215
|
-
module.exports = {
|
|
216
|
-
colors,
|
|
217
|
-
symbols,
|
|
218
|
-
createOutput,
|
|
219
|
-
...defaultOutput,
|
|
220
|
-
};
|
package/lib/prompts.js
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompts - Interactive prompts using native readline
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
const readline = require('readline');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Create readline interface
|
|
11
|
-
* @returns {readline.Interface}
|
|
12
|
-
*/
|
|
13
|
-
function createReadline() {
|
|
14
|
-
return readline.createInterface({
|
|
15
|
-
input: process.stdin,
|
|
16
|
-
output: process.stdout,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Ask a question and get answer
|
|
22
|
-
* @param {readline.Interface} rl - Readline interface
|
|
23
|
-
* @param {string} question - Question to ask
|
|
24
|
-
* @returns {Promise<string>}
|
|
25
|
-
*/
|
|
26
|
-
function ask(rl, question) {
|
|
27
|
-
return new Promise((resolve) => {
|
|
28
|
-
rl.question(question, (answer) => {
|
|
29
|
-
resolve(answer.trim());
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Ask for platform selection
|
|
36
|
-
* @param {readline.Interface} rl - Readline interface
|
|
37
|
-
* @param {string} defaultPlatform - Default platform
|
|
38
|
-
* @param {string} reason - Reason for default
|
|
39
|
-
* @returns {Promise<string>} 'claude' or 'codex'
|
|
40
|
-
*/
|
|
41
|
-
async function askPlatform(rl, defaultPlatform = 'claude', reason = '') {
|
|
42
|
-
const defaultNum = defaultPlatform === 'claude' ? '1' : '2';
|
|
43
|
-
const reasonText = reason ? ` (${reason})` : '';
|
|
44
|
-
|
|
45
|
-
console.log('');
|
|
46
|
-
console.log('选择目标平台:');
|
|
47
|
-
console.log(` 1. Claude Code${defaultPlatform === 'claude' ? ` [默认]${reasonText}` : ''}`);
|
|
48
|
-
console.log(` 2. Codex${defaultPlatform === 'codex' ? ` [默认]${reasonText}` : ''}`);
|
|
49
|
-
console.log('');
|
|
50
|
-
|
|
51
|
-
const answer = await ask(rl, `请输入选项 (1-2) [${defaultNum}]: `);
|
|
52
|
-
|
|
53
|
-
if (answer === '' || answer === defaultNum) {
|
|
54
|
-
return defaultPlatform;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (answer === '1') {
|
|
58
|
-
return 'claude';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (answer === '2') {
|
|
62
|
-
return 'codex';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Invalid input, ask again
|
|
66
|
-
console.log('无效输入,请输入 1 或 2');
|
|
67
|
-
return askPlatform(rl, defaultPlatform, reason);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Ask for language selection
|
|
72
|
-
* @param {readline.Interface} rl - Readline interface
|
|
73
|
-
* @param {string} defaultLang - Default language
|
|
74
|
-
* @returns {Promise<string>} 'cn' or 'en'
|
|
75
|
-
*/
|
|
76
|
-
async function askLanguage(rl, defaultLang = 'cn') {
|
|
77
|
-
const defaultNum = defaultLang === 'cn' ? '1' : '2';
|
|
78
|
-
|
|
79
|
-
console.log('');
|
|
80
|
-
console.log('选择语言版本:');
|
|
81
|
-
console.log(` 1. 中文${defaultLang === 'cn' ? ' [默认]' : ''}`);
|
|
82
|
-
console.log(` 2. English${defaultLang === 'en' ? ' [默认]' : ''}`);
|
|
83
|
-
console.log('');
|
|
84
|
-
|
|
85
|
-
const answer = await ask(rl, `请输入选项 (1-2) [${defaultNum}]: `);
|
|
86
|
-
|
|
87
|
-
if (answer === '' || answer === defaultNum) {
|
|
88
|
-
return defaultLang;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (answer === '1') {
|
|
92
|
-
return 'cn';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (answer === '2') {
|
|
96
|
-
return 'en';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Invalid input, ask again
|
|
100
|
-
console.log('无效输入,请输入 1 或 2');
|
|
101
|
-
return askLanguage(rl, defaultLang);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Ask for config file conflict resolution
|
|
106
|
-
* @param {readline.Interface} rl - Readline interface
|
|
107
|
-
* @param {string} fileName - Name of the config file
|
|
108
|
-
* @returns {Promise<string>} 'new' | 'backup' | 'overwrite' | 'skip'
|
|
109
|
-
*/
|
|
110
|
-
async function askConfigConflict(rl, fileName) {
|
|
111
|
-
console.log('');
|
|
112
|
-
console.log(`检测到 ${fileName} 已存在,请选择处理方式:`);
|
|
113
|
-
console.log(' 1. 生成 .helloagents.new 文件(保留原文件)[默认]');
|
|
114
|
-
console.log(' 2. 备份后覆盖');
|
|
115
|
-
console.log(' 3. 直接覆盖(不备份)');
|
|
116
|
-
console.log(' 4. 跳过');
|
|
117
|
-
console.log('');
|
|
118
|
-
|
|
119
|
-
const answer = await ask(rl, '请输入选项 (1-4) [1]: ');
|
|
120
|
-
|
|
121
|
-
switch (answer) {
|
|
122
|
-
case '':
|
|
123
|
-
case '1':
|
|
124
|
-
return 'new';
|
|
125
|
-
case '2':
|
|
126
|
-
return 'backup';
|
|
127
|
-
case '3':
|
|
128
|
-
return 'overwrite';
|
|
129
|
-
case '4':
|
|
130
|
-
return 'skip';
|
|
131
|
-
default:
|
|
132
|
-
console.log('无效输入,请输入 1-4');
|
|
133
|
-
return askConfigConflict(rl, fileName);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Ask for skills directory conflict resolution
|
|
139
|
-
* @param {readline.Interface} rl - Readline interface
|
|
140
|
-
* @returns {Promise<string>} 'backup' | 'overwrite' | 'skip'
|
|
141
|
-
*/
|
|
142
|
-
async function askSkillsConflict(rl) {
|
|
143
|
-
console.log('');
|
|
144
|
-
console.log('检测到 skills/helloagents 目录已存在,请选择处理方式:');
|
|
145
|
-
console.log(' 1. 备份后覆盖 [默认]');
|
|
146
|
-
console.log(' 2. 直接覆盖(不备份)');
|
|
147
|
-
console.log(' 3. 跳过');
|
|
148
|
-
console.log('');
|
|
149
|
-
|
|
150
|
-
const answer = await ask(rl, '请输入选项 (1-3) [1]: ');
|
|
151
|
-
|
|
152
|
-
switch (answer) {
|
|
153
|
-
case '':
|
|
154
|
-
case '1':
|
|
155
|
-
return 'backup';
|
|
156
|
-
case '2':
|
|
157
|
-
return 'overwrite';
|
|
158
|
-
case '3':
|
|
159
|
-
return 'skip';
|
|
160
|
-
default:
|
|
161
|
-
console.log('无效输入,请输入 1-3');
|
|
162
|
-
return askSkillsConflict(rl);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
module.exports = {
|
|
167
|
-
createReadline,
|
|
168
|
-
ask,
|
|
169
|
-
askPlatform,
|
|
170
|
-
askLanguage,
|
|
171
|
-
askConfigConflict,
|
|
172
|
-
askSkillsConflict,
|
|
173
|
-
};
|
package/lib/utils.js
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utils - Utility functions with dependency injection support
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const fsPromises = require('fs').promises;
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const os = require('os');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get home directory (injectable for testing)
|
|
14
|
-
* @param {Object} deps - Dependencies
|
|
15
|
-
* @returns {string} Home directory path
|
|
16
|
-
*/
|
|
17
|
-
function getHomeDir(deps = {}) {
|
|
18
|
-
if (deps.homeDir) {
|
|
19
|
-
return deps.homeDir;
|
|
20
|
-
}
|
|
21
|
-
return os.homedir();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get timestamp string (injectable for testing)
|
|
26
|
-
* @param {Object} deps - Dependencies
|
|
27
|
-
* @returns {string} Timestamp in YYYYMMDD-HHmmss format
|
|
28
|
-
*/
|
|
29
|
-
function getTimestamp(deps = {}) {
|
|
30
|
-
const now = deps.now ? deps.now() : new Date();
|
|
31
|
-
const year = now.getFullYear();
|
|
32
|
-
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
33
|
-
const day = String(now.getDate()).padStart(2, '0');
|
|
34
|
-
const hours = String(now.getHours()).padStart(2, '0');
|
|
35
|
-
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
36
|
-
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
37
|
-
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Generate random suffix (injectable for testing)
|
|
42
|
-
* @param {Object} deps - Dependencies
|
|
43
|
-
* @returns {string} Random 6-character string
|
|
44
|
-
*/
|
|
45
|
-
function randomSuffix(deps = {}) {
|
|
46
|
-
if (deps.randomSuffix) {
|
|
47
|
-
return deps.randomSuffix();
|
|
48
|
-
}
|
|
49
|
-
return Math.random().toString(36).slice(2, 8);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Check if file exists
|
|
54
|
-
* @param {string} filePath - Path to check
|
|
55
|
-
* @param {Object} deps - Dependencies
|
|
56
|
-
* @returns {Promise<boolean>}
|
|
57
|
-
*/
|
|
58
|
-
async function fileExists(filePath, deps = {}) {
|
|
59
|
-
const fsModule = deps.fs || fsPromises;
|
|
60
|
-
try {
|
|
61
|
-
const stat = await fsModule.stat(filePath);
|
|
62
|
-
return stat.isFile();
|
|
63
|
-
} catch (err) {
|
|
64
|
-
if (err.code === 'ENOENT') {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
throw err;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Check if directory exists
|
|
73
|
-
* @param {string} dirPath - Path to check
|
|
74
|
-
* @param {Object} deps - Dependencies
|
|
75
|
-
* @returns {Promise<boolean>}
|
|
76
|
-
*/
|
|
77
|
-
async function dirExists(dirPath, deps = {}) {
|
|
78
|
-
const fsModule = deps.fs || fsPromises;
|
|
79
|
-
try {
|
|
80
|
-
const stat = await fsModule.stat(dirPath);
|
|
81
|
-
return stat.isDirectory();
|
|
82
|
-
} catch (err) {
|
|
83
|
-
if (err.code === 'ENOENT') {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
throw err;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Read file content
|
|
92
|
-
* @param {string} filePath - Path to read
|
|
93
|
-
* @param {Object} deps - Dependencies
|
|
94
|
-
* @returns {Promise<string>}
|
|
95
|
-
*/
|
|
96
|
-
async function readFileContent(filePath, deps = {}) {
|
|
97
|
-
const fsModule = deps.fs || fsPromises;
|
|
98
|
-
return fsModule.readFile(filePath, 'utf8');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Recursive delete (compatible with Node 16)
|
|
103
|
-
* @param {string} targetPath - Path to delete
|
|
104
|
-
* @param {Object} deps - Dependencies
|
|
105
|
-
* @returns {Promise<void>}
|
|
106
|
-
*/
|
|
107
|
-
async function rmrf(targetPath, deps = {}) {
|
|
108
|
-
const fsModule = deps.fs || fsPromises;
|
|
109
|
-
|
|
110
|
-
// Check if path exists
|
|
111
|
-
try {
|
|
112
|
-
await fsModule.stat(targetPath);
|
|
113
|
-
} catch (err) {
|
|
114
|
-
if (err.code === 'ENOENT') {
|
|
115
|
-
return; // Already doesn't exist
|
|
116
|
-
}
|
|
117
|
-
throw err;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Try fs.rm first (Node 16+)
|
|
121
|
-
if (fsModule.rm) {
|
|
122
|
-
try {
|
|
123
|
-
await fsModule.rm(targetPath, { recursive: true, force: true });
|
|
124
|
-
return;
|
|
125
|
-
} catch (err) {
|
|
126
|
-
// Fall through to rmdir
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Fallback to fs.rmdir with recursive (Node 16+)
|
|
131
|
-
if (fsModule.rmdir) {
|
|
132
|
-
await fsModule.rmdir(targetPath, { recursive: true });
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Manual recursive delete as last resort
|
|
137
|
-
const stat = await fsModule.stat(targetPath);
|
|
138
|
-
if (stat.isDirectory()) {
|
|
139
|
-
const entries = await fsModule.readdir(targetPath);
|
|
140
|
-
for (const entry of entries) {
|
|
141
|
-
await rmrf(path.join(targetPath, entry), deps);
|
|
142
|
-
}
|
|
143
|
-
await fsModule.rmdir(targetPath);
|
|
144
|
-
} else {
|
|
145
|
-
await fsModule.unlink(targetPath);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Ensure directory exists (create if not)
|
|
151
|
-
* @param {string} dirPath - Directory path
|
|
152
|
-
* @param {Object} deps - Dependencies
|
|
153
|
-
* @returns {Promise<void>}
|
|
154
|
-
*/
|
|
155
|
-
async function ensureDir(dirPath, deps = {}) {
|
|
156
|
-
const fsModule = deps.fs || fsPromises;
|
|
157
|
-
try {
|
|
158
|
-
await fsModule.mkdir(dirPath, { recursive: true });
|
|
159
|
-
} catch (err) {
|
|
160
|
-
if (err.code !== 'EEXIST') {
|
|
161
|
-
throw err;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Get package root directory (where package.json is)
|
|
168
|
-
* @returns {string}
|
|
169
|
-
*/
|
|
170
|
-
function getPackageRoot() {
|
|
171
|
-
return path.resolve(__dirname, '..');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Get source path for platform and language
|
|
176
|
-
* @param {string} platform - 'claude' or 'codex'
|
|
177
|
-
* @param {string} lang - 'cn' or 'en'
|
|
178
|
-
* @returns {Object} { configFile, skillsDir }
|
|
179
|
-
*/
|
|
180
|
-
function getSourcePaths(platform, lang) {
|
|
181
|
-
const root = getPackageRoot();
|
|
182
|
-
const platformDir = platform === 'claude' ? 'Claude' : 'Codex';
|
|
183
|
-
const langDir = lang === 'cn' ? 'CN' : 'EN';
|
|
184
|
-
const configFileName = platform === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
configFile: path.join(root, platformDir, 'Skills', langDir, configFileName),
|
|
188
|
-
skillsDir: path.join(root, platformDir, 'Skills', langDir, 'skills', 'helloagents'),
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Get target path for platform
|
|
194
|
-
* @param {string} platform - 'claude' or 'codex'
|
|
195
|
-
* @param {Object} deps - Dependencies
|
|
196
|
-
* @returns {Object} { targetDir, configFile, skillsDir, newFile }
|
|
197
|
-
*/
|
|
198
|
-
function getTargetPaths(platform, deps = {}) {
|
|
199
|
-
const homeDir = getHomeDir(deps);
|
|
200
|
-
const targetDir = platform === 'claude'
|
|
201
|
-
? path.join(homeDir, '.claude')
|
|
202
|
-
: path.join(homeDir, '.codex');
|
|
203
|
-
const configFileName = platform === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
targetDir,
|
|
207
|
-
configFile: path.join(targetDir, configFileName),
|
|
208
|
-
skillsDir: path.join(targetDir, 'skills', 'helloagents'),
|
|
209
|
-
newFile: path.join(targetDir, '.helloagents.new'),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
module.exports = {
|
|
214
|
-
getHomeDir,
|
|
215
|
-
getTimestamp,
|
|
216
|
-
randomSuffix,
|
|
217
|
-
fileExists,
|
|
218
|
-
dirExists,
|
|
219
|
-
readFileContent,
|
|
220
|
-
rmrf,
|
|
221
|
-
ensureDir,
|
|
222
|
-
getPackageRoot,
|
|
223
|
-
getSourcePaths,
|
|
224
|
-
getTargetPaths,
|
|
225
|
-
};
|