@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.
Files changed (74) hide show
  1. package/dist/bin/gramatr-mcp.d.ts +5 -0
  2. package/dist/bin/gramatr-mcp.d.ts.map +1 -1
  3. package/dist/bin/gramatr-mcp.js +125 -2
  4. package/dist/bin/gramatr-mcp.js.map +1 -1
  5. package/dist/bin/setup.d.ts +16 -1
  6. package/dist/bin/setup.d.ts.map +1 -1
  7. package/dist/bin/setup.js +585 -2
  8. package/dist/bin/setup.js.map +1 -1
  9. package/dist/hooks/lib/feedback.d.ts +0 -2
  10. package/dist/hooks/lib/feedback.d.ts.map +1 -1
  11. package/dist/hooks/lib/feedback.js +18 -19
  12. package/dist/hooks/lib/feedback.js.map +1 -1
  13. package/dist/hooks/lib/formatting-compat.d.ts.map +1 -1
  14. package/dist/hooks/lib/formatting-compat.js +82 -55
  15. package/dist/hooks/lib/formatting-compat.js.map +1 -1
  16. package/dist/hooks/lib/gramatr-hook-utils.d.ts +0 -59
  17. package/dist/hooks/lib/gramatr-hook-utils.d.ts.map +1 -1
  18. package/dist/hooks/lib/gramatr-hook-utils.js +1 -212
  19. package/dist/hooks/lib/gramatr-hook-utils.js.map +1 -1
  20. package/dist/hooks/lib/hook-state.d.ts +159 -0
  21. package/dist/hooks/lib/hook-state.d.ts.map +1 -0
  22. package/dist/hooks/lib/hook-state.js +430 -0
  23. package/dist/hooks/lib/hook-state.js.map +1 -0
  24. package/dist/hooks/lib/intelligence.d.ts.map +1 -1
  25. package/dist/hooks/lib/intelligence.js +112 -88
  26. package/dist/hooks/lib/intelligence.js.map +1 -1
  27. package/dist/hooks/lib/routing.d.ts +2 -2
  28. package/dist/hooks/lib/routing.d.ts.map +1 -1
  29. package/dist/hooks/lib/routing.js +71 -25
  30. package/dist/hooks/lib/routing.js.map +1 -1
  31. package/dist/hooks/lib/session-end.d.ts +7 -0
  32. package/dist/hooks/lib/session-end.d.ts.map +1 -1
  33. package/dist/hooks/lib/session-end.js +33 -16
  34. package/dist/hooks/lib/session-end.js.map +1 -1
  35. package/dist/hooks/lib/session.d.ts +7 -38
  36. package/dist/hooks/lib/session.d.ts.map +1 -1
  37. package/dist/hooks/lib/session.js +43 -87
  38. package/dist/hooks/lib/session.js.map +1 -1
  39. package/dist/hooks/lib/types.d.ts +174 -36
  40. package/dist/hooks/lib/types.d.ts.map +1 -1
  41. package/dist/hooks/session-end.d.ts.map +1 -1
  42. package/dist/hooks/session-end.js +53 -45
  43. package/dist/hooks/session-end.js.map +1 -1
  44. package/dist/hooks/session-start.d.ts.map +1 -1
  45. package/dist/hooks/session-start.js +87 -77
  46. package/dist/hooks/session-start.js.map +1 -1
  47. package/dist/hooks/stop.d.ts.map +1 -1
  48. package/dist/hooks/stop.js +1 -10
  49. package/dist/hooks/stop.js.map +1 -1
  50. package/dist/hooks/user-prompt-submit.d.ts.map +1 -1
  51. package/dist/hooks/user-prompt-submit.js +57 -68
  52. package/dist/hooks/user-prompt-submit.js.map +1 -1
  53. package/dist/intelligence/packet2-fetcher.d.ts +3 -3
  54. package/dist/intelligence/packet2-fetcher.js +8 -8
  55. package/dist/intelligence/packet2-fetcher.js.map +1 -1
  56. package/dist/proxy/local-client.d.ts +49 -0
  57. package/dist/proxy/local-client.d.ts.map +1 -0
  58. package/dist/proxy/local-client.js +135 -0
  59. package/dist/proxy/local-client.js.map +1 -0
  60. package/dist/server/hooks-listener.d.ts +32 -0
  61. package/dist/server/hooks-listener.d.ts.map +1 -0
  62. package/dist/server/hooks-listener.js +144 -0
  63. package/dist/server/hooks-listener.js.map +1 -0
  64. package/dist/server/server.d.ts.map +1 -1
  65. package/dist/server/server.js +16 -1
  66. package/dist/server/server.js.map +1 -1
  67. package/dist/setup/instructions.js +13 -0
  68. package/dist/setup/instructions.js.map +1 -1
  69. package/dist/setup/integrations.js +3 -3
  70. package/dist/setup/integrations.js.map +1 -1
  71. package/dist/setup/web-connector.d.ts.map +1 -1
  72. package/dist/setup/web-connector.js +4 -0
  73. package/dist/setup/web-connector.js.map +1 -1
  74. 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