@gramatr/mcp 0.7.6 → 0.7.13

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 (99) hide show
  1. package/dist/__mocks__/bun-sqlite.d.ts +35 -0
  2. package/dist/__mocks__/bun-sqlite.d.ts.map +1 -0
  3. package/dist/__mocks__/bun-sqlite.js +89 -0
  4. package/dist/__mocks__/bun-sqlite.js.map +1 -0
  5. package/dist/bin/build-mcpb.d.ts +2 -5
  6. package/dist/bin/build-mcpb.d.ts.map +1 -1
  7. package/dist/bin/build-mcpb.js +20 -43
  8. package/dist/bin/build-mcpb.js.map +1 -1
  9. package/dist/bin/gramatr-mcp.d.ts +13 -0
  10. package/dist/bin/gramatr-mcp.d.ts.map +1 -1
  11. package/dist/bin/gramatr-mcp.js +190 -9
  12. package/dist/bin/gramatr-mcp.js.map +1 -1
  13. package/dist/bin/login.d.ts.map +1 -1
  14. package/dist/bin/login.js +5 -0
  15. package/dist/bin/login.js.map +1 -1
  16. package/dist/bin/setup.d.ts +27 -10
  17. package/dist/bin/setup.d.ts.map +1 -1
  18. package/dist/bin/setup.js +628 -29
  19. package/dist/bin/setup.js.map +1 -1
  20. package/dist/cache/lru-cache.js +1 -1
  21. package/dist/config-runtime.d.ts +13 -0
  22. package/dist/config-runtime.d.ts.map +1 -1
  23. package/dist/config-runtime.js +24 -0
  24. package/dist/config-runtime.js.map +1 -1
  25. package/dist/gramatr +0 -0
  26. package/dist/hooks/input-validator.d.ts.map +1 -1
  27. package/dist/hooks/input-validator.js +27 -9
  28. package/dist/hooks/input-validator.js.map +1 -1
  29. package/dist/hooks/lib/client-runtime.d.ts +7 -0
  30. package/dist/hooks/lib/client-runtime.d.ts.map +1 -0
  31. package/dist/hooks/lib/client-runtime.js +22 -0
  32. package/dist/hooks/lib/client-runtime.js.map +1 -0
  33. package/dist/hooks/lib/hook-state.d.ts +7 -2
  34. package/dist/hooks/lib/hook-state.d.ts.map +1 -1
  35. package/dist/hooks/lib/hook-state.js +56 -35
  36. package/dist/hooks/lib/hook-state.js.map +1 -1
  37. package/dist/hooks/lib/intelligence.d.ts.map +1 -1
  38. package/dist/hooks/lib/intelligence.js +229 -427
  39. package/dist/hooks/lib/intelligence.js.map +1 -1
  40. package/dist/hooks/lib/routing.d.ts +6 -1
  41. package/dist/hooks/lib/routing.d.ts.map +1 -1
  42. package/dist/hooks/lib/routing.js +35 -11
  43. package/dist/hooks/lib/routing.js.map +1 -1
  44. package/dist/hooks/lib/session.d.ts.map +1 -1
  45. package/dist/hooks/lib/session.js +3 -4
  46. package/dist/hooks/lib/session.js.map +1 -1
  47. package/dist/hooks/lib/tool-envelope.d.ts +9 -0
  48. package/dist/hooks/lib/tool-envelope.d.ts.map +1 -0
  49. package/dist/hooks/lib/tool-envelope.js +24 -0
  50. package/dist/hooks/lib/tool-envelope.js.map +1 -0
  51. package/dist/hooks/lib/types.d.ts +7 -0
  52. package/dist/hooks/lib/types.d.ts.map +1 -1
  53. package/dist/hooks/lib/version.d.ts.map +1 -1
  54. package/dist/hooks/lib/version.js +3 -0
  55. package/dist/hooks/lib/version.js.map +1 -1
  56. package/dist/hooks/rating-capture.d.ts.map +1 -1
  57. package/dist/hooks/rating-capture.js +7 -2
  58. package/dist/hooks/rating-capture.js.map +1 -1
  59. package/dist/hooks/session-end.d.ts.map +1 -1
  60. package/dist/hooks/session-end.js +8 -6
  61. package/dist/hooks/session-end.js.map +1 -1
  62. package/dist/hooks/session-start.d.ts.map +1 -1
  63. package/dist/hooks/session-start.js +39 -13
  64. package/dist/hooks/session-start.js.map +1 -1
  65. package/dist/hooks/stop.d.ts.map +1 -1
  66. package/dist/hooks/stop.js +18 -6
  67. package/dist/hooks/stop.js.map +1 -1
  68. package/dist/hooks/user-prompt-submit.d.ts.map +1 -1
  69. package/dist/hooks/user-prompt-submit.js +24 -11
  70. package/dist/hooks/user-prompt-submit.js.map +1 -1
  71. package/dist/intelligence/packet2-fetcher.d.ts +2 -2
  72. package/dist/intelligence/packet2-fetcher.d.ts.map +1 -1
  73. package/dist/intelligence/packet2-fetcher.js +24 -5
  74. package/dist/intelligence/packet2-fetcher.js.map +1 -1
  75. package/dist/intelligence/session-manager.d.ts.map +1 -1
  76. package/dist/intelligence/session-manager.js +25 -1
  77. package/dist/intelligence/session-manager.js.map +1 -1
  78. package/dist/proxy/tool-proxy.d.ts.map +1 -1
  79. package/dist/proxy/tool-proxy.js +57 -11
  80. package/dist/proxy/tool-proxy.js.map +1 -1
  81. package/dist/server/server.d.ts.map +1 -1
  82. package/dist/server/server.js +8 -9
  83. package/dist/server/server.js.map +1 -1
  84. package/dist/setup/instructions.d.ts +2 -2
  85. package/dist/setup/instructions.d.ts.map +1 -1
  86. package/dist/setup/instructions.js +40 -5
  87. package/dist/setup/instructions.js.map +1 -1
  88. package/dist/setup/integrations.d.ts +1 -1
  89. package/dist/setup/integrations.d.ts.map +1 -1
  90. package/dist/setup/integrations.js +7 -4
  91. package/dist/setup/integrations.js.map +1 -1
  92. package/dist/setup/targets.d.ts +1 -1
  93. package/dist/setup/targets.d.ts.map +1 -1
  94. package/dist/setup/targets.js +2 -3
  95. package/dist/setup/targets.js.map +1 -1
  96. package/dist/setup/web-connector.d.ts.map +1 -1
  97. package/dist/setup/web-connector.js +6 -0
  98. package/dist/setup/web-connector.js.map +1 -1
  99. package/package.json +10 -7
package/dist/bin/setup.js CHANGED
@@ -9,8 +9,8 @@
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';
13
- import { join, resolve, dirname } from 'node:path';
12
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, readdirSync, statSync, lstatSync, readlinkSync, unlinkSync, } from 'node:fs';
13
+ import { join, dirname } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  import { getGramatrDirFromEnv, getGramatrUrlFromEnv } from '../config-runtime.js';
16
16
  import { buildClaudeHooksFile, buildCodexHooksFile, buildClaudeMcpServerEntry, ensureCodexHooksFeature, mergeManagedHooks, } from '../setup/integrations.js';
@@ -21,17 +21,59 @@ 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
- * Resolve the path to the gramatr-mcp binary.
26
- * Prefers the compiled dist version, falls back to npx.
67
+ * Resolve the path to the gramatr binary.
68
+ * Prefers the compiled Bun binary at ~/.gramatr/bin/gramatr (self-contained,
69
+ * no Node version dependency). Falls back to npx for first-run / not-yet-installed.
27
70
  */
28
71
  export function resolveBinaryPath() {
29
- // Check if we're installed globally or locally
30
- const localBin = resolve(__dirname, '../bin/gramatr-mcp.js');
31
- if (existsSync(localBin)) {
32
- return { command: 'node', args: [localBin] };
72
+ const bunBin = join(HOME, '.gramatr', 'bin', 'gramatr');
73
+ if (existsSync(bunBin)) {
74
+ return { command: bunBin, args: [] };
33
75
  }
34
- // Fallback to npx (slower but always works)
76
+ // Fallback to npx (first install, before binary is deployed)
35
77
  return { command: 'npx', args: ['-y', '@gramatr/mcp'] };
36
78
  }
37
79
  /**
@@ -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, showPrompts = false) {
416
+ if (cleanInstall) {
417
+ runCleanInstall(dryRun);
418
+ }
128
419
  const configPath = getClaudeConfigPath();
129
420
  const settingsPath = getClaudeSettingsPath();
130
421
  const markdownPath = getClaudeMarkdownPath();
@@ -132,10 +423,11 @@ export function setupClaude(dryRun = false) {
132
423
  const settings = readClaudeConfig(settingsPath);
133
424
  const currentMarkdown = existsSync(markdownPath) ? readFileSync(markdownPath, 'utf8') : '';
134
425
  const localEntry = buildClaudeMcpServerEntry();
135
- const resolvedArgs = localEntry.args?.[0]?.startsWith('~/')
136
- ? [localEntry.args[0].replace(/^~\//, `${HOME}/`)]
137
- : (localEntry.args || []);
138
- const serverEntry = { command: localEntry.command };
426
+ const resolvedCommand = localEntry.command.startsWith('~/')
427
+ ? localEntry.command.replace(/^~\//, `${HOME}/`)
428
+ : localEntry.command;
429
+ const resolvedArgs = (localEntry.args || []).map(a => a.startsWith('~/') ? a.replace(/^~\//, `${HOME}/`) : a);
430
+ const serverEntry = { command: resolvedCommand };
139
431
  if (resolvedArgs.length > 0)
140
432
  serverEntry.args = resolvedArgs;
141
433
  const managedHooks = buildClaudeHooksFile(join(HOME, '.gramatr'));
@@ -180,6 +472,8 @@ export function setupClaude(dryRun = false) {
180
472
  process.stderr.write(JSON.stringify(mergedSettings.hooks, null, 2) + '\n');
181
473
  process.stderr.write('[gramatr-mcp] Dry run — would write Claude guidance to: ' + markdownPath + '\n');
182
474
  process.stderr.write(mergedMarkdown + '\n');
475
+ if (showPrompts)
476
+ emitInstallPromptSuggestion('claude-code');
183
477
  return;
184
478
  }
185
479
  // Write back
@@ -199,9 +493,10 @@ export function setupClaude(dryRun = false) {
199
493
  process.stderr.write(` args: ${JSON.stringify(serverEntry.args)}\n`);
200
494
  }
201
495
  process.stderr.write('\n');
202
- emitInstallPromptSuggestion('claude-code');
496
+ if (showPrompts)
497
+ emitInstallPromptSuggestion('claude-code');
203
498
  }
204
- export function setupCodex(dryRun = false) {
499
+ export function setupCodex(dryRun = false, showPrompts = false) {
205
500
  const hooksPath = getCodexHooksPath();
206
501
  const configPath = getCodexConfigPath();
207
502
  const agentsPath = getCodexAgentsPath();
@@ -230,7 +525,8 @@ export function setupCodex(dryRun = false) {
230
525
  process.stderr.write(`[gramatr-mcp] Enabled Codex hooks in ${configPath}\n`);
231
526
  process.stderr.write(`[gramatr-mcp] Configured Codex guidance in ${agentsPath}\n`);
232
527
  process.stderr.write('[gramatr-mcp] Restart Codex or start a new session to pick up the change.\n');
233
- emitInstallPromptSuggestion('codex');
528
+ if (showPrompts)
529
+ emitInstallPromptSuggestion('codex');
234
530
  }
235
531
  /**
236
532
  * Generic MCP-only target setup — merges the gramatr MCP server entry into
@@ -252,32 +548,109 @@ export function setupMcpTarget(targetName, configPath, dryRun) {
252
548
  process.stderr.write(`[gramatr-mcp] Configured ${targetName} MCP server in ${configPath}\n`);
253
549
  process.stderr.write(`[gramatr-mcp] Restart ${targetName} to pick up the change.\n`);
254
550
  }
255
- export function setupClaudeDesktop(dryRun = false) {
551
+ export function setupClaudeDesktop(dryRun = false, showPrompts = false) {
256
552
  setupMcpTarget('Claude Desktop', getClaudeDesktopConfigPath(HOME), dryRun);
257
- if (!dryRun)
553
+ if (showPrompts)
258
554
  emitInstallPromptSuggestion('claude-desktop');
259
555
  }
260
- export function setupChatgptDesktop(dryRun = false) {
556
+ export function setupChatgptDesktop(dryRun = false, showPrompts = false) {
261
557
  setupMcpTarget('ChatGPT Desktop', getChatgptDesktopConfigPath(HOME), dryRun);
262
- if (!dryRun)
558
+ if (showPrompts)
263
559
  emitInstallPromptSuggestion('chatgpt-desktop');
264
560
  }
265
- export function setupCursor(dryRun = false) {
561
+ export function setupCursor(dryRun = false, showPrompts = false) {
266
562
  setupMcpTarget('Cursor', getCursorConfigPath(HOME), dryRun);
267
- if (!dryRun)
563
+ if (showPrompts)
268
564
  emitInstallPromptSuggestion('cursor');
269
565
  }
270
- export function setupWindsurf(dryRun = false) {
566
+ export function setupWindsurf(dryRun = false, showPrompts = false) {
271
567
  setupMcpTarget('Windsurf', getWindsurfConfigPath(HOME), dryRun);
272
- if (!dryRun)
568
+ if (showPrompts)
273
569
  emitInstallPromptSuggestion('windsurf');
274
570
  }
275
- export function setupVscode(dryRun = false) {
571
+ export function setupVscode(dryRun = false, showPrompts = false) {
276
572
  setupMcpTarget('VS Code', getVscodeConfigPath(HOME), dryRun);
277
- if (!dryRun)
573
+ if (showPrompts)
278
574
  emitInstallPromptSuggestion('vscode');
279
575
  }
280
- export function setupGemini(dryRun = false) {
576
+ export function getAutoDetectedTargets() {
577
+ const checks = {
578
+ claude: existsSync(join(HOME, '.claude')) || existsSync(getClaudeConfigPath()),
579
+ codex: existsSync(join(HOME, '.codex')),
580
+ gemini: existsSync(join(HOME, '.gemini')),
581
+ 'claude-desktop': existsSync(dirname(getClaudeDesktopConfigPath(HOME))),
582
+ 'chatgpt-desktop': existsSync(dirname(getChatgptDesktopConfigPath(HOME))),
583
+ cursor: existsSync(join(HOME, '.cursor')),
584
+ windsurf: existsSync(join(HOME, '.windsurf')),
585
+ vscode: existsSync(join(HOME, '.vscode')),
586
+ };
587
+ return AUTO_TARGET_ORDER.filter((target) => checks[target]);
588
+ }
589
+ export function setupAutoInstall(options = {}) {
590
+ const dryRun = options.dryRun === true;
591
+ const cleanInstall = options.cleanInstall === true;
592
+ const listOnly = options.listOnly === true;
593
+ const showPrompts = options.showPrompts === true;
594
+ const detected = getAutoDetectedTargets();
595
+ const requested = Array.isArray(options.selectedTargets) && options.selectedTargets.length > 0
596
+ ? Array.from(new Set(options.selectedTargets))
597
+ : null;
598
+ const selected = requested ?? detected;
599
+ process.stderr.write('[gramatr-mcp] auto-detect scan complete\n');
600
+ if (detected.length === 0 && !requested) {
601
+ process.stderr.write('[gramatr-mcp] No supported local clients detected.\n');
602
+ process.stderr.write('[gramatr-mcp] Install manually with: setup <target>\n');
603
+ return 0;
604
+ }
605
+ process.stderr.write(`[gramatr-mcp] Detected targets: ${detected.join(', ')}\n`);
606
+ process.stderr.write(`[gramatr-mcp] Selected targets: ${selected.join(', ')}\n`);
607
+ if (requested) {
608
+ const undetected = requested.filter((target) => !detected.includes(target));
609
+ if (undetected.length > 0) {
610
+ process.stderr.write(`[gramatr-mcp] Requested targets not detected locally (will still configure): ${undetected.join(', ')}\n`);
611
+ }
612
+ }
613
+ if (listOnly) {
614
+ process.stderr.write('[gramatr-mcp] list-only mode: no setup changes made.\n');
615
+ return selected.length;
616
+ }
617
+ if (cleanInstall) {
618
+ runCleanInstall(dryRun);
619
+ }
620
+ for (const target of selected) {
621
+ switch (target) {
622
+ case 'claude':
623
+ setupClaude(dryRun, false, showPrompts);
624
+ break;
625
+ case 'codex':
626
+ setupCodex(dryRun, showPrompts);
627
+ break;
628
+ case 'gemini':
629
+ setupGemini(dryRun, showPrompts);
630
+ break;
631
+ case 'claude-desktop':
632
+ setupClaudeDesktop(dryRun, showPrompts);
633
+ break;
634
+ case 'chatgpt-desktop':
635
+ setupChatgptDesktop(dryRun, showPrompts);
636
+ break;
637
+ case 'cursor':
638
+ setupCursor(dryRun, showPrompts);
639
+ break;
640
+ case 'windsurf':
641
+ setupWindsurf(dryRun, showPrompts);
642
+ break;
643
+ case 'vscode':
644
+ setupVscode(dryRun, showPrompts);
645
+ break;
646
+ default:
647
+ break;
648
+ }
649
+ }
650
+ process.stderr.write(`[gramatr-mcp] Auto setup completed for ${selected.length} target(s).\n`);
651
+ return selected.length;
652
+ }
653
+ export function setupGemini(dryRun = false, showPrompts = false) {
281
654
  const extensionDir = getGeminiExtensionDir(HOME);
282
655
  const manifestPath = getGeminiManifestPath(HOME);
283
656
  const hooksPath = getGeminiHooksPath(HOME);
@@ -289,6 +662,8 @@ export function setupGemini(dryRun = false) {
289
662
  process.stderr.write(JSON.stringify(manifest, null, 2) + '\n');
290
663
  process.stderr.write('[gramatr-mcp] Dry run — would write Gemini hooks to: ' + hooksPath + '\n');
291
664
  process.stderr.write(JSON.stringify(hooks, null, 2) + '\n');
665
+ if (showPrompts)
666
+ emitInstallPromptSuggestion('gemini-cli');
292
667
  return;
293
668
  }
294
669
  mkdirSync(join(extensionDir, 'hooks'), { recursive: true });
@@ -298,7 +673,8 @@ export function setupGemini(dryRun = false) {
298
673
  process.stderr.write(`[gramatr-mcp] Configured Gemini extension manifest in ${manifestPath}\n`);
299
674
  process.stderr.write(`[gramatr-mcp] Configured Gemini hooks in ${hooksPath}\n`);
300
675
  process.stderr.write('[gramatr-mcp] Restart Gemini CLI to pick up the change.\n');
301
- emitInstallPromptSuggestion('gemini-cli');
676
+ if (showPrompts)
677
+ emitInstallPromptSuggestion('gemini-cli');
302
678
  }
303
679
  export async function setupWeb(target = 'claude-web') {
304
680
  const gramatrUrl = getGramatrUrlFromEnv() || 'https://api.gramatr.com/mcp';
@@ -330,4 +706,227 @@ export async function setupWeb(target = 'claude-web') {
330
706
  process.stderr.write('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
331
707
  process.stderr.write('[gramatr-mcp] Paste the prompt block above into your custom instructions / project knowledge.\n');
332
708
  }
709
+ function addResult(items, severity, label, detail) {
710
+ items.push({ severity, label, detail });
711
+ }
712
+ function parseJson(path) {
713
+ try {
714
+ return JSON.parse(readFileSync(path, 'utf8'));
715
+ }
716
+ catch {
717
+ return null;
718
+ }
719
+ }
720
+ function hasHookCommand(config, eventName, needle) {
721
+ if (!config || typeof config !== 'object')
722
+ return false;
723
+ const hooksRoot = config.hooks;
724
+ if (!hooksRoot || typeof hooksRoot !== 'object')
725
+ return false;
726
+ const entries = hooksRoot[eventName];
727
+ if (!Array.isArray(entries))
728
+ return false;
729
+ for (const entry of entries) {
730
+ if (!entry || typeof entry !== 'object')
731
+ continue;
732
+ const commands = entry.hooks;
733
+ if (!Array.isArray(commands))
734
+ continue;
735
+ for (const command of commands) {
736
+ const value = command?.command;
737
+ if (typeof value === 'string' && value.includes(needle)) {
738
+ return true;
739
+ }
740
+ }
741
+ }
742
+ return false;
743
+ }
744
+ function readManagedBlock(path, startMarker, endMarker) {
745
+ if (!existsSync(path))
746
+ return null;
747
+ const body = readFileSync(path, 'utf8');
748
+ const pattern = new RegExp(`${escapeRegExp(startMarker)}[\\s\\S]*?${escapeRegExp(endMarker)}`, 'm');
749
+ const match = body.match(pattern);
750
+ return match ? match[0] : null;
751
+ }
752
+ function verifyClaude(items) {
753
+ const configPath = getClaudeConfigPath();
754
+ const settingsPath = getClaudeSettingsPath();
755
+ const markdownPath = getClaudeMarkdownPath();
756
+ const config = parseJson(configPath);
757
+ const gramatrServer = config?.mcpServers && typeof config.mcpServers === 'object'
758
+ ? config.mcpServers.gramatr
759
+ : null;
760
+ if (gramatrServer) {
761
+ addResult(items, 'ok', 'claude.mcp_server', `${configPath} contains mcpServers.gramatr`);
762
+ }
763
+ else {
764
+ addResult(items, 'error', 'claude.mcp_server', `${configPath} missing mcpServers.gramatr`);
765
+ }
766
+ const settings = parseJson(settingsPath);
767
+ const hasPromptHook = hasHookCommand(settings, 'UserPromptSubmit', 'hook user-prompt-submit');
768
+ const hasSessionStartHook = hasHookCommand(settings, 'SessionStart', 'hook session-start');
769
+ if (hasPromptHook && hasSessionStartHook) {
770
+ addResult(items, 'ok', 'claude.hooks', `${settingsPath} includes session-start + user-prompt-submit`);
771
+ }
772
+ else {
773
+ addResult(items, 'error', 'claude.hooks', `${settingsPath} missing required hooks (session-start=${hasSessionStartHook}, user-prompt-submit=${hasPromptHook})`);
774
+ }
775
+ const managedBlock = readManagedBlock(markdownPath, CLAUDE_BLOCK_START, CLAUDE_BLOCK_END);
776
+ if (managedBlock) {
777
+ addResult(items, 'ok', 'claude.guidance', `${markdownPath} contains managed gramatr guidance block`);
778
+ }
779
+ else {
780
+ addResult(items, 'warn', 'claude.guidance', `${markdownPath} missing managed guidance block`);
781
+ }
782
+ }
783
+ function verifyCodex(items) {
784
+ const hooksPath = getCodexHooksPath();
785
+ const configPath = getCodexConfigPath();
786
+ const agentsPath = getCodexAgentsPath();
787
+ const hooks = parseJson(hooksPath);
788
+ const hasPromptHook = hasHookCommand(hooks, 'UserPromptSubmit', 'hook user-prompt-submit');
789
+ const hasSessionStartHook = hasHookCommand(hooks, 'SessionStart', 'hook session-start');
790
+ const hasStopHook = hasHookCommand(hooks, 'Stop', 'hook stop');
791
+ if (hasPromptHook && hasSessionStartHook && hasStopHook) {
792
+ addResult(items, 'ok', 'codex.hooks', `${hooksPath} includes session-start + user-prompt-submit + stop`);
793
+ }
794
+ else {
795
+ addResult(items, 'error', 'codex.hooks', `${hooksPath} missing required hooks (session-start=${hasSessionStartHook}, user-prompt-submit=${hasPromptHook}, stop=${hasStopHook})`);
796
+ }
797
+ const configToml = existsSync(configPath) ? readFileSync(configPath, 'utf8') : '';
798
+ const hooksEnabled = /^\s*codex_hooks\s*=\s*true\s*$/m.test(configToml);
799
+ if (hooksEnabled) {
800
+ addResult(items, 'ok', 'codex.feature_flag', `${configPath} enables codex_hooks`);
801
+ }
802
+ else {
803
+ addResult(items, 'error', 'codex.feature_flag', `${configPath} missing codex_hooks = true`);
804
+ }
805
+ const managedBlock = readManagedBlock(agentsPath, CODEX_BLOCK_START, CODEX_BLOCK_END);
806
+ if (managedBlock) {
807
+ addResult(items, 'ok', 'codex.guidance', `${agentsPath} contains managed gramatr guidance block`);
808
+ }
809
+ else {
810
+ addResult(items, 'warn', 'codex.guidance', `${agentsPath} missing managed guidance block`);
811
+ }
812
+ }
813
+ function verifyJsonMcpTarget(items, label, configPath) {
814
+ const json = parseJson(configPath);
815
+ const gramatrServer = json?.mcpServers && typeof json.mcpServers === 'object'
816
+ ? json.mcpServers.gramatr
817
+ : null;
818
+ if (gramatrServer) {
819
+ addResult(items, 'ok', `${label}.mcp_server`, `${configPath} contains mcpServers.gramatr`);
820
+ }
821
+ else {
822
+ addResult(items, 'warn', `${label}.mcp_server`, `${configPath} missing mcpServers.gramatr`);
823
+ }
824
+ }
825
+ function verifyGemini(items) {
826
+ const manifestPath = getGeminiManifestPath(HOME);
827
+ const hooksPath = getGeminiHooksPath(HOME);
828
+ const manifest = parseJson(manifestPath);
829
+ const geminiServer = manifest?.mcpServers && typeof manifest.mcpServers === 'object'
830
+ ? manifest.mcpServers.gramatr
831
+ : null;
832
+ if (geminiServer) {
833
+ addResult(items, 'ok', 'gemini.manifest', `${manifestPath} contains mcpServers.gramatr`);
834
+ }
835
+ else {
836
+ addResult(items, 'warn', 'gemini.manifest', `${manifestPath} missing mcpServers.gramatr`);
837
+ }
838
+ const hooks = parseJson(hooksPath);
839
+ const hasBeforeAgent = hasHookCommand(hooks, 'BeforeAgent', 'hook user-prompt-submit');
840
+ const hasSessionStart = hasHookCommand(hooks, 'SessionStart', 'hook session-start');
841
+ if (hasBeforeAgent && hasSessionStart) {
842
+ addResult(items, 'ok', 'gemini.hooks', `${hooksPath} includes BeforeAgent + SessionStart hooks`);
843
+ }
844
+ else {
845
+ addResult(items, 'warn', 'gemini.hooks', `${hooksPath} missing expected hooks (before-agent=${hasBeforeAgent}, session-start=${hasSessionStart})`);
846
+ }
847
+ }
848
+ function verifyLocalSettings(items) {
849
+ const settingsPath = getGramatrSettingsPath();
850
+ const settings = parseJson(settingsPath);
851
+ if (!settings) {
852
+ addResult(items, 'error', 'runtime.settings', `${settingsPath} is missing or invalid JSON`);
853
+ return;
854
+ }
855
+ const hasPrincipal = Boolean(settings.principal?.name);
856
+ const hasIdentity = Boolean(settings.daidentity?.name);
857
+ if (hasPrincipal && hasIdentity) {
858
+ addResult(items, 'ok', 'runtime.settings', `${settingsPath} initialized with principal + daidentity`);
859
+ }
860
+ else {
861
+ addResult(items, 'warn', 'runtime.settings', `${settingsPath} missing expected fields (principal=${hasPrincipal}, daidentity=${hasIdentity})`);
862
+ }
863
+ }
864
+ function printPromptBlocks(target) {
865
+ if (target === 'all' || target === 'claude') {
866
+ const block = readManagedBlock(getClaudeMarkdownPath(), CLAUDE_BLOCK_START, CLAUDE_BLOCK_END);
867
+ process.stderr.write('\n━━━ Claude Managed Guidance Block ━━━\n\n');
868
+ process.stdout.write((block || '[missing managed block]\n') + '\n');
869
+ }
870
+ if (target === 'all' || target === 'codex') {
871
+ const block = readManagedBlock(getCodexAgentsPath(), CODEX_BLOCK_START, CODEX_BLOCK_END);
872
+ process.stderr.write('\n━━━ Codex Managed Guidance Block ━━━\n\n');
873
+ process.stdout.write((block || '[missing managed block]\n') + '\n');
874
+ }
875
+ }
876
+ export function verifySetupInstall(target = 'all', options = {}) {
877
+ const items = [];
878
+ verifyLocalSettings(items);
879
+ if (target === 'all' || target === 'claude')
880
+ verifyClaude(items);
881
+ if (target === 'all' || target === 'codex')
882
+ verifyCodex(items);
883
+ if (target === 'all' || target === 'claude-desktop') {
884
+ verifyJsonMcpTarget(items, 'claude-desktop', getClaudeDesktopConfigPath(HOME));
885
+ }
886
+ if (target === 'all' || target === 'chatgpt-desktop') {
887
+ verifyJsonMcpTarget(items, 'chatgpt-desktop', getChatgptDesktopConfigPath(HOME));
888
+ }
889
+ if (target === 'all' || target === 'cursor') {
890
+ verifyJsonMcpTarget(items, 'cursor', getCursorConfigPath(HOME));
891
+ }
892
+ if (target === 'all' || target === 'windsurf') {
893
+ verifyJsonMcpTarget(items, 'windsurf', getWindsurfConfigPath(HOME));
894
+ }
895
+ if (target === 'all' || target === 'vscode') {
896
+ verifyJsonMcpTarget(items, 'vscode', getVscodeConfigPath(HOME));
897
+ }
898
+ if (target === 'all' || target === 'gemini')
899
+ verifyGemini(items);
900
+ const hasError = items.some((item) => item.severity === 'error');
901
+ const hasWarn = items.some((item) => item.severity === 'warn');
902
+ if (options.json) {
903
+ process.stdout.write(JSON.stringify({
904
+ ok: !hasError,
905
+ warnings: hasWarn,
906
+ target,
907
+ checks: items,
908
+ }, null, 2) + '\n');
909
+ }
910
+ else {
911
+ process.stderr.write(`\n[gramatr-mcp] Setup verification target=${target}\n`);
912
+ for (const item of items) {
913
+ const marker = item.severity === 'ok' ? 'OK' : item.severity === 'warn' ? 'WARN' : 'ERROR';
914
+ process.stderr.write(` [${marker}] ${item.label}: ${item.detail}\n`);
915
+ }
916
+ }
917
+ if (options.showPrompts) {
918
+ printPromptBlocks(target);
919
+ }
920
+ if (hasError) {
921
+ process.stderr.write('[gramatr-mcp] Verification failed. Re-run setup for the failing target(s).\n');
922
+ return 1;
923
+ }
924
+ if (hasWarn) {
925
+ process.stderr.write('[gramatr-mcp] Verification completed with warnings.\n');
926
+ }
927
+ else {
928
+ process.stderr.write('[gramatr-mcp] Verification passed.\n');
929
+ }
930
+ return 0;
931
+ }
333
932
  //# sourceMappingURL=setup.js.map