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/conflict.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conflict - Conflict detection and resolution
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const fsPromises = require('fs').promises;
|
|
9
|
-
const { fileExists, dirExists, readFileContent, getTimestamp } = require('./utils');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Check if config file exists
|
|
13
|
-
* @param {string} targetPath - Path to config file
|
|
14
|
-
* @param {Object} deps - Dependencies
|
|
15
|
-
* @returns {Promise<boolean>}
|
|
16
|
-
*/
|
|
17
|
-
async function checkConfigFile(targetPath, deps = {}) {
|
|
18
|
-
return fileExists(targetPath, deps);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Check if skills directory exists
|
|
23
|
-
* @param {string} targetPath - Path to skills directory
|
|
24
|
-
* @param {Object} deps - Dependencies
|
|
25
|
-
* @returns {Promise<boolean>}
|
|
26
|
-
*/
|
|
27
|
-
async function checkSkillsDir(targetPath, deps = {}) {
|
|
28
|
-
return dirExists(targetPath, deps);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Check if .new file exists and compare content
|
|
33
|
-
* @param {string} newFilePath - Path to .new file
|
|
34
|
-
* @param {string} sourceContent - Content to compare
|
|
35
|
-
* @param {Object} deps - Dependencies
|
|
36
|
-
* @returns {Promise<Object>} { exists, sameContent }
|
|
37
|
-
*/
|
|
38
|
-
async function checkNewFile(newFilePath, sourceContent, deps = {}) {
|
|
39
|
-
const exists = await fileExists(newFilePath, deps);
|
|
40
|
-
|
|
41
|
-
if (!exists) {
|
|
42
|
-
return { exists: false, sameContent: false };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const existingContent = await readFileContent(newFilePath, deps);
|
|
47
|
-
const sameContent = existingContent === sourceContent;
|
|
48
|
-
return { exists: true, sameContent };
|
|
49
|
-
} catch (err) {
|
|
50
|
-
return { exists: true, sameContent: false };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Backup existing .new file if content is different
|
|
56
|
-
* @param {string} newFilePath - Path to .new file
|
|
57
|
-
* @param {Object} deps - Dependencies
|
|
58
|
-
* @returns {Promise<string|null>} Backup path or null if no backup needed
|
|
59
|
-
*/
|
|
60
|
-
async function backupNewFileIfDifferent(newFilePath, deps = {}) {
|
|
61
|
-
const fsModule = deps.fs || fsPromises;
|
|
62
|
-
const timestamp = getTimestamp(deps);
|
|
63
|
-
|
|
64
|
-
const dir = path.dirname(newFilePath);
|
|
65
|
-
const name = path.basename(newFilePath);
|
|
66
|
-
const backupPath = path.join(dir, `${name}.backup-${timestamp}`);
|
|
67
|
-
|
|
68
|
-
await fsModule.rename(newFilePath, backupPath);
|
|
69
|
-
|
|
70
|
-
return backupPath;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Determine conflict resolution action
|
|
75
|
-
* @param {Object} options - CLI options
|
|
76
|
-
* @param {string} conflictType - 'config' or 'skills'
|
|
77
|
-
* @param {string} userChoice - User's choice from prompt
|
|
78
|
-
* @returns {string} Action to take
|
|
79
|
-
*/
|
|
80
|
-
function resolveConflict(options, conflictType, userChoice = null) {
|
|
81
|
-
// If user made a choice, use it
|
|
82
|
-
if (userChoice) {
|
|
83
|
-
return userChoice;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// -y mode: use defaults
|
|
87
|
-
if (options.yes) {
|
|
88
|
-
if (conflictType === 'config') {
|
|
89
|
-
return options.overwrite ? 'overwrite' : 'new';
|
|
90
|
-
}
|
|
91
|
-
if (conflictType === 'skills') {
|
|
92
|
-
return options.noBackup ? 'overwrite' : 'backup';
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// --overwrite flag for config
|
|
97
|
-
if (conflictType === 'config' && options.overwrite) {
|
|
98
|
-
return options.noBackup ? 'overwrite' : 'backup';
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Default actions
|
|
102
|
-
if (conflictType === 'config') {
|
|
103
|
-
return 'new';
|
|
104
|
-
}
|
|
105
|
-
if (conflictType === 'skills') {
|
|
106
|
-
return options.noBackup ? 'overwrite' : 'backup';
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return 'skip';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = {
|
|
113
|
-
checkConfigFile,
|
|
114
|
-
checkSkillsDir,
|
|
115
|
-
checkNewFile,
|
|
116
|
-
backupNewFileIfDifferent,
|
|
117
|
-
resolveConflict,
|
|
118
|
-
};
|
package/lib/copy.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copy - File copying with near-atomic writes
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const fsPromises = require('fs').promises;
|
|
9
|
-
const { randomSuffix, ensureDir, readFileContent } = require('./utils');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Copy a file with near-atomic write
|
|
13
|
-
* @param {string} src - Source file path
|
|
14
|
-
* @param {string} dest - Destination file path
|
|
15
|
-
* @param {Object} deps - Dependencies
|
|
16
|
-
* @returns {Promise<void>}
|
|
17
|
-
*/
|
|
18
|
-
async function copyFile(src, dest, deps = {}) {
|
|
19
|
-
const fsModule = deps.fs || fsPromises;
|
|
20
|
-
const suffix = randomSuffix(deps);
|
|
21
|
-
|
|
22
|
-
// Ensure destination directory exists
|
|
23
|
-
await ensureDir(path.dirname(dest), deps);
|
|
24
|
-
|
|
25
|
-
// Write to temporary file first
|
|
26
|
-
const tempPath = `${dest}.tmp-${suffix}`;
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
// Copy to temp file
|
|
30
|
-
await fsModule.copyFile(src, tempPath);
|
|
31
|
-
|
|
32
|
-
// Verify copy by comparing sizes
|
|
33
|
-
const srcStat = await fsModule.stat(src);
|
|
34
|
-
const tempStat = await fsModule.stat(tempPath);
|
|
35
|
-
|
|
36
|
-
if (srcStat.size !== tempStat.size) {
|
|
37
|
-
throw new Error('File copy verification failed: size mismatch');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Rename temp to final destination (atomic on most filesystems)
|
|
41
|
-
await fsModule.rename(tempPath, dest);
|
|
42
|
-
|
|
43
|
-
} catch (err) {
|
|
44
|
-
// Clean up temp file on failure
|
|
45
|
-
try {
|
|
46
|
-
await fsModule.unlink(tempPath);
|
|
47
|
-
} catch (cleanupErr) {
|
|
48
|
-
// Ignore cleanup errors
|
|
49
|
-
}
|
|
50
|
-
throw err;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Copy a directory recursively
|
|
56
|
-
* @param {string} src - Source directory path
|
|
57
|
-
* @param {string} dest - Destination directory path
|
|
58
|
-
* @param {Object} deps - Dependencies
|
|
59
|
-
* @returns {Promise<void>}
|
|
60
|
-
*/
|
|
61
|
-
async function copyDir(src, dest, deps = {}) {
|
|
62
|
-
const fsModule = deps.fs || fsPromises;
|
|
63
|
-
|
|
64
|
-
await ensureDir(dest, deps);
|
|
65
|
-
|
|
66
|
-
const entries = await fsModule.readdir(src, { withFileTypes: true });
|
|
67
|
-
|
|
68
|
-
for (const entry of entries) {
|
|
69
|
-
const srcPath = path.join(src, entry.name);
|
|
70
|
-
const destPath = path.join(dest, entry.name);
|
|
71
|
-
|
|
72
|
-
if (entry.isDirectory()) {
|
|
73
|
-
await copyDir(srcPath, destPath, deps);
|
|
74
|
-
} else {
|
|
75
|
-
await copyFile(srcPath, destPath, deps);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Write content to .new file
|
|
82
|
-
* @param {string} src - Source file path
|
|
83
|
-
* @param {string} dest - Destination .new file path
|
|
84
|
-
* @param {Object} deps - Dependencies
|
|
85
|
-
* @returns {Promise<void>}
|
|
86
|
-
*/
|
|
87
|
-
async function writeNewFile(src, dest, deps = {}) {
|
|
88
|
-
await copyFile(src, dest, deps);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Get list of files in a directory recursively
|
|
93
|
-
* @param {string} dir - Directory path
|
|
94
|
-
* @param {Object} deps - Dependencies
|
|
95
|
-
* @returns {Promise<string[]>} List of relative file paths
|
|
96
|
-
*/
|
|
97
|
-
async function listFiles(dir, deps = {}) {
|
|
98
|
-
const fsModule = deps.fs || fsPromises;
|
|
99
|
-
const files = [];
|
|
100
|
-
|
|
101
|
-
async function walk(currentDir, relativePath = '') {
|
|
102
|
-
const entries = await fsModule.readdir(currentDir, { withFileTypes: true });
|
|
103
|
-
|
|
104
|
-
for (const entry of entries) {
|
|
105
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
106
|
-
const relPath = path.join(relativePath, entry.name);
|
|
107
|
-
|
|
108
|
-
if (entry.isDirectory()) {
|
|
109
|
-
await walk(fullPath, relPath);
|
|
110
|
-
} else {
|
|
111
|
-
files.push(relPath);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
await walk(dir);
|
|
117
|
-
return files;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
module.exports = {
|
|
121
|
-
copyFile,
|
|
122
|
-
copyDir,
|
|
123
|
-
writeNewFile,
|
|
124
|
-
listFiles,
|
|
125
|
-
};
|
package/lib/defaults.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Defaults - Default value detection for platform and language
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const { dirExists, getHomeDir } = require('./utils');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Detect default platform based on existing directories
|
|
12
|
-
* @param {Object} deps - Dependencies
|
|
13
|
-
* @returns {Promise<Object>} { platform, reason }
|
|
14
|
-
*/
|
|
15
|
-
async function detectDefaultPlatform(deps = {}) {
|
|
16
|
-
const homeDir = getHomeDir(deps);
|
|
17
|
-
|
|
18
|
-
const claudeDir = path.join(homeDir, '.claude');
|
|
19
|
-
const codexDir = path.join(homeDir, '.codex');
|
|
20
|
-
|
|
21
|
-
const claudeExists = await dirExists(claudeDir, deps);
|
|
22
|
-
const codexExists = await dirExists(codexDir, deps);
|
|
23
|
-
|
|
24
|
-
if (codexExists && !claudeExists) {
|
|
25
|
-
return { platform: 'codex', reason: '检测到 ~/.codex' };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (claudeExists && !codexExists) {
|
|
29
|
-
return { platform: 'claude', reason: '检测到 ~/.claude' };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Both exist or neither exists -> default to claude
|
|
33
|
-
return { platform: 'claude', reason: '默认选择' };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Get default language
|
|
38
|
-
* @returns {Object} { lang, reason }
|
|
39
|
-
*/
|
|
40
|
-
function getDefaultLang() {
|
|
41
|
-
return { lang: 'cn', reason: '默认选择' };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
module.exports = {
|
|
45
|
-
detectDefaultPlatform,
|
|
46
|
-
getDefaultLang,
|
|
47
|
-
};
|
package/lib/index.js
DELETED
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Index - Main flow implementation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const fsPromises = require('fs').promises;
|
|
9
|
-
|
|
10
|
-
const { getHomeDir, getSourcePaths, getTargetPaths, fileExists, dirExists, readFileContent, rmrf, ensureDir } = require('./utils');
|
|
11
|
-
const { detectDefaultPlatform, getDefaultLang } = require('./defaults');
|
|
12
|
-
const { createReadline, askPlatform, askLanguage, askConfigConflict, askSkillsConflict } = require('./prompts');
|
|
13
|
-
const { backupFile, backupDir } = require('./backup');
|
|
14
|
-
const { checkConfigFile, checkSkillsDir, checkNewFile, backupNewFileIfDifferent, resolveConflict } = require('./conflict');
|
|
15
|
-
const { copyFile, copyDir, writeNewFile, listFiles } = require('./copy');
|
|
16
|
-
const { createOutput, printWelcome, printSummary, printDryRunSummary, printRecoveryInstructions, info, success, warn, error, log, newline, header, divider, operation } = require('./output');
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Main run function
|
|
20
|
-
* @param {Object} options - CLI options
|
|
21
|
-
* @param {Object} deps - Dependencies for testing
|
|
22
|
-
* @returns {Promise<void>}
|
|
23
|
-
*/
|
|
24
|
-
async function run(options, deps = {}) {
|
|
25
|
-
const fsModule = deps.fs || fsPromises;
|
|
26
|
-
const homeDir = getHomeDir(deps);
|
|
27
|
-
const output = deps.output || createOutput(deps);
|
|
28
|
-
|
|
29
|
-
// Track stats and backups
|
|
30
|
-
const stats = { write: 0, backup: 0, newFile: 0, skip: 0 };
|
|
31
|
-
const backups = [];
|
|
32
|
-
|
|
33
|
-
// Readline interface (only created if needed)
|
|
34
|
-
let rl = null;
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
// Print welcome message
|
|
38
|
-
output.printWelcome();
|
|
39
|
-
|
|
40
|
-
// Determine platform
|
|
41
|
-
let platform = options.platform;
|
|
42
|
-
let platformReason = '';
|
|
43
|
-
|
|
44
|
-
if (!platform) {
|
|
45
|
-
if (options.yes) {
|
|
46
|
-
// Auto-detect in -y mode
|
|
47
|
-
const detected = await detectDefaultPlatform(deps);
|
|
48
|
-
platform = detected.platform;
|
|
49
|
-
platformReason = detected.reason;
|
|
50
|
-
output.info(`[默认] ${platformReason},使用 ${platform === 'claude' ? 'Claude Code' : 'Codex'}`);
|
|
51
|
-
} else {
|
|
52
|
-
// Interactive mode
|
|
53
|
-
rl = createReadline();
|
|
54
|
-
const detected = await detectDefaultPlatform(deps);
|
|
55
|
-
platform = await askPlatform(rl, detected.platform, detected.reason);
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
output.info(`使用指定平台: ${platform === 'claude' ? 'Claude Code' : 'Codex'}`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Determine language
|
|
62
|
-
let lang = options.lang;
|
|
63
|
-
|
|
64
|
-
if (!lang) {
|
|
65
|
-
if (options.yes) {
|
|
66
|
-
// Use default in -y mode
|
|
67
|
-
const defaultLang = getDefaultLang();
|
|
68
|
-
lang = defaultLang.lang;
|
|
69
|
-
output.info(`[默认] 使用语言: ${lang === 'cn' ? '中文' : 'English'}`);
|
|
70
|
-
} else {
|
|
71
|
-
// Interactive mode
|
|
72
|
-
if (!rl) {
|
|
73
|
-
rl = createReadline();
|
|
74
|
-
}
|
|
75
|
-
const defaultLang = getDefaultLang();
|
|
76
|
-
lang = await askLanguage(rl, defaultLang.lang);
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
output.info(`使用指定语言: ${lang === 'cn' ? '中文' : 'English'}`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Get source and target paths
|
|
83
|
-
const sourcePaths = getSourcePaths(platform, lang);
|
|
84
|
-
const targetPaths = getTargetPaths(platform, deps);
|
|
85
|
-
|
|
86
|
-
// Validate source files exist
|
|
87
|
-
const sourceConfigExists = await fileExists(sourcePaths.configFile, deps);
|
|
88
|
-
const sourceSkillsExists = await dirExists(sourcePaths.skillsDir, deps);
|
|
89
|
-
|
|
90
|
-
if (!sourceConfigExists) {
|
|
91
|
-
throw new Error(`源配置文件不存在: ${sourcePaths.configFile}`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (!sourceSkillsExists) {
|
|
95
|
-
throw new Error(`源技能目录不存在: ${sourcePaths.skillsDir}`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Ensure target directory exists
|
|
99
|
-
await ensureDir(targetPaths.targetDir, deps);
|
|
100
|
-
|
|
101
|
-
// Dry run mode
|
|
102
|
-
if (options.dryRun) {
|
|
103
|
-
output.header('[Dry Run] 以下操作不会实际执行:');
|
|
104
|
-
|
|
105
|
-
// Check config file
|
|
106
|
-
if (!options.skillsOnly) {
|
|
107
|
-
const configExists = await checkConfigFile(targetPaths.configFile, deps);
|
|
108
|
-
const configFileName = platform === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
|
|
109
|
-
|
|
110
|
-
if (configExists) {
|
|
111
|
-
if (options.overwrite) {
|
|
112
|
-
output.operation(sourcePaths.configFile, targetPaths.configFile, '备份后覆盖');
|
|
113
|
-
stats.backup++;
|
|
114
|
-
stats.write++;
|
|
115
|
-
} else {
|
|
116
|
-
output.operation(sourcePaths.configFile, targetPaths.newFile, `生成 .helloagents.new(${configFileName} 已存在)`);
|
|
117
|
-
stats.newFile++;
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
output.operation(sourcePaths.configFile, targetPaths.configFile, '写入');
|
|
121
|
-
stats.write++;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Check skills directory
|
|
126
|
-
const skillsExists = await checkSkillsDir(targetPaths.skillsDir, deps);
|
|
127
|
-
|
|
128
|
-
if (skillsExists) {
|
|
129
|
-
if (options.noBackup) {
|
|
130
|
-
output.operation(sourcePaths.skillsDir, targetPaths.skillsDir, '直接覆盖');
|
|
131
|
-
} else {
|
|
132
|
-
output.operation(sourcePaths.skillsDir, targetPaths.skillsDir, '备份后覆盖');
|
|
133
|
-
stats.backup++;
|
|
134
|
-
}
|
|
135
|
-
stats.write++;
|
|
136
|
-
} else {
|
|
137
|
-
output.operation(sourcePaths.skillsDir, targetPaths.skillsDir, '写入');
|
|
138
|
-
stats.write++;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
output.printDryRunSummary(stats);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Process config file (unless --skills-only)
|
|
146
|
-
if (!options.skillsOnly) {
|
|
147
|
-
const configExists = await checkConfigFile(targetPaths.configFile, deps);
|
|
148
|
-
const configFileName = platform === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
|
|
149
|
-
|
|
150
|
-
if (configExists) {
|
|
151
|
-
// Determine action
|
|
152
|
-
let action;
|
|
153
|
-
if (options.yes) {
|
|
154
|
-
action = resolveConflict(options, 'config');
|
|
155
|
-
} else {
|
|
156
|
-
if (!rl) {
|
|
157
|
-
rl = createReadline();
|
|
158
|
-
}
|
|
159
|
-
action = await askConfigConflict(rl, configFileName);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
switch (action) {
|
|
163
|
-
case 'new': {
|
|
164
|
-
// Check if .new file already exists
|
|
165
|
-
const sourceContent = await readFileContent(sourcePaths.configFile, deps);
|
|
166
|
-
const newFileStatus = await checkNewFile(targetPaths.newFile, sourceContent, deps);
|
|
167
|
-
|
|
168
|
-
if (newFileStatus.exists) {
|
|
169
|
-
if (newFileStatus.sameContent) {
|
|
170
|
-
output.info(`.helloagents.new 已是最新,跳过`);
|
|
171
|
-
stats.skip++;
|
|
172
|
-
} else {
|
|
173
|
-
// Backup existing .new file
|
|
174
|
-
const newBackupPath = await backupNewFileIfDifferent(targetPaths.newFile, deps);
|
|
175
|
-
backups.push(newBackupPath);
|
|
176
|
-
output.info(`已备份旧的 .helloagents.new 至 ${path.basename(newBackupPath)}`);
|
|
177
|
-
stats.backup++;
|
|
178
|
-
|
|
179
|
-
// Write new .new file
|
|
180
|
-
await writeNewFile(sourcePaths.configFile, targetPaths.newFile, deps);
|
|
181
|
-
output.success(`已生成 .helloagents.new`);
|
|
182
|
-
stats.newFile++;
|
|
183
|
-
}
|
|
184
|
-
} else {
|
|
185
|
-
await writeNewFile(sourcePaths.configFile, targetPaths.newFile, deps);
|
|
186
|
-
output.success(`已生成 .helloagents.new`);
|
|
187
|
-
stats.newFile++;
|
|
188
|
-
}
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
case 'backup': {
|
|
193
|
-
const backupPath = await backupFile(targetPaths.configFile, deps);
|
|
194
|
-
backups.push(backupPath);
|
|
195
|
-
output.info(`已备份 ${configFileName} 至 ${path.basename(backupPath)}`);
|
|
196
|
-
stats.backup++;
|
|
197
|
-
|
|
198
|
-
await copyFile(sourcePaths.configFile, targetPaths.configFile, deps);
|
|
199
|
-
output.success(`已覆盖 ${configFileName}`);
|
|
200
|
-
stats.write++;
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
case 'overwrite': {
|
|
205
|
-
await copyFile(sourcePaths.configFile, targetPaths.configFile, deps);
|
|
206
|
-
output.success(`已覆盖 ${configFileName}`);
|
|
207
|
-
stats.write++;
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
case 'skip': {
|
|
212
|
-
output.info(`跳过 ${configFileName}`);
|
|
213
|
-
stats.skip++;
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
} else {
|
|
218
|
-
// No conflict, just copy
|
|
219
|
-
await copyFile(sourcePaths.configFile, targetPaths.configFile, deps);
|
|
220
|
-
output.success(`已写入 ${configFileName}`);
|
|
221
|
-
stats.write++;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Process skills directory
|
|
226
|
-
const skillsExists = await checkSkillsDir(targetPaths.skillsDir, deps);
|
|
227
|
-
|
|
228
|
-
if (skillsExists) {
|
|
229
|
-
// Determine action
|
|
230
|
-
let action;
|
|
231
|
-
if (options.yes) {
|
|
232
|
-
action = resolveConflict(options, 'skills');
|
|
233
|
-
} else {
|
|
234
|
-
if (!rl) {
|
|
235
|
-
rl = createReadline();
|
|
236
|
-
}
|
|
237
|
-
action = await askSkillsConflict(rl);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
switch (action) {
|
|
241
|
-
case 'backup': {
|
|
242
|
-
const backupPath = await backupDir(targetPaths.skillsDir, deps);
|
|
243
|
-
backups.push(backupPath);
|
|
244
|
-
output.info(`已备份 skills/helloagents 至 ${path.basename(backupPath)}`);
|
|
245
|
-
stats.backup++;
|
|
246
|
-
|
|
247
|
-
// Remove old directory and copy new
|
|
248
|
-
await rmrf(targetPaths.skillsDir, deps);
|
|
249
|
-
await copyDir(sourcePaths.skillsDir, targetPaths.skillsDir, deps);
|
|
250
|
-
output.success(`已更新 skills/helloagents`);
|
|
251
|
-
stats.write++;
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
case 'overwrite': {
|
|
256
|
-
await rmrf(targetPaths.skillsDir, deps);
|
|
257
|
-
await copyDir(sourcePaths.skillsDir, targetPaths.skillsDir, deps);
|
|
258
|
-
output.success(`已覆盖 skills/helloagents`);
|
|
259
|
-
stats.write++;
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
case 'skip': {
|
|
264
|
-
output.info(`跳过 skills/helloagents`);
|
|
265
|
-
stats.skip++;
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
} else {
|
|
270
|
-
// No conflict, ensure parent directory exists and copy
|
|
271
|
-
await ensureDir(path.dirname(targetPaths.skillsDir), deps);
|
|
272
|
-
await copyDir(sourcePaths.skillsDir, targetPaths.skillsDir, deps);
|
|
273
|
-
output.success(`已写入 skills/helloagents`);
|
|
274
|
-
stats.write++;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Print summary
|
|
278
|
-
output.printSummary(stats, backups);
|
|
279
|
-
|
|
280
|
-
} catch (err) {
|
|
281
|
-
// Print recovery instructions if we have backups
|
|
282
|
-
if (backups.length > 0) {
|
|
283
|
-
output.printRecoveryInstructions(err.message, backups, options.platform || 'claude');
|
|
284
|
-
}
|
|
285
|
-
throw err;
|
|
286
|
-
|
|
287
|
-
} finally {
|
|
288
|
-
// Close readline if opened
|
|
289
|
-
if (rl) {
|
|
290
|
-
rl.close();
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
module.exports = {
|
|
296
|
-
run,
|
|
297
|
-
};
|