ronds_ai 0.1.10 → 0.1.11
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 +21 -1
- package/bin/ronds_ai.js +59 -2
- package/lib/check_record.js +240 -106
- package/lib/skills_prompt.js +39 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,9 +49,29 @@ npx ronds_ai@latest check record
|
|
|
49
49
|
- 当前 Node 版本
|
|
50
50
|
- Node 是否满足 `>=16`
|
|
51
51
|
- 当前目录下读取到的 `git user.email`
|
|
52
|
-
- 当前 `
|
|
52
|
+
- 当前 `MCP_TRACKER_WORKER_ID` 的来源和取值
|
|
53
53
|
- `~/.profile` 路径
|
|
54
54
|
|
|
55
|
+
输出 JSON 后,命令还会继续在终端里显示当前 `MCP_TRACKER_WORKER_ID`,并询问是否要修改。
|
|
56
|
+
如果选择修改:
|
|
57
|
+
|
|
58
|
+
- Windows:写入用户级持久环境变量,新的终端可直接读取
|
|
59
|
+
- Linux:写入 `~/.profile`,新的 shell 可读取
|
|
60
|
+
|
|
61
|
+
如果不想进入交互流程,可加上:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
ronds_ai check record --no-prompt
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
或:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx ronds_ai@latest check record --no-prompt
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
交互式修改后,命令会再次显示更新后的 `MCP_TRACKER_WORKER_ID`。
|
|
74
|
+
|
|
55
75
|
输出是一个 JSON,例如:
|
|
56
76
|
|
|
57
77
|
```json
|
package/bin/ronds_ai.js
CHANGED
|
@@ -1,9 +1,61 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { runCodeRecord, saveCliError } = require('../lib/code_record');
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
ENVIRONMENT_VARIABLE_NAME,
|
|
6
|
+
getRefreshNotice,
|
|
7
|
+
getPersistentWorkerHint,
|
|
8
|
+
isPersistentWorkerUpdateSupported,
|
|
9
|
+
runCheckRecord,
|
|
10
|
+
savePersistentWorkerId,
|
|
11
|
+
} = require('../lib/check_record');
|
|
5
12
|
const { runDoctor } = require('../lib/doctor');
|
|
6
13
|
const { deployHooks } = require('../lib/hooks_deploy');
|
|
14
|
+
const { promptForText, promptYesNo } = require('../lib/skills_prompt');
|
|
15
|
+
|
|
16
|
+
function writeWorkerSummary(result) {
|
|
17
|
+
process.stderr.write(`${ENVIRONMENT_VARIABLE_NAME}: ${result.workerId.value || '(空)'}\n`);
|
|
18
|
+
process.stderr.write(`来源: ${result.workerId.source || '未设置'}\n`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function maybeUpdateWorkerId() {
|
|
22
|
+
const initialResult = runCheckRecord(process.cwd());
|
|
23
|
+
writeWorkerSummary(initialResult);
|
|
24
|
+
process.stderr.write(`${getPersistentWorkerHint()}\n`);
|
|
25
|
+
|
|
26
|
+
if (!isPersistentWorkerUpdateSupported()) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const shouldModify = await promptYesNo(`是否要修改 ${ENVIRONMENT_VARIABLE_NAME}?`);
|
|
31
|
+
if (!shouldModify) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const nextValue = await promptForText(`请输入新的 ${ENVIRONMENT_VARIABLE_NAME}:`, initialResult.workerId.value);
|
|
36
|
+
if (!nextValue) {
|
|
37
|
+
process.stderr.write(`输入为空,已跳过更新 ${ENVIRONMENT_VARIABLE_NAME}。\n`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const saveResult = savePersistentWorkerId(nextValue);
|
|
42
|
+
process.stderr.write(`${ENVIRONMENT_VARIABLE_NAME} 已保存到 ${saveResult.location}。\n`);
|
|
43
|
+
|
|
44
|
+
const refreshNotice = getRefreshNotice();
|
|
45
|
+
if (refreshNotice) {
|
|
46
|
+
process.stderr.write(`${refreshNotice}\n`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
writeWorkerSummary(runCheckRecord(process.cwd()));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function shouldPromptWorkerUpdate(args) {
|
|
53
|
+
return !args.includes('--no-prompt');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function stripCheckFlags(args) {
|
|
57
|
+
return args.filter((value) => value !== '--no-prompt');
|
|
58
|
+
}
|
|
7
59
|
|
|
8
60
|
const SUPPORTED_SOURCES = new Set(['claude', 'cursor', 'codex']);
|
|
9
61
|
|
|
@@ -47,7 +99,8 @@ async function runHooksCommand(args) {
|
|
|
47
99
|
}
|
|
48
100
|
|
|
49
101
|
async function runCheckCommand(args) {
|
|
50
|
-
const
|
|
102
|
+
const filteredArgs = stripCheckFlags(args);
|
|
103
|
+
const [target] = filteredArgs;
|
|
51
104
|
|
|
52
105
|
if (target !== 'record') {
|
|
53
106
|
throw new Error('Unsupported check command');
|
|
@@ -56,6 +109,10 @@ async function runCheckCommand(args) {
|
|
|
56
109
|
const result = runCheckRecord(process.cwd());
|
|
57
110
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
58
111
|
|
|
112
|
+
if (shouldPromptWorkerUpdate(args)) {
|
|
113
|
+
await maybeUpdateWorkerId();
|
|
114
|
+
}
|
|
115
|
+
|
|
59
116
|
if (!result.ok) {
|
|
60
117
|
process.exitCode = 1;
|
|
61
118
|
}
|
package/lib/check_record.js
CHANGED
|
@@ -1,106 +1,240 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const os = require('os');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const { execFileSync } = require('child_process');
|
|
5
|
-
|
|
6
|
-
const ENVIRONMENT_VARIABLE_NAME = process.env.ENVIRONMENT_VARIABLE_NAME || 'MCP_TRACKER_WORKER_ID';
|
|
7
|
-
const MINIMUM_NODE_MAJOR = 16;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
.replace(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { execFileSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const ENVIRONMENT_VARIABLE_NAME = process.env.ENVIRONMENT_VARIABLE_NAME || 'MCP_TRACKER_WORKER_ID';
|
|
7
|
+
const MINIMUM_NODE_MAJOR = 16;
|
|
8
|
+
const SUPPORTED_PERSISTENT_PLATFORMS = new Set(['win32', 'linux']);
|
|
9
|
+
|
|
10
|
+
function readGitUserEmail(baseDir) {
|
|
11
|
+
try {
|
|
12
|
+
return execFileSync('git', ['config', 'user.email'], {
|
|
13
|
+
cwd: baseDir,
|
|
14
|
+
encoding: 'utf8',
|
|
15
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
16
|
+
})
|
|
17
|
+
.trim()
|
|
18
|
+
.replace(/^["'“”]+/, '')
|
|
19
|
+
.replace(/["'“”]+$/, '');
|
|
20
|
+
} catch {
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getProfileFilePath() {
|
|
26
|
+
return path.join(os.homedir(), '.profile');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function stripShellQuotes(value) {
|
|
30
|
+
return String(value || '')
|
|
31
|
+
.trim()
|
|
32
|
+
.replace(/^['"]/, '')
|
|
33
|
+
.replace(/['"]$/, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readWorkerIdFromProfile() {
|
|
37
|
+
const profileFile = getProfileFilePath();
|
|
38
|
+
if (!fs.existsSync(profileFile)) {
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const lines = fs.readFileSync(profileFile, 'utf8').split('\n');
|
|
43
|
+
const prefix = `export ${ENVIRONMENT_VARIABLE_NAME}=`;
|
|
44
|
+
|
|
45
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
46
|
+
const line = lines[index].trim();
|
|
47
|
+
if (!line.startsWith(prefix)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return stripShellQuotes(line.slice(prefix.length));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readWorkerIdFromWindowsUserEnv() {
|
|
58
|
+
try {
|
|
59
|
+
return execFileSync('powershell', ['-NoProfile', '-Command', `[Environment]::GetEnvironmentVariable('${ENVIRONMENT_VARIABLE_NAME}', 'User')`], {
|
|
60
|
+
encoding: 'utf8',
|
|
61
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
62
|
+
}).trim();
|
|
63
|
+
} catch {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getCurrentWorkerId() {
|
|
69
|
+
const envValue = String(process.env[ENVIRONMENT_VARIABLE_NAME] || '').trim();
|
|
70
|
+
if (envValue) {
|
|
71
|
+
return {
|
|
72
|
+
name: ENVIRONMENT_VARIABLE_NAME,
|
|
73
|
+
value: envValue,
|
|
74
|
+
source: 'env',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (process.platform === 'win32') {
|
|
79
|
+
const userEnvValue = readWorkerIdFromWindowsUserEnv();
|
|
80
|
+
if (userEnvValue) {
|
|
81
|
+
return {
|
|
82
|
+
name: ENVIRONMENT_VARIABLE_NAME,
|
|
83
|
+
value: userEnvValue,
|
|
84
|
+
source: 'user_env',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const profileValue = readWorkerIdFromProfile();
|
|
90
|
+
if (profileValue) {
|
|
91
|
+
return {
|
|
92
|
+
name: ENVIRONMENT_VARIABLE_NAME,
|
|
93
|
+
value: profileValue,
|
|
94
|
+
source: 'profile',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
name: ENVIRONMENT_VARIABLE_NAME,
|
|
100
|
+
value: '',
|
|
101
|
+
source: '',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function escapeProfileValue(value) {
|
|
106
|
+
return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function upsertProfileWorkerId(value) {
|
|
110
|
+
const profileFile = getProfileFilePath();
|
|
111
|
+
const nextLine = `export ${ENVIRONMENT_VARIABLE_NAME}="${escapeProfileValue(value)}"`;
|
|
112
|
+
const currentContent = fs.existsSync(profileFile)
|
|
113
|
+
? fs.readFileSync(profileFile, 'utf8')
|
|
114
|
+
: '';
|
|
115
|
+
const lines = currentContent ? currentContent.split('\n') : [];
|
|
116
|
+
const prefix = `export ${ENVIRONMENT_VARIABLE_NAME}=`;
|
|
117
|
+
let updated = false;
|
|
118
|
+
|
|
119
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
120
|
+
if (!lines[index].trim().startsWith(prefix)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
lines[index] = nextLine;
|
|
125
|
+
updated = true;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const nextLines = updated ? lines : lines.concat(nextLine);
|
|
130
|
+
const nextContent = `${nextLines.join('\n').replace(/\n*$/, '\n')}`;
|
|
131
|
+
if (nextContent !== currentContent) {
|
|
132
|
+
fs.writeFileSync(profileFile, nextContent, 'utf8');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
location: profileFile,
|
|
137
|
+
source: 'profile',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function setWindowsUserWorkerId(value) {
|
|
142
|
+
execFileSync('setx', [ENVIRONMENT_VARIABLE_NAME, String(value)], {
|
|
143
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
location: 'Windows user environment',
|
|
148
|
+
source: 'user_env',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function setPersistentWorkerId(value) {
|
|
153
|
+
const normalizedValue = String(value || '').trim();
|
|
154
|
+
if (!normalizedValue) {
|
|
155
|
+
throw new Error(`${ENVIRONMENT_VARIABLE_NAME} cannot be empty`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!SUPPORTED_PERSISTENT_PLATFORMS.has(process.platform)) {
|
|
159
|
+
throw new Error(`Persistent ${ENVIRONMENT_VARIABLE_NAME} updates are not supported on ${process.platform}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (process.platform === 'win32') {
|
|
163
|
+
return setWindowsUserWorkerId(normalizedValue);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return upsertProfileWorkerId(normalizedValue);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function savePersistentWorkerId(value) {
|
|
170
|
+
const normalizedValue = String(value || '').trim();
|
|
171
|
+
const result = setPersistentWorkerId(normalizedValue);
|
|
172
|
+
process.env[ENVIRONMENT_VARIABLE_NAME] = normalizedValue;
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isPersistentWorkerUpdateSupported() {
|
|
177
|
+
return SUPPORTED_PERSISTENT_PLATFORMS.has(process.platform);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getPersistentWorkerHint() {
|
|
181
|
+
if (process.platform === 'win32') {
|
|
182
|
+
return '会写入 Windows 用户级环境变量,新开的终端可以读取到。';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (process.platform === 'linux') {
|
|
186
|
+
return `会写入 ${getProfileFilePath()},新开的 shell 可以读取到。`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return `当前平台暂不支持持久化修改 ${ENVIRONMENT_VARIABLE_NAME}。`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getRefreshNotice() {
|
|
193
|
+
if (process.platform === 'win32') {
|
|
194
|
+
return '请打开新的终端,让 shell 继承更新后的用户环境变量。';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (process.platform === 'linux') {
|
|
198
|
+
return `请打开新的 shell,或执行 source ${getProfileFilePath()} 以在当前 shell 中加载新变量。`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return '';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseNodeMajor(version) {
|
|
205
|
+
const normalized = String(version || '').trim();
|
|
206
|
+
const match = normalized.match(/^v?(\d+)/);
|
|
207
|
+
return match ? Number(match[1]) : 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function runCheckRecord(targetDir = process.cwd()) {
|
|
211
|
+
const baseDir = path.resolve(targetDir);
|
|
212
|
+
const nodeVersion = process.version;
|
|
213
|
+
const nodeMajor = parseNodeMajor(nodeVersion);
|
|
214
|
+
const gitUserEmail = readGitUserEmail(baseDir);
|
|
215
|
+
const worker = getCurrentWorkerId();
|
|
216
|
+
const nodeSatisfied = nodeMajor >= MINIMUM_NODE_MAJOR;
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
ok: nodeSatisfied,
|
|
220
|
+
targetDir: baseDir,
|
|
221
|
+
node: {
|
|
222
|
+
version: nodeVersion,
|
|
223
|
+
requirement: `>=${MINIMUM_NODE_MAJOR}`,
|
|
224
|
+
satisfied: nodeSatisfied,
|
|
225
|
+
},
|
|
226
|
+
gitUserEmail,
|
|
227
|
+
workerId: worker,
|
|
228
|
+
profileFile: getProfileFilePath(),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
ENVIRONMENT_VARIABLE_NAME,
|
|
234
|
+
getCurrentWorkerId,
|
|
235
|
+
getPersistentWorkerHint,
|
|
236
|
+
getRefreshNotice,
|
|
237
|
+
isPersistentWorkerUpdateSupported,
|
|
238
|
+
runCheckRecord,
|
|
239
|
+
savePersistentWorkerId,
|
|
240
|
+
};
|
package/lib/skills_prompt.js
CHANGED
|
@@ -81,11 +81,50 @@ async function promptForScope() {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
async function promptYesNo(message) {
|
|
85
|
+
const rl = readline.createInterface({
|
|
86
|
+
input: process.stdin,
|
|
87
|
+
output: process.stdout,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
process.stderr.write(`${message} [y/N]\n`);
|
|
92
|
+
const answer = String(await new Promise((resolve) => {
|
|
93
|
+
rl.question('> ', resolve);
|
|
94
|
+
})).trim().toLowerCase();
|
|
95
|
+
return answer === 'y' || answer === 'yes';
|
|
96
|
+
} finally {
|
|
97
|
+
rl.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function promptForText(message, defaultValue = '') {
|
|
102
|
+
const rl = readline.createInterface({
|
|
103
|
+
input: process.stdin,
|
|
104
|
+
output: process.stdout,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
process.stderr.write(`${message}\n`);
|
|
109
|
+
if (defaultValue) {
|
|
110
|
+
process.stderr.write(`Current value: ${defaultValue}\n`);
|
|
111
|
+
}
|
|
112
|
+
const answer = await new Promise((resolve) => {
|
|
113
|
+
rl.question('> ', resolve);
|
|
114
|
+
});
|
|
115
|
+
return String(answer).trim();
|
|
116
|
+
} finally {
|
|
117
|
+
rl.close();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
84
121
|
module.exports = {
|
|
85
122
|
SCOPE_CHOICES,
|
|
86
123
|
TOOL_CHOICES,
|
|
87
124
|
normalizeChoiceInput,
|
|
88
125
|
normalizeCsvInput,
|
|
89
126
|
promptForScope,
|
|
127
|
+
promptForText,
|
|
90
128
|
promptForTools,
|
|
129
|
+
promptYesNo,
|
|
91
130
|
};
|