cursor-guard 4.0.0 → 4.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.
- package/README.md +24 -17
- package/README.zh-CN.md +24 -17
- package/SKILL.md +1 -1
- package/package.json +2 -1
- package/references/bin/cursor-guard-init.js +120 -0
- package/references/lib/core/doctor.js +25 -7
- package/references/lib/core/snapshot.js +6 -5
- package/references/mcp/server.js +9 -1
package/README.md
CHANGED
|
@@ -29,40 +29,47 @@ When Cursor's AI agent edits your files, there's a risk of accidental overwrites
|
|
|
29
29
|
|
|
30
30
|
## Installation
|
|
31
31
|
|
|
32
|
-
### Method 1: npm
|
|
32
|
+
### Method 1: npm (Recommended)
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
npm install cursor-guard
|
|
36
|
+
npx cursor-guard-init
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
The `init` command copies skill files to `.cursor/skills/cursor-guard/`, installs MCP dependencies, and adds `.gitignore` entries — all in one step.
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
Options:
|
|
41
42
|
|
|
42
|
-
```
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# Per-project (current project only)
|
|
47
|
-
Copy-Item -Recurse node_modules/cursor-guard .cursor/skills/cursor-guard
|
|
43
|
+
```bash
|
|
44
|
+
npx cursor-guard-init # project-local (default)
|
|
45
|
+
npx cursor-guard-init --global # global (~/.cursor/skills/)
|
|
46
|
+
npx cursor-guard-init --path /my/project # specify project root
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
After init, the npm package in `node_modules` is no longer needed:
|
|
51
50
|
|
|
52
51
|
```bash
|
|
53
|
-
|
|
54
|
-
cp -r node_modules/cursor-guard ~/.cursor/skills/cursor-guard
|
|
55
|
-
|
|
56
|
-
# Per-project
|
|
57
|
-
cp -r node_modules/cursor-guard .cursor/skills/cursor-guard
|
|
52
|
+
npm uninstall cursor-guard
|
|
58
53
|
```
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
<details>
|
|
56
|
+
<summary>Manual installation (without init command)</summary>
|
|
57
|
+
|
|
58
|
+
If you prefer manual setup, copy files then install dependencies:
|
|
61
59
|
|
|
62
60
|
```bash
|
|
63
|
-
|
|
61
|
+
# Copy
|
|
62
|
+
cp -r node_modules/cursor-guard .cursor/skills/cursor-guard
|
|
63
|
+
|
|
64
|
+
# Install MCP dependencies in the skill directory
|
|
65
|
+
cd .cursor/skills/cursor-guard && npm install --omit=dev && cd -
|
|
66
|
+
|
|
67
|
+
# Add to .gitignore so node_modules aren't captured by git snapshots
|
|
68
|
+
echo ".cursor/skills/**/node_modules/" >> .gitignore
|
|
64
69
|
```
|
|
65
70
|
|
|
71
|
+
</details>
|
|
72
|
+
|
|
66
73
|
### Method 2: Git clone
|
|
67
74
|
|
|
68
75
|
```bash
|
package/README.zh-CN.md
CHANGED
|
@@ -29,40 +29,47 @@
|
|
|
29
29
|
|
|
30
30
|
## 安装
|
|
31
31
|
|
|
32
|
-
### 方式一:npm
|
|
32
|
+
### 方式一:npm 安装(推荐)
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
npm install cursor-guard
|
|
36
|
+
npx cursor-guard-init
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
`init` 命令一键完成:复制技能文件到 `.cursor/skills/cursor-guard/`、安装 MCP 依赖、添加 `.gitignore` 条目。
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
可选参数:
|
|
41
42
|
|
|
42
|
-
```
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# 项目级安装(仅当前项目生效)
|
|
47
|
-
Copy-Item -Recurse node_modules/cursor-guard .cursor/skills/cursor-guard
|
|
43
|
+
```bash
|
|
44
|
+
npx cursor-guard-init # 项目级安装(默认)
|
|
45
|
+
npx cursor-guard-init --global # 全局安装(~/.cursor/skills/)
|
|
46
|
+
npx cursor-guard-init --path /my/project # 指定项目根目录
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
初始化完成后,`node_modules` 中的 npm 包已不再需要:
|
|
51
50
|
|
|
52
51
|
```bash
|
|
53
|
-
|
|
54
|
-
cp -r node_modules/cursor-guard ~/.cursor/skills/cursor-guard
|
|
55
|
-
|
|
56
|
-
# 项目级安装
|
|
57
|
-
cp -r node_modules/cursor-guard .cursor/skills/cursor-guard
|
|
52
|
+
npm uninstall cursor-guard
|
|
58
53
|
```
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
<details>
|
|
56
|
+
<summary>手动安装(不使用 init 命令)</summary>
|
|
57
|
+
|
|
58
|
+
如果你更喜欢手动操作,复制文件后需要手动安装依赖:
|
|
61
59
|
|
|
62
60
|
```bash
|
|
63
|
-
|
|
61
|
+
# 复制
|
|
62
|
+
cp -r node_modules/cursor-guard .cursor/skills/cursor-guard
|
|
63
|
+
|
|
64
|
+
# 在 skill 目录中安装 MCP 依赖
|
|
65
|
+
cd .cursor/skills/cursor-guard && npm install --omit=dev && cd -
|
|
66
|
+
|
|
67
|
+
# 添加到 .gitignore,防止 node_modules 被 git 快照捕获
|
|
68
|
+
echo ".cursor/skills/**/node_modules/" >> .gitignore
|
|
64
69
|
```
|
|
65
70
|
|
|
71
|
+
</details>
|
|
72
|
+
|
|
66
73
|
### 方式二:Git 克隆
|
|
67
74
|
|
|
68
75
|
```bash
|
package/SKILL.md
CHANGED
|
@@ -202,7 +202,7 @@ git update-ref refs/guard/snapshot $commit
|
|
|
202
202
|
|
|
203
203
|
1. **Quick git init** (preferred):
|
|
204
204
|
```
|
|
205
|
-
git init
|
|
205
|
+
git init; git add -A; git commit -m "guard: initial snapshot" --no-verify
|
|
206
206
|
```
|
|
207
207
|
2. **Shadow copy** (fallback if user declines git):
|
|
208
208
|
- Copy the target file(s) to `.cursor-guard-backup/<timestamp>/` via Shell.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-guard",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Protects code from accidental AI overwrite or deletion in Cursor IDE — mandatory pre-write snapshots, review-before-apply, local Git safety net, and deterministic recovery. | 保护代码免受 Cursor AI 代理意外覆写或删除——强制写前快照、预览再执行、本地 Git 安全网、确定性恢复。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cursor",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"test": "node references/lib/utils.test.js && node references/lib/core/core.test.js && node references/mcp/mcp.test.js"
|
|
28
28
|
},
|
|
29
29
|
"bin": {
|
|
30
|
+
"cursor-guard-init": "references/bin/cursor-guard-init.js",
|
|
30
31
|
"cursor-guard-backup": "references/bin/cursor-guard-backup.js",
|
|
31
32
|
"cursor-guard-doctor": "references/bin/cursor-guard-doctor.js",
|
|
32
33
|
"cursor-guard-mcp": "references/mcp/server.js"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const { execFileSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
11
|
+
console.log(`Usage: cursor-guard-init [options]
|
|
12
|
+
|
|
13
|
+
Installs cursor-guard skill into your Cursor skills directory, including
|
|
14
|
+
MCP dependencies and .gitignore entries.
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--global Install to ~/.cursor/skills/ (default: project-local)
|
|
18
|
+
--path <dir> Project directory (default: current dir)
|
|
19
|
+
--help, -h Show this help message
|
|
20
|
+
--version, -v Show version number`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
25
|
+
const pkg = require('../../package.json');
|
|
26
|
+
console.log(pkg.version);
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const isGlobal = args.includes('--global');
|
|
31
|
+
const pathIdx = args.indexOf('--path');
|
|
32
|
+
const projectDir = path.resolve(pathIdx >= 0 && args[pathIdx + 1] ? args[pathIdx + 1] : '.');
|
|
33
|
+
|
|
34
|
+
const skillSource = path.resolve(__dirname, '../..');
|
|
35
|
+
const skillTarget = isGlobal
|
|
36
|
+
? path.join(process.env.USERPROFILE || process.env.HOME || os.homedir(), '.cursor', 'skills', 'cursor-guard')
|
|
37
|
+
: path.join(projectDir, '.cursor', 'skills', 'cursor-guard');
|
|
38
|
+
|
|
39
|
+
function copyRecursive(src, dest) {
|
|
40
|
+
const stat = fs.statSync(src);
|
|
41
|
+
if (stat.isDirectory()) {
|
|
42
|
+
if (path.basename(src) === 'node_modules') return;
|
|
43
|
+
if (path.basename(src) === '.git') return;
|
|
44
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
45
|
+
for (const entry of fs.readdirSync(src)) {
|
|
46
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
50
|
+
fs.copyFileSync(src, dest);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(`\n cursor-guard init\n`);
|
|
55
|
+
console.log(` Source: ${skillSource}`);
|
|
56
|
+
console.log(` Target: ${skillTarget}`);
|
|
57
|
+
console.log(` Mode: ${isGlobal ? 'global (~/.cursor/skills/)' : 'project-local (.cursor/skills/)'}\n`);
|
|
58
|
+
|
|
59
|
+
// Step 1: Copy skill files (excluding node_modules and .git)
|
|
60
|
+
console.log(' [1/4] Copying skill files...');
|
|
61
|
+
if (fs.existsSync(skillTarget)) {
|
|
62
|
+
fs.rmSync(skillTarget, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
copyRecursive(skillSource, skillTarget);
|
|
65
|
+
console.log(' Done.');
|
|
66
|
+
|
|
67
|
+
// Step 2: Install MCP dependencies in skill directory
|
|
68
|
+
console.log(' [2/4] Installing MCP dependencies...');
|
|
69
|
+
try {
|
|
70
|
+
execFileSync('npm', ['install', '--omit=dev', '--ignore-scripts'], {
|
|
71
|
+
cwd: skillTarget,
|
|
72
|
+
stdio: 'pipe',
|
|
73
|
+
shell: process.platform === 'win32',
|
|
74
|
+
});
|
|
75
|
+
console.log(' Done.');
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.log(` Warning: npm install failed (${e.message}). MCP tools may not work.`);
|
|
78
|
+
console.log(' You can fix this later: cd "' + skillTarget + '" ; npm install');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Step 3: Add .gitignore entries for skill node_modules
|
|
82
|
+
console.log(' [3/4] Updating .gitignore...');
|
|
83
|
+
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
84
|
+
const entries = ['.cursor/skills/**/node_modules/'];
|
|
85
|
+
let gitignoreUpdated = false;
|
|
86
|
+
if (!isGlobal) {
|
|
87
|
+
let existing = '';
|
|
88
|
+
try { existing = fs.readFileSync(gitignorePath, 'utf-8'); } catch { /* doesn't exist */ }
|
|
89
|
+
const missing = entries.filter(e => !existing.includes(e));
|
|
90
|
+
if (missing.length > 0) {
|
|
91
|
+
const newline = existing.endsWith('\n') || !existing ? '' : '\n';
|
|
92
|
+
fs.appendFileSync(gitignorePath, `${newline}# cursor-guard skill dependencies\n${missing.join('\n')}\n`);
|
|
93
|
+
gitignoreUpdated = true;
|
|
94
|
+
console.log(' Added: ' + missing.join(', '));
|
|
95
|
+
} else {
|
|
96
|
+
console.log(' Already configured.');
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
console.log(' Skipped (global install, not inside a project).');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Step 4: Summary
|
|
103
|
+
console.log(' [4/4] Verifying...');
|
|
104
|
+
const serverExists = fs.existsSync(path.join(skillTarget, 'references', 'mcp', 'server.js'));
|
|
105
|
+
const sdkExists = fs.existsSync(path.join(skillTarget, 'node_modules', '@modelcontextprotocol', 'sdk'));
|
|
106
|
+
const skillMdExists = fs.existsSync(path.join(skillTarget, 'SKILL.md'));
|
|
107
|
+
|
|
108
|
+
console.log(` SKILL.md: ${skillMdExists ? 'OK' : 'MISSING'}`);
|
|
109
|
+
console.log(` MCP server: ${serverExists ? 'OK' : 'MISSING'}`);
|
|
110
|
+
console.log(` MCP SDK: ${sdkExists ? 'OK' : 'MISSING — run npm install in skill dir'}`);
|
|
111
|
+
|
|
112
|
+
console.log(`\n Installation complete!\n`);
|
|
113
|
+
console.log(' Next steps:');
|
|
114
|
+
console.log(' 1. The skill activates automatically in Cursor Agent conversations.');
|
|
115
|
+
console.log(' 2. (Optional) Copy example config to project root:');
|
|
116
|
+
console.log(` cp "${path.join(skillTarget, 'references', 'cursor-guard.example.json')}" .cursor-guard.json`);
|
|
117
|
+
console.log(' 3. (Optional) Enable MCP — add to .cursor/mcp.json:');
|
|
118
|
+
console.log(` { "mcpServers": { "cursor-guard": { "command": "node", "args": ["${path.join(skillTarget, 'references', 'mcp', 'server.js').replace(/\\/g, '/')}"] } } }`);
|
|
119
|
+
console.log(' 4. (Optional) Start auto-backup:');
|
|
120
|
+
console.log(` npx cursor-guard-backup --path "${projectDir}"\n`);
|
|
@@ -217,17 +217,35 @@ function runDiagnostics(projectDir) {
|
|
|
217
217
|
|
|
218
218
|
let mcpSdkAvailable = false;
|
|
219
219
|
let mcpSdkVersion = null;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
220
|
+
// Try resolving SDK from the skill package's own node_modules first,
|
|
221
|
+
// then fall back to the running process's require paths.
|
|
222
|
+
const skillRoot = path.resolve(__dirname, '../../..');
|
|
223
|
+
const sdkCandidates = [
|
|
224
|
+
path.join(skillRoot, 'node_modules', '@modelcontextprotocol', 'sdk', 'package.json'),
|
|
225
|
+
];
|
|
226
|
+
for (const candidate of sdkCandidates) {
|
|
227
|
+
try {
|
|
228
|
+
if (fs.existsSync(candidate)) {
|
|
229
|
+
const mcpPkg = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
|
|
230
|
+
mcpSdkAvailable = true;
|
|
231
|
+
mcpSdkVersion = mcpPkg.version;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
} catch { /* ignore */ }
|
|
235
|
+
}
|
|
236
|
+
if (!mcpSdkAvailable) {
|
|
237
|
+
try {
|
|
238
|
+
const mcpPkgPath = require.resolve('@modelcontextprotocol/sdk/package.json');
|
|
239
|
+
const mcpPkg = JSON.parse(fs.readFileSync(mcpPkgPath, 'utf-8'));
|
|
240
|
+
mcpSdkAvailable = true;
|
|
241
|
+
mcpSdkVersion = mcpPkg.version;
|
|
242
|
+
} catch { /* not installed */ }
|
|
243
|
+
}
|
|
226
244
|
|
|
227
245
|
if (mcpServerExists && mcpSdkAvailable) {
|
|
228
246
|
check('MCP server', 'PASS', `server.js found, SDK ${mcpSdkVersion}`);
|
|
229
247
|
} else if (mcpServerExists && !mcpSdkAvailable) {
|
|
230
|
-
check('MCP server', 'WARN', 'server.js found but @modelcontextprotocol/sdk not installed — run npm install');
|
|
248
|
+
check('MCP server', 'WARN', 'server.js found but @modelcontextprotocol/sdk not installed — run: cd <skill-dir> && npm install');
|
|
231
249
|
} else if (!mcpServerExists && mcpSdkAvailable) {
|
|
232
250
|
check('MCP server', 'WARN', `SDK installed (${mcpSdkVersion}) but server.js not found at expected path`);
|
|
233
251
|
} else {
|
|
@@ -121,13 +121,13 @@ function createGitSnapshot(projectDir, cfg, opts = {}) {
|
|
|
121
121
|
|
|
122
122
|
git(['update-ref', branchRef, commitHash], { cwd });
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
const lsOut = git(['ls-tree', '--name-only', '-r', newTree], { cwd, allowFail: true });
|
|
125
|
+
const fileCount = lsOut ? lsOut.split('\n').filter(Boolean).length : 0;
|
|
126
|
+
|
|
127
|
+
let changedCount;
|
|
125
128
|
if (parentTree) {
|
|
126
129
|
const diff = git(['diff-tree', '--no-commit-id', '--name-only', '-r', parentTree, newTree], { cwd, allowFail: true });
|
|
127
|
-
|
|
128
|
-
} else {
|
|
129
|
-
const all = git(['ls-tree', '--name-only', '-r', newTree], { cwd, allowFail: true });
|
|
130
|
-
fileCount = all ? all.split('\n').filter(Boolean).length : 0;
|
|
130
|
+
changedCount = diff ? diff.split('\n').filter(Boolean).length : 0;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
return {
|
|
@@ -135,6 +135,7 @@ function createGitSnapshot(projectDir, cfg, opts = {}) {
|
|
|
135
135
|
commitHash,
|
|
136
136
|
shortHash: commitHash.substring(0, 7),
|
|
137
137
|
fileCount,
|
|
138
|
+
changedCount,
|
|
138
139
|
secretsExcluded: secretsExcluded.length > 0 ? secretsExcluded : undefined,
|
|
139
140
|
};
|
|
140
141
|
} catch (e) {
|
package/references/mcp/server.js
CHANGED
|
@@ -91,12 +91,20 @@ server.tool(
|
|
|
91
91
|
path: z.string().describe('Absolute path to the project directory'),
|
|
92
92
|
strategy: z.enum(['git', 'shadow', 'both']).optional().describe('Backup strategy (default: from config, or "git")'),
|
|
93
93
|
message: z.string().optional().describe('Custom commit message for git snapshot'),
|
|
94
|
+
scope: z.enum(['protected', 'all']).optional().describe('Snapshot scope: "protected" = only files matching protect patterns (default when protect is configured); "all" = all files regardless of protect config'),
|
|
94
95
|
},
|
|
95
|
-
async ({ path: projectPath, strategy, message }) => {
|
|
96
|
+
async ({ path: projectPath, strategy, message, scope }) => {
|
|
96
97
|
const resolved = path.resolve(projectPath);
|
|
97
98
|
const { loadConfig } = require('../lib/utils');
|
|
98
99
|
const { cfg } = loadConfig(resolved);
|
|
99
100
|
|
|
101
|
+
if (scope === 'all') {
|
|
102
|
+
cfg.protect = [];
|
|
103
|
+
} else if (scope === 'protected' && cfg.protect.length === 0) {
|
|
104
|
+
// "protected" requested but no protect patterns configured — snapshot all
|
|
105
|
+
// (no way to filter without patterns)
|
|
106
|
+
}
|
|
107
|
+
|
|
100
108
|
const effectiveStrategy = strategy || cfg.backup_strategy || 'git';
|
|
101
109
|
const results = {};
|
|
102
110
|
|