@softerist/heuristic-mcp 3.2.3 → 3.2.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 +387 -376
- package/config.jsonc +800 -800
- package/features/ann-config.js +102 -110
- package/features/clear-cache.js +81 -84
- package/features/find-similar-code.js +265 -286
- package/features/hybrid-search.js +487 -536
- package/features/index-codebase.js +3139 -3270
- package/features/lifecycle.js +1011 -1063
- package/features/package-version.js +277 -291
- package/features/register.js +351 -370
- package/features/resources.js +115 -130
- package/features/set-workspace.js +214 -240
- package/index.js +693 -758
- package/lib/cache-ops.js +22 -22
- package/lib/cache-utils.js +465 -519
- package/lib/cache.js +1749 -1849
- package/lib/call-graph.js +396 -396
- package/lib/cli.js +232 -226
- package/lib/config.js +1483 -1495
- package/lib/constants.js +511 -493
- package/lib/embed-query-process.js +206 -212
- package/lib/embedding-process.js +434 -451
- package/lib/embedding-worker.js +862 -934
- package/lib/ignore-patterns.js +276 -316
- package/lib/json-worker.js +14 -14
- package/lib/json-writer.js +302 -310
- package/lib/logging.js +116 -127
- package/lib/memory-logger.js +13 -13
- package/lib/onnx-backend.js +188 -193
- package/lib/path-utils.js +18 -23
- package/lib/project-detector.js +82 -84
- package/lib/server-lifecycle.js +133 -145
- package/lib/settings-editor.js +738 -739
- package/lib/slice-normalize.js +25 -31
- package/lib/tokenizer.js +168 -203
- package/lib/utils.js +364 -409
- package/lib/vector-store-binary.js +973 -991
- package/lib/vector-store-sqlite.js +377 -414
- package/lib/workspace-env.js +32 -34
- package/mcp_config.json +9 -9
- package/package.json +86 -86
- package/scripts/clear-cache.js +20 -20
- package/scripts/download-model.js +43 -43
- package/scripts/mcp-launcher.js +49 -49
- package/scripts/postinstall.js +12 -12
- package/search-configs.js +36 -36
package/features/lifecycle.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { exec } from 'child_process';
|
|
2
|
-
import util from 'util';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
|
-
import fs from 'fs/promises';
|
|
6
|
-
import fsSync from 'fs';
|
|
7
|
-
import { loadConfig } from '../lib/config.js';
|
|
8
|
-
import { getLogFilePath } from '../lib/logging.js';
|
|
9
|
-
import { clearStaleCaches } from '../lib/cache-utils.js';
|
|
10
|
-
import {
|
|
11
|
-
findMcpServerEntry,
|
|
12
|
-
parseJsonc,
|
|
13
|
-
setMcpServerDisabledInToml,
|
|
14
|
-
upsertMcpServerEntryInText,
|
|
15
|
-
} from '../lib/settings-editor.js';
|
|
16
|
-
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import util from 'util';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import fs from 'fs/promises';
|
|
6
|
+
import fsSync from 'fs';
|
|
7
|
+
import { loadConfig } from '../lib/config.js';
|
|
8
|
+
import { getLogFilePath } from '../lib/logging.js';
|
|
9
|
+
import { clearStaleCaches } from '../lib/cache-utils.js';
|
|
10
|
+
import {
|
|
11
|
+
findMcpServerEntry,
|
|
12
|
+
parseJsonc,
|
|
13
|
+
setMcpServerDisabledInToml,
|
|
14
|
+
upsertMcpServerEntryInText,
|
|
15
|
+
} from '../lib/settings-editor.js';
|
|
16
|
+
|
|
17
17
|
const execPromise = util.promisify(exec);
|
|
18
18
|
const PID_FILE_NAME = '.heuristic-mcp.pid';
|
|
19
19
|
const BINARY_TELEMETRY_FILE = 'binary-store-telemetry.json';
|
|
@@ -31,350 +31,328 @@ function hasNonZeroBinaryTelemetry(totals) {
|
|
|
31
31
|
if (!totals || typeof totals !== 'object') return false;
|
|
32
32
|
return Object.values(totals).some((value) => Number.isFinite(value) && value > 0);
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
function getUserHomeDir() {
|
|
36
|
-
if (process.platform === 'win32' && process.env.USERPROFILE) {
|
|
37
|
-
return process.env.USERPROFILE;
|
|
38
|
-
}
|
|
39
|
-
return os.homedir();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function listPidFilePaths() {
|
|
43
|
-
const pidFiles = new Set();
|
|
44
|
-
pidFiles.add(path.join(getUserHomeDir(), PID_FILE_NAME));
|
|
45
|
-
const globalCacheRoot = path.join(getGlobalCacheDir(), 'heuristic-mcp');
|
|
46
|
-
let cacheDirs = [];
|
|
47
|
-
try {
|
|
48
|
-
cacheDirs = await fs.readdir(globalCacheRoot);
|
|
49
|
-
} catch {
|
|
50
|
-
cacheDirs = [];
|
|
51
|
-
}
|
|
52
|
-
if (!Array.isArray(cacheDirs)) {
|
|
53
|
-
cacheDirs = [];
|
|
54
|
-
}
|
|
55
|
-
for (const dir of cacheDirs) {
|
|
56
|
-
pidFiles.add(path.join(globalCacheRoot, dir, PID_FILE_NAME));
|
|
57
|
-
}
|
|
58
|
-
return Array.from(pidFiles);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function readPidFromFile(filePath) {
|
|
62
|
-
try {
|
|
63
|
-
const raw = await fs.readFile(filePath, 'utf-8');
|
|
64
|
-
const trimmed = String(raw || '').trim();
|
|
65
|
-
if (!trimmed) return null;
|
|
66
|
-
if (trimmed.startsWith('{')) {
|
|
67
|
-
try {
|
|
68
|
-
const parsed = JSON.parse(trimmed);
|
|
69
|
-
const pid = Number(parsed?.pid);
|
|
70
|
-
if (Number.isInteger(pid)) return pid;
|
|
71
|
-
} catch {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
console.
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const
|
|
352
|
-
if (!
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const updatedToml = setMcpServerDisabledInToml(raw, target, !enabled);
|
|
357
|
-
if (updatedToml === raw) {
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
await fs.writeFile(configPath, updatedToml);
|
|
361
|
-
changed++;
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const parsed = parseJsonc(raw);
|
|
366
|
-
if (!parsed) {
|
|
367
|
-
console.warn(
|
|
368
|
-
`[Lifecycle] Skipping ${name} config: not valid JSON/JSONC (won't overwrite).`
|
|
369
|
-
);
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const found = findMcpServerEntry(parsed, target);
|
|
374
|
-
if (!found || !found.entry || typeof found.entry !== 'object') {
|
|
375
|
-
continue;
|
|
376
|
-
}
|
|
377
|
-
|
|
34
|
+
|
|
35
|
+
function getUserHomeDir() {
|
|
36
|
+
if (process.platform === 'win32' && process.env.USERPROFILE) {
|
|
37
|
+
return process.env.USERPROFILE;
|
|
38
|
+
}
|
|
39
|
+
return os.homedir();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function listPidFilePaths() {
|
|
43
|
+
const pidFiles = new Set();
|
|
44
|
+
pidFiles.add(path.join(getUserHomeDir(), PID_FILE_NAME));
|
|
45
|
+
const globalCacheRoot = path.join(getGlobalCacheDir(), 'heuristic-mcp');
|
|
46
|
+
let cacheDirs = [];
|
|
47
|
+
try {
|
|
48
|
+
cacheDirs = await fs.readdir(globalCacheRoot);
|
|
49
|
+
} catch {
|
|
50
|
+
cacheDirs = [];
|
|
51
|
+
}
|
|
52
|
+
if (!Array.isArray(cacheDirs)) {
|
|
53
|
+
cacheDirs = [];
|
|
54
|
+
}
|
|
55
|
+
for (const dir of cacheDirs) {
|
|
56
|
+
pidFiles.add(path.join(globalCacheRoot, dir, PID_FILE_NAME));
|
|
57
|
+
}
|
|
58
|
+
return Array.from(pidFiles);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function readPidFromFile(filePath) {
|
|
62
|
+
try {
|
|
63
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
64
|
+
const trimmed = String(raw || '').trim();
|
|
65
|
+
if (!trimmed) return null;
|
|
66
|
+
if (trimmed.startsWith('{')) {
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(trimmed);
|
|
69
|
+
const pid = Number(parsed?.pid);
|
|
70
|
+
if (Number.isInteger(pid)) return pid;
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
const pid = parseInt(trimmed, 10);
|
|
74
|
+
if (!Number.isNaN(pid)) return pid;
|
|
75
|
+
} catch {}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function stop() {
|
|
80
|
+
console.info('[Lifecycle] Stopping Heuristic MCP servers...');
|
|
81
|
+
try {
|
|
82
|
+
const platform = process.platform;
|
|
83
|
+
const currentPid = process.pid;
|
|
84
|
+
let pids = [];
|
|
85
|
+
const cmdByPid = new Map();
|
|
86
|
+
const manualPid = process.env.HEURISTIC_MCP_PID;
|
|
87
|
+
|
|
88
|
+
if (platform === 'win32') {
|
|
89
|
+
const pidFiles = await listPidFilePaths();
|
|
90
|
+
for (const pidFile of pidFiles) {
|
|
91
|
+
const pid = await readPidFromFile(pidFile);
|
|
92
|
+
if (!Number.isInteger(pid) || pid === currentPid) continue;
|
|
93
|
+
try {
|
|
94
|
+
process.kill(pid, 0);
|
|
95
|
+
const pidValue = String(pid);
|
|
96
|
+
if (!pids.includes(pidValue)) pids.push(pidValue);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
if (e.code === 'EPERM') {
|
|
99
|
+
const pidValue = String(pid);
|
|
100
|
+
if (!pids.includes(pidValue)) pids.push(pidValue);
|
|
101
|
+
} else {
|
|
102
|
+
await fs.unlink(pidFile).catch(() => {});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const { stdout } = await execPromise(
|
|
109
|
+
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.Name -match '^node(\\\\.exe)?$' -and $_.CommandLine -and ($_.CommandLine -like '*heuristic-mcp\\\\index.js*' -or $_.CommandLine -like '*heuristic-mcp/index.js*') } | Select-Object -ExpandProperty ProcessId"`
|
|
110
|
+
);
|
|
111
|
+
const listPids = stdout
|
|
112
|
+
.trim()
|
|
113
|
+
.split(/\s+/)
|
|
114
|
+
.filter((p) => p && !isNaN(p) && parseInt(p) !== currentPid);
|
|
115
|
+
|
|
116
|
+
if (listPids.length > 0) {
|
|
117
|
+
const { stdout: cmdOut } = await execPromise(
|
|
118
|
+
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${listPids.join(',')}) } | Select-Object ProcessId, CommandLine"`
|
|
119
|
+
);
|
|
120
|
+
const lines = cmdOut.trim().split(/\r?\n/);
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
const trimmed = line.trim();
|
|
123
|
+
if (!trimmed || trimmed.startsWith('ProcessId')) continue;
|
|
124
|
+
const match = trimmed.match(/^(\d+)\s+(.*)$/);
|
|
125
|
+
if (match) {
|
|
126
|
+
const pid = parseInt(match[1], 10);
|
|
127
|
+
const cmd = match[2];
|
|
128
|
+
if (
|
|
129
|
+
cmd.includes('embedding-worker') ||
|
|
130
|
+
cmd.includes('embedding-process') ||
|
|
131
|
+
cmd.includes('json-worker')
|
|
132
|
+
) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (pid && !pids.includes(String(pid))) {
|
|
136
|
+
pids.push(String(pid));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch (_e) {}
|
|
142
|
+
} else {
|
|
143
|
+
try {
|
|
144
|
+
const { stdout } = await execPromise(`pgrep -fl "heuristic-mcp"`);
|
|
145
|
+
const lines = stdout.trim().split(/\r?\n/);
|
|
146
|
+
|
|
147
|
+
pids = [];
|
|
148
|
+
for (const line of lines) {
|
|
149
|
+
const tokens = line.trim().split(/\s+/).filter(Boolean);
|
|
150
|
+
if (tokens.length === 0) continue;
|
|
151
|
+
|
|
152
|
+
const allNumeric = tokens.every((token) => /^\d+$/.test(token));
|
|
153
|
+
const candidatePids = allNumeric ? tokens : [tokens[0]];
|
|
154
|
+
|
|
155
|
+
for (const candidate of candidatePids) {
|
|
156
|
+
const pid = parseInt(candidate, 10);
|
|
157
|
+
if (!Number.isFinite(pid) || pid === currentPid) continue;
|
|
158
|
+
|
|
159
|
+
if (
|
|
160
|
+
!allNumeric &&
|
|
161
|
+
(line.includes('embedding-worker') ||
|
|
162
|
+
line.includes('embedding-process') ||
|
|
163
|
+
line.includes('json-worker'))
|
|
164
|
+
) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
process.kill(pid, 0);
|
|
170
|
+
const pidValue = String(pid);
|
|
171
|
+
if (!pids.includes(pidValue)) {
|
|
172
|
+
pids.push(pidValue);
|
|
173
|
+
}
|
|
174
|
+
} catch (_e) {}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch (e) {
|
|
178
|
+
if (e.code === 1) pids = [];
|
|
179
|
+
else throw e;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (manualPid) {
|
|
184
|
+
const parts = String(manualPid)
|
|
185
|
+
.split(/[,\s]+/)
|
|
186
|
+
.map((part) => part.trim())
|
|
187
|
+
.filter(Boolean);
|
|
188
|
+
for (const part of parts) {
|
|
189
|
+
if (!isNaN(part)) {
|
|
190
|
+
const pidValue = String(parseInt(part, 10));
|
|
191
|
+
if (pidValue && !pids.includes(pidValue)) {
|
|
192
|
+
pids.push(pidValue);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (pids.length === 0) {
|
|
199
|
+
console.info('[Lifecycle] No running instances found (already stopped).');
|
|
200
|
+
await setMcpServerEnabled(false);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
if (platform === 'win32') {
|
|
206
|
+
const { stdout } = await execPromise(
|
|
207
|
+
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${pids.join(',')}) } | Select-Object ProcessId, CommandLine"`
|
|
208
|
+
);
|
|
209
|
+
const lines = stdout.trim().split(/\r?\n/);
|
|
210
|
+
for (const line of lines) {
|
|
211
|
+
const trimmed = line.trim();
|
|
212
|
+
if (!trimmed || trimmed.startsWith('ProcessId')) continue;
|
|
213
|
+
const match = trimmed.match(/^(\d+)\s+(.*)$/);
|
|
214
|
+
if (match) {
|
|
215
|
+
cmdByPid.set(parseInt(match[1], 10), match[2]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
const { stdout } = await execPromise(`ps -o pid=,command= -p ${pids.join(',')}`);
|
|
220
|
+
const lines = stdout.trim().split(/\r?\n/);
|
|
221
|
+
for (const line of lines) {
|
|
222
|
+
const match = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
223
|
+
if (match) {
|
|
224
|
+
cmdByPid.set(parseInt(match[1], 10), match[2]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (_e) {}
|
|
229
|
+
|
|
230
|
+
let killedCount = 0;
|
|
231
|
+
const killedPids = [];
|
|
232
|
+
const failedPids = [];
|
|
233
|
+
for (const pid of pids) {
|
|
234
|
+
try {
|
|
235
|
+
if (platform === 'win32') {
|
|
236
|
+
try {
|
|
237
|
+
await execPromise(`taskkill /PID ${pid} /T`);
|
|
238
|
+
} catch (e) {
|
|
239
|
+
const message = String(e?.message || '');
|
|
240
|
+
if (message.includes('not found') || message.includes('not be found')) {
|
|
241
|
+
killedCount++;
|
|
242
|
+
killedPids.push(pid);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
await execPromise(`taskkill /PID ${pid} /T /F`);
|
|
247
|
+
} catch (e2) {
|
|
248
|
+
const message2 = String(e2?.message || '');
|
|
249
|
+
if (message2.includes('not found') || message2.includes('not be found')) {
|
|
250
|
+
killedCount++;
|
|
251
|
+
killedPids.push(pid);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
throw e2;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
process.kill(parseInt(pid), 'SIGTERM');
|
|
259
|
+
}
|
|
260
|
+
killedCount++;
|
|
261
|
+
killedPids.push(pid);
|
|
262
|
+
} catch (e) {
|
|
263
|
+
if (e.code !== 'ESRCH') {
|
|
264
|
+
failedPids.push(pid);
|
|
265
|
+
console.warn(`[Lifecycle] Failed to kill PID ${pid}: ${e.message}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
console.info(`[Lifecycle] ✅ Stopped ${killedCount} running instance(s).`);
|
|
271
|
+
if (killedPids.length > 0) {
|
|
272
|
+
console.info('[Lifecycle] Killed processes:');
|
|
273
|
+
for (const pid of killedPids) {
|
|
274
|
+
const cmd = cmdByPid.get(parseInt(pid, 10));
|
|
275
|
+
if (cmd) {
|
|
276
|
+
console.info(` ${pid}: ${cmd}`);
|
|
277
|
+
} else {
|
|
278
|
+
console.info(` ${pid}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (failedPids.length > 0) {
|
|
283
|
+
console.info('[Lifecycle] Failed to kill:');
|
|
284
|
+
for (const pid of failedPids) {
|
|
285
|
+
const cmd = cmdByPid.get(parseInt(pid, 10));
|
|
286
|
+
if (cmd) {
|
|
287
|
+
console.info(` ${pid}: ${cmd}`);
|
|
288
|
+
} else {
|
|
289
|
+
console.info(` ${pid}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
await setMcpServerEnabled(false);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.warn(`[Lifecycle] Warning: Stop command encountered an error: ${error.message}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export async function start(filter = null) {
|
|
301
|
+
console.info('[Lifecycle] Ensuring server is configured...');
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const { register } = await import('./register.js');
|
|
305
|
+
await register(filter);
|
|
306
|
+
await setMcpServerEnabled(true);
|
|
307
|
+
console.info('[Lifecycle] ✅ Configuration checked.');
|
|
308
|
+
console.info(
|
|
309
|
+
'[Lifecycle] To start the server, please reload your IDE window or restart the IDE.'
|
|
310
|
+
);
|
|
311
|
+
} catch (err) {
|
|
312
|
+
console.error(`[Lifecycle] Failed to configure server: ${err.message}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function setMcpServerEnabled(enabled) {
|
|
317
|
+
const paths = getMcpConfigPaths();
|
|
318
|
+
const target = 'heuristic-mcp';
|
|
319
|
+
let changed = 0;
|
|
320
|
+
|
|
321
|
+
for (const { name, path: configPath, format } of paths) {
|
|
322
|
+
try {
|
|
323
|
+
await fs.access(configPath);
|
|
324
|
+
} catch {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const raw = await fs.readFile(configPath, 'utf-8');
|
|
330
|
+
if (!raw || !raw.trim()) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (format === 'toml') {
|
|
334
|
+
const updatedToml = setMcpServerDisabledInToml(raw, target, !enabled);
|
|
335
|
+
if (updatedToml === raw) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
await fs.writeFile(configPath, updatedToml);
|
|
339
|
+
changed++;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const parsed = parseJsonc(raw);
|
|
344
|
+
if (!parsed) {
|
|
345
|
+
console.warn(
|
|
346
|
+
`[Lifecycle] Skipping ${name} config: not valid JSON/JSONC (won't overwrite).`
|
|
347
|
+
);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const found = findMcpServerEntry(parsed, target);
|
|
352
|
+
if (!found || !found.entry || typeof found.entry !== 'object') {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
378
356
|
const updatedEntry = { ...found.entry };
|
|
379
357
|
if (enabled) {
|
|
380
358
|
delete updatedEntry.disabled;
|
|
@@ -382,183 +360,181 @@ async function setMcpServerEnabled(enabled) {
|
|
|
382
360
|
updatedEntry.disabled = true;
|
|
383
361
|
}
|
|
384
362
|
const updatedText = upsertMcpServerEntryInText(raw, target, updatedEntry);
|
|
385
|
-
if (!updatedText) {
|
|
386
|
-
console.warn(`[Lifecycle] Failed to update ${name} config (unparseable layout).`);
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
await fs.writeFile(configPath, updatedText);
|
|
391
|
-
changed++;
|
|
392
|
-
} catch (err) {
|
|
393
|
-
console.warn(`[Lifecycle] Failed to update ${name} config: ${err.message}`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (changed > 0) {
|
|
398
|
-
console.info(
|
|
399
|
-
`[Lifecycle] MCP server ${enabled ? 'enabled' : 'disabled'} in ${changed} config file(s).`
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function getMcpConfigPaths() {
|
|
405
|
-
const home = getUserHomeDir();
|
|
406
|
-
const configLocations = [
|
|
407
|
-
{
|
|
408
|
-
name: 'Antigravity',
|
|
409
|
-
path: path.join(home, '.gemini', 'antigravity', 'mcp_config.json'),
|
|
410
|
-
format: 'json',
|
|
411
|
-
},
|
|
412
|
-
{
|
|
413
|
-
name: 'Codex',
|
|
414
|
-
path: path.join(home, '.codex', 'config.toml'),
|
|
415
|
-
format: 'toml',
|
|
416
|
-
},
|
|
417
|
-
{
|
|
418
|
-
name: 'Claude Desktop',
|
|
419
|
-
path: path.join(home, '.config', 'Claude', 'claude_desktop_config.json'),
|
|
420
|
-
format: 'json',
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
name: 'VS Code',
|
|
424
|
-
path: path.join(home, '.config', 'Code', 'User', 'mcp.json'),
|
|
425
|
-
format: 'json',
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
name: 'VS Code Insiders',
|
|
429
|
-
path: path.join(home, '.config', 'Code - Insiders', 'User', 'mcp.json'),
|
|
430
|
-
format: 'json',
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
name: 'Cursor',
|
|
434
|
-
path: path.join(home, '.config', 'Cursor', 'User', 'settings.json'),
|
|
435
|
-
format: 'json',
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
name: 'Cursor Global',
|
|
439
|
-
path: path.join(home, '.cursor', 'mcp.json'),
|
|
440
|
-
format: 'json',
|
|
441
|
-
},
|
|
442
|
-
{
|
|
443
|
-
name: 'Windsurf',
|
|
444
|
-
path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json'),
|
|
445
|
-
format: 'json',
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
name: 'Warp',
|
|
449
|
-
path: path.join(home, '.warp', 'mcp_settings.json'),
|
|
450
|
-
format: 'json',
|
|
451
|
-
},
|
|
452
|
-
];
|
|
453
|
-
|
|
454
|
-
if (process.platform === 'darwin') {
|
|
455
|
-
configLocations[2].path = path.join(
|
|
456
|
-
home,
|
|
457
|
-
'Library',
|
|
458
|
-
'Application Support',
|
|
459
|
-
'Claude',
|
|
460
|
-
'claude_desktop_config.json'
|
|
461
|
-
);
|
|
462
|
-
configLocations[3].path = path.join(
|
|
463
|
-
home,
|
|
464
|
-
'Library',
|
|
465
|
-
'Application Support',
|
|
466
|
-
'Code',
|
|
467
|
-
'User',
|
|
468
|
-
'mcp.json'
|
|
469
|
-
);
|
|
470
|
-
configLocations[4].path = path.join(
|
|
471
|
-
home,
|
|
472
|
-
'Library',
|
|
473
|
-
'Application Support',
|
|
474
|
-
'Code - Insiders',
|
|
475
|
-
'User',
|
|
476
|
-
'mcp.json'
|
|
477
|
-
);
|
|
478
|
-
configLocations[5].path = path.join(
|
|
479
|
-
home,
|
|
480
|
-
'Library',
|
|
481
|
-
'Application Support',
|
|
482
|
-
'Cursor',
|
|
483
|
-
'User',
|
|
484
|
-
'settings.json'
|
|
485
|
-
);
|
|
486
|
-
} else if (process.platform === 'win32') {
|
|
487
|
-
configLocations[2].path = path.join(
|
|
488
|
-
process.env.APPDATA || '',
|
|
489
|
-
'Claude',
|
|
490
|
-
'claude_desktop_config.json'
|
|
491
|
-
);
|
|
492
|
-
configLocations[3].path = path.join(process.env.APPDATA || '', 'Code', 'User', 'mcp.json');
|
|
493
|
-
configLocations[4].path = path.join(
|
|
494
|
-
process.env.APPDATA || '',
|
|
495
|
-
'Code - Insiders',
|
|
496
|
-
'User',
|
|
497
|
-
'mcp.json'
|
|
498
|
-
);
|
|
499
|
-
configLocations[5].path = path.join(
|
|
500
|
-
process.env.APPDATA || '',
|
|
501
|
-
'Cursor',
|
|
502
|
-
'User',
|
|
503
|
-
'settings.json'
|
|
504
|
-
);
|
|
505
|
-
configLocations.push({
|
|
506
|
-
name: 'Warp AppData',
|
|
507
|
-
path: path.join(process.env.APPDATA || '', 'Warp', 'mcp_settings.json'),
|
|
508
|
-
format: 'json',
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
return configLocations;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
async function readTail(filePath, maxLines) {
|
|
516
|
-
const data = await fs.readFile(filePath, 'utf-8');
|
|
517
|
-
if (!data) return '';
|
|
518
|
-
const lines = data.split(/\r?\n/);
|
|
519
|
-
const tail = lines.slice(-maxLines).join('\n');
|
|
520
|
-
return tail.trimEnd();
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
async function followFile(filePath, startPosition) {
|
|
524
|
-
let position = startPosition;
|
|
525
|
-
const watcher = fsSync.watch(filePath, { persistent: true }, async (event) => {
|
|
526
|
-
if (event !== 'change') return;
|
|
527
|
-
try {
|
|
528
|
-
const stats = await fs.stat(filePath);
|
|
529
|
-
if (stats.size < position) {
|
|
530
|
-
position = 0;
|
|
531
|
-
}
|
|
532
|
-
if (stats.size === position) return;
|
|
533
|
-
const stream = fsSync.createReadStream(filePath, { start: position, end: stats.size - 1 });
|
|
534
|
-
stream.pipe(process.stdout, { end: false });
|
|
535
|
-
position = stats.size;
|
|
536
|
-
} catch {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
|
|
363
|
+
if (!updatedText) {
|
|
364
|
+
console.warn(`[Lifecycle] Failed to update ${name} config (unparseable layout).`);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
await fs.writeFile(configPath, updatedText);
|
|
369
|
+
changed++;
|
|
370
|
+
} catch (err) {
|
|
371
|
+
console.warn(`[Lifecycle] Failed to update ${name} config: ${err.message}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (changed > 0) {
|
|
376
|
+
console.info(
|
|
377
|
+
`[Lifecycle] MCP server ${enabled ? 'enabled' : 'disabled'} in ${changed} config file(s).`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function getMcpConfigPaths() {
|
|
383
|
+
const home = getUserHomeDir();
|
|
384
|
+
const configLocations = [
|
|
385
|
+
{
|
|
386
|
+
name: 'Antigravity',
|
|
387
|
+
path: path.join(home, '.gemini', 'antigravity', 'mcp_config.json'),
|
|
388
|
+
format: 'json',
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: 'Codex',
|
|
392
|
+
path: path.join(home, '.codex', 'config.toml'),
|
|
393
|
+
format: 'toml',
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: 'Claude Desktop',
|
|
397
|
+
path: path.join(home, '.config', 'Claude', 'claude_desktop_config.json'),
|
|
398
|
+
format: 'json',
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: 'VS Code',
|
|
402
|
+
path: path.join(home, '.config', 'Code', 'User', 'mcp.json'),
|
|
403
|
+
format: 'json',
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: 'VS Code Insiders',
|
|
407
|
+
path: path.join(home, '.config', 'Code - Insiders', 'User', 'mcp.json'),
|
|
408
|
+
format: 'json',
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: 'Cursor',
|
|
412
|
+
path: path.join(home, '.config', 'Cursor', 'User', 'settings.json'),
|
|
413
|
+
format: 'json',
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: 'Cursor Global',
|
|
417
|
+
path: path.join(home, '.cursor', 'mcp.json'),
|
|
418
|
+
format: 'json',
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: 'Windsurf',
|
|
422
|
+
path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json'),
|
|
423
|
+
format: 'json',
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: 'Warp',
|
|
427
|
+
path: path.join(home, '.warp', 'mcp_settings.json'),
|
|
428
|
+
format: 'json',
|
|
429
|
+
},
|
|
430
|
+
];
|
|
431
|
+
|
|
432
|
+
if (process.platform === 'darwin') {
|
|
433
|
+
configLocations[2].path = path.join(
|
|
434
|
+
home,
|
|
435
|
+
'Library',
|
|
436
|
+
'Application Support',
|
|
437
|
+
'Claude',
|
|
438
|
+
'claude_desktop_config.json'
|
|
439
|
+
);
|
|
440
|
+
configLocations[3].path = path.join(
|
|
441
|
+
home,
|
|
442
|
+
'Library',
|
|
443
|
+
'Application Support',
|
|
444
|
+
'Code',
|
|
445
|
+
'User',
|
|
446
|
+
'mcp.json'
|
|
447
|
+
);
|
|
448
|
+
configLocations[4].path = path.join(
|
|
449
|
+
home,
|
|
450
|
+
'Library',
|
|
451
|
+
'Application Support',
|
|
452
|
+
'Code - Insiders',
|
|
453
|
+
'User',
|
|
454
|
+
'mcp.json'
|
|
455
|
+
);
|
|
456
|
+
configLocations[5].path = path.join(
|
|
457
|
+
home,
|
|
458
|
+
'Library',
|
|
459
|
+
'Application Support',
|
|
460
|
+
'Cursor',
|
|
461
|
+
'User',
|
|
462
|
+
'settings.json'
|
|
463
|
+
);
|
|
464
|
+
} else if (process.platform === 'win32') {
|
|
465
|
+
configLocations[2].path = path.join(
|
|
466
|
+
process.env.APPDATA || '',
|
|
467
|
+
'Claude',
|
|
468
|
+
'claude_desktop_config.json'
|
|
469
|
+
);
|
|
470
|
+
configLocations[3].path = path.join(process.env.APPDATA || '', 'Code', 'User', 'mcp.json');
|
|
471
|
+
configLocations[4].path = path.join(
|
|
472
|
+
process.env.APPDATA || '',
|
|
473
|
+
'Code - Insiders',
|
|
474
|
+
'User',
|
|
475
|
+
'mcp.json'
|
|
476
|
+
);
|
|
477
|
+
configLocations[5].path = path.join(
|
|
478
|
+
process.env.APPDATA || '',
|
|
479
|
+
'Cursor',
|
|
480
|
+
'User',
|
|
481
|
+
'settings.json'
|
|
482
|
+
);
|
|
483
|
+
configLocations.push({
|
|
484
|
+
name: 'Warp AppData',
|
|
485
|
+
path: path.join(process.env.APPDATA || '', 'Warp', 'mcp_settings.json'),
|
|
486
|
+
format: 'json',
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return configLocations;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async function readTail(filePath, maxLines) {
|
|
494
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
495
|
+
if (!data) return '';
|
|
496
|
+
const lines = data.split(/\r?\n/);
|
|
497
|
+
const tail = lines.slice(-maxLines).join('\n');
|
|
498
|
+
return tail.trimEnd();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function followFile(filePath, startPosition) {
|
|
502
|
+
let position = startPosition;
|
|
503
|
+
const watcher = fsSync.watch(filePath, { persistent: true }, async (event) => {
|
|
504
|
+
if (event !== 'change') return;
|
|
505
|
+
try {
|
|
506
|
+
const stats = await fs.stat(filePath);
|
|
507
|
+
if (stats.size < position) {
|
|
508
|
+
position = 0;
|
|
509
|
+
}
|
|
510
|
+
if (stats.size === position) return;
|
|
511
|
+
const stream = fsSync.createReadStream(filePath, { start: position, end: stats.size - 1 });
|
|
512
|
+
stream.pipe(process.stdout, { end: false });
|
|
513
|
+
position = stats.size;
|
|
514
|
+
} catch {}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const stop = () => {
|
|
518
|
+
watcher.close();
|
|
519
|
+
process.exit(0);
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
process.on('SIGINT', stop);
|
|
523
|
+
process.on('SIGTERM', stop);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function formatDurationMs(ms) {
|
|
527
|
+
if (!Number.isFinite(ms) || ms < 0) return null;
|
|
528
|
+
const totalSeconds = Math.round(ms / 1000);
|
|
529
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
530
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
531
|
+
const seconds = totalSeconds % 60;
|
|
532
|
+
|
|
533
|
+
if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
|
|
534
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
535
|
+
return `${seconds}s`;
|
|
536
|
+
}
|
|
537
|
+
|
|
562
538
|
function formatDateTime(value) {
|
|
563
539
|
const date = value instanceof Date ? value : new Date(value);
|
|
564
540
|
if (Number.isNaN(date.getTime())) return null;
|
|
@@ -718,216 +694,206 @@ function selectRuntimeForStatus({ pids, runtimeByPid, requestedWorkspace }) {
|
|
|
718
694
|
}
|
|
719
695
|
|
|
720
696
|
async function captureConsoleOutput(fn) {
|
|
721
|
-
const original = {
|
|
722
|
-
info: console.info,
|
|
723
|
-
warn: console.warn,
|
|
724
|
-
error: console.error,
|
|
725
|
-
};
|
|
726
|
-
const lines = [];
|
|
727
|
-
const collect = (...args) => {
|
|
728
|
-
const message = util.format(...args);
|
|
729
|
-
if (message && message.trim()) {
|
|
730
|
-
lines.push(message);
|
|
731
|
-
}
|
|
732
|
-
};
|
|
733
|
-
console.info = collect;
|
|
734
|
-
console.warn = collect;
|
|
735
|
-
console.error = collect;
|
|
736
|
-
try {
|
|
737
|
-
const result = await fn();
|
|
738
|
-
return { result, lines };
|
|
739
|
-
} finally {
|
|
740
|
-
console.info = original.info;
|
|
741
|
-
console.warn = original.warn;
|
|
742
|
-
console.error = original.error;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
export async function logs({ workspaceDir = null, tailLines = 200, follow = true } = {}) {
|
|
747
|
-
const config = await loadConfig(workspaceDir);
|
|
748
|
-
const logPath = getLogFilePath(config);
|
|
749
|
-
|
|
750
|
-
try {
|
|
751
|
-
const stats = await fs.stat(logPath);
|
|
752
|
-
const tail = await readTail(logPath, tailLines);
|
|
753
|
-
if (tail) {
|
|
754
|
-
process.stdout.write(tail + '\n');
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
if (!follow) {
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
console.info(`[Logs] Following ${logPath} (Ctrl+C to stop)...`);
|
|
762
|
-
await followFile(logPath, stats.size);
|
|
763
|
-
} catch (err) {
|
|
764
|
-
if (err.code === 'ENOENT') {
|
|
765
|
-
console.error(`[Logs] No log file found for workspace.`);
|
|
766
|
-
console.error(`[Logs] Expected location: ${logPath}`);
|
|
767
|
-
console.error(`[Logs] Start the server from your IDE, then run: heuristic-mcp --logs`);
|
|
768
|
-
return;
|
|
769
|
-
}
|
|
770
|
-
console.error(`[Logs] Failed to read log file: ${err.message}`);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// Helper to get global cache dir
|
|
775
|
-
function getGlobalCacheDir() {
|
|
776
|
-
const home = getUserHomeDir();
|
|
777
|
-
if (process.platform === 'win32') {
|
|
778
|
-
return process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
779
|
-
} else if (process.platform === 'darwin') {
|
|
780
|
-
return path.join(home, 'Library', 'Caches');
|
|
781
|
-
}
|
|
782
|
-
return process.env.XDG_CACHE_HOME || path.join(home, '.cache');
|
|
783
|
-
}
|
|
784
|
-
|
|
697
|
+
const original = {
|
|
698
|
+
info: console.info,
|
|
699
|
+
warn: console.warn,
|
|
700
|
+
error: console.error,
|
|
701
|
+
};
|
|
702
|
+
const lines = [];
|
|
703
|
+
const collect = (...args) => {
|
|
704
|
+
const message = util.format(...args);
|
|
705
|
+
if (message && message.trim()) {
|
|
706
|
+
lines.push(message);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
console.info = collect;
|
|
710
|
+
console.warn = collect;
|
|
711
|
+
console.error = collect;
|
|
712
|
+
try {
|
|
713
|
+
const result = await fn();
|
|
714
|
+
return { result, lines };
|
|
715
|
+
} finally {
|
|
716
|
+
console.info = original.info;
|
|
717
|
+
console.warn = original.warn;
|
|
718
|
+
console.error = original.error;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
export async function logs({ workspaceDir = null, tailLines = 200, follow = true } = {}) {
|
|
723
|
+
const config = await loadConfig(workspaceDir);
|
|
724
|
+
const logPath = getLogFilePath(config);
|
|
725
|
+
|
|
726
|
+
try {
|
|
727
|
+
const stats = await fs.stat(logPath);
|
|
728
|
+
const tail = await readTail(logPath, tailLines);
|
|
729
|
+
if (tail) {
|
|
730
|
+
process.stdout.write(tail + '\n');
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (!follow) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
console.info(`[Logs] Following ${logPath} (Ctrl+C to stop)...`);
|
|
738
|
+
await followFile(logPath, stats.size);
|
|
739
|
+
} catch (err) {
|
|
740
|
+
if (err.code === 'ENOENT') {
|
|
741
|
+
console.error(`[Logs] No log file found for workspace.`);
|
|
742
|
+
console.error(`[Logs] Expected location: ${logPath}`);
|
|
743
|
+
console.error(`[Logs] Start the server from your IDE, then run: heuristic-mcp --logs`);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
console.error(`[Logs] Failed to read log file: ${err.message}`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Helper to get global cache dir
|
|
751
|
+
function getGlobalCacheDir() {
|
|
752
|
+
const home = getUserHomeDir();
|
|
753
|
+
if (process.platform === 'win32') {
|
|
754
|
+
return process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
755
|
+
} else if (process.platform === 'darwin') {
|
|
756
|
+
return path.join(home, 'Library', 'Caches');
|
|
757
|
+
}
|
|
758
|
+
return process.env.XDG_CACHE_HOME || path.join(home, '.cache');
|
|
759
|
+
}
|
|
760
|
+
|
|
785
761
|
export async function status({ fix = false, cacheOnly = false, workspaceDir = null } = {}) {
|
|
786
762
|
try {
|
|
787
763
|
const pids = [];
|
|
788
|
-
const now = new Date();
|
|
789
|
-
const globalCacheRoot = path.join(getGlobalCacheDir(), 'heuristic-mcp');
|
|
790
|
-
let logPath = 'unknown';
|
|
791
|
-
let logStatus = '';
|
|
764
|
+
const now = new Date();
|
|
765
|
+
const globalCacheRoot = path.join(getGlobalCacheDir(), 'heuristic-mcp');
|
|
766
|
+
let logPath = 'unknown';
|
|
767
|
+
let logStatus = '';
|
|
792
768
|
let cacheSummary = null;
|
|
793
769
|
let config = null;
|
|
794
770
|
let configLogs = [];
|
|
795
771
|
const cmdByPid = new Map();
|
|
796
772
|
let runtimeByPid = new Map();
|
|
797
773
|
let selectedRuntime = null;
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
) {
|
|
866
|
-
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
if (!cacheOnly) {
|
|
886
|
-
|
|
887
|
-
console.info('');
|
|
888
|
-
if (pids.length > 0) {
|
|
889
|
-
console.info(`[Lifecycle] 🟢 Server is RUNNING. PID(s): ${pids.join(', ')}`);
|
|
890
|
-
} else {
|
|
891
|
-
console.info('[Lifecycle] ⚪ Server is STOPPED.');
|
|
892
|
-
}
|
|
774
|
+
|
|
775
|
+
const pidFiles = await listPidFilePaths();
|
|
776
|
+
for (const pidFile of pidFiles) {
|
|
777
|
+
const pid = await readPidFromFile(pidFile);
|
|
778
|
+
if (!Number.isInteger(pid)) continue;
|
|
779
|
+
|
|
780
|
+
try {
|
|
781
|
+
process.kill(pid, 0);
|
|
782
|
+
pids.push(pid);
|
|
783
|
+
} catch (_e) {
|
|
784
|
+
await fs.unlink(pidFile).catch(() => {});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (pids.length === 0) {
|
|
789
|
+
try {
|
|
790
|
+
const myPid = process.pid;
|
|
791
|
+
if (process.platform === 'win32') {
|
|
792
|
+
const { stdout } = await execPromise(
|
|
793
|
+
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.Name -match '^node(\\\\.exe)?$' -and $_.CommandLine -and ($_.CommandLine -like '*heuristic-mcp\\\\index.js*' -or $_.CommandLine -like '*heuristic-mcp/index.js*') } | Select-Object -ExpandProperty ProcessId"`
|
|
794
|
+
);
|
|
795
|
+
const winPids = stdout
|
|
796
|
+
.trim()
|
|
797
|
+
.split(/\s+/)
|
|
798
|
+
.filter((p) => p && !isNaN(p));
|
|
799
|
+
|
|
800
|
+
if (winPids.length > 0) {
|
|
801
|
+
const { stdout: cmdOut } = await execPromise(
|
|
802
|
+
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${winPids.join(',')}) } | Select-Object ProcessId, CommandLine"`
|
|
803
|
+
);
|
|
804
|
+
const lines = cmdOut.trim().split(/\r?\n/);
|
|
805
|
+
for (const line of lines) {
|
|
806
|
+
const trimmed = line.trim();
|
|
807
|
+
if (!trimmed || trimmed.startsWith('ProcessId')) continue;
|
|
808
|
+
const match = trimmed.match(/^(\d+)\s+(.*)$/);
|
|
809
|
+
if (match) {
|
|
810
|
+
const pid = parseInt(match[1], 10);
|
|
811
|
+
const cmd = match[2];
|
|
812
|
+
if (
|
|
813
|
+
cmd.includes('embedding-worker') ||
|
|
814
|
+
cmd.includes('embedding-process') ||
|
|
815
|
+
cmd.includes('json-worker')
|
|
816
|
+
) {
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
if (pid && pid !== myPid) {
|
|
820
|
+
if (!pids.includes(pid)) pids.push(pid);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
} else {
|
|
826
|
+
const { stdout } = await execPromise('ps aux');
|
|
827
|
+
const lines = stdout.split('\n');
|
|
828
|
+
const validPids = [];
|
|
829
|
+
|
|
830
|
+
for (const line of lines) {
|
|
831
|
+
if (line.includes('heuristic-mcp/index.js') || line.includes('heuristic-mcp')) {
|
|
832
|
+
if (
|
|
833
|
+
line.includes('embedding-worker') ||
|
|
834
|
+
line.includes('embedding-process') ||
|
|
835
|
+
line.includes('json-worker')
|
|
836
|
+
) {
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
const parts = line.trim().split(/\s+/);
|
|
840
|
+
const pid = parseInt(parts[1], 10);
|
|
841
|
+
if (pid && !isNaN(pid) && pid !== myPid && !line.includes(' grep ')) {
|
|
842
|
+
validPids.push(pid);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
for (const p of validPids) {
|
|
848
|
+
if (!pids.includes(p)) pids.push(p);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
} catch (_e) {}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (!cacheOnly) {
|
|
855
|
+
console.info('');
|
|
856
|
+
if (pids.length > 0) {
|
|
857
|
+
console.info(`[Lifecycle] 🟢 Server is RUNNING. PID(s): ${pids.join(', ')}`);
|
|
858
|
+
} else {
|
|
859
|
+
console.info('[Lifecycle] ⚪ Server is STOPPED.');
|
|
860
|
+
}
|
|
893
861
|
if (pids.length > 1) {
|
|
894
862
|
console.info('[Lifecycle] ⚠️ Multiple servers detected; progress may be inconsistent.');
|
|
895
863
|
}
|
|
896
864
|
if (pids.length > 0) {
|
|
897
865
|
try {
|
|
898
866
|
if (process.platform === 'win32') {
|
|
899
|
-
const { stdout } = await execPromise(
|
|
900
|
-
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${pids.join(',')}) } | Select-Object ProcessId, CommandLine"`
|
|
901
|
-
);
|
|
902
|
-
const lines = stdout.trim().split(/\r?\n/);
|
|
903
|
-
for (const line of lines) {
|
|
904
|
-
const trimmed = line.trim();
|
|
905
|
-
if (!trimmed || trimmed.startsWith('ProcessId')) continue;
|
|
906
|
-
const match = trimmed.match(/^(\d+)\s+(.*)$/);
|
|
907
|
-
if (match) {
|
|
908
|
-
cmdByPid.set(parseInt(match[1], 10), match[2]);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
} else {
|
|
912
|
-
const { stdout } = await execPromise(`ps -o pid=,command= -p ${pids.join(',')}`);
|
|
913
|
-
const lines = stdout.trim().split(/\r?\n/);
|
|
914
|
-
for (const line of lines) {
|
|
915
|
-
const match = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
916
|
-
if (match) {
|
|
917
|
-
cmdByPid.set(parseInt(match[1], 10), match[2]);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
} catch (_e) {
|
|
922
|
-
|
|
923
|
-
}
|
|
867
|
+
const { stdout } = await execPromise(
|
|
868
|
+
`powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -in @(${pids.join(',')}) } | Select-Object ProcessId, CommandLine"`
|
|
869
|
+
);
|
|
870
|
+
const lines = stdout.trim().split(/\r?\n/);
|
|
871
|
+
for (const line of lines) {
|
|
872
|
+
const trimmed = line.trim();
|
|
873
|
+
if (!trimmed || trimmed.startsWith('ProcessId')) continue;
|
|
874
|
+
const match = trimmed.match(/^(\d+)\s+(.*)$/);
|
|
875
|
+
if (match) {
|
|
876
|
+
cmdByPid.set(parseInt(match[1], 10), match[2]);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
} else {
|
|
880
|
+
const { stdout } = await execPromise(`ps -o pid=,command= -p ${pids.join(',')}`);
|
|
881
|
+
const lines = stdout.trim().split(/\r?\n/);
|
|
882
|
+
for (const line of lines) {
|
|
883
|
+
const match = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
884
|
+
if (match) {
|
|
885
|
+
cmdByPid.set(parseInt(match[1], 10), match[2]);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
} catch (_e) {}
|
|
924
890
|
if (cmdByPid.size > 0) {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
const cmd = cmdByPid.get(pid);
|
|
928
|
-
if (cmd) {
|
|
929
|
-
console.info(` ${pid}: ${cmd}`);
|
|
930
|
-
}
|
|
891
|
+
console.info('[Lifecycle] Active command lines:');
|
|
892
|
+
for (const pid of pids) {
|
|
893
|
+
const cmd = cmdByPid.get(pid);
|
|
894
|
+
if (cmd) {
|
|
895
|
+
console.info(` ${pid}: ${cmd}`);
|
|
896
|
+
}
|
|
931
897
|
}
|
|
932
898
|
}
|
|
933
899
|
|
|
@@ -947,8 +913,8 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
947
913
|
requestedWorkspace: workspaceDir,
|
|
948
914
|
});
|
|
949
915
|
}
|
|
950
|
-
console.info('');
|
|
951
|
-
}
|
|
916
|
+
console.info('');
|
|
917
|
+
}
|
|
952
918
|
|
|
953
919
|
if (!cacheOnly) {
|
|
954
920
|
try {
|
|
@@ -960,36 +926,37 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
960
926
|
try {
|
|
961
927
|
await fs.access(logPath);
|
|
962
928
|
logStatus = '(exists)';
|
|
963
|
-
} catch {
|
|
964
|
-
logStatus = '(not found)';
|
|
965
|
-
}
|
|
929
|
+
} catch {
|
|
930
|
+
logStatus = '(not found)';
|
|
931
|
+
}
|
|
966
932
|
const statusCacheDirectory = selectedRuntime?.cacheDirectory || config?.cacheDirectory;
|
|
967
933
|
if (statusCacheDirectory) {
|
|
968
934
|
const metaFile = path.join(statusCacheDirectory, 'meta.json');
|
|
969
935
|
const progressFile = path.join(statusCacheDirectory, 'progress.json');
|
|
970
936
|
let metaData = null;
|
|
971
937
|
let progressData = null;
|
|
972
|
-
try {
|
|
973
|
-
metaData = JSON.parse(await fs.readFile(metaFile, 'utf-8'));
|
|
974
|
-
} catch {
|
|
975
|
-
metaData = null;
|
|
976
|
-
}
|
|
977
|
-
try {
|
|
978
|
-
progressData = JSON.parse(await fs.readFile(progressFile, 'utf-8'));
|
|
979
|
-
} catch {
|
|
980
|
-
progressData = null;
|
|
981
|
-
}
|
|
938
|
+
try {
|
|
939
|
+
metaData = JSON.parse(await fs.readFile(metaFile, 'utf-8'));
|
|
940
|
+
} catch {
|
|
941
|
+
metaData = null;
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
progressData = JSON.parse(await fs.readFile(progressFile, 'utf-8'));
|
|
945
|
+
} catch {
|
|
946
|
+
progressData = null;
|
|
947
|
+
}
|
|
982
948
|
cacheSummary = {
|
|
983
949
|
cacheDir: statusCacheDirectory,
|
|
984
950
|
hasSnapshot: !!metaData,
|
|
985
951
|
snapshotTime: metaData?.lastSaveTime || null,
|
|
986
|
-
progress:
|
|
952
|
+
progress:
|
|
953
|
+
progressData && typeof progressData.progress === 'number' ? progressData : null,
|
|
987
954
|
};
|
|
988
955
|
}
|
|
989
|
-
} catch {
|
|
990
|
-
logPath = 'unknown';
|
|
991
|
-
}
|
|
992
|
-
|
|
956
|
+
} catch {
|
|
957
|
+
logPath = 'unknown';
|
|
958
|
+
}
|
|
959
|
+
|
|
993
960
|
const displayWorkspace = selectedRuntime?.workspace || config?.searchDirectory;
|
|
994
961
|
if (displayWorkspace) {
|
|
995
962
|
console.info(`[Lifecycle] Workspace: ${displayWorkspace}`);
|
|
@@ -1000,52 +967,51 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1000
967
|
);
|
|
1001
968
|
}
|
|
1002
969
|
console.info(` Log file: ${logPath} ${logStatus}`.trimEnd());
|
|
1003
|
-
if (cacheSummary?.cacheDir) {
|
|
1004
|
-
const snapshotLabel = cacheSummary.hasSnapshot ? 'available' : 'none';
|
|
1005
|
-
console.info(`[Cache] Snapshot: ${snapshotLabel}`);
|
|
1006
|
-
if (cacheSummary.snapshotTime) {
|
|
1007
|
-
console.info(
|
|
1008
|
-
`[Cache] Snapshot saved: ${formatDateTime(cacheSummary.snapshotTime) || cacheSummary.snapshotTime}`
|
|
1009
|
-
);
|
|
1010
|
-
}
|
|
1011
|
-
if (cacheSummary.progress) {
|
|
1012
|
-
const progress = cacheSummary.progress;
|
|
1013
|
-
console.info(
|
|
1014
|
-
`[Cache] Progress: ${progress.progress}/${progress.total} (${progress.message || 'n/a'})`
|
|
1015
|
-
);
|
|
1016
|
-
} else {
|
|
1017
|
-
console.info('[Cache] Progress: idle');
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
console.info('');
|
|
1021
|
-
|
|
1022
|
-
if (configLogs.length > 0) {
|
|
1023
|
-
for (const line of configLogs) {
|
|
1024
|
-
console.info(line);
|
|
1025
|
-
}
|
|
1026
|
-
console.info('');
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
if (cacheOnly) {
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
console.info(
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
970
|
+
if (cacheSummary?.cacheDir) {
|
|
971
|
+
const snapshotLabel = cacheSummary.hasSnapshot ? 'available' : 'none';
|
|
972
|
+
console.info(`[Cache] Snapshot: ${snapshotLabel}`);
|
|
973
|
+
if (cacheSummary.snapshotTime) {
|
|
974
|
+
console.info(
|
|
975
|
+
`[Cache] Snapshot saved: ${formatDateTime(cacheSummary.snapshotTime) || cacheSummary.snapshotTime}`
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
if (cacheSummary.progress) {
|
|
979
|
+
const progress = cacheSummary.progress;
|
|
980
|
+
console.info(
|
|
981
|
+
`[Cache] Progress: ${progress.progress}/${progress.total} (${progress.message || 'n/a'})`
|
|
982
|
+
);
|
|
983
|
+
} else {
|
|
984
|
+
console.info('[Cache] Progress: idle');
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
console.info('');
|
|
988
|
+
|
|
989
|
+
if (configLogs.length > 0) {
|
|
990
|
+
for (const line of configLogs) {
|
|
991
|
+
console.info(line);
|
|
992
|
+
}
|
|
993
|
+
console.info('');
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (cacheOnly) {
|
|
998
|
+
console.info('[Status] Inspecting cache status...\n');
|
|
999
|
+
|
|
1000
|
+
if (fix) {
|
|
1001
|
+
console.info('[Status] Fixing stale caches...\n');
|
|
1002
|
+
await clearStaleCaches();
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const cacheDirs = await fs.readdir(globalCacheRoot).catch(() => []);
|
|
1006
|
+
|
|
1007
|
+
if (cacheDirs.length === 0) {
|
|
1008
|
+
console.info('[Status] No cache directories found.');
|
|
1009
|
+
console.info(`[Status] Expected location: ${globalCacheRoot}`);
|
|
1010
|
+
} else {
|
|
1011
|
+
console.info(
|
|
1012
|
+
`[Status] Found ${cacheDirs.length} cache director${cacheDirs.length === 1 ? 'y' : 'ies'} in ${globalCacheRoot}`
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1049
1015
|
for (const dir of cacheDirs) {
|
|
1050
1016
|
const cacheDir = path.join(globalCacheRoot, dir);
|
|
1051
1017
|
const metaFile = path.join(cacheDir, 'meta.json');
|
|
@@ -1053,9 +1019,7 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1053
1019
|
let progressData = null;
|
|
1054
1020
|
try {
|
|
1055
1021
|
progressData = JSON.parse(await fs.readFile(progressFile, 'utf-8'));
|
|
1056
|
-
} catch {
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1022
|
+
} catch {}
|
|
1059
1023
|
const hasNumericProgress = progressData && typeof progressData.progress === 'number';
|
|
1060
1024
|
const isProgressIncomplete =
|
|
1061
1025
|
!!hasNumericProgress &&
|
|
@@ -1068,70 +1032,68 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1068
1032
|
console.info(`📁 Cache: ${dir}`);
|
|
1069
1033
|
console.info(` Path: ${cacheDir}`);
|
|
1070
1034
|
|
|
1071
|
-
let metaData = null;
|
|
1072
|
-
try {
|
|
1073
|
-
metaData = JSON.parse(await fs.readFile(metaFile, 'utf-8'));
|
|
1074
|
-
|
|
1075
|
-
console.info(` Status: ✅ Valid cache`);
|
|
1076
|
-
console.info(` Workspace: ${metaData.workspace || 'Unknown'}`);
|
|
1077
|
-
console.info(` Files indexed: ${metaData.filesIndexed ?? 'N/A'}`);
|
|
1078
|
-
console.info(` Chunks stored: ${metaData.chunksStored ?? 'N/A'}`);
|
|
1079
|
-
|
|
1080
|
-
if (Number.isFinite(metaData.lastDiscoveredFiles)) {
|
|
1081
|
-
console.info(` Files discovered (last run): ${metaData.lastDiscoveredFiles}`);
|
|
1082
|
-
}
|
|
1083
|
-
if (Number.isFinite(metaData.lastFilesProcessed)) {
|
|
1084
|
-
console.info(` Files processed (last run): ${metaData.lastFilesProcessed}`);
|
|
1085
|
-
}
|
|
1086
|
-
if (
|
|
1087
|
-
Number.isFinite(metaData.lastDiscoveredFiles) &&
|
|
1088
|
-
Number.isFinite(metaData.lastFilesProcessed)
|
|
1089
|
-
) {
|
|
1090
|
-
const delta = metaData.lastDiscoveredFiles - metaData.lastFilesProcessed;
|
|
1091
|
-
console.info(` Discovery delta (last run): ${delta >= 0 ? delta : 0}`);
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
if (metaData.lastSaveTime) {
|
|
1095
|
-
const saveDate = new Date(metaData.lastSaveTime);
|
|
1096
|
-
const ageMs = now - saveDate;
|
|
1097
|
-
const ageHours = Math.floor(ageMs / (1000 * 60 * 60));
|
|
1098
|
-
const ageMins = Math.floor((ageMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
1099
|
-
console.info(
|
|
1100
|
-
` Cached snapshot saved: ${formatDateTime(saveDate)} (${ageHours}h ${ageMins}m ago)`
|
|
1101
|
-
);
|
|
1102
|
-
const ageLabel = formatDurationMs(ageMs);
|
|
1103
|
-
if (ageLabel) {
|
|
1104
|
-
console.info(` Cached snapshot age: ${ageLabel}`);
|
|
1105
|
-
}
|
|
1106
|
-
console.info(` Initial index complete at: ${formatDateTime(saveDate)}`);
|
|
1107
|
-
}
|
|
1108
|
-
if (metaData.lastIndexStartedAt) {
|
|
1109
|
-
console.info(` Last index started: ${formatDateTime(metaData.lastIndexStartedAt)}`);
|
|
1110
|
-
}
|
|
1111
|
-
if (metaData.lastIndexEndedAt) {
|
|
1112
|
-
console.info(` Last index ended: ${formatDateTime(metaData.lastIndexEndedAt)}`);
|
|
1113
|
-
}
|
|
1114
|
-
if (Number.isFinite(metaData.indexDurationMs)) {
|
|
1115
|
-
const duration = formatDurationMs(metaData.indexDurationMs);
|
|
1116
|
-
if (duration) {
|
|
1117
|
-
console.info(` Last full index duration: ${duration}`);
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
if (metaData.lastIndexMode) {
|
|
1121
|
-
console.info(` Last index mode: ${String(metaData.lastIndexMode)}`);
|
|
1122
|
-
}
|
|
1123
|
-
if (Number.isFinite(metaData.lastBatchSize)) {
|
|
1124
|
-
console.info(` Last batch size: ${metaData.lastBatchSize}`);
|
|
1125
|
-
}
|
|
1126
|
-
if (Number.isFinite(metaData.lastWorkerThreads)) {
|
|
1127
|
-
console.info(` Last worker threads: ${metaData.lastWorkerThreads}`);
|
|
1128
|
-
}
|
|
1035
|
+
let metaData = null;
|
|
1036
|
+
try {
|
|
1037
|
+
metaData = JSON.parse(await fs.readFile(metaFile, 'utf-8'));
|
|
1038
|
+
|
|
1039
|
+
console.info(` Status: ✅ Valid cache`);
|
|
1040
|
+
console.info(` Workspace: ${metaData.workspace || 'Unknown'}`);
|
|
1041
|
+
console.info(` Files indexed: ${metaData.filesIndexed ?? 'N/A'}`);
|
|
1042
|
+
console.info(` Chunks stored: ${metaData.chunksStored ?? 'N/A'}`);
|
|
1043
|
+
|
|
1044
|
+
if (Number.isFinite(metaData.lastDiscoveredFiles)) {
|
|
1045
|
+
console.info(` Files discovered (last run): ${metaData.lastDiscoveredFiles}`);
|
|
1046
|
+
}
|
|
1047
|
+
if (Number.isFinite(metaData.lastFilesProcessed)) {
|
|
1048
|
+
console.info(` Files processed (last run): ${metaData.lastFilesProcessed}`);
|
|
1049
|
+
}
|
|
1050
|
+
if (
|
|
1051
|
+
Number.isFinite(metaData.lastDiscoveredFiles) &&
|
|
1052
|
+
Number.isFinite(metaData.lastFilesProcessed)
|
|
1053
|
+
) {
|
|
1054
|
+
const delta = metaData.lastDiscoveredFiles - metaData.lastFilesProcessed;
|
|
1055
|
+
console.info(` Discovery delta (last run): ${delta >= 0 ? delta : 0}`);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
if (metaData.lastSaveTime) {
|
|
1059
|
+
const saveDate = new Date(metaData.lastSaveTime);
|
|
1060
|
+
const ageMs = now - saveDate;
|
|
1061
|
+
const ageHours = Math.floor(ageMs / (1000 * 60 * 60));
|
|
1062
|
+
const ageMins = Math.floor((ageMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
1063
|
+
console.info(
|
|
1064
|
+
` Cached snapshot saved: ${formatDateTime(saveDate)} (${ageHours}h ${ageMins}m ago)`
|
|
1065
|
+
);
|
|
1066
|
+
const ageLabel = formatDurationMs(ageMs);
|
|
1067
|
+
if (ageLabel) {
|
|
1068
|
+
console.info(` Cached snapshot age: ${ageLabel}`);
|
|
1069
|
+
}
|
|
1070
|
+
console.info(` Initial index complete at: ${formatDateTime(saveDate)}`);
|
|
1071
|
+
}
|
|
1072
|
+
if (metaData.lastIndexStartedAt) {
|
|
1073
|
+
console.info(` Last index started: ${formatDateTime(metaData.lastIndexStartedAt)}`);
|
|
1074
|
+
}
|
|
1075
|
+
if (metaData.lastIndexEndedAt) {
|
|
1076
|
+
console.info(` Last index ended: ${formatDateTime(metaData.lastIndexEndedAt)}`);
|
|
1077
|
+
}
|
|
1078
|
+
if (Number.isFinite(metaData.indexDurationMs)) {
|
|
1079
|
+
const duration = formatDurationMs(metaData.indexDurationMs);
|
|
1080
|
+
if (duration) {
|
|
1081
|
+
console.info(` Last full index duration: ${duration}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (metaData.lastIndexMode) {
|
|
1085
|
+
console.info(` Last index mode: ${String(metaData.lastIndexMode)}`);
|
|
1086
|
+
}
|
|
1087
|
+
if (Number.isFinite(metaData.lastBatchSize)) {
|
|
1088
|
+
console.info(` Last batch size: ${metaData.lastBatchSize}`);
|
|
1089
|
+
}
|
|
1090
|
+
if (Number.isFinite(metaData.lastWorkerThreads)) {
|
|
1091
|
+
console.info(` Last worker threads: ${metaData.lastWorkerThreads}`);
|
|
1092
|
+
}
|
|
1129
1093
|
try {
|
|
1130
1094
|
const dirStats = await fs.stat(cacheDir);
|
|
1131
1095
|
console.info(` Cache dir last write: ${formatDateTime(dirStats.mtime)}`);
|
|
1132
|
-
} catch {
|
|
1133
|
-
|
|
1134
|
-
}
|
|
1096
|
+
} catch {}
|
|
1135
1097
|
|
|
1136
1098
|
const progressAfterSnapshot =
|
|
1137
1099
|
hasNumericProgress &&
|
|
@@ -1142,7 +1104,6 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1142
1104
|
hasNumericProgress && (isProgressIncomplete || progressAfterSnapshot)
|
|
1143
1105
|
);
|
|
1144
1106
|
|
|
1145
|
-
|
|
1146
1107
|
if (metaData.filesIndexed && metaData.filesIndexed > 0) {
|
|
1147
1108
|
if (isIncrementalUpdateActive) {
|
|
1148
1109
|
console.info(` Cached snapshot: ✅ COMPLETE (${metaData.filesIndexed} files)`);
|
|
@@ -1170,20 +1131,20 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1170
1131
|
if (err.code === 'ENOENT') {
|
|
1171
1132
|
try {
|
|
1172
1133
|
const stats = await fs.stat(cacheDir);
|
|
1173
|
-
const ageMs = new Date() - stats.mtime;
|
|
1174
|
-
if (ageMs < 10 * 60 * 1000) {
|
|
1175
|
-
console.info(` Status: ⏳ Initializing / Indexing in progress...`);
|
|
1176
|
-
console.info(` (Metadata file has not been written yet using ID ${dir})`);
|
|
1177
|
-
console.info(' Initial index: ⏳ IN PROGRESS');
|
|
1178
|
-
} else {
|
|
1179
|
-
console.info(` Status: ⚠️ Incomplete cache (stale)`);
|
|
1180
|
-
}
|
|
1181
|
-
console.info(` Cache dir last write: ${stats.mtime.toLocaleString()}`);
|
|
1182
|
-
} catch {
|
|
1183
|
-
console.info(` Status: ❌ Invalid cache directory`);
|
|
1184
|
-
}
|
|
1185
|
-
} else {
|
|
1186
|
-
console.info(` Status: ❌ Invalid or corrupted (${err.message})`);
|
|
1134
|
+
const ageMs = new Date() - stats.mtime;
|
|
1135
|
+
if (ageMs < 10 * 60 * 1000) {
|
|
1136
|
+
console.info(` Status: ⏳ Initializing / Indexing in progress...`);
|
|
1137
|
+
console.info(` (Metadata file has not been written yet using ID ${dir})`);
|
|
1138
|
+
console.info(' Initial index: ⏳ IN PROGRESS');
|
|
1139
|
+
} else {
|
|
1140
|
+
console.info(` Status: ⚠️ Incomplete cache (stale)`);
|
|
1141
|
+
}
|
|
1142
|
+
console.info(` Cache dir last write: ${stats.mtime.toLocaleString()}`);
|
|
1143
|
+
} catch {
|
|
1144
|
+
console.info(` Status: ❌ Invalid cache directory`);
|
|
1145
|
+
}
|
|
1146
|
+
} else {
|
|
1147
|
+
console.info(` Status: ❌ Invalid or corrupted (${err.message})`);
|
|
1187
1148
|
}
|
|
1188
1149
|
}
|
|
1189
1150
|
|
|
@@ -1191,47 +1152,47 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1191
1152
|
const updatedAt = progressData.updatedAt
|
|
1192
1153
|
? formatDateTime(progressData.updatedAt)
|
|
1193
1154
|
: 'Unknown';
|
|
1194
|
-
const progressLabel = metaData
|
|
1195
|
-
? 'Incremental update (post-snapshot)'
|
|
1196
|
-
: 'Initial index progress';
|
|
1197
|
-
console.info(
|
|
1198
|
-
` ${progressLabel}: ${progressData.progress}/${progressData.total} (${progressData.message || 'n/a'})`
|
|
1199
|
-
);
|
|
1200
|
-
console.info(` Progress updated: ${updatedAt}`);
|
|
1201
|
-
|
|
1202
|
-
if (progressData.updatedAt) {
|
|
1203
|
-
const updatedDate = new Date(progressData.updatedAt);
|
|
1204
|
-
const ageMs = now - updatedDate;
|
|
1205
|
-
const staleMs = 5 * 60 * 1000;
|
|
1206
|
-
const ageLabel = formatDurationMs(ageMs);
|
|
1207
|
-
if (ageLabel) {
|
|
1208
|
-
console.info(` Progress age: ${ageLabel}`);
|
|
1209
|
-
}
|
|
1210
|
-
if (Number.isFinite(ageMs) && ageMs > staleMs) {
|
|
1211
|
-
const staleLabel = formatDurationMs(ageMs);
|
|
1212
|
-
console.info(` Progress stale: last update ${staleLabel} ago`);
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
if (progressData.updatedAt && metaData?.lastSaveTime) {
|
|
1217
|
-
const updatedDate = new Date(progressData.updatedAt);
|
|
1218
|
-
const saveDate = new Date(metaData.lastSaveTime);
|
|
1219
|
-
if (updatedDate > saveDate) {
|
|
1220
|
-
console.info(' Note: Incremental update in progress; cached snapshot may lag.');
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
if (progressData.indexMode) {
|
|
1224
|
-
console.info(` Current index mode: ${String(progressData.indexMode)}`);
|
|
1225
|
-
}
|
|
1155
|
+
const progressLabel = metaData
|
|
1156
|
+
? 'Incremental update (post-snapshot)'
|
|
1157
|
+
: 'Initial index progress';
|
|
1158
|
+
console.info(
|
|
1159
|
+
` ${progressLabel}: ${progressData.progress}/${progressData.total} (${progressData.message || 'n/a'})`
|
|
1160
|
+
);
|
|
1161
|
+
console.info(` Progress updated: ${updatedAt}`);
|
|
1162
|
+
|
|
1163
|
+
if (progressData.updatedAt) {
|
|
1164
|
+
const updatedDate = new Date(progressData.updatedAt);
|
|
1165
|
+
const ageMs = now - updatedDate;
|
|
1166
|
+
const staleMs = 5 * 60 * 1000;
|
|
1167
|
+
const ageLabel = formatDurationMs(ageMs);
|
|
1168
|
+
if (ageLabel) {
|
|
1169
|
+
console.info(` Progress age: ${ageLabel}`);
|
|
1170
|
+
}
|
|
1171
|
+
if (Number.isFinite(ageMs) && ageMs > staleMs) {
|
|
1172
|
+
const staleLabel = formatDurationMs(ageMs);
|
|
1173
|
+
console.info(` Progress stale: last update ${staleLabel} ago`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
if (progressData.updatedAt && metaData?.lastSaveTime) {
|
|
1178
|
+
const updatedDate = new Date(progressData.updatedAt);
|
|
1179
|
+
const saveDate = new Date(metaData.lastSaveTime);
|
|
1180
|
+
if (updatedDate > saveDate) {
|
|
1181
|
+
console.info(' Note: Incremental update in progress; cached snapshot may lag.');
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (progressData.indexMode) {
|
|
1185
|
+
console.info(` Current index mode: ${String(progressData.indexMode)}`);
|
|
1186
|
+
}
|
|
1226
1187
|
if (
|
|
1227
1188
|
progressData.workerCircuitOpen &&
|
|
1228
1189
|
Number.isFinite(progressData.workersDisabledUntil)
|
|
1229
1190
|
) {
|
|
1230
|
-
const remainingMs = progressData.workersDisabledUntil - Date.now();
|
|
1231
|
-
const remainingLabel = formatDurationMs(Math.max(0, remainingMs));
|
|
1232
|
-
console.info(` Workers paused: ${remainingLabel || '0s'} remaining`);
|
|
1233
|
-
console.info(
|
|
1234
|
-
` Workers disabled until: ${formatDateTime(progressData.workersDisabledUntil)}`
|
|
1191
|
+
const remainingMs = progressData.workersDisabledUntil - Date.now();
|
|
1192
|
+
const remainingLabel = formatDurationMs(Math.max(0, remainingMs));
|
|
1193
|
+
console.info(` Workers paused: ${remainingLabel || '0s'} remaining`);
|
|
1194
|
+
console.info(
|
|
1195
|
+
` Workers disabled until: ${formatDateTime(progressData.workersDisabledUntil)}`
|
|
1235
1196
|
);
|
|
1236
1197
|
}
|
|
1237
1198
|
} else {
|
|
@@ -1302,156 +1263,143 @@ export async function status({ fix = false, cacheOnly = false, workspaceDir = nu
|
|
|
1302
1263
|
}
|
|
1303
1264
|
|
|
1304
1265
|
if (metaData && isProgressIncomplete) {
|
|
1305
|
-
console.info(
|
|
1266
|
+
console.info(
|
|
1267
|
+
' Indexing state: Cached snapshot available; incremental update running.'
|
|
1268
|
+
);
|
|
1306
1269
|
} else if (metaData) {
|
|
1307
1270
|
console.info(' Indexing state: Cached snapshot available; idle.');
|
|
1308
1271
|
} else if (progressData && typeof progressData.progress === 'number') {
|
|
1309
|
-
console.info(' Indexing state: Initial index in progress; no cached snapshot yet.');
|
|
1310
|
-
} else {
|
|
1311
|
-
console.info(' Indexing state: No cached snapshot; idle.');
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
console.info(`${'─'.repeat(60)}`);
|
|
1315
|
-
}
|
|
1316
|
-
} else {
|
|
1317
|
-
if (fix) {
|
|
1318
|
-
const results = await clearStaleCaches();
|
|
1319
|
-
if (results.removed > 0) {
|
|
1320
|
-
console.info(
|
|
1321
|
-
`[Status] Cache cleanup removed ${results.removed} stale cache${results.removed === 1 ? '' : 's'}`
|
|
1322
|
-
);
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
const cacheDirs = await fs.readdir(globalCacheRoot).catch(() => null);
|
|
1326
|
-
if (Array.isArray(cacheDirs)) {
|
|
1327
|
-
console.info(
|
|
1328
|
-
`[Status] Cache: ${cacheDirs.length} director${cacheDirs.length === 1 ? 'y' : 'ies'} in ${globalCacheRoot}`
|
|
1329
|
-
);
|
|
1330
|
-
} else {
|
|
1331
|
-
console.info(`[Status] Cache: ${globalCacheRoot} (not found)`);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
{
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
'
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
);
|
|
1425
|
-
configLocations
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
});
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
} catch {
|
|
1444
|
-
|
|
1445
|
-
}
|
|
1446
|
-
console.info(` - ${loc.name}: ${loc.path} ${status}`);
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
console.info(` 📝 Log file: ${logPath} ${logStatus}`.trimEnd());
|
|
1450
|
-
console.info(` 💾 Cache root: ${globalCacheRoot}`);
|
|
1451
|
-
console.info(` 📁 Current dir: ${process.cwd()}`);
|
|
1452
|
-
console.info('');
|
|
1453
|
-
}
|
|
1454
|
-
} catch (error) {
|
|
1455
|
-
console.error(`[Lifecycle] Failed to check status: ${error.message}`);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1272
|
+
console.info(' Indexing state: Initial index in progress; no cached snapshot yet.');
|
|
1273
|
+
} else {
|
|
1274
|
+
console.info(' Indexing state: No cached snapshot; idle.');
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
console.info(`${'─'.repeat(60)}`);
|
|
1278
|
+
}
|
|
1279
|
+
} else {
|
|
1280
|
+
if (fix) {
|
|
1281
|
+
const results = await clearStaleCaches();
|
|
1282
|
+
if (results.removed > 0) {
|
|
1283
|
+
console.info(
|
|
1284
|
+
`[Status] Cache cleanup removed ${results.removed} stale cache${results.removed === 1 ? '' : 's'}`
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
const cacheDirs = await fs.readdir(globalCacheRoot).catch(() => null);
|
|
1289
|
+
if (Array.isArray(cacheDirs)) {
|
|
1290
|
+
console.info(
|
|
1291
|
+
`[Status] Cache: ${cacheDirs.length} director${cacheDirs.length === 1 ? 'y' : 'ies'} in ${globalCacheRoot}`
|
|
1292
|
+
);
|
|
1293
|
+
} else {
|
|
1294
|
+
console.info(`[Status] Cache: ${globalCacheRoot} (not found)`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
if (!cacheOnly) {
|
|
1299
|
+
console.info('\n[Paths] Important locations:');
|
|
1300
|
+
|
|
1301
|
+
let npmBin = 'unknown';
|
|
1302
|
+
try {
|
|
1303
|
+
const { stdout } = await execPromise('npm config get prefix');
|
|
1304
|
+
npmBin = path.join(stdout.trim(), 'bin');
|
|
1305
|
+
} catch {}
|
|
1306
|
+
console.info(` 📦 Global npm bin: ${npmBin}`);
|
|
1307
|
+
|
|
1308
|
+
const configLocations = [
|
|
1309
|
+
{
|
|
1310
|
+
name: 'Antigravity',
|
|
1311
|
+
path: path.join(getUserHomeDir(), '.gemini', 'antigravity', 'mcp_config.json'),
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
name: 'Codex',
|
|
1315
|
+
path: path.join(getUserHomeDir(), '.codex', 'config.toml'),
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
name: 'Claude Desktop',
|
|
1319
|
+
path: path.join(getUserHomeDir(), '.config', 'Claude', 'claude_desktop_config.json'),
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
name: 'VS Code',
|
|
1323
|
+
path: path.join(getUserHomeDir(), '.config', 'Code', 'User', 'mcp.json'),
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
name: 'Cursor',
|
|
1327
|
+
path: path.join(getUserHomeDir(), '.config', 'Cursor', 'User', 'settings.json'),
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
name: 'Cursor Global',
|
|
1331
|
+
path: path.join(getUserHomeDir(), '.cursor', 'mcp.json'),
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
name: 'Windsurf',
|
|
1335
|
+
path: path.join(getUserHomeDir(), '.codeium', 'windsurf', 'mcp_config.json'),
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
name: 'Warp',
|
|
1339
|
+
path: path.join(getUserHomeDir(), '.warp', 'mcp_settings.json'),
|
|
1340
|
+
},
|
|
1341
|
+
];
|
|
1342
|
+
|
|
1343
|
+
if (process.platform === 'darwin') {
|
|
1344
|
+
configLocations[2].path = path.join(
|
|
1345
|
+
os.homedir(),
|
|
1346
|
+
|
|
1347
|
+
'Library',
|
|
1348
|
+
'Application Support',
|
|
1349
|
+
'Claude',
|
|
1350
|
+
'claude_desktop_config.json'
|
|
1351
|
+
);
|
|
1352
|
+
configLocations[3].path = path.join(
|
|
1353
|
+
os.homedir(),
|
|
1354
|
+
'Library',
|
|
1355
|
+
'Application Support',
|
|
1356
|
+
'Code',
|
|
1357
|
+
'User',
|
|
1358
|
+
'mcp.json'
|
|
1359
|
+
);
|
|
1360
|
+
configLocations[4].path = path.join(
|
|
1361
|
+
os.homedir(),
|
|
1362
|
+
'Library',
|
|
1363
|
+
'Application Support',
|
|
1364
|
+
'Cursor',
|
|
1365
|
+
'User',
|
|
1366
|
+
'settings.json'
|
|
1367
|
+
);
|
|
1368
|
+
} else if (process.platform === 'win32') {
|
|
1369
|
+
configLocations[2].path = path.join(
|
|
1370
|
+
process.env.APPDATA || '',
|
|
1371
|
+
'Claude',
|
|
1372
|
+
'claude_desktop_config.json'
|
|
1373
|
+
);
|
|
1374
|
+
configLocations[3].path = path.join(process.env.APPDATA || '', 'Code', 'User', 'mcp.json');
|
|
1375
|
+
configLocations[4].path = path.join(
|
|
1376
|
+
process.env.APPDATA || '',
|
|
1377
|
+
'Cursor',
|
|
1378
|
+
'User',
|
|
1379
|
+
'settings.json'
|
|
1380
|
+
);
|
|
1381
|
+
configLocations.push({
|
|
1382
|
+
name: 'Warp AppData',
|
|
1383
|
+
path: path.join(process.env.APPDATA || '', 'Warp', 'mcp_settings.json'),
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
console.info(' ⚙️ MCP configs:');
|
|
1388
|
+
for (const loc of configLocations) {
|
|
1389
|
+
let status = '(not found)';
|
|
1390
|
+
try {
|
|
1391
|
+
await fs.access(loc.path);
|
|
1392
|
+
status = '(exists)';
|
|
1393
|
+
} catch {}
|
|
1394
|
+
console.info(` - ${loc.name}: ${loc.path} ${status}`);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
console.info(` 📝 Log file: ${logPath} ${logStatus}`.trimEnd());
|
|
1398
|
+
console.info(` 💾 Cache root: ${globalCacheRoot}`);
|
|
1399
|
+
console.info(` 📁 Current dir: ${process.cwd()}`);
|
|
1400
|
+
console.info('');
|
|
1401
|
+
}
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
console.error(`[Lifecycle] Failed to check status: ${error.message}`);
|
|
1404
|
+
}
|
|
1405
|
+
}
|