@gramatr/mcp 0.10.14 → 0.10.17

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 (57) hide show
  1. package/dist/__mocks__/bun-sqlite.d.ts +9 -10
  2. package/dist/__mocks__/bun-sqlite.d.ts.map +1 -1
  3. package/dist/__mocks__/bun-sqlite.js +29 -15
  4. package/dist/__mocks__/bun-sqlite.js.map +1 -1
  5. package/dist/bin/gramatr-mcp.d.ts.map +1 -1
  6. package/dist/bin/gramatr-mcp.js +17 -0
  7. package/dist/bin/gramatr-mcp.js.map +1 -1
  8. package/dist/bin/login.js +1 -1
  9. package/dist/bin/login.js.map +1 -1
  10. package/dist/bin/setup-config-io.d.ts +5 -0
  11. package/dist/bin/setup-config-io.d.ts.map +1 -1
  12. package/dist/bin/setup-config-io.js +53 -0
  13. package/dist/bin/setup-config-io.js.map +1 -1
  14. package/dist/bin/setup-legacy.d.ts.map +1 -1
  15. package/dist/bin/setup-legacy.js +6 -18
  16. package/dist/bin/setup-legacy.js.map +1 -1
  17. package/dist/bin/setup.d.ts +6 -6
  18. package/dist/bin/setup.d.ts.map +1 -1
  19. package/dist/bin/setup.js +184 -194
  20. package/dist/bin/setup.js.map +1 -1
  21. package/dist/hooks/agent-gate.js +1 -1
  22. package/dist/hooks/agent-gate.js.map +1 -1
  23. package/dist/hooks/agent-verify.d.ts +1 -1
  24. package/dist/hooks/agent-verify.js +3 -3
  25. package/dist/hooks/agent-verify.js.map +1 -1
  26. package/dist/hooks/generated/schema-constants.d.ts +2 -1
  27. package/dist/hooks/generated/schema-constants.d.ts.map +1 -1
  28. package/dist/hooks/generated/schema-constants.js +3 -1
  29. package/dist/hooks/generated/schema-constants.js.map +1 -1
  30. package/dist/hooks/lib/formatting-compat.js +1 -1
  31. package/dist/hooks/lib/formatting-compat.js.map +1 -1
  32. package/dist/hooks/lib/hook-state.d.ts +2 -0
  33. package/dist/hooks/lib/hook-state.d.ts.map +1 -1
  34. package/dist/hooks/lib/hook-state.js +18 -7
  35. package/dist/hooks/lib/hook-state.js.map +1 -1
  36. package/dist/hooks/lib/intelligence.js +1 -1
  37. package/dist/hooks/lib/intelligence.js.map +1 -1
  38. package/dist/hooks/manifest.d.ts +1 -1
  39. package/dist/hooks/manifest.js +1 -1
  40. package/dist/hooks/manifest.js.map +1 -1
  41. package/dist/hooks/user-prompt-submit.d.ts.map +1 -1
  42. package/dist/hooks/user-prompt-submit.js +9 -1
  43. package/dist/hooks/user-prompt-submit.js.map +1 -1
  44. package/dist/server/hooks-listener.d.ts.map +1 -1
  45. package/dist/server/hooks-listener.js +91 -15
  46. package/dist/server/hooks-listener.js.map +1 -1
  47. package/dist/setup/generated/instruction-blocks.d.ts +11 -11
  48. package/dist/setup/generated/instruction-blocks.d.ts.map +1 -1
  49. package/dist/setup/generated/instruction-blocks.js +11 -11
  50. package/dist/setup/generated/instruction-blocks.js.map +1 -1
  51. package/dist/setup/instructions.js +3 -3
  52. package/dist/setup/integrations.d.ts +1 -0
  53. package/dist/setup/integrations.d.ts.map +1 -1
  54. package/dist/setup/integrations.js +7 -0
  55. package/dist/setup/integrations.js.map +1 -1
  56. package/package.json +2 -5
  57. package/scripts/postinstall.mjs +8 -3
package/dist/bin/setup.js CHANGED
@@ -15,32 +15,31 @@
15
15
  * gramatr-mcp setup claude Configure Claude Code
16
16
  * gramatr-mcp setup claude --dry Run without writing
17
17
  */
18
- import { readFileSync, writeFileSync, existsSync, mkdirSync, } from 'node:fs';
19
- import { join, dirname } from 'node:path';
20
- import { getGramatrUrlFromEnv } from '../config-runtime.js';
21
- import { buildClaudeHooksFile, buildClaudeMcpServerEntry, mergeManagedHooks, } from '../setup/integrations.js';
22
- import { CLAUDE_BLOCK_END, CLAUDE_BLOCK_START, CLAUDE_CODE_GUIDANCE, CODEX_BLOCK_END, CODEX_BLOCK_START, } from '../setup/instructions.js';
23
- import { getChatgptDesktopConfigPath, getClaudeDesktopConfigPath, getCursorConfigPath, getGeminiHooksPath, getGeminiManifestPath, getOpenCodeConfigPath, getVscodeConfigPath, getWindsurfConfigPath, } from '../setup/targets.js';
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
+ import { dirname, join } from "node:path";
20
+ import { CLAUDE_BLOCK_END, CLAUDE_BLOCK_START, CLAUDE_CODE_GUIDANCE, CODEX_BLOCK_END, CODEX_BLOCK_START, } from "../setup/instructions.js";
21
+ import { buildPluginHooksJson } from "../setup/integrations.js";
22
+ import { getChatgptDesktopConfigPath, getClaudeDesktopConfigPath, getCursorConfigPath, getGeminiHooksPath, getGeminiManifestPath, getOpenCodeConfigPath, getVscodeConfigPath, getWindsurfConfigPath, } from "../setup/targets.js";
24
23
  // ── Re-export everything from sub-modules so consumers keep importing from setup.js ──
25
- export { getClaudeConfigPath, getClaudeSettingsPath, getCodexHooksPath, getCodexConfigPath, getClaudeMarkdownPath, getCodexAgentsPath, getGramatrSettingsPath, readJsonFile, readClaudeConfig, escapeRegExp, upsertManagedBlock, ensureLocalSettings, parseJson, readManagedBlock, hasHookCommand, } from './setup-config-io.js';
26
- export { runCleanInstall, } from './setup-legacy.js';
27
- export { ensureCodexMcpServerConfig, emitInstallPromptSuggestion, setupCodex, setupMcpTarget, setupGemini, setupOpenCode, setupWeb, } from './setup-platforms.js';
28
- export { resolveBinaryPath, deployPlatformBinary, } from './setup-shared.js';
24
+ export { addPluginRegistration, ensureLocalSettings, escapeRegExp, getClaudeConfigPath, getClaudeMarkdownPath, getClaudeSettingsPath, getCodexAgentsPath, getCodexConfigPath, getCodexHooksPath, getGramatrPluginDir, getGramatrSettingsPath, hasHookCommand, parseJson, readClaudeConfig, readJsonFile, readManagedBlock, removeGramatrHooks, upsertManagedBlock, writeMarketplaceManifest, writePluginFiles, } from "./setup-config-io.js";
25
+ export { runCleanInstall } from "./setup-legacy.js";
26
+ export { emitInstallPromptSuggestion, ensureCodexMcpServerConfig, setupCodex, setupGemini, setupMcpTarget, setupOpenCode, setupWeb, } from "./setup-platforms.js";
27
+ export { deployPlatformBinary, resolveBinaryPath, } from "./setup-shared.js";
29
28
  // ── Local imports from sub-modules ──
30
- import { readClaudeConfig, upsertManagedBlock, ensureLocalSettings, getClaudeConfigPath, getClaudeSettingsPath, getClaudeMarkdownPath, getCodexHooksPath as getCodexHooksPathFn, getCodexConfigPath as getCodexConfigPathFn, getCodexAgentsPath, getGramatrSettingsPath as getGramatrSettingsPathFn, parseJson, readManagedBlock, hasHookCommand, HOME, } from './setup-config-io.js';
31
- import { runCleanInstall } from './setup-legacy.js';
32
- import { emitInstallPromptSuggestion, setupCodex, setupMcpTarget, setupGemini, setupOpenCode, } from './setup-platforms.js';
33
- import { deployPlatformBinary } from './setup-shared.js';
29
+ import { addPluginRegistration, ensureLocalSettings, getClaudeConfigPath, getClaudeMarkdownPath, getClaudeSettingsPath, getCodexAgentsPath, getCodexConfigPath as getCodexConfigPathFn, getCodexHooksPath as getCodexHooksPathFn, getGramatrPluginDir, getGramatrSettingsPath as getGramatrSettingsPathFn, HOME, hasHookCommand, parseJson, readClaudeConfig, readManagedBlock, removeGramatrHooks, upsertManagedBlock, writeMarketplaceManifest, writePluginFiles, } from "./setup-config-io.js";
30
+ import { runCleanInstall } from "./setup-legacy.js";
31
+ import { emitInstallPromptSuggestion, setupCodex, setupGemini, setupMcpTarget, setupOpenCode, } from "./setup-platforms.js";
32
+ import { deployPlatformBinary } from "./setup-shared.js";
34
33
  export const AUTO_TARGET_ORDER = [
35
- 'claude',
36
- 'codex',
37
- 'opencode',
38
- 'gemini',
39
- 'claude-desktop',
40
- 'chatgpt-desktop',
41
- 'cursor',
42
- 'windsurf',
43
- 'vscode',
34
+ "claude",
35
+ "codex",
36
+ "opencode",
37
+ "gemini",
38
+ "claude-desktop",
39
+ "chatgpt-desktop",
40
+ "cursor",
41
+ "windsurf",
42
+ "vscode",
44
43
  ];
45
44
  // ── setupClaude — the primary Claude Code setup ──
46
45
  export function setupClaude(dryRun = false, cleanInstall = false, showPrompts = false) {
@@ -48,124 +47,98 @@ export function setupClaude(dryRun = false, cleanInstall = false, showPrompts =
48
47
  runCleanInstall(dryRun);
49
48
  }
50
49
  deployPlatformBinary(dryRun);
51
- const configPath = getClaudeConfigPath();
52
50
  const settingsPath = getClaudeSettingsPath();
53
51
  const markdownPath = getClaudeMarkdownPath();
54
- const config = readClaudeConfig(configPath);
55
52
  const settings = readClaudeConfig(settingsPath);
56
- const currentMarkdown = existsSync(markdownPath) ? readFileSync(markdownPath, 'utf8') : '';
57
- const localEntry = buildClaudeMcpServerEntry();
58
- const resolvedCommand = localEntry.command.startsWith('~/')
59
- ? localEntry.command.replace(/^~\//, `${HOME}/`)
60
- : localEntry.command;
61
- const resolvedArgs = (localEntry.args || []).map(a => a.startsWith('~/') ? a.replace(/^~\//, `${HOME}/`) : a);
62
- const serverEntry = { command: resolvedCommand };
63
- if (resolvedArgs.length > 0)
64
- serverEntry.args = resolvedArgs;
65
- const managedHooks = buildClaudeHooksFile(join(HOME, '.gramatr'));
66
- const gramatrUrl = getGramatrUrlFromEnv() || 'https://api.gramatr.com/mcp';
67
- const mergedSettings = {
68
- ...settings,
69
- ...mergeManagedHooks(settings, managedHooks),
53
+ const currentMarkdown = existsSync(markdownPath) ? readFileSync(markdownPath, "utf8") : "";
54
+ const gramatrDir = join(HOME, ".gramatr");
55
+ const pluginDir = getGramatrPluginDir();
56
+ const pluginJson = {
57
+ name: "gramatr",
58
+ version: "0.10.17",
59
+ description: "gramatr Real-Time Intelligent Context Engineering. Hooks inject a gmtr.intelligence.contract.v2 packet into every prompt, pre-classifying requests and loading memory so Claude skips routing overhead and starts working immediately.",
60
+ author: { name: "gramatr", email: "support@gramatr.com", url: "https://gramatr.com" },
61
+ homepage: "https://gramatr.com",
62
+ repository: "https://github.com/gramatr/gramatr",
63
+ license: "MIT",
64
+ keywords: ["intelligence", "memory", "routing", "context", "mcp"],
65
+ };
66
+ const mcpJson = { gramatr: { command: "npx", args: ["-y", "@gramatr/mcp"] } };
67
+ // Strip gramatr-owned keys and hooks — plugin owns all of these now
68
+ const { statusLine: _statusLine, daidentity: _daidentity, principal: _principal, attribution: _attribution, alwaysThinkingEnabled: _alwaysThinkingEnabled, ...settingsWithoutGramatrKeys } = removeGramatrHooks(settings);
69
+ const mergedSettings = addPluginRegistration({
70
+ ...settingsWithoutGramatrKeys,
70
71
  env: {
71
- ...settings.env,
72
- GRAMATR_DIR: join(HOME, '.gramatr'),
73
- GRAMATR_URL: gramatrUrl,
72
+ ...settingsWithoutGramatrKeys.env,
73
+ GRAMATR_DIR: gramatrDir,
74
74
  PATH: `${HOME}/.gramatr/bin:/usr/local/bin:/usr/bin:/bin`,
75
75
  },
76
- statusLine: {
77
- type: 'command',
78
- command: `npx tsx ${join(HOME, '.gramatr', 'bin', 'statusline.ts')}`,
79
- },
80
- };
76
+ }, gramatrDir);
81
77
  const mergedMarkdown = upsertManagedBlock(currentMarkdown, CLAUDE_CODE_GUIDANCE, CLAUDE_BLOCK_START, CLAUDE_BLOCK_END);
82
- // Check if already configured
83
- const existing = config.mcpServers?.['gramatr'];
84
- if (existing) {
85
- const existingCmd = existing.command;
86
- const existingArgs = existing.args || [];
87
- if (existingCmd === serverEntry.command
88
- && JSON.stringify(existingArgs) === JSON.stringify(serverEntry.args || [])) {
89
- process.stderr.write('[gramatr-mcp] Claude Code MCP server already configured. Refreshing hooks and guidance.\n');
90
- }
91
- else {
92
- process.stderr.write('[gramatr-mcp] Updating existing gramatr MCP server config.\n');
93
- }
94
- }
95
- // Merge into config
96
- if (!config.mcpServers) {
97
- config.mcpServers = {};
98
- }
99
- config.mcpServers['gramatr'] = serverEntry;
100
78
  if (dryRun) {
101
- process.stderr.write('[gramatr-mcp] Dry run — would write to: ' + configPath + '\n');
102
- process.stderr.write(JSON.stringify(config.mcpServers['gramatr'], null, 2) + '\n');
103
- process.stderr.write('[gramatr-mcp] Dry run — would write Claude hooks to: ' + settingsPath + '\n');
104
- process.stderr.write(JSON.stringify(mergedSettings.hooks, null, 2) + '\n');
105
- process.stderr.write('[gramatr-mcp] Dry run — would write Claude guidance to: ' + markdownPath + '\n');
106
- process.stderr.write(mergedMarkdown + '\n');
79
+ process.stderr.write("[gramatr-mcp] Dry run — would write plugin to: " + pluginDir + "\n");
80
+ process.stderr.write("[gramatr-mcp] Dry run — would write Claude settings to: " + settingsPath + "\n");
81
+ process.stderr.write(JSON.stringify({
82
+ extraKnownMarketplaces: mergedSettings.extraKnownMarketplaces,
83
+ enabledPlugins: mergedSettings.enabledPlugins,
84
+ }, null, 2) + "\n");
85
+ process.stderr.write("[gramatr-mcp] Dry run — would write Claude guidance to: " + markdownPath + "\n");
86
+ process.stderr.write(mergedMarkdown + "\n");
107
87
  if (showPrompts)
108
- emitInstallPromptSuggestion('claude-code');
88
+ emitInstallPromptSuggestion("claude-code");
109
89
  return;
110
90
  }
111
- // Write back
112
- mkdirSync(join(HOME, '.claude'), { recursive: true });
91
+ mkdirSync(join(HOME, ".claude"), { recursive: true });
113
92
  ensureLocalSettings();
114
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
115
- writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + '\n', 'utf8');
116
- writeFileSync(markdownPath, mergedMarkdown, 'utf8');
117
- process.stderr.write(`[gramatr-mcp] Configured Claude Code MCP server in ${configPath}\n`);
118
- process.stderr.write(`[gramatr-mcp] Configured Claude hooks in ${settingsPath}\n`);
93
+ writeMarketplaceManifest(gramatrDir);
94
+ writePluginFiles(pluginDir, pluginJson, buildPluginHooksJson(), mcpJson);
95
+ writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n", "utf8");
96
+ writeFileSync(markdownPath, mergedMarkdown, "utf8");
97
+ process.stderr.write(`[gramatr-mcp] Wrote plugin files to ${pluginDir}\n`);
98
+ process.stderr.write(`[gramatr-mcp] Registered plugin in ${settingsPath}\n`);
119
99
  process.stderr.write(`[gramatr-mcp] Configured Claude guidance in ${markdownPath}\n`);
120
- process.stderr.write('[gramatr-mcp] Restart Claude Code to pick up the change.\n');
121
- // Show what was written
122
- process.stderr.write('\n mcpServers.gramatr:\n');
123
- process.stderr.write(` command: ${serverEntry.command}\n`);
124
- if ((serverEntry.args || []).length > 0) {
125
- process.stderr.write(` args: ${JSON.stringify(serverEntry.args)}\n`);
126
- }
127
- process.stderr.write('\n');
100
+ process.stderr.write("[gramatr-mcp] Restart Claude Code to pick up the change.\n");
128
101
  if (showPrompts)
129
- emitInstallPromptSuggestion('claude-code');
102
+ emitInstallPromptSuggestion("claude-code");
130
103
  }
131
104
  // ── Convenience wrappers for MCP-only targets ──
132
105
  export function setupClaudeDesktop(dryRun = false, showPrompts = false) {
133
- setupMcpTarget('Claude Desktop', getClaudeDesktopConfigPath(HOME), dryRun);
106
+ setupMcpTarget("Claude Desktop", getClaudeDesktopConfigPath(HOME), dryRun);
134
107
  if (showPrompts)
135
- emitInstallPromptSuggestion('claude-desktop');
108
+ emitInstallPromptSuggestion("claude-desktop");
136
109
  }
137
110
  export function setupChatgptDesktop(dryRun = false, showPrompts = false) {
138
- setupMcpTarget('ChatGPT Desktop', getChatgptDesktopConfigPath(HOME), dryRun);
111
+ setupMcpTarget("ChatGPT Desktop", getChatgptDesktopConfigPath(HOME), dryRun);
139
112
  if (showPrompts)
140
- emitInstallPromptSuggestion('chatgpt-desktop');
113
+ emitInstallPromptSuggestion("chatgpt-desktop");
141
114
  }
142
115
  export function setupCursor(dryRun = false, showPrompts = false) {
143
- setupMcpTarget('Cursor', getCursorConfigPath(HOME), dryRun);
116
+ setupMcpTarget("Cursor", getCursorConfigPath(HOME), dryRun);
144
117
  if (showPrompts)
145
- emitInstallPromptSuggestion('cursor');
118
+ emitInstallPromptSuggestion("cursor");
146
119
  }
147
120
  export function setupWindsurf(dryRun = false, showPrompts = false) {
148
- setupMcpTarget('Windsurf', getWindsurfConfigPath(HOME), dryRun);
121
+ setupMcpTarget("Windsurf", getWindsurfConfigPath(HOME), dryRun);
149
122
  if (showPrompts)
150
- emitInstallPromptSuggestion('windsurf');
123
+ emitInstallPromptSuggestion("windsurf");
151
124
  }
152
125
  export function setupVscode(dryRun = false, showPrompts = false) {
153
- setupMcpTarget('VS Code', getVscodeConfigPath(HOME), dryRun);
126
+ setupMcpTarget("VS Code", getVscodeConfigPath(HOME), dryRun);
154
127
  if (showPrompts)
155
- emitInstallPromptSuggestion('vscode');
128
+ emitInstallPromptSuggestion("vscode");
156
129
  }
157
130
  // ── Auto-detect + auto-install ──
158
131
  export function getAutoDetectedTargets() {
159
132
  const checks = {
160
- claude: existsSync(join(HOME, '.claude')) || existsSync(getClaudeConfigPath()),
161
- codex: existsSync(join(HOME, '.codex')),
162
- opencode: existsSync(join(HOME, '.config', 'opencode')) || existsSync('opencode.json'),
163
- gemini: existsSync(join(HOME, '.gemini')),
164
- 'claude-desktop': existsSync(dirname(getClaudeDesktopConfigPath(HOME))),
165
- 'chatgpt-desktop': existsSync(dirname(getChatgptDesktopConfigPath(HOME))),
166
- cursor: existsSync(join(HOME, '.cursor')),
167
- windsurf: existsSync(join(HOME, '.windsurf')),
168
- vscode: existsSync(join(HOME, '.vscode')),
133
+ claude: existsSync(join(HOME, ".claude")) || existsSync(getClaudeConfigPath()),
134
+ codex: existsSync(join(HOME, ".codex")),
135
+ opencode: existsSync(join(HOME, ".config", "opencode")) || existsSync("opencode.json"),
136
+ gemini: existsSync(join(HOME, ".gemini")),
137
+ "claude-desktop": existsSync(dirname(getClaudeDesktopConfigPath(HOME))),
138
+ "chatgpt-desktop": existsSync(dirname(getChatgptDesktopConfigPath(HOME))),
139
+ cursor: existsSync(join(HOME, ".cursor")),
140
+ windsurf: existsSync(join(HOME, ".windsurf")),
141
+ vscode: existsSync(join(HOME, ".vscode")),
169
142
  };
170
143
  return AUTO_TARGET_ORDER.filter((target) => checks[target]);
171
144
  }
@@ -179,22 +152,22 @@ export function setupAutoInstall(options = {}) {
179
152
  ? Array.from(new Set(options.selectedTargets))
180
153
  : null;
181
154
  const selected = requested ?? detected;
182
- process.stderr.write('[gramatr-mcp] auto-detect scan complete\n');
155
+ process.stderr.write("[gramatr-mcp] auto-detect scan complete\n");
183
156
  if (detected.length === 0 && !requested) {
184
- process.stderr.write('[gramatr-mcp] No supported local clients detected.\n');
185
- process.stderr.write('[gramatr-mcp] Install manually with: setup <target>\n');
157
+ process.stderr.write("[gramatr-mcp] No supported local clients detected.\n");
158
+ process.stderr.write("[gramatr-mcp] Install manually with: setup <target>\n");
186
159
  return 0;
187
160
  }
188
- process.stderr.write(`[gramatr-mcp] Detected targets: ${detected.join(', ')}\n`);
189
- process.stderr.write(`[gramatr-mcp] Selected targets: ${selected.join(', ')}\n`);
161
+ process.stderr.write(`[gramatr-mcp] Detected targets: ${detected.join(", ")}\n`);
162
+ process.stderr.write(`[gramatr-mcp] Selected targets: ${selected.join(", ")}\n`);
190
163
  if (requested) {
191
164
  const undetected = requested.filter((target) => !detected.includes(target));
192
165
  if (undetected.length > 0) {
193
- process.stderr.write(`[gramatr-mcp] Requested targets not detected locally (will still configure): ${undetected.join(', ')}\n`);
166
+ process.stderr.write(`[gramatr-mcp] Requested targets not detected locally (will still configure): ${undetected.join(", ")}\n`);
194
167
  }
195
168
  }
196
169
  if (listOnly) {
197
- process.stderr.write('[gramatr-mcp] list-only mode: no setup changes made.\n');
170
+ process.stderr.write("[gramatr-mcp] list-only mode: no setup changes made.\n");
198
171
  return selected.length;
199
172
  }
200
173
  if (cleanInstall) {
@@ -202,31 +175,31 @@ export function setupAutoInstall(options = {}) {
202
175
  }
203
176
  for (const target of selected) {
204
177
  switch (target) {
205
- case 'claude':
178
+ case "claude":
206
179
  setupClaude(dryRun, false, showPrompts);
207
180
  break;
208
- case 'codex':
181
+ case "codex":
209
182
  setupCodex(dryRun, showPrompts);
210
183
  break;
211
- case 'opencode':
184
+ case "opencode":
212
185
  setupOpenCode(dryRun, showPrompts);
213
186
  break;
214
- case 'gemini':
187
+ case "gemini":
215
188
  setupGemini(dryRun, showPrompts);
216
189
  break;
217
- case 'claude-desktop':
190
+ case "claude-desktop":
218
191
  setupClaudeDesktop(dryRun, showPrompts);
219
192
  break;
220
- case 'chatgpt-desktop':
193
+ case "chatgpt-desktop":
221
194
  setupChatgptDesktop(dryRun, showPrompts);
222
195
  break;
223
- case 'cursor':
196
+ case "cursor":
224
197
  setupCursor(dryRun, showPrompts);
225
198
  break;
226
- case 'windsurf':
199
+ case "windsurf":
227
200
  setupWindsurf(dryRun, showPrompts);
228
201
  break;
229
- case 'vscode':
202
+ case "vscode":
230
203
  setupVscode(dryRun, showPrompts);
231
204
  break;
232
205
  default:
@@ -241,34 +214,52 @@ function addResult(items, severity, label, detail) {
241
214
  items.push({ severity, label, detail });
242
215
  }
243
216
  function verifyClaude(items) {
244
- const configPath = getClaudeConfigPath();
245
217
  const settingsPath = getClaudeSettingsPath();
246
218
  const markdownPath = getClaudeMarkdownPath();
247
- const config = parseJson(configPath);
248
- const gramatrServer = config?.mcpServers && typeof config.mcpServers === 'object'
249
- ? config.mcpServers.gramatr
250
- : null;
251
- if (gramatrServer) {
252
- addResult(items, 'ok', 'claude.mcp_server', `${configPath} contains mcpServers.gramatr`);
219
+ const settings = parseJson(settingsPath);
220
+ const enabledPlugins = settings?.enabledPlugins;
221
+ const pluginEnabled = enabledPlugins && enabledPlugins["gramatr@gramatr"] === true;
222
+ if (pluginEnabled) {
223
+ addResult(items, "ok", "claude.plugin", `${settingsPath} has enabledPlugins['gramatr@gramatr']`);
253
224
  }
254
225
  else {
255
- addResult(items, 'error', 'claude.mcp_server', `${configPath} missing mcpServers.gramatr`);
226
+ addResult(items, "error", "claude.plugin", `${settingsPath} missing enabledPlugins['gramatr@gramatr'] — run: npx @gramatr/mcp setup claude`);
256
227
  }
257
- const settings = parseJson(settingsPath);
258
- const hasPromptHook = hasHookCommand(settings, 'UserPromptSubmit', 'hook user-prompt-submit');
259
- const hasSessionStartHook = hasHookCommand(settings, 'SessionStart', 'hook session-start');
228
+ // Check that the gramatr marketplace is registered — without this, Claude Code
229
+ // silently fails to install gramatr@gramatr even when enabledPlugins is set.
230
+ const extraMarketplaces = settings?.extraKnownMarketplaces;
231
+ const marketplaceRegistered = extraMarketplaces && extraMarketplaces["gramatr"];
232
+ if (marketplaceRegistered) {
233
+ addResult(items, "ok", "claude.marketplace", `${settingsPath} has extraKnownMarketplaces['gramatr']`);
234
+ }
235
+ else {
236
+ addResult(items, "error", "claude.marketplace", `${settingsPath} missing extraKnownMarketplaces['gramatr'] — plugin will silently fail to install. Run: npx @gramatr/mcp setup claude`);
237
+ }
238
+ // Check that plugin files exist on disk
239
+ const pluginJsonPath = join(getGramatrPluginDir(), ".claude-plugin", "plugin.json");
240
+ if (existsSync(pluginJsonPath)) {
241
+ addResult(items, "ok", "claude.plugin_files", `Plugin files present at ${getGramatrPluginDir()}`);
242
+ }
243
+ else {
244
+ addResult(items, "error", "claude.plugin_files", `Plugin files missing at ${getGramatrPluginDir()} — run: npx @gramatr/mcp setup claude`);
245
+ }
246
+ const pluginDir = getGramatrPluginDir();
247
+ const pluginHooksPath = join(pluginDir, "hooks", "hooks.json");
248
+ const pluginHooks = parseJson(pluginHooksPath);
249
+ const hasPromptHook = hasHookCommand(pluginHooks, "UserPromptSubmit", "hook user-prompt-submit");
250
+ const hasSessionStartHook = hasHookCommand(pluginHooks, "SessionStart", "hook session-start");
260
251
  if (hasPromptHook && hasSessionStartHook) {
261
- addResult(items, 'ok', 'claude.hooks', `${settingsPath} includes session-start + user-prompt-submit`);
252
+ addResult(items, "ok", "claude.hooks", `${pluginHooksPath} includes session-start + user-prompt-submit`);
262
253
  }
263
254
  else {
264
- addResult(items, 'error', 'claude.hooks', `${settingsPath} missing required hooks (session-start=${hasSessionStartHook}, user-prompt-submit=${hasPromptHook})`);
255
+ addResult(items, "error", "claude.hooks", `${pluginHooksPath} missing required hooks (session-start=${hasSessionStartHook}, user-prompt-submit=${hasPromptHook})`);
265
256
  }
266
257
  const managedBlock = readManagedBlock(markdownPath, CLAUDE_BLOCK_START, CLAUDE_BLOCK_END);
267
258
  if (managedBlock) {
268
- addResult(items, 'ok', 'claude.guidance', `${markdownPath} contains managed gramatr guidance block`);
259
+ addResult(items, "ok", "claude.guidance", `${markdownPath} contains managed gramatr guidance block`);
269
260
  }
270
261
  else {
271
- addResult(items, 'warn', 'claude.guidance', `${markdownPath} missing managed guidance block`);
262
+ addResult(items, "warn", "claude.guidance", `${markdownPath} missing managed guidance block`);
272
263
  }
273
264
  }
274
265
  function verifyCodex(items) {
@@ -276,145 +267,144 @@ function verifyCodex(items) {
276
267
  const configPath = getCodexConfigPathFn();
277
268
  const agentsPath = getCodexAgentsPath();
278
269
  const hooks = parseJson(hooksPath);
279
- const hasPromptHook = hasHookCommand(hooks, 'UserPromptSubmit', 'hook user-prompt-submit');
280
- const hasSessionStartHook = hasHookCommand(hooks, 'SessionStart', 'hook session-start');
281
- const hasStopHook = hasHookCommand(hooks, 'Stop', 'hook stop');
270
+ const hasPromptHook = hasHookCommand(hooks, "UserPromptSubmit", "hook user-prompt-submit");
271
+ const hasSessionStartHook = hasHookCommand(hooks, "SessionStart", "hook session-start");
272
+ const hasStopHook = hasHookCommand(hooks, "Stop", "hook stop");
282
273
  if (hasPromptHook && hasSessionStartHook && hasStopHook) {
283
- addResult(items, 'ok', 'codex.hooks', `${hooksPath} includes session-start + user-prompt-submit + stop`);
274
+ addResult(items, "ok", "codex.hooks", `${hooksPath} includes session-start + user-prompt-submit + stop`);
284
275
  }
285
276
  else {
286
- addResult(items, 'error', 'codex.hooks', `${hooksPath} missing required hooks (session-start=${hasSessionStartHook}, user-prompt-submit=${hasPromptHook}, stop=${hasStopHook})`);
277
+ addResult(items, "error", "codex.hooks", `${hooksPath} missing required hooks (session-start=${hasSessionStartHook}, user-prompt-submit=${hasPromptHook}, stop=${hasStopHook})`);
287
278
  }
288
- const configToml = existsSync(configPath) ? readFileSync(configPath, 'utf8') : '';
279
+ const configToml = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
289
280
  const hooksEnabled = /^\s*codex_hooks\s*=\s*true\s*$/m.test(configToml);
290
281
  if (hooksEnabled) {
291
- addResult(items, 'ok', 'codex.feature_flag', `${configPath} enables codex_hooks`);
282
+ addResult(items, "ok", "codex.feature_flag", `${configPath} enables codex_hooks`);
292
283
  }
293
284
  else {
294
- addResult(items, 'error', 'codex.feature_flag', `${configPath} missing codex_hooks = true`);
285
+ addResult(items, "error", "codex.feature_flag", `${configPath} missing codex_hooks = true`);
295
286
  }
296
- const hasMcpServer = /^\[mcp_servers\.gramatr\]\s*$/m.test(configToml)
297
- && /^\s*command\s*=\s*.+$/m.test(configToml);
287
+ const hasMcpServer = /^\[mcp_servers\.gramatr\]\s*$/m.test(configToml) && /^\s*command\s*=\s*.+$/m.test(configToml);
298
288
  if (hasMcpServer) {
299
- addResult(items, 'ok', 'codex.mcp_server', `${configPath} contains mcp_servers.gramatr`);
289
+ addResult(items, "ok", "codex.mcp_server", `${configPath} contains mcp_servers.gramatr`);
300
290
  }
301
291
  else {
302
- addResult(items, 'error', 'codex.mcp_server', `${configPath} missing mcp_servers.gramatr`);
292
+ addResult(items, "error", "codex.mcp_server", `${configPath} missing mcp_servers.gramatr`);
303
293
  }
304
294
  const managedBlock = readManagedBlock(agentsPath, CODEX_BLOCK_START, CODEX_BLOCK_END);
305
295
  if (managedBlock) {
306
- addResult(items, 'ok', 'codex.guidance', `${agentsPath} contains managed gramatr guidance block`);
296
+ addResult(items, "ok", "codex.guidance", `${agentsPath} contains managed gramatr guidance block`);
307
297
  }
308
298
  else {
309
- addResult(items, 'warn', 'codex.guidance', `${agentsPath} missing managed guidance block`);
299
+ addResult(items, "warn", "codex.guidance", `${agentsPath} missing managed guidance block`);
310
300
  }
311
301
  }
312
302
  function verifyJsonMcpTarget(items, label, configPath) {
313
303
  const json = parseJson(configPath);
314
- const gramatrServer = json?.mcpServers && typeof json.mcpServers === 'object'
304
+ const gramatrServer = json?.mcpServers && typeof json.mcpServers === "object"
315
305
  ? json.mcpServers.gramatr
316
306
  : null;
317
307
  if (gramatrServer) {
318
- addResult(items, 'ok', `${label}.mcp_server`, `${configPath} contains mcpServers.gramatr`);
308
+ addResult(items, "ok", `${label}.mcp_server`, `${configPath} contains mcpServers.gramatr`);
319
309
  }
320
310
  else {
321
- addResult(items, 'warn', `${label}.mcp_server`, `${configPath} missing mcpServers.gramatr`);
311
+ addResult(items, "warn", `${label}.mcp_server`, `${configPath} missing mcpServers.gramatr`);
322
312
  }
323
313
  }
324
314
  function verifyOpenCode(items) {
325
- verifyJsonMcpTarget(items, 'opencode', getOpenCodeConfigPath(HOME));
315
+ verifyJsonMcpTarget(items, "opencode", getOpenCodeConfigPath(HOME));
326
316
  }
327
317
  function verifyGemini(items) {
328
318
  const manifestPath = getGeminiManifestPath(HOME);
329
319
  const hooksPath = getGeminiHooksPath(HOME);
330
320
  const manifest = parseJson(manifestPath);
331
- const geminiServer = manifest?.mcpServers && typeof manifest.mcpServers === 'object'
321
+ const geminiServer = manifest?.mcpServers && typeof manifest.mcpServers === "object"
332
322
  ? manifest.mcpServers.gramatr
333
323
  : null;
334
324
  if (geminiServer) {
335
- addResult(items, 'ok', 'gemini.manifest', `${manifestPath} contains mcpServers.gramatr`);
325
+ addResult(items, "ok", "gemini.manifest", `${manifestPath} contains mcpServers.gramatr`);
336
326
  }
337
327
  else {
338
- addResult(items, 'warn', 'gemini.manifest', `${manifestPath} missing mcpServers.gramatr`);
328
+ addResult(items, "warn", "gemini.manifest", `${manifestPath} missing mcpServers.gramatr`);
339
329
  }
340
330
  const hooks = parseJson(hooksPath);
341
- const hasBeforeAgent = hasHookCommand(hooks, 'BeforeAgent', 'hook user-prompt-submit');
342
- const hasSessionStart = hasHookCommand(hooks, 'SessionStart', 'hook session-start');
331
+ const hasBeforeAgent = hasHookCommand(hooks, "BeforeAgent", "hook user-prompt-submit");
332
+ const hasSessionStart = hasHookCommand(hooks, "SessionStart", "hook session-start");
343
333
  if (hasBeforeAgent && hasSessionStart) {
344
- addResult(items, 'ok', 'gemini.hooks', `${hooksPath} includes BeforeAgent + SessionStart hooks`);
334
+ addResult(items, "ok", "gemini.hooks", `${hooksPath} includes BeforeAgent + SessionStart hooks`);
345
335
  }
346
336
  else {
347
- addResult(items, 'warn', 'gemini.hooks', `${hooksPath} missing expected hooks (before-agent=${hasBeforeAgent}, session-start=${hasSessionStart})`);
337
+ addResult(items, "warn", "gemini.hooks", `${hooksPath} missing expected hooks (before-agent=${hasBeforeAgent}, session-start=${hasSessionStart})`);
348
338
  }
349
339
  }
350
340
  function verifyLocalSettings(items) {
351
341
  const settingsPath = getGramatrSettingsPathFn();
352
342
  const settings = parseJson(settingsPath);
353
343
  if (!settings) {
354
- addResult(items, 'error', 'runtime.settings', `${settingsPath} is missing or invalid JSON`);
344
+ addResult(items, "error", "runtime.settings", `${settingsPath} is missing or invalid JSON`);
355
345
  return;
356
346
  }
357
347
  const hasPrincipal = Boolean(settings.principal?.name);
358
348
  const hasIdentity = Boolean(settings.daidentity?.name);
359
349
  if (hasPrincipal && hasIdentity) {
360
- addResult(items, 'ok', 'runtime.settings', `${settingsPath} initialized with principal + daidentity`);
350
+ addResult(items, "ok", "runtime.settings", `${settingsPath} initialized with principal + daidentity`);
361
351
  }
362
352
  else {
363
- addResult(items, 'warn', 'runtime.settings', `${settingsPath} missing expected fields (principal=${hasPrincipal}, daidentity=${hasIdentity})`);
353
+ addResult(items, "warn", "runtime.settings", `${settingsPath} missing expected fields (principal=${hasPrincipal}, daidentity=${hasIdentity})`);
364
354
  }
365
355
  }
366
356
  function printPromptBlocks(target) {
367
- if (target === 'all' || target === 'claude') {
357
+ if (target === "all" || target === "claude") {
368
358
  const block = readManagedBlock(getClaudeMarkdownPath(), CLAUDE_BLOCK_START, CLAUDE_BLOCK_END);
369
- process.stderr.write('\n━━━ Claude Managed Guidance Block ━━━\n\n');
370
- process.stdout.write((block || '[missing managed block]\n') + '\n');
359
+ process.stderr.write("\n━━━ Claude Managed Guidance Block ━━━\n\n");
360
+ process.stdout.write((block || "[missing managed block]\n") + "\n");
371
361
  }
372
- if (target === 'all' || target === 'codex') {
362
+ if (target === "all" || target === "codex") {
373
363
  const block = readManagedBlock(getCodexAgentsPath(), CODEX_BLOCK_START, CODEX_BLOCK_END);
374
- process.stderr.write('\n━━━ Codex Managed Guidance Block ━━━\n\n');
375
- process.stdout.write((block || '[missing managed block]\n') + '\n');
364
+ process.stderr.write("\n━━━ Codex Managed Guidance Block ━━━\n\n");
365
+ process.stdout.write((block || "[missing managed block]\n") + "\n");
376
366
  }
377
367
  }
378
- export function verifySetupInstall(target = 'all', options = {}) {
368
+ export function verifySetupInstall(target = "all", options = {}) {
379
369
  const items = [];
380
370
  verifyLocalSettings(items);
381
- if (target === 'all' || target === 'claude')
371
+ if (target === "all" || target === "claude")
382
372
  verifyClaude(items);
383
- if (target === 'all' || target === 'codex')
373
+ if (target === "all" || target === "codex")
384
374
  verifyCodex(items);
385
- if (target === 'all' || target === 'opencode')
375
+ if (target === "all" || target === "opencode")
386
376
  verifyOpenCode(items);
387
- if (target === 'all' || target === 'claude-desktop') {
388
- verifyJsonMcpTarget(items, 'claude-desktop', getClaudeDesktopConfigPath(HOME));
377
+ if (target === "all" || target === "claude-desktop") {
378
+ verifyJsonMcpTarget(items, "claude-desktop", getClaudeDesktopConfigPath(HOME));
389
379
  }
390
- if (target === 'all' || target === 'chatgpt-desktop') {
391
- verifyJsonMcpTarget(items, 'chatgpt-desktop', getChatgptDesktopConfigPath(HOME));
380
+ if (target === "all" || target === "chatgpt-desktop") {
381
+ verifyJsonMcpTarget(items, "chatgpt-desktop", getChatgptDesktopConfigPath(HOME));
392
382
  }
393
- if (target === 'all' || target === 'cursor') {
394
- verifyJsonMcpTarget(items, 'cursor', getCursorConfigPath(HOME));
383
+ if (target === "all" || target === "cursor") {
384
+ verifyJsonMcpTarget(items, "cursor", getCursorConfigPath(HOME));
395
385
  }
396
- if (target === 'all' || target === 'windsurf') {
397
- verifyJsonMcpTarget(items, 'windsurf', getWindsurfConfigPath(HOME));
386
+ if (target === "all" || target === "windsurf") {
387
+ verifyJsonMcpTarget(items, "windsurf", getWindsurfConfigPath(HOME));
398
388
  }
399
- if (target === 'all' || target === 'vscode') {
400
- verifyJsonMcpTarget(items, 'vscode', getVscodeConfigPath(HOME));
389
+ if (target === "all" || target === "vscode") {
390
+ verifyJsonMcpTarget(items, "vscode", getVscodeConfigPath(HOME));
401
391
  }
402
- if (target === 'all' || target === 'gemini')
392
+ if (target === "all" || target === "gemini")
403
393
  verifyGemini(items);
404
- const hasError = items.some((item) => item.severity === 'error');
405
- const hasWarn = items.some((item) => item.severity === 'warn');
394
+ const hasError = items.some((item) => item.severity === "error");
395
+ const hasWarn = items.some((item) => item.severity === "warn");
406
396
  if (options.json) {
407
397
  process.stdout.write(JSON.stringify({
408
398
  ok: !hasError,
409
399
  warnings: hasWarn,
410
400
  target,
411
401
  checks: items,
412
- }, null, 2) + '\n');
402
+ }, null, 2) + "\n");
413
403
  }
414
404
  else {
415
405
  process.stderr.write(`\n[gramatr-mcp] Setup verification target=${target}\n`);
416
406
  for (const item of items) {
417
- const marker = item.severity === 'ok' ? 'OK' : item.severity === 'warn' ? 'WARN' : 'ERROR';
407
+ const marker = item.severity === "ok" ? "OK" : item.severity === "warn" ? "WARN" : "ERROR";
418
408
  process.stderr.write(` [${marker}] ${item.label}: ${item.detail}\n`);
419
409
  }
420
410
  }
@@ -422,14 +412,14 @@ export function verifySetupInstall(target = 'all', options = {}) {
422
412
  printPromptBlocks(target);
423
413
  }
424
414
  if (hasError) {
425
- process.stderr.write('[gramatr-mcp] Verification failed. Re-run setup for the failing target(s).\n');
415
+ process.stderr.write("[gramatr-mcp] Verification failed. Re-run setup for the failing target(s).\n");
426
416
  return 1;
427
417
  }
428
418
  if (hasWarn) {
429
- process.stderr.write('[gramatr-mcp] Verification completed with warnings.\n');
419
+ process.stderr.write("[gramatr-mcp] Verification completed with warnings.\n");
430
420
  }
431
421
  else {
432
- process.stderr.write('[gramatr-mcp] Verification passed.\n');
422
+ process.stderr.write("[gramatr-mcp] Verification passed.\n");
433
423
  }
434
424
  return 0;
435
425
  }