@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 +32 -3
- package/package.json +2 -2
- package/scripts/install-skill.js +117 -5
- package/scripts/uninstall-skill.js +128 -20
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
|
|
70
|
+
npm run uninstall:global
|
|
71
|
+
npm uninstall -g @catcuts-skills/commit
|
|
67
72
|
|
|
68
73
|
# 项目级卸载
|
|
69
|
-
npm uninstall
|
|
74
|
+
npm run uninstall:local
|
|
75
|
+
npm uninstall @catcuts-skills/commit
|
|
70
76
|
```
|
|
71
77
|
|
|
72
|
-
|
|
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.
|
|
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
|
-
"
|
|
18
|
+
"skills": "^1.1.2"
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
|
21
21
|
"claude-code",
|
package/scripts/install-skill.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Commit Skill Installation Script
|
|
5
|
-
* Install skill to Claude Code using
|
|
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
|
|
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
|
-
//
|
|
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
|
-
'
|
|
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
|
|
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
|
-
//
|
|
32
|
-
function
|
|
55
|
+
// Safely remove a path (supports symlinks, files, directories)
|
|
56
|
+
function safeRemovePath(filePath, description) {
|
|
33
57
|
try {
|
|
34
|
-
if (fs.existsSync(
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
109
|
+
if (isGlobal) {
|
|
110
|
+
// Global uninstallation
|
|
111
|
+
const homeDir = os.homedir();
|
|
51
112
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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');
|