@eevee-ta/cli 0.1.0-beta.1 → 1.0.1
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/README.md +76 -154
- package/assets/skills/eevee-mcp/SKILL.md +26 -0
- package/assets/skills/eevee-mcp/reference.md +12 -0
- package/dist/src/bin/cli.js +48 -25
- package/dist/src/bin/cli.js.map +1 -1
- package/dist/src/commands/business-command.js +9 -4
- package/dist/src/commands/business-command.js.map +1 -1
- package/dist/src/commands/skill-command.js +40 -5
- package/dist/src/commands/skill-command.js.map +1 -1
- package/dist/src/lib/config.js +1 -1
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/http.js +95 -0
- package/dist/src/lib/http.js.map +1 -0
- package/dist/src/lib/mcp-client.js +5 -4
- package/dist/src/lib/mcp-client.js.map +1 -1
- package/dist/src/lib/skill-installer.js +134 -45
- package/dist/src/lib/skill-installer.js.map +1 -1
- package/dist/src/lib/updater.js +129 -0
- package/dist/src/lib/updater.js.map +1 -0
- package/dist/src/lib/version.js +55 -0
- package/dist/src/lib/version.js.map +1 -0
- package/install.sh +203 -0
- package/package.json +8 -8
- package/scripts/postinstall.mjs +31 -0
- package/assets/skills/eevee-migration/SKILL.md +0 -181
- package/assets/skills/eevee-project-setup/SKILL.md +0 -266
- package/dist/src/commands/intl-command.js +0 -131
- package/dist/src/commands/intl-command.js.map +0 -1
- package/dist/src/commands/lint-command.js +0 -105
- package/dist/src/commands/lint-command.js.map +0 -1
- package/dist/src/lib/intl-catalog.js +0 -58
- package/dist/src/lib/intl-catalog.js.map +0 -1
- package/dist/src/lib/intl-config.js +0 -125
- package/dist/src/lib/intl-config.js.map +0 -1
- package/dist/src/lib/intl-downloader.js +0 -217
- package/dist/src/lib/intl-downloader.js.map +0 -1
- package/dist/src/lib/intl-serializers/csv.js +0 -25
- package/dist/src/lib/intl-serializers/csv.js.map +0 -1
- package/dist/src/lib/intl-serializers/custom.js +0 -23
- package/dist/src/lib/intl-serializers/custom.js.map +0 -1
- package/dist/src/lib/intl-serializers/index.js +0 -22
- package/dist/src/lib/intl-serializers/index.js.map +0 -1
- package/dist/src/lib/intl-serializers/js.js +0 -10
- package/dist/src/lib/intl-serializers/js.js.map +0 -1
- package/dist/src/lib/intl-serializers/json.js +0 -7
- package/dist/src/lib/intl-serializers/json.js.map +0 -1
- package/dist/src/lib/intl-serializers/properties.js +0 -43
- package/dist/src/lib/intl-serializers/properties.js.map +0 -1
- package/dist/src/lib/intl-serializers/ts.js +0 -16
- package/dist/src/lib/intl-serializers/ts.js.map +0 -1
- package/dist/src/lib/intl-serializers/xml.js +0 -21
- package/dist/src/lib/intl-serializers/xml.js.map +0 -1
- package/dist/src/lib/lint-config.js +0 -180
- package/dist/src/lib/lint-config.js.map +0 -1
- package/dist/src/lib/lint-java.js +0 -226
- package/dist/src/lib/lint-java.js.map +0 -1
- package/dist/src/lib/lint-types.js +0 -2
- package/dist/src/lib/lint-types.js.map +0 -1
- package/dist/src/lib/lint-web.js +0 -183
- package/dist/src/lib/lint-web.js.map +0 -1
- package/dist/src/lib/lint.js +0 -192
- package/dist/src/lib/lint.js.map +0 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { access, constants } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { checkLatestVersion, CLI_VERSION, NPM_PACKAGE_NAME, npmRegistry, } from './version.js';
|
|
6
|
+
export { CLI_VERSION };
|
|
7
|
+
export function updateCheckDisabled() {
|
|
8
|
+
return process.env.EEVEE_SKIP_UPDATE_CHECK === '1'
|
|
9
|
+
|| process.env.EEVEE_UPDATE_CHECK === '0'
|
|
10
|
+
|| process.env.CI === 'true'
|
|
11
|
+
|| process.env.CI === '1';
|
|
12
|
+
}
|
|
13
|
+
export async function checkForUpdate(registry) {
|
|
14
|
+
try {
|
|
15
|
+
return await checkLatestVersion(registry);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// 版本检查是辅助能力,网络或镜像异常不能阻塞业务命令。
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function confirmUpdate(current, latest) {
|
|
23
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
24
|
+
return false;
|
|
25
|
+
const terminal = createInterface({ input: process.stdin, output: process.stdout });
|
|
26
|
+
try {
|
|
27
|
+
const answer = await question(terminal, `发现 @eevee/cli 新版本 ${current} -> ${latest},是否现在更新?[y/N] `);
|
|
28
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
terminal.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function question(terminal, prompt) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
terminal.question(prompt, (answer) => resolve(answer));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function npmExecutable() {
|
|
40
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
41
|
+
}
|
|
42
|
+
function run(command, args) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const child = spawn(command, args, {
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
env: { ...process.env, EEVEE_SKIP_UPDATE_CHECK: '1' },
|
|
47
|
+
});
|
|
48
|
+
child.once('error', reject);
|
|
49
|
+
child.once('exit', (code) => {
|
|
50
|
+
if (code === 0)
|
|
51
|
+
resolve();
|
|
52
|
+
else
|
|
53
|
+
reject(new Error(`${command} ${args.join(' ')} 退出码为 ${code ?? 'null'}`));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function capture(command, args) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const child = spawn(command, args, {
|
|
60
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
61
|
+
env: { ...process.env, EEVEE_SKIP_UPDATE_CHECK: '1' },
|
|
62
|
+
});
|
|
63
|
+
let stdout = '';
|
|
64
|
+
let stderr = '';
|
|
65
|
+
child.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
|
|
66
|
+
child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
|
67
|
+
child.once('error', reject);
|
|
68
|
+
child.once('exit', (code) => {
|
|
69
|
+
if (code === 0)
|
|
70
|
+
resolve(stdout.trim());
|
|
71
|
+
else
|
|
72
|
+
reject(new Error(stderr.trim() || `${command} ${args.join(' ')} 退出码为 ${code ?? 'null'}`));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export async function installLatestCli(registryInput) {
|
|
77
|
+
const registry = npmRegistry(registryInput);
|
|
78
|
+
await run(npmExecutable(), [
|
|
79
|
+
'install',
|
|
80
|
+
'--global',
|
|
81
|
+
`${NPM_PACKAGE_NAME}@latest`,
|
|
82
|
+
`--registry=${registry}`,
|
|
83
|
+
`--@eevee:registry=${registry}`,
|
|
84
|
+
'--ignore-scripts',
|
|
85
|
+
'--fund=false',
|
|
86
|
+
'--audit=false',
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
async function globalCliEntryPoint(registryInput) {
|
|
90
|
+
const root = await capture(npmExecutable(), ['root', '--global', `--registry=${npmRegistry(registryInput)}`]);
|
|
91
|
+
const entry = path.join(root, '@eevee', 'cli', 'dist', 'src', 'bin', 'cli.js');
|
|
92
|
+
try {
|
|
93
|
+
await access(entry, constants.R_OK);
|
|
94
|
+
return entry;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function migrateSkillsAfterCliUpdate(registryInput) {
|
|
101
|
+
const entry = await globalCliEntryPoint(registryInput).catch(() => undefined);
|
|
102
|
+
if (!entry) {
|
|
103
|
+
console.warn('⚠ npm 更新完成,但无法定位新版 CLI;如 Skill 未自动迁移,请执行: eevee skills update --auto --global --force');
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
await run(process.execPath, [entry, 'skills', 'update', '--auto', '--global', '--force', '--yes']);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
export async function updateCli(options = {}) {
|
|
110
|
+
const check = await checkForUpdate(options.registry);
|
|
111
|
+
if (!check) {
|
|
112
|
+
console.error('⚠ 无法获取 @eevee/cli 最新版本,请稍后重试或检查 npm 镜像。');
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!check.updateAvailable) {
|
|
117
|
+
console.log(`@eevee/cli 已是最新版本 ${check.latest}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const accepted = options.yes || await confirmUpdate(check.current, check.latest);
|
|
121
|
+
if (!accepted) {
|
|
122
|
+
console.log(`已跳过更新。可稍后执行: npm install --global ${NPM_PACKAGE_NAME}@latest --registry=${check.registry}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await installLatestCli(options.registry);
|
|
126
|
+
const migrated = await migrateSkillsAfterCliUpdate(options.registry);
|
|
127
|
+
console.log(`✓ @eevee/cli 已更新到 ${check.latest},${migrated ? 'Skill 已同步' : 'Skill 未能自动同步'}。请重新执行刚才的命令。`);
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=updater.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updater.js","sourceRoot":"","sources":["../../../src/lib/updater.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { fetchWithTimeout } from './http.js';
|
|
3
|
+
export const NPM_PACKAGE_NAME = '@eevee/cli';
|
|
4
|
+
export const DEFAULT_NPM_REGISTRY = 'https://registry.npmmirror.com';
|
|
5
|
+
// 编译产物位于 dist/src/lib,package.json 在包根目录。
|
|
6
|
+
const packageMetadata = createRequire(import.meta.url)('../../../package.json');
|
|
7
|
+
export const CLI_VERSION = packageMetadata.version;
|
|
8
|
+
export function npmRegistry(input) {
|
|
9
|
+
const value = (input || process.env.EEVEE_NPM_REGISTRY || DEFAULT_NPM_REGISTRY).trim();
|
|
10
|
+
if (!value)
|
|
11
|
+
return DEFAULT_NPM_REGISTRY;
|
|
12
|
+
return value.endsWith('/') ? value.slice(0, -1) : value;
|
|
13
|
+
}
|
|
14
|
+
function parseVersion(version) {
|
|
15
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version.trim());
|
|
16
|
+
return match ? match.slice(1, 4).map(Number) : undefined;
|
|
17
|
+
}
|
|
18
|
+
export function isValidSemver(version) {
|
|
19
|
+
return parseVersion(version) !== undefined;
|
|
20
|
+
}
|
|
21
|
+
export function compareSemver(left, right) {
|
|
22
|
+
const parsedLeft = parseVersion(left);
|
|
23
|
+
const parsedRight = parseVersion(right);
|
|
24
|
+
if (!parsedLeft || !parsedRight)
|
|
25
|
+
throw new Error(`无效语义化版本: ${!parsedLeft ? left : right}`);
|
|
26
|
+
for (let index = 0; index < 3; index++) {
|
|
27
|
+
if (parsedLeft[index] !== parsedRight[index])
|
|
28
|
+
return parsedLeft[index] - parsedRight[index];
|
|
29
|
+
}
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
export async function fetchLatestVersion(registryInput) {
|
|
33
|
+
const registry = npmRegistry(registryInput);
|
|
34
|
+
const response = await fetchWithTimeout(`${registry}/${NPM_PACKAGE_NAME}`, {
|
|
35
|
+
headers: { accept: 'application/json' },
|
|
36
|
+
}, 3_000);
|
|
37
|
+
if (!response.ok)
|
|
38
|
+
throw new Error(`npm registry 返回 ${response.status}`);
|
|
39
|
+
const document = await response.json();
|
|
40
|
+
const latest = document['dist-tags']?.latest;
|
|
41
|
+
return latest && isValidSemver(latest) ? latest : undefined;
|
|
42
|
+
}
|
|
43
|
+
export async function checkLatestVersion(registryInput) {
|
|
44
|
+
const latest = await fetchLatestVersion(registryInput);
|
|
45
|
+
if (!latest)
|
|
46
|
+
return undefined;
|
|
47
|
+
const registry = npmRegistry(registryInput);
|
|
48
|
+
return {
|
|
49
|
+
current: CLI_VERSION,
|
|
50
|
+
latest,
|
|
51
|
+
updateAvailable: compareSemver(latest, CLI_VERSION) > 0,
|
|
52
|
+
registry,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../../src/lib/version.ts"],"names":[],"mappings":"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"}
|
package/install.sh
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -Eeuo pipefail
|
|
4
|
+
|
|
5
|
+
PACKAGE_NAME="${EEVEE_PACKAGE_NAME:-@eevee/cli}"
|
|
6
|
+
NODE_VERSION="${EEVEE_NODE_VERSION:-22.21.1}"
|
|
7
|
+
NODE_VERSION="${NODE_VERSION#v}"
|
|
8
|
+
NODE_MIRROR="${EEVEE_NODE_MIRROR:-https://npmmirror.com/mirrors/node}"
|
|
9
|
+
NPM_REGISTRY="${EEVEE_NPM_REGISTRY:-https://registry.npmmirror.com}"
|
|
10
|
+
NODE_ROOT="${EEVEE_NODE_HOME:-$HOME/.eevee/node}"
|
|
11
|
+
NODE_BIN_DIR="$NODE_ROOT/bin"
|
|
12
|
+
NPM_PREFIX="${EEVEE_NPM_PREFIX:-$HOME/.eevee/npm}"
|
|
13
|
+
NPM_BIN_DIR="$NPM_PREFIX/bin"
|
|
14
|
+
|
|
15
|
+
usage() {
|
|
16
|
+
cat <<'EOF'
|
|
17
|
+
Usage: install.sh [-h|--help]
|
|
18
|
+
|
|
19
|
+
Environment:
|
|
20
|
+
EEVEE_PACKAGE_NAME npm package name (default: @eevee/cli)
|
|
21
|
+
EEVEE_NODE_VERSION Node.js version installed when needed (default: 22.21.1)
|
|
22
|
+
EEVEE_NODE_MIRROR Node.js mirror (default: https://npmmirror.com/mirrors/node)
|
|
23
|
+
EEVEE_NPM_REGISTRY npm registry (default: https://registry.npmmirror.com)
|
|
24
|
+
EEVEE_NODE_HOME user-local Node.js root (default: ~/.eevee/node)
|
|
25
|
+
EEVEE_NPM_PREFIX user-local npm global prefix (default: ~/.eevee/npm)
|
|
26
|
+
EOF
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
info() { printf '\033[36m[eevee]\033[0m %s\n' "$*"; }
|
|
30
|
+
warn() { printf '\033[33m[eevee]\033[0m %s\n' "$*" >&2; }
|
|
31
|
+
fail() { printf '\033[31m[eevee]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
32
|
+
|
|
33
|
+
tmpdir=''
|
|
34
|
+
cleanup() {
|
|
35
|
+
if [[ -n "$tmpdir" ]]; then rm -rf "$tmpdir"; fi
|
|
36
|
+
}
|
|
37
|
+
trap cleanup EXIT
|
|
38
|
+
|
|
39
|
+
command_satisfied() {
|
|
40
|
+
command -v "$1" >/dev/null 2>&1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
node_version_ok() {
|
|
44
|
+
command_satisfied node || return 1
|
|
45
|
+
command_satisfied npm || return 1
|
|
46
|
+
node -e '
|
|
47
|
+
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
48
|
+
process.exit(major >= 16 ? 0 : 1);
|
|
49
|
+
' >/dev/null 2>&1
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
detect_platform() {
|
|
53
|
+
local system machine
|
|
54
|
+
system="$(uname -s)"
|
|
55
|
+
machine="$(uname -m)"
|
|
56
|
+
case "$system:$machine" in
|
|
57
|
+
Linux:x86_64) printf 'linux-x64' ;;
|
|
58
|
+
Linux:aarch64|Linux:arm64) printf 'linux-arm64' ;;
|
|
59
|
+
Darwin:x86_64) printf 'darwin-x64' ;;
|
|
60
|
+
Darwin:arm64|Darwin:aarch64) printf 'darwin-arm64' ;;
|
|
61
|
+
*) fail "不支持的系统或架构: $system/$machine" ;;
|
|
62
|
+
esac
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
check_environment() {
|
|
66
|
+
info '检查安装环境...'
|
|
67
|
+
[[ -d "$HOME" ]] || fail '无法确定用户 HOME 目录'
|
|
68
|
+
command_satisfied curl || command_satisfied wget || fail '缺少 curl 或 wget,请先安装下载工具'
|
|
69
|
+
command_satisfied tar || fail '缺少 tar,无法解压 Node.js'
|
|
70
|
+
if [[ "$(uname -s)" == 'Linux' ]] && command_satisfied ldd; then
|
|
71
|
+
if ldd --version 2>&1 | grep -qi musl; then
|
|
72
|
+
fail '当前 Linux 使用 musl libc,官方 Node.js 二进制包不兼容。请先安装 glibc 环境或 Node.js 16+。'
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
legacy_global_root() {
|
|
78
|
+
if command_satisfied npm; then
|
|
79
|
+
npm root --global 2>/dev/null || true
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
remove_legacy_cli() {
|
|
84
|
+
info '检查旧平台 CLI tant-intl-agent-cli...'
|
|
85
|
+
local root prefix package legacy_bin legacy_real
|
|
86
|
+
root="$(legacy_global_root)"
|
|
87
|
+
if [[ -n "$root" && -d "$root/tant-intl-agent-cli" ]]; then
|
|
88
|
+
prefix="$(cd "$root/../.." && pwd)"
|
|
89
|
+
info "发现旧版 CLI,尝试从 $prefix 卸载..."
|
|
90
|
+
if ! npm uninstall --global --prefix "$prefix" tant-intl-agent-cli >/dev/null 2>&1; then
|
|
91
|
+
rm -rf "$root/tant-intl-agent-cli"
|
|
92
|
+
rm -f "$prefix/bin/eevee" "$prefix/bin/eevee-cli"
|
|
93
|
+
fi
|
|
94
|
+
hash -r 2>/dev/null || true
|
|
95
|
+
info '旧版 CLI 已删除'
|
|
96
|
+
return
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
if command_satisfied eevee && command_satisfied realpath; then
|
|
100
|
+
legacy_bin="$(command -v eevee)"
|
|
101
|
+
legacy_real="$(realpath "$legacy_bin" 2>/dev/null || true)"
|
|
102
|
+
case "$legacy_real" in
|
|
103
|
+
*/lib/node_modules/tant-intl-agent-cli/*)
|
|
104
|
+
package="${legacy_real%%/lib/node_modules/tant-intl-agent-cli/*}/lib/node_modules/tant-intl-agent-cli"
|
|
105
|
+
warn "检测到 eevee 命令仍指向旧平台 CLI: $legacy_bin"
|
|
106
|
+
rm -rf "$package"
|
|
107
|
+
rm -f "$legacy_bin"
|
|
108
|
+
hash -r 2>/dev/null || true
|
|
109
|
+
info '旧版 CLI 已删除'
|
|
110
|
+
;;
|
|
111
|
+
esac
|
|
112
|
+
fi
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
add_install_path() {
|
|
116
|
+
local file line
|
|
117
|
+
line="export PATH=\"$NPM_BIN_DIR:$NODE_BIN_DIR:\$PATH\""
|
|
118
|
+
for file in "$HOME/.profile" "$HOME/.bashrc" "$HOME/.zprofile" "$HOME/.zshrc"; do
|
|
119
|
+
if ! grep -qF "$line" "$file"; then
|
|
120
|
+
printf '\n# eevee-cli Node.js runtime\n%s\n' "$line" >>"$file"
|
|
121
|
+
fi
|
|
122
|
+
done
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
install_node() {
|
|
126
|
+
local platform archive staging backup url
|
|
127
|
+
platform="$(detect_platform)"
|
|
128
|
+
archive="node-v${NODE_VERSION}-${platform}.tar.gz"
|
|
129
|
+
url="$NODE_MIRROR/v${NODE_VERSION}/$archive"
|
|
130
|
+
staging="$NODE_ROOT.staging.$$"
|
|
131
|
+
backup="$NODE_ROOT.backup.$$"
|
|
132
|
+
tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/eevee-node-install.XXXXXX")"
|
|
133
|
+
|
|
134
|
+
info "Node.js 不满足 16+,从国内镜像安装 v$NODE_VERSION..."
|
|
135
|
+
info "$url"
|
|
136
|
+
if command_satisfied curl; then
|
|
137
|
+
curl -fL --retry 3 --connect-timeout 15 "$url" -o "$tmpdir/$archive"
|
|
138
|
+
else
|
|
139
|
+
wget --tries=3 --timeout=15 "$url" -O "$tmpdir/$archive"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
rm -rf "$staging"
|
|
143
|
+
mkdir -p "$staging"
|
|
144
|
+
tar -xzf "$tmpdir/$archive" -C "$staging" --strip-components=1
|
|
145
|
+
"$staging/bin/node" -e 'process.exit(0)'
|
|
146
|
+
|
|
147
|
+
mkdir -p "$(dirname "$NODE_ROOT")"
|
|
148
|
+
if [[ -d "$NODE_ROOT" ]]; then
|
|
149
|
+
mv "$NODE_ROOT" "$backup"
|
|
150
|
+
fi
|
|
151
|
+
if ! mv "$staging" "$NODE_ROOT"; then
|
|
152
|
+
if [[ -d "$backup" ]]; then mv "$backup" "$NODE_ROOT"; fi
|
|
153
|
+
fail '写入用户级 Node.js 目录失败'
|
|
154
|
+
fi
|
|
155
|
+
rm -rf "$backup"
|
|
156
|
+
export PATH="$NPM_BIN_DIR:$NODE_BIN_DIR:$PATH"
|
|
157
|
+
hash -r 2>/dev/null || true
|
|
158
|
+
node_version_ok || fail 'Node.js 安装后校验失败'
|
|
159
|
+
info "Node.js 已安装到 $NODE_ROOT"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
install_cli() {
|
|
163
|
+
info "从 npm 镜像安装 $PACKAGE_NAME@latest..."
|
|
164
|
+
npm install --global --prefix "$NPM_PREFIX" "$PACKAGE_NAME@latest" \
|
|
165
|
+
"--registry=$NPM_REGISTRY" "--@eevee:registry=$NPM_REGISTRY" \
|
|
166
|
+
--fund=false --audit=false --ignore-scripts
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
migrate_skills() {
|
|
170
|
+
local cli_entry
|
|
171
|
+
cli_entry="$NPM_BIN_DIR/eevee"
|
|
172
|
+
if [[ ! -x "$cli_entry" ]]; then
|
|
173
|
+
warn '未定位到新安装的 CLI,请手动执行: eevee skills update --auto --global --force'
|
|
174
|
+
return
|
|
175
|
+
fi
|
|
176
|
+
info '同步 Agent Skill 到检测到的 Codex/Claude Code/Cursor/OpenCode...'
|
|
177
|
+
"$cli_entry" skills update --auto --global --force --yes
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
main() {
|
|
181
|
+
if [[ "${1:-}" == '-h' || "${1:-}" == '--help' ]]; then
|
|
182
|
+
usage
|
|
183
|
+
return
|
|
184
|
+
fi
|
|
185
|
+
info '@eevee/cli 一键安装器'
|
|
186
|
+
check_environment
|
|
187
|
+
remove_legacy_cli
|
|
188
|
+
if ! node_version_ok; then
|
|
189
|
+
install_node
|
|
190
|
+
remove_legacy_cli
|
|
191
|
+
else
|
|
192
|
+
info "使用当前 Node.js $(node --version)"
|
|
193
|
+
fi
|
|
194
|
+
mkdir -p "$NPM_PREFIX"
|
|
195
|
+
export PATH="$NPM_BIN_DIR:$NODE_BIN_DIR:$PATH"
|
|
196
|
+
hash -r 2>/dev/null || true
|
|
197
|
+
install_cli
|
|
198
|
+
migrate_skills
|
|
199
|
+
add_install_path
|
|
200
|
+
info "安装完成。CLI 位于 $NPM_BIN_DIR/eevee;若当前终端找不到命令,请重新打开终端或执行: export PATH=\"$NPM_BIN_DIR:$NODE_BIN_DIR:\$PATH\""
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
main "$@"
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eevee-ta/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Command-line client for the eevee MCP server",
|
|
5
|
+
"private": false,
|
|
5
6
|
"type": "module",
|
|
6
7
|
"bin": {
|
|
7
8
|
"eevee": "./dist/src/bin/cli.js",
|
|
@@ -11,23 +12,22 @@
|
|
|
11
12
|
"scripts": {
|
|
12
13
|
"build": "tsc -p tsconfig.json",
|
|
13
14
|
"prepack": "npm run build",
|
|
15
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
14
16
|
"check": "npm run build && node dist/test/cli.test.js",
|
|
15
17
|
"cli": "npm run build && node dist/src/bin/cli.js"
|
|
16
18
|
},
|
|
17
19
|
"files": [
|
|
18
20
|
"dist/src",
|
|
21
|
+
"scripts/postinstall.mjs",
|
|
22
|
+
"install.sh",
|
|
19
23
|
"assets",
|
|
20
24
|
"README.md"
|
|
21
25
|
],
|
|
22
26
|
"engines": {
|
|
23
|
-
"node": ">=
|
|
24
|
-
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"java-parser": "^3.0.1",
|
|
27
|
-
"minimatch": "^10.2.6",
|
|
28
|
-
"typescript": "^5.9.3"
|
|
27
|
+
"node": ">=16.0.0"
|
|
29
28
|
},
|
|
30
29
|
"devDependencies": {
|
|
31
|
-
"@types/node": "^24.0.0"
|
|
30
|
+
"@types/node": "^24.0.0",
|
|
31
|
+
"typescript": "^5.7.0"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const cli = fileURLToPath(new URL('../dist/src/bin/cli.js', import.meta.url));
|
|
7
|
+
|
|
8
|
+
if (!existsSync(cli)) {
|
|
9
|
+
console.log('eevee: dist 尚未构建,跳过 Agent Skill 自动迁移');
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const result = spawnSync(process.execPath, [
|
|
14
|
+
cli,
|
|
15
|
+
'skills',
|
|
16
|
+
'update',
|
|
17
|
+
'--auto',
|
|
18
|
+
'--global',
|
|
19
|
+
'--force',
|
|
20
|
+
'--yes',
|
|
21
|
+
], {
|
|
22
|
+
stdio: 'inherit',
|
|
23
|
+
env: { ...process.env, EEVEE_SKIP_UPDATE_CHECK: '1' },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (result.error) {
|
|
27
|
+
console.warn(`eevee: Agent Skill 自动迁移失败:${result.error.message}`);
|
|
28
|
+
console.warn('eevee: 可稍后执行 eevee skills update --auto --global --force');
|
|
29
|
+
} else if (result.status !== 0) {
|
|
30
|
+
console.warn(`eevee: Agent Skill 自动迁移退出码为 ${result.status},可稍后执行 eevee skills update --auto --global --force`);
|
|
31
|
+
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: eevee-migration
|
|
3
|
-
description: Guide i18n term migration from code repositories into eevee — incremental versioning, param extraction, Git source_info, deduplication, and API-first operations.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# eevee i18n 迁移技能
|
|
7
|
-
|
|
8
|
-
当需要将代码仓库中的 i18n 词条迁移到 eevee 平台时使用此技能。
|
|
9
|
-
核心原则:**所有操作走 API,不直接修改数据库**。
|
|
10
|
-
|
|
11
|
-
## 操作人规范
|
|
12
|
-
|
|
13
|
-
- 统一使用超管用户(如 id=1)的 token,不要通过 mock 登录创建多余用户
|
|
14
|
-
- Token 通过 `Authorization: Bearer <token>` 传递
|
|
15
|
-
- 不确定 token 时先用 `GET /api/v1/users` 确认用户身份
|
|
16
|
-
|
|
17
|
-
## 完整迁移流程
|
|
18
|
-
|
|
19
|
-
### 第一步:切换代码仓库分支
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
cd /path/to/repo && git checkout release/X.Y
|
|
23
|
-
git branch --show-current # 确认分支
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### 第二步:提取代码中使用的 i18n key
|
|
27
|
-
|
|
28
|
-
**Java 项目(properties + MessageFormat)**:
|
|
29
|
-
|
|
30
|
-
1. 扫描所有 `.java` 文件,匹配以下模式:
|
|
31
|
-
- `getLocaleMessage("key")` / `tryLocaleMessage("key", params)`
|
|
32
|
-
- `new BizException(ReturnCode.XXX, "key", params...)`
|
|
33
|
-
- 字符串字面量 `"gaia.xxx.yyy"`(带包名前缀)
|
|
34
|
-
2. 扫描枚举文件(`ReturnCode.java` 等),提取 `localeMsgRef` 值
|
|
35
|
-
3. 读取 `messages_zh_CN.properties` 获取所有已翻译 key
|
|
36
|
-
4. 计算交集:`matched = codeKeys ∩ propsKeys`
|
|
37
|
-
|
|
38
|
-
**前端项目(JSON/tsx formatMessage)**:
|
|
39
|
-
|
|
40
|
-
1. 扫描 `intl.tsx`/`intl.ts` 文件,匹配 `formatMessage({ id: 'key' }, { 0: x })`
|
|
41
|
-
2. 从函数签名提取参数名和类型(`x: number` → 数量,`x: ReactNode` → 名称)
|
|
42
|
-
3. 通过英文内容反向搜索匹配未直接引用的 key
|
|
43
|
-
|
|
44
|
-
### 第三步:对比上一版本获取增量
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
新增 key = 当前版本 matched - 上一版本 matched
|
|
48
|
-
删除 key = 上一版本 matched - 当前版本 matched
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
只将**新增 key** 迁移到新版本(增量模式),不重复已有词条。
|
|
52
|
-
|
|
53
|
-
### 第四步:通过 API 创建版本
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
curl -X POST /api/v1/projects/{projectId}/versions \
|
|
57
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
58
|
-
-H "Content-Type: application/json" \
|
|
59
|
-
-d '{"name": "release-X.Y"}'
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
⚠️ 如果同名版本软删后唯一键冲突,需要应用层在软删时改名(`name__del__{id}`)或硬删旧记录。
|
|
63
|
-
|
|
64
|
-
### 第五步:通过 API 创建词条(含翻译、描述、参数)
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
curl -X POST /api/v1/projects/{projectId}/versions/{versionId}/terms \
|
|
68
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
69
|
-
-H "Content-Type: application/json" \
|
|
70
|
-
-d '{
|
|
71
|
-
"name": "gaia.service.error.xxx",
|
|
72
|
-
"description": "模块:错误描述",
|
|
73
|
-
"params": [{"name":"count","value":"1","desc":"数量"}],
|
|
74
|
-
"translations": {"zh-CN":"中文","en-US":"English","ja-JP":"日本語","ko-KR":"한국어"}
|
|
75
|
-
}'
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**描述生成规则**:
|
|
79
|
-
从 key 路径推断模块前缀 + zh-CN 内容:
|
|
80
|
-
`gaia.service.workflow.error.task_check.xxx` → `"任务校验:xxx"`
|
|
81
|
-
|
|
82
|
-
**参数提取规则**:
|
|
83
|
-
扫描 zh-CN 翻译中的 `{0}` `{1}` 占位符,结合上下文推断:
|
|
84
|
-
|
|
85
|
-
| 上下文 | 推断 | 示例 |
|
|
86
|
-
|--------|------|------|
|
|
87
|
-
| `{0}` 后跟 个/条/项 | count | `{"name":"count","value":"1","desc":"数量(个)"}` |
|
|
88
|
-
| `{0}` 成功 + `{1}` 失败 | successCount/failCount | `{"name":"successCount","value":"1","desc":"成功数"}` |
|
|
89
|
-
| `【{0}】` 括号内 + 表 | tableName | `{"name":"tableName","value":"table_name","desc":"表名"}` |
|
|
90
|
-
| `{0}` + 原因/失败 | errorMsg | `{"name":"errorMsg","value":"错误详情","desc":"错误信息"}` |
|
|
91
|
-
| 通知模板 `{0}`~`{4}` | 依次: 空间/时间/名称/原因/链接 | 见下方模板映射 |
|
|
92
|
-
|
|
93
|
-
通知模板映射:
|
|
94
|
-
`{0}`→spaceName, `{1}`→timestamp, `{2}`→flowName/taskName, `{3}`→failReason, `{4}`→detailUrl
|
|
95
|
-
|
|
96
|
-
⚠️ **同一词条内参数名不可重复**。如两个占位符推断出同名(如两个 `tableName`),需根据位置区分(如 `spaceName` + `tableName`)。
|
|
97
|
-
|
|
98
|
-
**source_info(Git 信息)**:
|
|
99
|
-
API 要求 `repo/branch/path/line` 四字段完整或全部留空:
|
|
100
|
-
|
|
101
|
-
```json
|
|
102
|
-
{
|
|
103
|
-
"repo": "https://gitlab.example.com/team/repo",
|
|
104
|
-
"branch": "release/6.1",
|
|
105
|
-
"path": "gaia-common/src/main/java/.../ReturnCode.java",
|
|
106
|
-
"line": 0
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
通过已有词条 PATCH 补充:
|
|
111
|
-
```bash
|
|
112
|
-
curl -X PATCH /api/v1/projects/{projectId}/terms/{termId} \
|
|
113
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
114
|
-
-H "Content-Type: application/json" \
|
|
115
|
-
-d '{"source_info": {...}, "params": [...]}'
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### 第六步:去重(新版本不含旧版本已有的词条)
|
|
119
|
-
|
|
120
|
-
对比新旧版本的 term name 集合,删除新版本中与旧版本重复的:
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
V6.0 增量 = V6.0 全量 keys - V5.0 keys
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
逐个通过 `DELETE /api/v1/projects/{projectId}/terms/{termId}` 删除。
|
|
127
|
-
|
|
128
|
-
### 第七步:清理"待分类"中的重复词条
|
|
129
|
-
|
|
130
|
-
新版本发布后,"待分类"中与正式版本重复的词条应删除,只保留独有词条。
|
|
131
|
-
|
|
132
|
-
## 质量检查
|
|
133
|
-
|
|
134
|
-
### 遗漏 key 报告
|
|
135
|
-
|
|
136
|
-
分类输出:
|
|
137
|
-
|
|
138
|
-
| 分类 | 说明 |
|
|
139
|
-
|------|------|
|
|
140
|
-
| 代码引用但 properties 缺失 | 枚举或代码中定义了 key 但 properties 里没翻译 |
|
|
141
|
-
| properties 有但代码未引用 | 历史遗留或预留 |
|
|
142
|
-
| 前缀 key(尾带 `.`) | 动态拼接,非缺失 |
|
|
143
|
-
| 误报 | 非 i18n 字符串被正则误匹配 |
|
|
144
|
-
|
|
145
|
-
### 参数覆盖率
|
|
146
|
-
|
|
147
|
-
检查翻译中含 `{N}` 但 params 为空的词条,通过 PATCH 补齐。
|
|
148
|
-
|
|
149
|
-
## 常见坑
|
|
150
|
-
|
|
151
|
-
### ❌ 不要做的事
|
|
152
|
-
|
|
153
|
-
1. **不要直接修改数据库**——绕过校验、日志和数据完整性
|
|
154
|
-
2. **不要用 `DELETE /projects/:id/terms` 清空单版本**——这会清空整个项目所有版本的词条
|
|
155
|
-
3. **不要 mock 登录创建多余用户**——统一用超管 token
|
|
156
|
-
4. **不要假设 properties 文件名用横杠**——Java 习惯用下划线(`messages_zh_CN.properties` 不是 `messages_zh-CN.properties`)
|
|
157
|
-
5. **不要用 SQL LIKE 搜索 `__del__`**——`_` 在 LIKE 中是通配符,会误匹配 `delete_failed` 等正常 key
|
|
158
|
-
|
|
159
|
-
### ⚠️ 注意事项
|
|
160
|
-
|
|
161
|
-
1. source_info 四字段要么全填要么全空,不能只填部分
|
|
162
|
-
2. 同一词条的 params 数组中 name 不可重复
|
|
163
|
-
3. 版本删除前必须清空其下所有词条
|
|
164
|
-
4. 软删记录的唯一键冲突需在应用层处理(改名后软删)
|
|
165
|
-
5. 批量 API 调用时控制并发(建议 3~5),避免打垮服务
|
|
166
|
-
|
|
167
|
-
## 多版本增量模式图
|
|
168
|
-
|
|
169
|
-
```
|
|
170
|
-
V4.5(全量 311)
|
|
171
|
-
└── V5.0(增量 184 = 5.0新增key)
|
|
172
|
-
└── V6.0(增量 194 = 6.0新增key)
|
|
173
|
-
└── V6.1(增量 0 = 无新增,复用 V6.0)
|
|
174
|
-
|
|
175
|
-
待分类 = 全量导入 - 各正式版本已有的 key
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
每次切换分支后:
|
|
179
|
-
1. 提取 key → 对比上一版 → 得到增量
|
|
180
|
-
2. 创建新版本 → 创建增量词条
|
|
181
|
-
3. 无增量则不创建新版本,仅更新版本名
|