@softerist/heuristic-mcp 3.0.17 → 3.2.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/lib/cli.js CHANGED
@@ -1,251 +1,259 @@
1
- export const DEFAULT_LOG_TAIL_LINES = 200;
2
- const FLAG_ARGS_WITH_VALUES = new Set(['--tail', '--workspace', '--start', '--register', '--clear']);
3
- const COMMAND_ALIASES = Object.freeze({
4
- status: '--status',
5
- stat: '--status',
6
- log: '--logs',
7
- logs: '--logs',
8
- start: '--start',
9
- stop: '--stop',
10
- cache: '--cache',
11
- 'clear-cache': '--clear-cache',
12
- clearcache: '--clear-cache',
1
+ export const DEFAULT_LOG_TAIL_LINES = 200;
2
+ const FLAG_ARGS_WITH_VALUES = new Set(['--tail', '--workspace', '--start', '--register', '--clear']);
3
+ const COMMAND_ALIASES = Object.freeze({
4
+ status: '--status',
5
+ stat: '--status',
6
+ log: '--logs',
7
+ logs: '--logs',
8
+ start: '--start',
9
+ stop: '--stop',
10
+ cache: '--cache',
11
+ 'clear-cache': '--clear-cache',
12
+ clearcache: '--clear-cache',
13
13
  clear: '--clear',
14
+ clean: '--clear',
14
15
  mem: '--mem',
15
- memory: '--mem',
16
- version: '--version',
17
- help: '--help',
18
- register: '--register',
19
- });
20
- const FLAG_ALIASES = Object.freeze({
21
- '--log': '--logs',
22
- });
23
-
24
- export function printHelp(defaultTailLines = DEFAULT_LOG_TAIL_LINES) {
25
- console.info(`Heuristic MCP Server
26
-
27
- Usage:
28
- heuristic-mcp [options]
29
- heuristic-mcp <command> [args]
30
-
31
- Options:
32
- --cache Show cache status and cleanup recommendations (dry-run)
33
- --cache --clean Remove stale cache directories (performs cleanup)
34
- --status Show server status and cache summary
35
- --clear <cache_id> Remove a specific cache by ID
36
- --clear-cache Remove cache for current workspace (and stale global caches)
37
- --logs Tail server logs (defaults to last 200 lines, follows)
38
- --mem Show last memory snapshot from logs (requires verbose logging)
39
- --tail <lines> Lines to show with --logs (default: ${defaultTailLines})
40
- --no-follow Do not follow log output with --logs
41
- --start [ide] Register + enable in IDE config (antigravity|codex|cursor|vscode|windsurf|warp|"Claude Desktop")
42
- --stop Stop running server instances
43
- --workspace <path> Workspace path (used by IDE launch / log viewer)
44
- --version, -v Show version
45
- --help, -h Show this help
46
-
47
- `);
48
- }
49
-
50
- export function normalizeCliArgs(rawArgs = []) {
51
- const normalized = [];
52
- let expectsValue = false;
53
-
54
- for (const token of rawArgs) {
55
- if (expectsValue) {
56
- normalized.push(token);
57
- expectsValue = false;
58
- continue;
59
- }
60
-
61
- if (token.startsWith('--')) {
62
- const eqIdx = token.indexOf('=');
63
- if (eqIdx !== -1) {
64
- const flagPart = token.slice(0, eqIdx);
65
- const valuePart = token.slice(eqIdx + 1);
66
- const mappedFlag = FLAG_ALIASES[flagPart] || flagPart;
67
- normalized.push(`${mappedFlag}=${valuePart}`);
68
- continue;
69
- }
70
-
71
- const mappedFlag = FLAG_ALIASES[token] || token;
72
- normalized.push(mappedFlag);
73
- if (FLAG_ARGS_WITH_VALUES.has(mappedFlag)) {
74
- expectsValue = true;
75
- }
76
- continue;
77
- }
78
-
79
- if (token.startsWith('-')) {
80
- normalized.push(token);
81
- continue;
82
- }
83
-
84
- const mappedCommand = COMMAND_ALIASES[token.toLowerCase()];
85
- if (mappedCommand) {
86
- normalized.push(mappedCommand);
87
- if (FLAG_ARGS_WITH_VALUES.has(mappedCommand)) {
88
- expectsValue = true;
89
- }
90
- continue;
91
- }
92
-
93
- normalized.push(token);
94
- }
95
-
96
- return normalized;
97
- }
98
-
99
- export function shouldDefaultToHelp(
100
- args,
101
- runtime = { stdinIsTTY: process.stdin.isTTY, stdoutIsTTY: process.stdout.isTTY }
102
- ) {
103
- return args.length === 0 && Boolean(runtime.stdinIsTTY && runtime.stdoutIsTTY);
104
- }
105
-
16
+ memory: '--mem',
17
+ version: '--version',
18
+ help: '--help',
19
+ register: '--register',
20
+ });
21
+ const FLAG_ALIASES = Object.freeze({
22
+ '--log': '--logs',
23
+ });
24
+
25
+ export function printHelp(defaultTailLines = DEFAULT_LOG_TAIL_LINES) {
26
+ console.info(`Heuristic MCP Server
27
+
28
+ Usage:
29
+ heuristic-mcp [options]
30
+ heuristic-mcp <command> [args]
31
+
32
+ Options:
33
+ --cache Show cache status and cleanup recommendations (dry-run)
34
+ --cache --clean Remove stale cache directories (performs cleanup)
35
+ --status Show server status and cache summary
36
+ --clear <cache_id> Remove a specific cache by ID
37
+ --clear-cache Remove cache for current workspace (and stale global caches)
38
+ --logs Tail server logs (defaults to last 200 lines, follows)
39
+ --mem Show last memory snapshot from logs (requires verbose logging)
40
+ --tail <lines> Lines to show with --logs (default: ${defaultTailLines})
41
+ --no-follow Do not follow log output with --logs
42
+ --start [ide] Register + enable in IDE config (antigravity|codex|cursor|vscode|windsurf|warp|"Claude Desktop")
43
+ --stop Stop running server instances
44
+ --workspace <path> Workspace path (used by IDE launch / log viewer)
45
+ --version, -v Show version
46
+ --help, -h Show this help
47
+
48
+ `);
49
+ }
50
+
51
+ export function normalizeCliArgs(rawArgs = []) {
52
+ const normalized = [];
53
+ let expectsValue = false;
54
+
55
+ for (const token of rawArgs) {
56
+ if (expectsValue) {
57
+ normalized.push(token);
58
+ expectsValue = false;
59
+ continue;
60
+ }
61
+
62
+ if (token.startsWith('--')) {
63
+ const eqIdx = token.indexOf('=');
64
+ if (eqIdx !== -1) {
65
+ const flagPart = token.slice(0, eqIdx);
66
+ const valuePart = token.slice(eqIdx + 1);
67
+ const mappedFlag = FLAG_ALIASES[flagPart] || flagPart;
68
+ normalized.push(`${mappedFlag}=${valuePart}`);
69
+ continue;
70
+ }
71
+
72
+ const mappedFlag = FLAG_ALIASES[token] || token;
73
+ normalized.push(mappedFlag);
74
+ if (FLAG_ARGS_WITH_VALUES.has(mappedFlag)) {
75
+ expectsValue = true;
76
+ }
77
+ continue;
78
+ }
79
+
80
+ if (token.startsWith('-')) {
81
+ normalized.push(token);
82
+ continue;
83
+ }
84
+
85
+ const mappedCommand = COMMAND_ALIASES[token.toLowerCase()];
86
+ if (mappedCommand) {
87
+ normalized.push(mappedCommand);
88
+ if (FLAG_ARGS_WITH_VALUES.has(mappedCommand)) {
89
+ expectsValue = true;
90
+ }
91
+ continue;
92
+ }
93
+
94
+ normalized.push(token);
95
+ }
96
+
97
+ return normalized;
98
+ }
99
+
100
+ export function shouldDefaultToHelp(
101
+ args,
102
+ runtime = { stdinIsTTY: process.stdin.isTTY, stdoutIsTTY: process.stdout.isTTY }
103
+ ) {
104
+ return args.length === 0 && Boolean(runtime.stdinIsTTY && runtime.stdoutIsTTY);
105
+ }
106
+
106
107
  export function parseWorkspaceDir(args) {
107
- const workspaceIndex = args.findIndex((arg) => arg.startsWith('--workspace'));
108
- if (workspaceIndex === -1) return null;
109
-
110
- const arg = args[workspaceIndex];
111
- let rawWorkspace = null;
108
+ const isUnexpandedWorkspaceValue = (value) =>
109
+ value.includes('${') || /\{\{.+\}\}/.test(value) || /%[A-Za-z_][A-Za-z0-9_]*%/.test(value);
112
110
 
113
- if (arg.includes('=')) {
114
- rawWorkspace = arg.split('=')[1];
115
- } else if (workspaceIndex + 1 < args.length) {
116
- rawWorkspace = args[workspaceIndex + 1];
117
- }
111
+ for (let i = 0; i < args.length; i += 1) {
112
+ const arg = args[i];
113
+ if (!arg.startsWith('--workspace')) continue;
118
114
 
119
- // Check if IDE variable wasn't expanded (contains ${})
120
- if (rawWorkspace && rawWorkspace.includes('${')) {
121
- console.error(
122
- `[Server] IDE variable not expanded: ${rawWorkspace}, falling back to auto-detected workspace`
123
- );
124
- return null;
125
- }
126
-
127
- return rawWorkspace || null;
128
- }
129
-
130
- export function collectUnknownFlags(rawArgs, knownFlags, flagsWithValue) {
131
- const unknownFlags = [];
132
- for (let i = 0; i < rawArgs.length; i += 1) {
133
- const arg = rawArgs[i];
134
- if (flagsWithValue.has(arg)) {
135
- if (arg.includes('=')) continue;
136
- const next = rawArgs[i + 1];
137
- if (next && !next.startsWith('-')) {
138
- i += 1;
139
- }
140
- continue;
115
+ let rawWorkspace = null;
116
+ if (arg.startsWith('--workspace=')) {
117
+ rawWorkspace = arg.slice('--workspace='.length);
118
+ } else if (arg === '--workspace' && i + 1 < args.length) {
119
+ rawWorkspace = args[i + 1];
120
+ i += 1;
141
121
  }
142
- if (arg.startsWith('-') && !knownFlags.has(arg) && !arg.startsWith('--workspace=')) {
143
- unknownFlags.push(arg);
144
- }
145
- }
146
- return unknownFlags;
147
- }
148
-
149
- export function parseArgs(
150
- argv = process.argv,
151
- runtime = { stdinIsTTY: process.stdin.isTTY, stdoutIsTTY: process.stdout.isTTY }
152
- ) {
153
- const args = normalizeCliArgs(argv.slice(2));
154
- const rawArgs = [...args];
155
-
156
- const wantsVersion = args.includes('--version') || args.includes('-v');
157
- const wantsHelp = args.includes('--help') || args.includes('-h') || shouldDefaultToHelp(args, runtime);
158
- const wantsCache = args.includes('--cache');
159
- const wantsClean = args.includes('--clean');
160
- const wantsStatus = args.includes('--status');
161
- const wantsClearCache = args.includes('--clear-cache');
162
- const wantsLogs = args.includes('--logs');
163
- const wantsMem = args.includes('--mem');
164
- const wantsRegister = args.includes('--register');
165
- const wantsStart = args.includes('--start') || wantsRegister;
166
- const wantsStop = args.includes('--stop');
167
- const wantsFix = args.includes('--fix');
168
- const wantsNoFollow = args.includes('--no-follow');
169
122
 
170
- const isServerMode = !(
171
- wantsCache ||
172
- wantsStatus ||
173
- wantsClearCache ||
174
- wantsLogs ||
175
- wantsMem ||
176
- wantsStart ||
177
- wantsStop ||
178
- wantsHelp ||
179
- wantsVersion
180
- );
123
+ if (!rawWorkspace) continue;
181
124
 
182
- const workspaceDir = parseWorkspaceDir(args);
183
-
184
- let tailLines = DEFAULT_LOG_TAIL_LINES;
185
- if (wantsLogs) {
186
- const tailIndex = args.indexOf('--tail');
187
- if (tailIndex !== -1 && args[tailIndex + 1]) {
188
- const parsed = parseInt(args[tailIndex + 1], 10);
189
- if (!isNaN(parsed) && parsed > 0) {
190
- tailLines = parsed;
191
- }
125
+ if (isUnexpandedWorkspaceValue(rawWorkspace)) {
126
+ console.error(
127
+ `[Server] IDE variable not expanded: ${rawWorkspace}, falling back to next workspace candidate`
128
+ );
129
+ continue;
192
130
  }
193
- }
194
131
 
195
- let startFilter = null;
196
- if (wantsStart) {
197
- const getFilter = (flag) => {
198
- const filterIndex = args.indexOf(flag);
199
- if (filterIndex === -1) return null;
200
- const value = args[filterIndex + 1];
201
- return value && !value.startsWith('-') ? value : null;
202
- };
203
- startFilter = getFilter('--start') ?? getFilter('--register');
132
+ return rawWorkspace;
204
133
  }
205
134
 
206
- const knownFlags = new Set([
207
- '--cache',
208
- '--clean',
209
- '--status',
210
- '--clear',
211
- '--clear-cache',
212
- '--logs',
213
- '--mem',
214
- '--tail',
215
- '--no-follow',
216
- '--start',
217
- '--register',
218
- '--stop',
219
- '--workspace',
220
- '--fix',
221
- '--version',
222
- '-v',
223
- '--help',
224
- '-h',
225
- ]);
226
- const flagsWithValue = FLAG_ARGS_WITH_VALUES;
227
- const unknownFlags = collectUnknownFlags(rawArgs, knownFlags, flagsWithValue);
228
-
229
- return {
230
- args,
231
- rawArgs,
232
- isServerMode,
233
- workspaceDir,
234
- wantsVersion,
235
- wantsHelp,
236
- wantsCache,
237
- wantsClean,
238
- wantsStatus,
239
- wantsClearCache,
240
- wantsLogs,
241
- wantsMem,
242
- wantsStart,
243
- wantsStop,
244
- wantsFix,
245
- wantsNoFollow,
246
- tailLines,
247
- startFilter,
248
- unknownFlags,
249
- };
135
+ return null;
250
136
  }
251
-
137
+
138
+ export function collectUnknownFlags(rawArgs, knownFlags, flagsWithValue) {
139
+ const unknownFlags = [];
140
+ for (let i = 0; i < rawArgs.length; i += 1) {
141
+ const arg = rawArgs[i];
142
+ if (flagsWithValue.has(arg)) {
143
+ if (arg.includes('=')) continue;
144
+ const next = rawArgs[i + 1];
145
+ if (next && !next.startsWith('-')) {
146
+ i += 1;
147
+ }
148
+ continue;
149
+ }
150
+ if (arg.startsWith('-') && !knownFlags.has(arg) && !arg.startsWith('--workspace=')) {
151
+ unknownFlags.push(arg);
152
+ }
153
+ }
154
+ return unknownFlags;
155
+ }
156
+
157
+ export function parseArgs(
158
+ argv = process.argv,
159
+ runtime = { stdinIsTTY: process.stdin.isTTY, stdoutIsTTY: process.stdout.isTTY }
160
+ ) {
161
+ const args = normalizeCliArgs(argv.slice(2));
162
+ const rawArgs = [...args];
163
+
164
+ const wantsVersion = args.includes('--version') || args.includes('-v');
165
+ const wantsHelp = args.includes('--help') || args.includes('-h') || shouldDefaultToHelp(args, runtime);
166
+ const wantsCache = args.includes('--cache');
167
+ const wantsClean = args.includes('--clean');
168
+ const wantsStatus = args.includes('--status');
169
+ const wantsClearCache = args.includes('--clear-cache');
170
+ const wantsLogs = args.includes('--logs');
171
+ const wantsMem = args.includes('--mem');
172
+ const wantsRegister = args.includes('--register');
173
+ const wantsStart = args.includes('--start') || wantsRegister;
174
+ const wantsStop = args.includes('--stop');
175
+ const wantsFix = args.includes('--fix');
176
+ const wantsNoFollow = args.includes('--no-follow');
177
+
178
+ const isServerMode = !(
179
+ wantsCache ||
180
+ wantsStatus ||
181
+ wantsClearCache ||
182
+ wantsLogs ||
183
+ wantsMem ||
184
+ wantsStart ||
185
+ wantsStop ||
186
+ wantsHelp ||
187
+ wantsVersion
188
+ );
189
+
190
+ const workspaceDir = parseWorkspaceDir(args);
191
+
192
+ let tailLines = DEFAULT_LOG_TAIL_LINES;
193
+ if (wantsLogs) {
194
+ const tailIndex = args.indexOf('--tail');
195
+ if (tailIndex !== -1 && args[tailIndex + 1]) {
196
+ const parsed = parseInt(args[tailIndex + 1], 10);
197
+ if (!isNaN(parsed) && parsed > 0) {
198
+ tailLines = parsed;
199
+ }
200
+ }
201
+ }
202
+
203
+ let startFilter = null;
204
+ if (wantsStart) {
205
+ const getFilter = (flag) => {
206
+ const filterIndex = args.indexOf(flag);
207
+ if (filterIndex === -1) return null;
208
+ const value = args[filterIndex + 1];
209
+ return value && !value.startsWith('-') ? value : null;
210
+ };
211
+ startFilter = getFilter('--start') ?? getFilter('--register');
212
+ }
213
+
214
+ const knownFlags = new Set([
215
+ '--cache',
216
+ '--clean',
217
+ '--status',
218
+ '--clear',
219
+ '--clear-cache',
220
+ '--logs',
221
+ '--mem',
222
+ '--tail',
223
+ '--no-follow',
224
+ '--start',
225
+ '--register',
226
+ '--stop',
227
+ '--workspace',
228
+ '--fix',
229
+ '--version',
230
+ '-v',
231
+ '--help',
232
+ '-h',
233
+ ]);
234
+ const flagsWithValue = FLAG_ARGS_WITH_VALUES;
235
+ const unknownFlags = collectUnknownFlags(rawArgs, knownFlags, flagsWithValue);
236
+
237
+ return {
238
+ args,
239
+ rawArgs,
240
+ isServerMode,
241
+ workspaceDir,
242
+ wantsVersion,
243
+ wantsHelp,
244
+ wantsCache,
245
+ wantsClean,
246
+ wantsStatus,
247
+ wantsClearCache,
248
+ wantsLogs,
249
+ wantsMem,
250
+ wantsStart,
251
+ wantsStop,
252
+ wantsFix,
253
+ wantsNoFollow,
254
+ tailLines,
255
+ startFilter,
256
+ unknownFlags,
257
+ };
258
+ }
259
+