@42ailab/42plugin 0.3.7 → 0.3.9

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 (2) hide show
  1. package/bin/42plugin-repair +187 -2
  2. package/package.json +6 -6
@@ -7,11 +7,12 @@
7
7
 
8
8
  const { platform, arch, env, argv } = process;
9
9
  const { execSync } = require("child_process");
10
- const { existsSync, rmSync, writeFileSync } = require("fs");
10
+ const { existsSync, readFileSync, rmSync, readdirSync, lstatSync, unlinkSync, writeFileSync } = require("fs");
11
11
  const { join, isAbsolute } = require("path");
12
12
 
13
13
  // 解析命令行参数
14
14
  const uninstallOnly = argv.includes("--uninstall-only") || argv.includes("--remove");
15
+ const purgeData = argv.includes("--purge");
15
16
  const packages = ["@42ailab/42plugin", `@42ailab/42plugin-${platform}-${arch}`];
16
17
 
17
18
  // 颜色输出
@@ -68,6 +69,165 @@ function detectPackageManager() {
68
69
  return "npm";
69
70
  }
70
71
 
72
+ function cleanupClaudeCodeConfig() {
73
+ const homeDir = env.HOME || env.USERPROFILE || "";
74
+ if (!homeDir) return;
75
+
76
+ const claudeDir = join(homeDir, ".claude");
77
+ if (!existsSync(claudeDir)) {
78
+ log(" ℹ️ 未找到 ~/.claude 目录,跳过", "yellow");
79
+ return;
80
+ }
81
+
82
+ // 1. 清理 settings.json 中的 42plugin hook 条目
83
+ const settingsPath = join(claudeDir, "settings.json");
84
+ if (existsSync(settingsPath)) {
85
+ try {
86
+ const raw = readFileSync(settingsPath, "utf-8");
87
+ const settings = JSON.parse(raw);
88
+
89
+ if (settings.hooks) {
90
+ let modified = false;
91
+
92
+ for (const eventType of Object.keys(settings.hooks)) {
93
+ if (!Array.isArray(settings.hooks[eventType])) continue;
94
+
95
+ const original = settings.hooks[eventType];
96
+ const filtered = original.filter((entry) => {
97
+ // 移除带 42plugin- 前缀 _marker 的条目
98
+ if (entry._marker && entry._marker.startsWith("42plugin-")) {
99
+ modified = true;
100
+ return false;
101
+ }
102
+ // 移除无 _marker 但命令包含 42plugin __hook 的遗留条目
103
+ if (!entry._marker && entry.hooks) {
104
+ const isOurHook = entry.hooks.some((h) => {
105
+ const cmd = h.command || "";
106
+ return /42plugin\s+__hook/.test(cmd);
107
+ });
108
+ if (isOurHook) {
109
+ modified = true;
110
+ return false;
111
+ }
112
+ }
113
+ // 移除无 _marker 但命令指向 42plugin hook 脚本的遗留条目
114
+ if (!entry._marker && entry.hooks) {
115
+ const isOurScript = entry.hooks.some((h) => {
116
+ const cmd = h.command || "";
117
+ return /\.claude[\\/]hooks[\\/](skill-explorer-|42plugin)/.test(cmd);
118
+ });
119
+ if (isOurScript) {
120
+ modified = true;
121
+ return false;
122
+ }
123
+ }
124
+ return true;
125
+ });
126
+
127
+ settings.hooks[eventType] = filtered;
128
+
129
+ // 清理空数组
130
+ if (filtered.length === 0) {
131
+ delete settings.hooks[eventType];
132
+ }
133
+ }
134
+
135
+ // 清理空 hooks 对象
136
+ if (Object.keys(settings.hooks).length === 0) {
137
+ delete settings.hooks;
138
+ }
139
+
140
+ if (modified) {
141
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
142
+ log(" ✅ settings.json hook 条目已清理", "green");
143
+ } else {
144
+ log(" ℹ️ settings.json 中无 42plugin hook 条目", "yellow");
145
+ }
146
+ }
147
+ } catch (error) {
148
+ log(` ⚠️ settings.json 清理失败: ${error.message}`, "yellow");
149
+ }
150
+ }
151
+
152
+ // 2. 清理 ~/.claude/skills/ 中的 42plugin 相关目录/符号链接
153
+ const skillsDir = join(claudeDir, "skills");
154
+ if (existsSync(skillsDir)) {
155
+ try {
156
+ const entries = readdirSync(skillsDir);
157
+ let cleaned = 0;
158
+ for (const entry of entries) {
159
+ if (entry.startsWith("42plugin")) {
160
+ const fullPath = join(skillsDir, entry);
161
+ try {
162
+ const stat = lstatSync(fullPath);
163
+ if (stat.isSymbolicLink()) {
164
+ unlinkSync(fullPath);
165
+ cleaned++;
166
+ } else if (stat.isDirectory()) {
167
+ rmSync(fullPath, { recursive: true, force: true });
168
+ cleaned++;
169
+ } else if (stat.isFile()) {
170
+ unlinkSync(fullPath);
171
+ cleaned++;
172
+ }
173
+ } catch (e) {
174
+ log(` ⚠️ 清理 ${fullPath} 失败: ${e.message}`, "yellow");
175
+ }
176
+ }
177
+ }
178
+ if (cleaned > 0) {
179
+ log(` ✅ 已清理 ${cleaned} 个 skills 目录/链接`, "green");
180
+ }
181
+ } catch {}
182
+ }
183
+
184
+ // 3. 清理 ~/.claude/agents/ 中的 42plugin 相关文件
185
+ const agentsDir = join(claudeDir, "agents");
186
+ if (existsSync(agentsDir)) {
187
+ try {
188
+ const entries = readdirSync(agentsDir);
189
+ let cleaned = 0;
190
+ for (const entry of entries) {
191
+ if (entry.startsWith("42plugin-") && entry.endsWith(".md")) {
192
+ const fullPath = join(agentsDir, entry);
193
+ try {
194
+ unlinkSync(fullPath);
195
+ cleaned++;
196
+ } catch (e) {
197
+ log(` ⚠️ 清理 ${fullPath} 失败: ${e.message}`, "yellow");
198
+ }
199
+ }
200
+ }
201
+ if (cleaned > 0) {
202
+ log(` ✅ 已清理 ${cleaned} 个 agent 文件`, "green");
203
+ }
204
+ } catch {}
205
+ }
206
+
207
+ // 4. 清理 ~/.claude/hooks/ 中的 42plugin 相关脚本
208
+ const hooksDir = join(claudeDir, "hooks");
209
+ if (existsSync(hooksDir)) {
210
+ try {
211
+ const entries = readdirSync(hooksDir);
212
+ let cleaned = 0;
213
+ for (const entry of entries) {
214
+ if (entry.startsWith("skill-explorer-") && entry.endsWith(".sh")) {
215
+ const fullPath = join(hooksDir, entry);
216
+ try {
217
+ unlinkSync(fullPath);
218
+ cleaned++;
219
+ } catch (e) {
220
+ log(` ⚠️ 清理 ${fullPath} 失败: ${e.message}`, "yellow");
221
+ }
222
+ }
223
+ }
224
+ if (cleaned > 0) {
225
+ log(` ✅ 已清理 ${cleaned} 个 hook 脚本`, "green");
226
+ }
227
+ } catch {}
228
+ }
229
+ }
230
+
71
231
  async function repair() {
72
232
  if (uninstallOnly) {
73
233
  log("\n🗑️ 42plugin 卸载工具", "cyan");
@@ -210,8 +370,33 @@ async function repair() {
210
370
  }
211
371
  }
212
372
 
213
- // 仅卸载模式:跳过重新安装和验证
373
+ // 仅卸载模式:清理 Claude Code 配置并跳过重新安装
214
374
  if (uninstallOnly) {
375
+ log("\n步骤 4: 清理 Claude Code 配置...", "yellow");
376
+ cleanupClaudeCodeConfig();
377
+
378
+ // --purge: 删除用户数据目录 ~/.42plugin/
379
+ const homeDir = env.HOME || env.USERPROFILE || "";
380
+ if (homeDir) {
381
+ const pluginDataDir = join(homeDir, ".42plugin");
382
+ if (purgeData) {
383
+ if (existsSync(pluginDataDir)) {
384
+ log("\n步骤 5: 清理用户数据 (~/.42plugin/)...", "yellow");
385
+ try {
386
+ rmSync(pluginDataDir, { recursive: true, force: true });
387
+ log(" ✅ 用户数据已删除", "green");
388
+ } catch (error) {
389
+ log(` ⚠️ 用户数据清理失败: ${error.message}`, "yellow");
390
+ }
391
+ }
392
+ } else if (existsSync(pluginDataDir)) {
393
+ log("\n📁 数据保留提示:", "cyan");
394
+ log(` 用户数据目录 ${pluginDataDir} 已保留(含配置和历史记录)。`, "blue");
395
+ log(" 如需彻底删除,请运行:", "blue");
396
+ log(" 42plugin-repair --remove --purge", "blue");
397
+ }
398
+ }
399
+
215
400
  log("\n✅ 42plugin 已彻底卸载。", "green");
216
401
  log(" 如需重新安装,请运行:", "blue");
217
402
  log(" npm install -g @42ailab/42plugin", "blue");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@42ailab/42plugin",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "42plugin CLI - AI 插件管理工具",
5
5
  "bin": {
6
6
  "42plugin": "bin/42plugin",
@@ -36,10 +36,10 @@
36
36
  "node": ">=18.0.0"
37
37
  },
38
38
  "optionalDependencies": {
39
- "@42ailab/42plugin-darwin-arm64": "0.3.7",
40
- "@42ailab/42plugin-darwin-x64": "0.3.7",
41
- "@42ailab/42plugin-linux-arm64": "0.3.7",
42
- "@42ailab/42plugin-linux-x64": "0.3.7",
43
- "@42ailab/42plugin-win32-x64": "0.3.7"
39
+ "@42ailab/42plugin-darwin-arm64": "0.3.9",
40
+ "@42ailab/42plugin-darwin-x64": "0.3.9",
41
+ "@42ailab/42plugin-linux-arm64": "0.3.9",
42
+ "@42ailab/42plugin-linux-x64": "0.3.9",
43
+ "@42ailab/42plugin-win32-x64": "0.3.9"
44
44
  }
45
45
  }