@gramatr/mcp 0.7.5 → 0.7.10
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/dist/bin/gramatr-mcp.d.ts +5 -0
- package/dist/bin/gramatr-mcp.d.ts.map +1 -1
- package/dist/bin/gramatr-mcp.js +125 -2
- package/dist/bin/gramatr-mcp.js.map +1 -1
- package/dist/bin/setup.d.ts +16 -1
- package/dist/bin/setup.d.ts.map +1 -1
- package/dist/bin/setup.js +585 -2
- package/dist/bin/setup.js.map +1 -1
- package/dist/hooks/lib/feedback.d.ts +0 -2
- package/dist/hooks/lib/feedback.d.ts.map +1 -1
- package/dist/hooks/lib/feedback.js +18 -19
- package/dist/hooks/lib/feedback.js.map +1 -1
- package/dist/hooks/lib/formatting-compat.d.ts.map +1 -1
- package/dist/hooks/lib/formatting-compat.js +82 -55
- package/dist/hooks/lib/formatting-compat.js.map +1 -1
- package/dist/hooks/lib/gramatr-hook-utils.d.ts +0 -59
- package/dist/hooks/lib/gramatr-hook-utils.d.ts.map +1 -1
- package/dist/hooks/lib/gramatr-hook-utils.js +1 -212
- package/dist/hooks/lib/gramatr-hook-utils.js.map +1 -1
- package/dist/hooks/lib/hook-state.d.ts +159 -0
- package/dist/hooks/lib/hook-state.d.ts.map +1 -0
- package/dist/hooks/lib/hook-state.js +430 -0
- package/dist/hooks/lib/hook-state.js.map +1 -0
- package/dist/hooks/lib/intelligence.d.ts.map +1 -1
- package/dist/hooks/lib/intelligence.js +112 -88
- package/dist/hooks/lib/intelligence.js.map +1 -1
- package/dist/hooks/lib/routing.d.ts +2 -2
- package/dist/hooks/lib/routing.d.ts.map +1 -1
- package/dist/hooks/lib/routing.js +71 -25
- package/dist/hooks/lib/routing.js.map +1 -1
- package/dist/hooks/lib/session-end.d.ts +7 -0
- package/dist/hooks/lib/session-end.d.ts.map +1 -1
- package/dist/hooks/lib/session-end.js +33 -16
- package/dist/hooks/lib/session-end.js.map +1 -1
- package/dist/hooks/lib/session.d.ts +7 -38
- package/dist/hooks/lib/session.d.ts.map +1 -1
- package/dist/hooks/lib/session.js +43 -87
- package/dist/hooks/lib/session.js.map +1 -1
- package/dist/hooks/lib/types.d.ts +174 -36
- package/dist/hooks/lib/types.d.ts.map +1 -1
- package/dist/hooks/session-end.d.ts.map +1 -1
- package/dist/hooks/session-end.js +53 -45
- package/dist/hooks/session-end.js.map +1 -1
- package/dist/hooks/session-start.d.ts.map +1 -1
- package/dist/hooks/session-start.js +87 -77
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/hooks/stop.d.ts.map +1 -1
- package/dist/hooks/stop.js +1 -10
- package/dist/hooks/stop.js.map +1 -1
- package/dist/hooks/user-prompt-submit.d.ts.map +1 -1
- package/dist/hooks/user-prompt-submit.js +57 -68
- package/dist/hooks/user-prompt-submit.js.map +1 -1
- package/dist/intelligence/packet2-fetcher.d.ts +3 -3
- package/dist/intelligence/packet2-fetcher.js +8 -8
- package/dist/intelligence/packet2-fetcher.js.map +1 -1
- package/dist/proxy/local-client.d.ts +49 -0
- package/dist/proxy/local-client.d.ts.map +1 -0
- package/dist/proxy/local-client.js +135 -0
- package/dist/proxy/local-client.js.map +1 -0
- package/dist/server/hooks-listener.d.ts +32 -0
- package/dist/server/hooks-listener.d.ts.map +1 -0
- package/dist/server/hooks-listener.js +144 -0
- package/dist/server/hooks-listener.js.map +1 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +16 -1
- package/dist/server/server.js.map +1 -1
- package/dist/setup/instructions.js +13 -0
- package/dist/setup/instructions.js.map +1 -1
- package/dist/setup/integrations.js +3 -3
- package/dist/setup/integrations.js.map +1 -1
- package/dist/setup/web-connector.d.ts.map +1 -1
- package/dist/setup/web-connector.js +4 -0
- package/dist/setup/web-connector.js.map +1 -1
- package/package.json +5 -2
package/dist/bin/setup.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* gramatr-mcp setup claude Configure Claude Code
|
|
10
10
|
* gramatr-mcp setup claude --dry Run without writing
|
|
11
11
|
*/
|
|
12
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, readdirSync, statSync, lstatSync, readlinkSync, unlinkSync, } from 'node:fs';
|
|
13
13
|
import { join, resolve, dirname } from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import { getGramatrDirFromEnv, getGramatrUrlFromEnv } from '../config-runtime.js';
|
|
@@ -21,6 +21,48 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
21
21
|
const __dirname = dirname(__filename);
|
|
22
22
|
// gramatr-allow: C1 — CLI entry point, reads HOME for config path
|
|
23
23
|
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
24
|
+
export const AUTO_TARGET_ORDER = [
|
|
25
|
+
'claude',
|
|
26
|
+
'codex',
|
|
27
|
+
'gemini',
|
|
28
|
+
'claude-desktop',
|
|
29
|
+
'chatgpt-desktop',
|
|
30
|
+
'cursor',
|
|
31
|
+
'windsurf',
|
|
32
|
+
'vscode',
|
|
33
|
+
];
|
|
34
|
+
const LEGACY_HOOK_BASENAMES = [
|
|
35
|
+
'LoadContext.hook.ts',
|
|
36
|
+
'SecurityValidator.hook.ts',
|
|
37
|
+
'RatingCapture.hook.ts',
|
|
38
|
+
'VoiceGate.hook.ts',
|
|
39
|
+
'AutoWorkCreation.hook.ts',
|
|
40
|
+
'WorkCompletionLearning.hook.ts',
|
|
41
|
+
'RelationshipMemory.hook.ts',
|
|
42
|
+
'SessionSummary.hook.ts',
|
|
43
|
+
'UpdateCounts.hook.ts',
|
|
44
|
+
'IntegrityCheck.hook.ts',
|
|
45
|
+
];
|
|
46
|
+
const LEGACY_MCP_KEY_PATTERNS = [/^aios/i, /^pai$/i, /^pai[-_]/i, /^fabric/i];
|
|
47
|
+
const LEGACY_CLAUDE_ARTIFACT_PATTERNS = [
|
|
48
|
+
/^pai[-_]/i,
|
|
49
|
+
/^pai$/i,
|
|
50
|
+
/^fabric[-_]/i,
|
|
51
|
+
/^fabric$/i,
|
|
52
|
+
/^aios[-_]/i,
|
|
53
|
+
/^aios$/i,
|
|
54
|
+
/^extract[-_]?wisdom/i,
|
|
55
|
+
/^pattern[-_]/i,
|
|
56
|
+
/^official[-_]?pattern/i,
|
|
57
|
+
/^becreative$/i,
|
|
58
|
+
/^beexpert$/i,
|
|
59
|
+
];
|
|
60
|
+
const LEGACY_MANAGED_BLOCK_MARKERS = [
|
|
61
|
+
{ start: '<!-- AIOS-START -->', end: '<!-- AIOS-END -->' },
|
|
62
|
+
{ start: '<!-- PAI-START -->', end: '<!-- PAI-END -->' },
|
|
63
|
+
{ start: '<!-- FABRIC-START -->', end: '<!-- FABRIC-END -->' },
|
|
64
|
+
{ start: '<!-- GMTR-START -->', end: '<!-- GMTR-END -->' },
|
|
65
|
+
];
|
|
24
66
|
/**
|
|
25
67
|
* Resolve the path to the gramatr-mcp binary.
|
|
26
68
|
* Prefers the compiled dist version, falls back to npx.
|
|
@@ -117,6 +159,252 @@ export function ensureLocalSettings() {
|
|
|
117
159
|
mkdirSync(settingsDir, { recursive: true });
|
|
118
160
|
writeFileSync(settingsPath, JSON.stringify(next, null, 2) + '\n', 'utf8');
|
|
119
161
|
}
|
|
162
|
+
function matchesLegacyClaudeArtifact(name) {
|
|
163
|
+
const stem = name.replace(/\.md$/i, '');
|
|
164
|
+
return LEGACY_CLAUDE_ARTIFACT_PATTERNS.some((pattern) => pattern.test(stem));
|
|
165
|
+
}
|
|
166
|
+
function sanitizeLegacyMcpServers(raw) {
|
|
167
|
+
if (!raw.mcpServers || typeof raw.mcpServers !== 'object')
|
|
168
|
+
return raw;
|
|
169
|
+
const servers = raw.mcpServers;
|
|
170
|
+
const next = {};
|
|
171
|
+
for (const [key, value] of Object.entries(servers)) {
|
|
172
|
+
if (LEGACY_MCP_KEY_PATTERNS.some((pattern) => pattern.test(key)))
|
|
173
|
+
continue;
|
|
174
|
+
next[key] = value;
|
|
175
|
+
}
|
|
176
|
+
return { ...raw, mcpServers: next };
|
|
177
|
+
}
|
|
178
|
+
function stripManagedBlock(content, startMarker, endMarker) {
|
|
179
|
+
const pattern = new RegExp(`${escapeRegExp(startMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}\\n?`, 'gm');
|
|
180
|
+
return content.replace(pattern, '').trimEnd() + '\n';
|
|
181
|
+
}
|
|
182
|
+
function isLegacyHookCommand(command) {
|
|
183
|
+
if (command.includes('/.claude/hooks/'))
|
|
184
|
+
return true;
|
|
185
|
+
if (command.includes('/aios-v2-client/'))
|
|
186
|
+
return true;
|
|
187
|
+
if (command.includes('/fabric/'))
|
|
188
|
+
return true;
|
|
189
|
+
if (command.includes('/pai/'))
|
|
190
|
+
return true;
|
|
191
|
+
return LEGACY_HOOK_BASENAMES.some((basename) => command.includes(`/${basename}`));
|
|
192
|
+
}
|
|
193
|
+
function sanitizeHookEventArray(value) {
|
|
194
|
+
if (!Array.isArray(value))
|
|
195
|
+
return value;
|
|
196
|
+
return value
|
|
197
|
+
.map((entry) => {
|
|
198
|
+
if (!entry || typeof entry !== 'object')
|
|
199
|
+
return entry;
|
|
200
|
+
const commands = entry.hooks;
|
|
201
|
+
if (!Array.isArray(commands))
|
|
202
|
+
return entry;
|
|
203
|
+
const filtered = commands.filter((cmd) => {
|
|
204
|
+
const command = typeof cmd?.command === 'string' ? cmd.command : '';
|
|
205
|
+
return !command || !isLegacyHookCommand(command);
|
|
206
|
+
});
|
|
207
|
+
if (filtered.length === 0)
|
|
208
|
+
return null;
|
|
209
|
+
return { ...entry, hooks: filtered };
|
|
210
|
+
})
|
|
211
|
+
.filter(Boolean);
|
|
212
|
+
}
|
|
213
|
+
function sanitizeHookFile(hookFile) {
|
|
214
|
+
const hooksRoot = hookFile.hooks;
|
|
215
|
+
if (!hooksRoot || typeof hooksRoot !== 'object')
|
|
216
|
+
return hookFile;
|
|
217
|
+
const nextHooks = { ...hooksRoot };
|
|
218
|
+
for (const [eventName, value] of Object.entries(nextHooks)) {
|
|
219
|
+
nextHooks[eventName] = sanitizeHookEventArray(value);
|
|
220
|
+
}
|
|
221
|
+
return { ...hookFile, hooks: nextHooks };
|
|
222
|
+
}
|
|
223
|
+
function removeDirectoryIfExists(path, dryRun) {
|
|
224
|
+
if (!existsSync(path))
|
|
225
|
+
return;
|
|
226
|
+
if (dryRun) {
|
|
227
|
+
process.stderr.write(`[gramatr-mcp] clean-install dry-run: would remove ${path}\n`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
rmSync(path, { recursive: true, force: true });
|
|
231
|
+
process.stderr.write(`[gramatr-mcp] clean-install: removed ${path}\n`);
|
|
232
|
+
}
|
|
233
|
+
function removeFileIfExists(path, dryRun) {
|
|
234
|
+
if (!existsSync(path))
|
|
235
|
+
return;
|
|
236
|
+
if (dryRun) {
|
|
237
|
+
process.stderr.write(`[gramatr-mcp] clean-install dry-run: would remove ${path}\n`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
rmSync(path, { force: true });
|
|
241
|
+
process.stderr.write(`[gramatr-mcp] clean-install: removed ${path}\n`);
|
|
242
|
+
}
|
|
243
|
+
function cleanLegacyClaudeArtifacts(dryRun) {
|
|
244
|
+
process.stderr.write('[gramatr-mcp] clean-install: removing legacy Claude + PAI/Fabric/AIOS artifacts\n');
|
|
245
|
+
const legacyDirs = [
|
|
246
|
+
join(HOME, 'aios-v2-client'),
|
|
247
|
+
join(HOME, '.claude', 'hooks'),
|
|
248
|
+
];
|
|
249
|
+
for (const dir of legacyDirs)
|
|
250
|
+
removeDirectoryIfExists(dir, dryRun);
|
|
251
|
+
const commandsDir = join(HOME, '.claude', 'commands');
|
|
252
|
+
if (existsSync(commandsDir)) {
|
|
253
|
+
try {
|
|
254
|
+
for (const entry of readdirSync(commandsDir)) {
|
|
255
|
+
if (!entry.endsWith('.md'))
|
|
256
|
+
continue;
|
|
257
|
+
if (!matchesLegacyClaudeArtifact(entry))
|
|
258
|
+
continue;
|
|
259
|
+
removeFileIfExists(join(commandsDir, entry), dryRun);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// non-critical
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const skillsDir = join(HOME, '.claude', 'skills');
|
|
267
|
+
if (existsSync(skillsDir)) {
|
|
268
|
+
try {
|
|
269
|
+
for (const entry of readdirSync(skillsDir)) {
|
|
270
|
+
const path = join(skillsDir, entry);
|
|
271
|
+
if (!statSync(path).isDirectory())
|
|
272
|
+
continue;
|
|
273
|
+
if (!matchesLegacyClaudeArtifact(entry))
|
|
274
|
+
continue;
|
|
275
|
+
removeDirectoryIfExists(path, dryRun);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// non-critical
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const clientHooksDir = join(HOME, '.gramatr', 'hooks');
|
|
283
|
+
if (existsSync(clientHooksDir)) {
|
|
284
|
+
for (const basename of LEGACY_HOOK_BASENAMES) {
|
|
285
|
+
removeFileIfExists(join(clientHooksDir, basename), dryRun);
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
for (const entry of readdirSync(clientHooksDir)) {
|
|
289
|
+
if (!entry.endsWith('.sh'))
|
|
290
|
+
continue;
|
|
291
|
+
removeFileIfExists(join(clientHooksDir, entry), dryRun);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// non-critical
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function sanitizeLegacyMcpServersInFile(path, dryRun) {
|
|
300
|
+
const current = readJsonFile(path, {});
|
|
301
|
+
const next = sanitizeLegacyMcpServers(current);
|
|
302
|
+
if (JSON.stringify(next) === JSON.stringify(current))
|
|
303
|
+
return;
|
|
304
|
+
if (dryRun) {
|
|
305
|
+
process.stderr.write(`[gramatr-mcp] clean-install dry-run: would sanitize legacy mcpServers in ${path}\n`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
309
|
+
writeFileSync(path, JSON.stringify(next, null, 2) + '\n', 'utf8');
|
|
310
|
+
process.stderr.write(`[gramatr-mcp] clean-install: sanitized legacy mcpServers in ${path}\n`);
|
|
311
|
+
}
|
|
312
|
+
function sanitizeHookFileAtPath(path, dryRun, label) {
|
|
313
|
+
const current = readJsonFile(path, {});
|
|
314
|
+
const next = sanitizeHookFile(current);
|
|
315
|
+
if (JSON.stringify(next) === JSON.stringify(current))
|
|
316
|
+
return;
|
|
317
|
+
if (dryRun) {
|
|
318
|
+
process.stderr.write(`[gramatr-mcp] clean-install dry-run: would sanitize legacy hooks in ${path}\n`);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
322
|
+
writeFileSync(path, JSON.stringify(next, null, 2) + '\n', 'utf8');
|
|
323
|
+
process.stderr.write(`[gramatr-mcp] clean-install: sanitized legacy hooks (${label}) in ${path}\n`);
|
|
324
|
+
}
|
|
325
|
+
function stripLegacyManagedBlocks(path, dryRun) {
|
|
326
|
+
if (!existsSync(path))
|
|
327
|
+
return;
|
|
328
|
+
const current = readFileSync(path, 'utf8');
|
|
329
|
+
let next = current;
|
|
330
|
+
for (const marker of LEGACY_MANAGED_BLOCK_MARKERS) {
|
|
331
|
+
next = stripManagedBlock(next, marker.start, marker.end);
|
|
332
|
+
}
|
|
333
|
+
if (next === current)
|
|
334
|
+
return;
|
|
335
|
+
if (dryRun) {
|
|
336
|
+
process.stderr.write(`[gramatr-mcp] clean-install dry-run: would strip legacy managed blocks in ${path}\n`);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
writeFileSync(path, next, 'utf8');
|
|
340
|
+
process.stderr.write(`[gramatr-mcp] clean-install: stripped legacy managed blocks in ${path}\n`);
|
|
341
|
+
}
|
|
342
|
+
function cleanAllInstallTargets(dryRun) {
|
|
343
|
+
sanitizeLegacyMcpServersInFile(getClaudeConfigPath(), dryRun);
|
|
344
|
+
sanitizeLegacyMcpServersInFile(join(HOME, '.claude', 'mcp.json'), dryRun);
|
|
345
|
+
sanitizeLegacyMcpServersInFile(getClaudeDesktopConfigPath(HOME), dryRun);
|
|
346
|
+
sanitizeLegacyMcpServersInFile(getChatgptDesktopConfigPath(HOME), dryRun);
|
|
347
|
+
sanitizeLegacyMcpServersInFile(getCursorConfigPath(HOME), dryRun);
|
|
348
|
+
sanitizeLegacyMcpServersInFile(getWindsurfConfigPath(HOME), dryRun);
|
|
349
|
+
sanitizeLegacyMcpServersInFile(getVscodeConfigPath(HOME), dryRun);
|
|
350
|
+
sanitizeLegacyMcpServersInFile(getGeminiManifestPath(HOME), dryRun);
|
|
351
|
+
sanitizeHookFileAtPath(getClaudeSettingsPath(), dryRun, 'claude');
|
|
352
|
+
sanitizeHookFileAtPath(getCodexHooksPath(), dryRun, 'codex');
|
|
353
|
+
sanitizeHookFileAtPath(getGeminiHooksPath(HOME), dryRun, 'gemini');
|
|
354
|
+
stripLegacyManagedBlocks(getClaudeMarkdownPath(), dryRun);
|
|
355
|
+
stripLegacyManagedBlocks(getCodexAgentsPath(), dryRun);
|
|
356
|
+
}
|
|
357
|
+
function cleanLegacyHomeNodeShims(dryRun) {
|
|
358
|
+
const candidates = new Set();
|
|
359
|
+
candidates.add(join(HOME, '.local', 'bin', 'gramatr-mcp'));
|
|
360
|
+
candidates.add(join(HOME, '.local', 'bin', 'gramatr'));
|
|
361
|
+
candidates.add(join(HOME, 'bin', 'gramatr-mcp'));
|
|
362
|
+
candidates.add(join(HOME, 'bin', 'gramatr'));
|
|
363
|
+
candidates.add(join(HOME, 'bin', 'gramatr-mac'));
|
|
364
|
+
for (const shimPath of candidates) {
|
|
365
|
+
if (!existsSync(shimPath))
|
|
366
|
+
continue;
|
|
367
|
+
try {
|
|
368
|
+
let stale = false;
|
|
369
|
+
if (lstatSync(shimPath).isSymbolicLink()) {
|
|
370
|
+
const target = readlinkSync(shimPath);
|
|
371
|
+
if (target.includes('aios-v2-client')
|
|
372
|
+
|| target.includes('/packages/client/bin/gramatr')
|
|
373
|
+
|| target.includes('/fabric/')
|
|
374
|
+
|| target.includes('/pai/')) {
|
|
375
|
+
stale = true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
const body = readFileSync(shimPath, 'utf8');
|
|
380
|
+
if (body.includes('aios-v2-client')
|
|
381
|
+
|| body.includes('/packages/client/bin/gramatr')
|
|
382
|
+
|| body.includes('/fabric/')
|
|
383
|
+
|| body.includes('/pai/')) {
|
|
384
|
+
stale = true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (!stale)
|
|
388
|
+
continue;
|
|
389
|
+
if (dryRun) {
|
|
390
|
+
process.stderr.write(`[gramatr-mcp] clean-install dry-run: would remove stale shim ${shimPath}\n`);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
unlinkSync(shimPath);
|
|
394
|
+
process.stderr.write(`[gramatr-mcp] clean-install: removed stale shim ${shimPath}\n`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// non-critical
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
export function runCleanInstall(dryRun) {
|
|
403
|
+
process.stderr.write('[gramatr-mcp] clean-install: running global legacy cleanup across known install targets\n');
|
|
404
|
+
cleanAllInstallTargets(dryRun);
|
|
405
|
+
cleanLegacyClaudeArtifacts(dryRun);
|
|
406
|
+
cleanLegacyHomeNodeShims(dryRun);
|
|
407
|
+
}
|
|
120
408
|
export function emitInstallPromptSuggestion(target) {
|
|
121
409
|
const promptBlock = buildInstallPromptSuggestion(target);
|
|
122
410
|
process.stderr.write('\n━━━ Prompt Suggestion (copy into custom instructions) ━━━\n\n');
|
|
@@ -124,7 +412,10 @@ export function emitInstallPromptSuggestion(target) {
|
|
|
124
412
|
process.stderr.write('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
125
413
|
process.stderr.write('[gramatr-mcp] Paste the prompt block above into your custom instructions / project knowledge.\n');
|
|
126
414
|
}
|
|
127
|
-
export function setupClaude(dryRun = false) {
|
|
415
|
+
export function setupClaude(dryRun = false, cleanInstall = false) {
|
|
416
|
+
if (cleanInstall) {
|
|
417
|
+
runCleanInstall(dryRun);
|
|
418
|
+
}
|
|
128
419
|
const configPath = getClaudeConfigPath();
|
|
129
420
|
const settingsPath = getClaudeSettingsPath();
|
|
130
421
|
const markdownPath = getClaudeMarkdownPath();
|
|
@@ -277,6 +568,75 @@ export function setupVscode(dryRun = false) {
|
|
|
277
568
|
if (!dryRun)
|
|
278
569
|
emitInstallPromptSuggestion('vscode');
|
|
279
570
|
}
|
|
571
|
+
export function getAutoDetectedTargets() {
|
|
572
|
+
const checks = {
|
|
573
|
+
claude: existsSync(join(HOME, '.claude')) || existsSync(getClaudeConfigPath()),
|
|
574
|
+
codex: existsSync(join(HOME, '.codex')),
|
|
575
|
+
gemini: existsSync(join(HOME, '.gemini')),
|
|
576
|
+
'claude-desktop': existsSync(dirname(getClaudeDesktopConfigPath(HOME))),
|
|
577
|
+
'chatgpt-desktop': existsSync(dirname(getChatgptDesktopConfigPath(HOME))),
|
|
578
|
+
cursor: existsSync(join(HOME, '.cursor')),
|
|
579
|
+
windsurf: existsSync(join(HOME, '.windsurf')),
|
|
580
|
+
vscode: existsSync(join(HOME, '.vscode')),
|
|
581
|
+
};
|
|
582
|
+
return AUTO_TARGET_ORDER.filter((target) => checks[target]);
|
|
583
|
+
}
|
|
584
|
+
export function setupAutoInstall(options = {}) {
|
|
585
|
+
const dryRun = options.dryRun === true;
|
|
586
|
+
const cleanInstall = options.cleanInstall === true;
|
|
587
|
+
const listOnly = options.listOnly === true;
|
|
588
|
+
const detected = getAutoDetectedTargets();
|
|
589
|
+
const selected = Array.isArray(options.selectedTargets) && options.selectedTargets.length > 0
|
|
590
|
+
? detected.filter((target) => options.selectedTargets.includes(target))
|
|
591
|
+
: detected;
|
|
592
|
+
process.stderr.write('[gramatr-mcp] auto-detect scan complete\n');
|
|
593
|
+
if (detected.length === 0) {
|
|
594
|
+
process.stderr.write('[gramatr-mcp] No supported local clients detected.\n');
|
|
595
|
+
process.stderr.write('[gramatr-mcp] Install manually with: setup <target>\n');
|
|
596
|
+
return 0;
|
|
597
|
+
}
|
|
598
|
+
process.stderr.write(`[gramatr-mcp] Detected targets: ${detected.join(', ')}\n`);
|
|
599
|
+
process.stderr.write(`[gramatr-mcp] Selected targets: ${selected.join(', ')}\n`);
|
|
600
|
+
if (listOnly) {
|
|
601
|
+
process.stderr.write('[gramatr-mcp] list-only mode: no setup changes made.\n');
|
|
602
|
+
return selected.length;
|
|
603
|
+
}
|
|
604
|
+
if (cleanInstall) {
|
|
605
|
+
runCleanInstall(dryRun);
|
|
606
|
+
}
|
|
607
|
+
for (const target of selected) {
|
|
608
|
+
switch (target) {
|
|
609
|
+
case 'claude':
|
|
610
|
+
setupClaude(dryRun, false);
|
|
611
|
+
break;
|
|
612
|
+
case 'codex':
|
|
613
|
+
setupCodex(dryRun);
|
|
614
|
+
break;
|
|
615
|
+
case 'gemini':
|
|
616
|
+
setupGemini(dryRun);
|
|
617
|
+
break;
|
|
618
|
+
case 'claude-desktop':
|
|
619
|
+
setupClaudeDesktop(dryRun);
|
|
620
|
+
break;
|
|
621
|
+
case 'chatgpt-desktop':
|
|
622
|
+
setupChatgptDesktop(dryRun);
|
|
623
|
+
break;
|
|
624
|
+
case 'cursor':
|
|
625
|
+
setupCursor(dryRun);
|
|
626
|
+
break;
|
|
627
|
+
case 'windsurf':
|
|
628
|
+
setupWindsurf(dryRun);
|
|
629
|
+
break;
|
|
630
|
+
case 'vscode':
|
|
631
|
+
setupVscode(dryRun);
|
|
632
|
+
break;
|
|
633
|
+
default:
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
process.stderr.write(`[gramatr-mcp] Auto setup completed for ${selected.length} target(s).\n`);
|
|
638
|
+
return selected.length;
|
|
639
|
+
}
|
|
280
640
|
export function setupGemini(dryRun = false) {
|
|
281
641
|
const extensionDir = getGeminiExtensionDir(HOME);
|
|
282
642
|
const manifestPath = getGeminiManifestPath(HOME);
|
|
@@ -330,4 +690,227 @@ export async function setupWeb(target = 'claude-web') {
|
|
|
330
690
|
process.stderr.write('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
331
691
|
process.stderr.write('[gramatr-mcp] Paste the prompt block above into your custom instructions / project knowledge.\n');
|
|
332
692
|
}
|
|
693
|
+
function addResult(items, severity, label, detail) {
|
|
694
|
+
items.push({ severity, label, detail });
|
|
695
|
+
}
|
|
696
|
+
function parseJson(path) {
|
|
697
|
+
try {
|
|
698
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
699
|
+
}
|
|
700
|
+
catch {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function hasHookCommand(config, eventName, needle) {
|
|
705
|
+
if (!config || typeof config !== 'object')
|
|
706
|
+
return false;
|
|
707
|
+
const hooksRoot = config.hooks;
|
|
708
|
+
if (!hooksRoot || typeof hooksRoot !== 'object')
|
|
709
|
+
return false;
|
|
710
|
+
const entries = hooksRoot[eventName];
|
|
711
|
+
if (!Array.isArray(entries))
|
|
712
|
+
return false;
|
|
713
|
+
for (const entry of entries) {
|
|
714
|
+
if (!entry || typeof entry !== 'object')
|
|
715
|
+
continue;
|
|
716
|
+
const commands = entry.hooks;
|
|
717
|
+
if (!Array.isArray(commands))
|
|
718
|
+
continue;
|
|
719
|
+
for (const command of commands) {
|
|
720
|
+
const value = command?.command;
|
|
721
|
+
if (typeof value === 'string' && value.includes(needle)) {
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
function readManagedBlock(path, startMarker, endMarker) {
|
|
729
|
+
if (!existsSync(path))
|
|
730
|
+
return null;
|
|
731
|
+
const body = readFileSync(path, 'utf8');
|
|
732
|
+
const pattern = new RegExp(`${escapeRegExp(startMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}`, 'm');
|
|
733
|
+
const match = body.match(pattern);
|
|
734
|
+
return match ? match[0] : null;
|
|
735
|
+
}
|
|
736
|
+
function verifyClaude(items) {
|
|
737
|
+
const configPath = getClaudeConfigPath();
|
|
738
|
+
const settingsPath = getClaudeSettingsPath();
|
|
739
|
+
const markdownPath = getClaudeMarkdownPath();
|
|
740
|
+
const config = parseJson(configPath);
|
|
741
|
+
const gramatrServer = config?.mcpServers && typeof config.mcpServers === 'object'
|
|
742
|
+
? config.mcpServers.gramatr
|
|
743
|
+
: null;
|
|
744
|
+
if (gramatrServer) {
|
|
745
|
+
addResult(items, 'ok', 'claude.mcp_server', `${configPath} contains mcpServers.gramatr`);
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
addResult(items, 'error', 'claude.mcp_server', `${configPath} missing mcpServers.gramatr`);
|
|
749
|
+
}
|
|
750
|
+
const settings = parseJson(settingsPath);
|
|
751
|
+
const hasPromptHook = hasHookCommand(settings, 'UserPromptSubmit', 'hook user-prompt-submit');
|
|
752
|
+
const hasSessionStartHook = hasHookCommand(settings, 'SessionStart', 'hook session-start');
|
|
753
|
+
if (hasPromptHook && hasSessionStartHook) {
|
|
754
|
+
addResult(items, 'ok', 'claude.hooks', `${settingsPath} includes session-start + user-prompt-submit`);
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
addResult(items, 'error', 'claude.hooks', `${settingsPath} missing required hooks (session-start=${hasSessionStartHook}, user-prompt-submit=${hasPromptHook})`);
|
|
758
|
+
}
|
|
759
|
+
const managedBlock = readManagedBlock(markdownPath, CLAUDE_BLOCK_START, CLAUDE_BLOCK_END);
|
|
760
|
+
if (managedBlock) {
|
|
761
|
+
addResult(items, 'ok', 'claude.guidance', `${markdownPath} contains managed gramatr guidance block`);
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
addResult(items, 'warn', 'claude.guidance', `${markdownPath} missing managed guidance block`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
function verifyCodex(items) {
|
|
768
|
+
const hooksPath = getCodexHooksPath();
|
|
769
|
+
const configPath = getCodexConfigPath();
|
|
770
|
+
const agentsPath = getCodexAgentsPath();
|
|
771
|
+
const hooks = parseJson(hooksPath);
|
|
772
|
+
const hasPromptHook = hasHookCommand(hooks, 'UserPromptSubmit', 'hook user-prompt-submit');
|
|
773
|
+
const hasSessionStartHook = hasHookCommand(hooks, 'SessionStart', 'hook session-start');
|
|
774
|
+
const hasStopHook = hasHookCommand(hooks, 'Stop', 'hook stop');
|
|
775
|
+
if (hasPromptHook && hasSessionStartHook && hasStopHook) {
|
|
776
|
+
addResult(items, 'ok', 'codex.hooks', `${hooksPath} includes session-start + user-prompt-submit + stop`);
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
addResult(items, 'error', 'codex.hooks', `${hooksPath} missing required hooks (session-start=${hasSessionStartHook}, user-prompt-submit=${hasPromptHook}, stop=${hasStopHook})`);
|
|
780
|
+
}
|
|
781
|
+
const configToml = existsSync(configPath) ? readFileSync(configPath, 'utf8') : '';
|
|
782
|
+
const hooksEnabled = /^\s*codex_hooks\s*=\s*true\s*$/m.test(configToml);
|
|
783
|
+
if (hooksEnabled) {
|
|
784
|
+
addResult(items, 'ok', 'codex.feature_flag', `${configPath} enables codex_hooks`);
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
addResult(items, 'error', 'codex.feature_flag', `${configPath} missing codex_hooks = true`);
|
|
788
|
+
}
|
|
789
|
+
const managedBlock = readManagedBlock(agentsPath, CODEX_BLOCK_START, CODEX_BLOCK_END);
|
|
790
|
+
if (managedBlock) {
|
|
791
|
+
addResult(items, 'ok', 'codex.guidance', `${agentsPath} contains managed gramatr guidance block`);
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
addResult(items, 'warn', 'codex.guidance', `${agentsPath} missing managed guidance block`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function verifyJsonMcpTarget(items, label, configPath) {
|
|
798
|
+
const json = parseJson(configPath);
|
|
799
|
+
const gramatrServer = json?.mcpServers && typeof json.mcpServers === 'object'
|
|
800
|
+
? json.mcpServers.gramatr
|
|
801
|
+
: null;
|
|
802
|
+
if (gramatrServer) {
|
|
803
|
+
addResult(items, 'ok', `${label}.mcp_server`, `${configPath} contains mcpServers.gramatr`);
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
addResult(items, 'warn', `${label}.mcp_server`, `${configPath} missing mcpServers.gramatr`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function verifyGemini(items) {
|
|
810
|
+
const manifestPath = getGeminiManifestPath(HOME);
|
|
811
|
+
const hooksPath = getGeminiHooksPath(HOME);
|
|
812
|
+
const manifest = parseJson(manifestPath);
|
|
813
|
+
const geminiServer = manifest?.mcpServers && typeof manifest.mcpServers === 'object'
|
|
814
|
+
? manifest.mcpServers.gramatr
|
|
815
|
+
: null;
|
|
816
|
+
if (geminiServer) {
|
|
817
|
+
addResult(items, 'ok', 'gemini.manifest', `${manifestPath} contains mcpServers.gramatr`);
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
addResult(items, 'warn', 'gemini.manifest', `${manifestPath} missing mcpServers.gramatr`);
|
|
821
|
+
}
|
|
822
|
+
const hooks = parseJson(hooksPath);
|
|
823
|
+
const hasBeforeAgent = hasHookCommand(hooks, 'BeforeAgent', 'hook user-prompt-submit');
|
|
824
|
+
const hasSessionStart = hasHookCommand(hooks, 'SessionStart', 'hook session-start');
|
|
825
|
+
if (hasBeforeAgent && hasSessionStart) {
|
|
826
|
+
addResult(items, 'ok', 'gemini.hooks', `${hooksPath} includes BeforeAgent + SessionStart hooks`);
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
addResult(items, 'warn', 'gemini.hooks', `${hooksPath} missing expected hooks (before-agent=${hasBeforeAgent}, session-start=${hasSessionStart})`);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
function verifyLocalSettings(items) {
|
|
833
|
+
const settingsPath = getGramatrSettingsPath();
|
|
834
|
+
const settings = parseJson(settingsPath);
|
|
835
|
+
if (!settings) {
|
|
836
|
+
addResult(items, 'error', 'runtime.settings', `${settingsPath} is missing or invalid JSON`);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const hasPrincipal = Boolean(settings.principal?.name);
|
|
840
|
+
const hasIdentity = Boolean(settings.daidentity?.name);
|
|
841
|
+
if (hasPrincipal && hasIdentity) {
|
|
842
|
+
addResult(items, 'ok', 'runtime.settings', `${settingsPath} initialized with principal + daidentity`);
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
addResult(items, 'warn', 'runtime.settings', `${settingsPath} missing expected fields (principal=${hasPrincipal}, daidentity=${hasIdentity})`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
function printPromptBlocks(target) {
|
|
849
|
+
if (target === 'all' || target === 'claude') {
|
|
850
|
+
const block = readManagedBlock(getClaudeMarkdownPath(), CLAUDE_BLOCK_START, CLAUDE_BLOCK_END);
|
|
851
|
+
process.stderr.write('\n━━━ Claude Managed Guidance Block ━━━\n\n');
|
|
852
|
+
process.stdout.write((block || '[missing managed block]\n') + '\n');
|
|
853
|
+
}
|
|
854
|
+
if (target === 'all' || target === 'codex') {
|
|
855
|
+
const block = readManagedBlock(getCodexAgentsPath(), CODEX_BLOCK_START, CODEX_BLOCK_END);
|
|
856
|
+
process.stderr.write('\n━━━ Codex Managed Guidance Block ━━━\n\n');
|
|
857
|
+
process.stdout.write((block || '[missing managed block]\n') + '\n');
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
export function verifySetupInstall(target = 'all', options = {}) {
|
|
861
|
+
const items = [];
|
|
862
|
+
verifyLocalSettings(items);
|
|
863
|
+
if (target === 'all' || target === 'claude')
|
|
864
|
+
verifyClaude(items);
|
|
865
|
+
if (target === 'all' || target === 'codex')
|
|
866
|
+
verifyCodex(items);
|
|
867
|
+
if (target === 'all' || target === 'claude-desktop') {
|
|
868
|
+
verifyJsonMcpTarget(items, 'claude-desktop', getClaudeDesktopConfigPath(HOME));
|
|
869
|
+
}
|
|
870
|
+
if (target === 'all' || target === 'chatgpt-desktop') {
|
|
871
|
+
verifyJsonMcpTarget(items, 'chatgpt-desktop', getChatgptDesktopConfigPath(HOME));
|
|
872
|
+
}
|
|
873
|
+
if (target === 'all' || target === 'cursor') {
|
|
874
|
+
verifyJsonMcpTarget(items, 'cursor', getCursorConfigPath(HOME));
|
|
875
|
+
}
|
|
876
|
+
if (target === 'all' || target === 'windsurf') {
|
|
877
|
+
verifyJsonMcpTarget(items, 'windsurf', getWindsurfConfigPath(HOME));
|
|
878
|
+
}
|
|
879
|
+
if (target === 'all' || target === 'vscode') {
|
|
880
|
+
verifyJsonMcpTarget(items, 'vscode', getVscodeConfigPath(HOME));
|
|
881
|
+
}
|
|
882
|
+
if (target === 'all' || target === 'gemini')
|
|
883
|
+
verifyGemini(items);
|
|
884
|
+
const hasError = items.some((item) => item.severity === 'error');
|
|
885
|
+
const hasWarn = items.some((item) => item.severity === 'warn');
|
|
886
|
+
if (options.json) {
|
|
887
|
+
process.stdout.write(JSON.stringify({
|
|
888
|
+
ok: !hasError,
|
|
889
|
+
warnings: hasWarn,
|
|
890
|
+
target,
|
|
891
|
+
checks: items,
|
|
892
|
+
}, null, 2) + '\n');
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
process.stderr.write(`\n[gramatr-mcp] Setup verification target=${target}\n`);
|
|
896
|
+
for (const item of items) {
|
|
897
|
+
const marker = item.severity === 'ok' ? 'OK' : item.severity === 'warn' ? 'WARN' : 'ERROR';
|
|
898
|
+
process.stderr.write(` [${marker}] ${item.label}: ${item.detail}\n`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (options.showPrompts) {
|
|
902
|
+
printPromptBlocks(target);
|
|
903
|
+
}
|
|
904
|
+
if (hasError) {
|
|
905
|
+
process.stderr.write('[gramatr-mcp] Verification failed. Re-run setup for the failing target(s).\n');
|
|
906
|
+
return 1;
|
|
907
|
+
}
|
|
908
|
+
if (hasWarn) {
|
|
909
|
+
process.stderr.write('[gramatr-mcp] Verification completed with warnings.\n');
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
process.stderr.write('[gramatr-mcp] Verification passed.\n');
|
|
913
|
+
}
|
|
914
|
+
return 0;
|
|
915
|
+
}
|
|
333
916
|
//# sourceMappingURL=setup.js.map
|