@catcuts-skills/commit 1.0.2 → 1.0.4

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 CHANGED
@@ -61,15 +61,44 @@ npm test
61
61
 
62
62
  ### 卸载
63
63
 
64
+ **重要**:由于 npm 的限制,全局卸载时 preuninstall hook 可能不会执行。请按照以下步骤正确卸载:
65
+
66
+ #### 方式 1:使用 npm scripts(推荐)
67
+
64
68
  ```bash
65
69
  # 全局卸载
66
- npm uninstall -g @<your-username>/commit
70
+ npm run uninstall:global
71
+ npm uninstall -g @catcuts-skills/commit
67
72
 
68
73
  # 项目级卸载
69
- npm uninstall @<your-username>/commit
74
+ npm run uninstall:local
75
+ npm uninstall @catcuts-skills/commit
70
76
  ```
71
77
 
72
- 卸载时会自动清理 skill 文件。
78
+ #### 方式 2:手动清理(如果方式 1 失败)
79
+
80
+ ```bash
81
+ # 1. 清理技能文件
82
+ rm -rf ~/.claude/skills/commit
83
+ rm -rf ~/.agents/skills/commit
84
+
85
+ # 2. 卸载 npm 包
86
+ npm uninstall -g @catcuts-skills/commit
87
+ ```
88
+
89
+ **Windows PowerShell**:
90
+ ```powershell
91
+ # 1. 清理技能文件
92
+ Remove-Item -Recurse -Force "$env:USERPROFILE\.claude\skills\commit"
93
+ Remove-Item -Recurse -Force "$env:USERPROFILE\.agents\skills\commit"
94
+
95
+ # 2. 卸载 npm 包
96
+ npm uninstall -g @catcuts-skills/commit
97
+ ```
98
+
99
+ #### 为什么需要两步?
100
+
101
+ npm 的 `preuninstall` hook 在全局卸载时**不保证被执行**,这是 npm 的已知限制。因此需要先手动清理技能文件,再卸载 npm 包。
73
102
 
74
103
  ## 使用示例
75
104
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catcuts-skills/commit",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "读取 staged 代码差异,自动生成符合 Conventional Commits 规范的提交文本",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -15,7 +15,7 @@
15
15
  "scripts/"
16
16
  ],
17
17
  "optionalDependencies": {
18
- "add-skill": "^1.0.29"
18
+ "skills": "^1.1.2"
19
19
  },
20
20
  "keywords": [
21
21
  "claude-code",
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * Commit Skill Installation Script
5
- * Install skill to Claude Code using add-skill
5
+ * Install skill to Claude Code using skills
6
6
  *
7
7
  * Command Line Arguments (Recommended):
8
8
  * --dry-run: Test mode, show command without executing
@@ -15,15 +15,32 @@
15
15
 
16
16
  const { execSync } = require('child_process');
17
17
  const path = require('path');
18
+ const fs = require('fs');
19
+ const os = require('os');
18
20
  const { printUsageGuide } = require('./usage-guide');
19
21
 
20
22
  // Get package root directory
21
23
  const packageRoot = path.resolve(__dirname, '..');
22
24
 
25
+ // Get user home directory
26
+ const homeDir = os.homedir();
27
+
23
28
  // Read package.json to get skill name
24
29
  const packageJson = require(path.join(packageRoot, 'package.json'));
25
30
  const skillName = packageJson.name.split('/')[1] || packageJson.name;
26
31
 
32
+ // Paths to clean
33
+ const pathsToClean = {
34
+ // skills CLI canonical copy directory (root cause of the issue)
35
+ canonical: path.join(homeDir, '.agents', 'skills', skillName),
36
+
37
+ // Claude Code global skill directory (symlink)
38
+ claudeGlobal: path.join(homeDir, '.claude', 'skills', skillName),
39
+
40
+ // Claude Code project-level skill directory (if exists)
41
+ claudeLocal: path.join(process.cwd(), '.claude', 'skills', skillName),
42
+ };
43
+
27
44
  // Parse command line arguments
28
45
  const args = process.argv.slice(2);
29
46
  const dryRun = args.includes('--dry-run');
@@ -54,11 +71,102 @@ function log(message, type = 'info') {
54
71
  console.log(`${prefix} ${message}`);
55
72
  }
56
73
 
74
+ // Safely remove a path (supports symlinks, files, directories)
75
+ function safeRemovePath(filePath, description) {
76
+ try {
77
+ if (!fs.existsSync(filePath)) {
78
+ return { success: true, removed: false, message: 'Not found' };
79
+ }
80
+
81
+ const stats = fs.lstatSync(filePath);
82
+ const isLink = stats.isSymbolicLink();
83
+ const isDir = stats.isDirectory();
84
+
85
+ if (isLink) {
86
+ fs.unlinkSync(filePath);
87
+ return {
88
+ success: true,
89
+ removed: true,
90
+ message: `Removed symlink: ${description}`
91
+ };
92
+ } else if (isDir) {
93
+ fs.rmSync(filePath, { recursive: true, force: true });
94
+ return {
95
+ success: true,
96
+ removed: true,
97
+ message: `Removed directory: ${description}`
98
+ };
99
+ } else {
100
+ fs.unlinkSync(filePath);
101
+ return {
102
+ success: true,
103
+ removed: true,
104
+ message: `Removed file: ${description}`
105
+ };
106
+ }
107
+ } catch (error) {
108
+ return {
109
+ success: false,
110
+ removed: false,
111
+ message: `Failed to remove: ${error.message}`
112
+ };
113
+ }
114
+ }
115
+
116
+ // Clean old installation files
117
+ function cleanOldInstallations() {
118
+ log('\nCleaning up old installation files...', 'info');
119
+
120
+ const results = [];
121
+
122
+ // 1. Clean canonical copy directory (.agents/skills/<skill>)
123
+ const canonicalResult = safeRemovePath(
124
+ pathsToClean.canonical,
125
+ 'canonical copy'
126
+ );
127
+ results.push({ path: pathsToClean.canonical, ...canonicalResult });
128
+
129
+ // 2. Clean Claude Code global symlink
130
+ const globalResult = safeRemovePath(
131
+ pathsToClean.claudeGlobal,
132
+ 'global skill link'
133
+ );
134
+ results.push({ path: pathsToClean.claudeGlobal, ...globalResult });
135
+
136
+ // 3. Clean Claude Code project-level installation (if exists)
137
+ const localResult = safeRemovePath(
138
+ pathsToClean.claudeLocal,
139
+ 'project-level skill'
140
+ );
141
+ results.push({ path: pathsToClean.claudeLocal, ...localResult });
142
+
143
+ // Output cleanup results
144
+ let removedCount = 0;
145
+ results.forEach((result) => {
146
+ if (result.removed) {
147
+ log(` ✓ ${result.message}`, 'success');
148
+ removedCount++;
149
+ } else if (result.success) {
150
+ log(` ⊗ ${result.message} (skipped)`, 'info');
151
+ } else {
152
+ log(` ✗ ${result.message}`, 'warning');
153
+ }
154
+ });
155
+
156
+ if (removedCount > 0) {
157
+ log(`\nCleaned up ${removedCount} old installation(s)`, 'success');
158
+ } else {
159
+ log('No old installations found to clean', 'info');
160
+ }
161
+
162
+ return removedCount > 0;
163
+ }
164
+
57
165
  // Error handler
58
166
  function handleError(error) {
59
167
  log(`Installation failed: ${error.message}`, 'error');
60
168
  log('\nYou can try manual installation:', 'warning');
61
- log(` npx add-skill "${packageRoot}" -a claude-code ${isGlobal ? '-g' : ''} -y`);
169
+ log(` npx skills add "${packageRoot}" ${isGlobal ? '-g' : ''} -y`);
62
170
  process.exit(1);
63
171
  }
64
172
 
@@ -66,10 +174,14 @@ try {
66
174
  log(`Starting installation of ${skillName}...`, 'info');
67
175
  log(`Installation scope: ${isGlobal ? 'Global (GLOBAL)' : 'Project-level (LOCAL)'}`, 'info');
68
176
 
69
- // Build add-skill command
177
+ // Clean old installation files (solves incremental update file residue issue)
178
+ cleanOldInstallations();
179
+
180
+ // Build skills command
70
181
  const commandParts = [
71
182
  'npx',
72
- 'add-skill',
183
+ 'skills',
184
+ 'add',
73
185
  `"${packageRoot}"`,
74
186
  ];
75
187
 
@@ -89,7 +201,7 @@ try {
89
201
  }
90
202
 
91
203
  // Execute installation
92
- log('\nExecuting add-skill...', 'info');
204
+ log('\nExecuting skills add...', 'info');
93
205
  execSync(command, {
94
206
  stdio: 'inherit',
95
207
  cwd: packageRoot
@@ -2,7 +2,14 @@
2
2
 
3
3
  /**
4
4
  * Commit Skill Uninstallation Script
5
- * Remove installed skill files
5
+ * Remove installed skill files from Claude Code
6
+ *
7
+ * Command Line Arguments:
8
+ * --global: Force global uninstallation
9
+ * --local: Force project-level uninstallation
10
+ *
11
+ * Environment Variables:
12
+ * - SKILL_SCOPE: Installation scope, GLOBAL or LOCAL, default: GLOBAL
6
13
  */
7
14
 
8
15
  const fs = require('fs');
@@ -16,6 +23,23 @@ const packageRoot = path.resolve(__dirname, '..');
16
23
  const packageName = require(path.join(packageRoot, 'package.json')).name;
17
24
  const skillName = packageName.split('/')[1] || packageName;
18
25
 
26
+ // Parse command line arguments
27
+ const args = process.argv.slice(2);
28
+ const forceGlobal = args.includes('--global');
29
+ const forceLocal = args.includes('--local');
30
+
31
+ // Determine uninstallation scope
32
+ let scope;
33
+ if (forceGlobal) {
34
+ scope = 'GLOBAL';
35
+ } else if (forceLocal) {
36
+ scope = 'LOCAL';
37
+ } else {
38
+ scope = (process.env.SKILL_SCOPE || 'GLOBAL').toUpperCase();
39
+ }
40
+
41
+ const isGlobal = scope === 'GLOBAL';
42
+
19
43
  // Logging function
20
44
  function log(message, type = 'info') {
21
45
  const prefix = {
@@ -28,39 +52,123 @@ function log(message, type = 'info') {
28
52
  console.log(`${prefix} ${message}`);
29
53
  }
30
54
 
31
- // Remove function
32
- function removeSkill(dir, description) {
55
+ // Safely remove a path (supports symlinks, files, directories)
56
+ function safeRemovePath(filePath, description) {
33
57
  try {
34
- if (fs.existsSync(dir)) {
35
- fs.rmSync(dir, { recursive: true, force: true });
36
- log(`Removed ${description}: ${dir}`, 'success');
58
+ if (!fs.existsSync(filePath)) {
59
+ return { success: true, removed: false, message: 'Not found' };
60
+ }
61
+
62
+ const stats = fs.lstatSync(filePath);
63
+ const isLink = stats.isSymbolicLink();
64
+ const isDir = stats.isDirectory();
65
+
66
+ if (isLink) {
67
+ fs.unlinkSync(filePath);
68
+ return {
69
+ success: true,
70
+ removed: true,
71
+ message: `Removed symlink: ${description}`
72
+ };
73
+ } else if (isDir) {
74
+ fs.rmSync(filePath, { recursive: true, force: true });
75
+ return {
76
+ success: true,
77
+ removed: true,
78
+ message: `Removed directory: ${description}`
79
+ };
37
80
  } else {
38
- log(`${description} does not exist: ${dir}`, 'info');
81
+ fs.unlinkSync(filePath);
82
+ return {
83
+ success: true,
84
+ removed: true,
85
+ message: `Removed file: ${description}`
86
+ };
39
87
  }
40
88
  } catch (error) {
41
- log(`Failed to remove ${description}: ${error.message}`, 'error');
89
+ // Ignore "file not found" errors, may have been deleted concurrently
90
+ if (error.code === 'ENOENT') {
91
+ return { success: true, removed: false, message: 'Already removed' };
92
+ }
93
+ return {
94
+ success: false,
95
+ removed: false,
96
+ message: `Failed: ${error.message}`
97
+ };
42
98
  }
43
99
  }
44
100
 
45
101
  try {
46
102
  log(`Starting uninstallation of ${skillName}...`, 'info');
103
+ log(`Uninstallation scope: ${isGlobal ? 'Global (GLOBAL)' : 'Project-level (LOCAL)'}`, 'info');
104
+
105
+ log('\nCleaning up skill files...', 'info');
106
+
107
+ const results = [];
47
108
 
48
- // Remove global installation
49
- const globalDir = path.join(os.homedir(), '.claude', 'skills', skillName);
50
- removeSkill(globalDir, 'global installation');
109
+ if (isGlobal) {
110
+ // Global uninstallation
111
+ const homeDir = os.homedir();
51
112
 
52
- // Remove project-level installation
53
- const localDir = path.join(process.cwd(), '.claude', 'skills', skillName);
54
- removeSkill(localDir, 'project-level installation');
113
+ // 1. Clean canonical copy directory (.agents/skills/<skill>)
114
+ const canonicalResult = safeRemovePath(
115
+ path.join(homeDir, '.agents', 'skills', skillName),
116
+ 'canonical copy'
117
+ );
118
+ results.push(canonicalResult);
55
119
 
56
- // Remove actual storage directories (.agents/)
57
- const globalAgentsDir = path.join(os.homedir(), '.agents', 'skills', skillName);
58
- removeSkill(globalAgentsDir, 'global storage directory');
120
+ // 2. Clean Claude Code global symlink
121
+ const globalResult = safeRemovePath(
122
+ path.join(homeDir, '.claude', 'skills', skillName),
123
+ 'global skill link'
124
+ );
125
+ results.push(globalResult);
126
+ } else {
127
+ // Project-level uninstallation
128
+ const cwd = process.cwd();
59
129
 
60
- const localAgentsDir = path.join(process.cwd(), '.agents', 'skills', skillName);
61
- removeSkill(localAgentsDir, 'project-level storage directory');
130
+ // 1. Clean project-level canonical copy (if exists)
131
+ const canonicalResult = safeRemovePath(
132
+ path.join(cwd, '.agents', 'skills', skillName),
133
+ 'project-level canonical copy'
134
+ );
135
+ results.push(canonicalResult);
62
136
 
63
- log('\nUninstallation completed!', 'success');
137
+ // 2. Clean Claude Code project-level skill
138
+ const localResult = safeRemovePath(
139
+ path.join(cwd, '.claude', 'skills', skillName),
140
+ 'project-level skill'
141
+ );
142
+ results.push(localResult);
143
+ }
144
+
145
+ // Output cleanup results
146
+ let removedCount = 0;
147
+ let errorCount = 0;
148
+
149
+ results.forEach((result) => {
150
+ if (result.removed) {
151
+ log(` ✓ ${result.message}`, 'success');
152
+ removedCount++;
153
+ } else if (result.success) {
154
+ log(` ⊗ ${result.message} (skipped)`, 'info');
155
+ } else {
156
+ log(` ✗ ${result.message}`, 'error');
157
+ errorCount++;
158
+ }
159
+ });
160
+
161
+ // Summary
162
+ if (removedCount > 0) {
163
+ log(`\n✓ Uninstallation successful! Removed ${removedCount} file(s)/director(y)(ies)`, 'success');
164
+ } else if (errorCount === 0) {
165
+ log('\n⊗ No files found to uninstall', 'warning');
166
+ }
167
+
168
+ if (errorCount > 0) {
169
+ log(`\n⚠ ${errorCount} item(s) failed to remove, please clean up manually`, 'warning');
170
+ process.exit(1);
171
+ }
64
172
 
65
173
  } catch (error) {
66
174
  log(`Uninstallation failed: ${error.message}`, 'error');