@mison/wecom-cleaner 1.0.0 → 1.1.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.
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import { installSkill, resolveDefaultSkillsRoot } from './skill-installer.js';
3
+
4
+ function printHelp() {
5
+ console.log(`wecom-cleaner-skill
6
+
7
+ 用法:
8
+ wecom-cleaner-skill install [--target <目录>] [--force] [--dry-run]
9
+ wecom-cleaner-skill path
10
+
11
+ 说明:
12
+ - install: 安装 wecom-cleaner-agent 到 Codex 技能目录
13
+ - path: 输出默认技能目录(由 CODEX_HOME 或 ~/.codex 推导)
14
+ `);
15
+ }
16
+
17
+ function parseArgs(argv) {
18
+ const parsed = {
19
+ command: 'install',
20
+ target: '',
21
+ force: false,
22
+ dryRun: false,
23
+ help: false,
24
+ };
25
+
26
+ const tokens = [...argv];
27
+ const first = tokens[0] || '';
28
+ if (first && !first.startsWith('-')) {
29
+ parsed.command = first;
30
+ tokens.shift();
31
+ }
32
+
33
+ for (let i = 0; i < tokens.length; i += 1) {
34
+ const token = tokens[i];
35
+ if (token === '-h' || token === '--help') {
36
+ parsed.help = true;
37
+ continue;
38
+ }
39
+ if (token === '--force') {
40
+ parsed.force = true;
41
+ continue;
42
+ }
43
+ if (token === '--dry-run') {
44
+ parsed.dryRun = true;
45
+ continue;
46
+ }
47
+ if (token === '--target') {
48
+ const next = tokens[i + 1];
49
+ if (!next || next.startsWith('-')) {
50
+ throw new Error('参数 --target 缺少目录值');
51
+ }
52
+ parsed.target = next;
53
+ i += 1;
54
+ continue;
55
+ }
56
+ throw new Error(`未知参数: ${token}`);
57
+ }
58
+
59
+ return parsed;
60
+ }
61
+
62
+ async function main() {
63
+ try {
64
+ const args = parseArgs(process.argv.slice(2));
65
+
66
+ if (args.help) {
67
+ printHelp();
68
+ return;
69
+ }
70
+
71
+ if (args.command === 'path') {
72
+ console.log(resolveDefaultSkillsRoot());
73
+ return;
74
+ }
75
+
76
+ if (args.command !== 'install') {
77
+ throw new Error(`不支持的命令: ${args.command}`);
78
+ }
79
+
80
+ const result = await installSkill({
81
+ targetRoot: args.target,
82
+ force: args.force,
83
+ dryRun: args.dryRun,
84
+ });
85
+
86
+ const mode = result.dryRun ? '预演' : '安装';
87
+ const replaceText = result.replaced ? '(覆盖已存在版本)' : '';
88
+ console.log(`${mode}成功${replaceText}`);
89
+ console.log(`技能: ${result.skillName}`);
90
+ console.log(`目标: ${result.targetSkillDir}`);
91
+ } catch (error) {
92
+ console.error(`执行失败: ${error.message}`);
93
+ process.exitCode = 1;
94
+ }
95
+ }
96
+
97
+ main();
@@ -0,0 +1,76 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { access, cp, mkdir, rm } from 'node:fs/promises';
5
+ import { constants as fsConstants } from 'node:fs';
6
+
7
+ export const SKILL_NAME = 'wecom-cleaner-agent';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const PROJECT_ROOT = path.resolve(__dirname, '..');
12
+ const DEFAULT_SOURCE_SKILL_DIR = path.join(PROJECT_ROOT, 'skills', SKILL_NAME);
13
+
14
+ export function resolveDefaultSkillsRoot(env = process.env) {
15
+ const codexHome = typeof env.CODEX_HOME === 'string' ? env.CODEX_HOME.trim() : '';
16
+ if (codexHome) {
17
+ return path.resolve(codexHome, 'skills');
18
+ }
19
+ return path.resolve(os.homedir(), '.codex', 'skills');
20
+ }
21
+
22
+ export function resolveTargetSkillsRoot(rawTarget, env = process.env) {
23
+ if (typeof rawTarget === 'string' && rawTarget.trim()) {
24
+ return path.resolve(rawTarget.trim());
25
+ }
26
+ return resolveDefaultSkillsRoot(env);
27
+ }
28
+
29
+ async function exists(targetPath) {
30
+ try {
31
+ await access(targetPath, fsConstants.F_OK);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ export async function installSkill(options = {}) {
39
+ const sourceSkillDir = path.resolve(options.sourceSkillDir || DEFAULT_SOURCE_SKILL_DIR);
40
+ const targetRoot = resolveTargetSkillsRoot(options.targetRoot);
41
+ const dryRun = options.dryRun === true;
42
+ const force = options.force === true;
43
+
44
+ const sourceSkillFile = path.join(sourceSkillDir, 'SKILL.md');
45
+ const sourceExists = await exists(sourceSkillFile);
46
+ if (!sourceExists) {
47
+ throw new Error(`技能源目录无效,缺少 SKILL.md: ${sourceSkillDir}`);
48
+ }
49
+
50
+ const targetSkillDir = path.join(targetRoot, SKILL_NAME);
51
+ const targetExists = await exists(targetSkillDir);
52
+ if (targetExists && !force) {
53
+ throw new Error(`目标技能已存在:${targetSkillDir}。如需覆盖请加 --force`);
54
+ }
55
+
56
+ if (!dryRun) {
57
+ await mkdir(targetRoot, { recursive: true });
58
+ if (targetExists) {
59
+ await rm(targetSkillDir, { recursive: true, force: true });
60
+ }
61
+ await cp(sourceSkillDir, targetSkillDir, {
62
+ recursive: true,
63
+ force: true,
64
+ preserveTimestamps: true,
65
+ });
66
+ }
67
+
68
+ return {
69
+ skillName: SKILL_NAME,
70
+ sourceSkillDir,
71
+ targetRoot,
72
+ targetSkillDir,
73
+ dryRun,
74
+ replaced: targetExists,
75
+ };
76
+ }