gramatr 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CLAUDE.md +18 -0
  2. package/README.md +78 -0
  3. package/bin/clean-legacy-install.ts +28 -0
  4. package/bin/get-token.py +3 -0
  5. package/bin/gmtr-login.ts +547 -0
  6. package/bin/gramatr.js +33 -0
  7. package/bin/gramatr.ts +248 -0
  8. package/bin/install.ts +756 -0
  9. package/bin/render-claude-hooks.ts +16 -0
  10. package/bin/statusline.ts +437 -0
  11. package/bin/uninstall.ts +289 -0
  12. package/bin/version-sync.ts +46 -0
  13. package/codex/README.md +28 -0
  14. package/codex/hooks/session-start.ts +73 -0
  15. package/codex/hooks/stop.ts +34 -0
  16. package/codex/hooks/user-prompt-submit.ts +76 -0
  17. package/codex/install.ts +99 -0
  18. package/codex/lib/codex-hook-utils.ts +48 -0
  19. package/codex/lib/codex-install-utils.ts +123 -0
  20. package/core/feedback.ts +55 -0
  21. package/core/formatting.ts +167 -0
  22. package/core/install.ts +114 -0
  23. package/core/installer-cli.ts +122 -0
  24. package/core/migration.ts +244 -0
  25. package/core/routing.ts +98 -0
  26. package/core/session.ts +202 -0
  27. package/core/targets.ts +292 -0
  28. package/core/types.ts +178 -0
  29. package/core/version.ts +2 -0
  30. package/gemini/README.md +95 -0
  31. package/gemini/hooks/session-start.ts +72 -0
  32. package/gemini/hooks/stop.ts +30 -0
  33. package/gemini/hooks/user-prompt-submit.ts +74 -0
  34. package/gemini/install.ts +272 -0
  35. package/gemini/lib/gemini-hook-utils.ts +63 -0
  36. package/gemini/lib/gemini-install-utils.ts +169 -0
  37. package/hooks/GMTRPromptEnricher.hook.ts +650 -0
  38. package/hooks/GMTRRatingCapture.hook.ts +198 -0
  39. package/hooks/GMTRSecurityValidator.hook.ts +399 -0
  40. package/hooks/GMTRToolTracker.hook.ts +181 -0
  41. package/hooks/StopOrchestrator.hook.ts +78 -0
  42. package/hooks/gmtr-tool-tracker-utils.ts +105 -0
  43. package/hooks/lib/gmtr-hook-utils.ts +771 -0
  44. package/hooks/lib/identity.ts +227 -0
  45. package/hooks/lib/notify.ts +46 -0
  46. package/hooks/lib/paths.ts +104 -0
  47. package/hooks/lib/transcript-parser.ts +452 -0
  48. package/hooks/session-end.hook.ts +168 -0
  49. package/hooks/session-start.hook.ts +490 -0
  50. package/package.json +54 -0
@@ -0,0 +1,490 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * session-start.hook.ts — gramatr SessionStart Hook
4
+ *
5
+ * Replaces session-start.sh (529 lines). Initializes project context,
6
+ * registers session with gramatr server, displays banner and restore info.
7
+ *
8
+ * TRIGGER: SessionStart
9
+ * OUTPUT: stderr (user display), stdout (Claude context via current-project-context.json)
10
+ *
11
+ * ZERO external CLI dependencies — no jq, sed, curl, awk.
12
+ */
13
+
14
+ import { spawn } from 'child_process';
15
+ import { existsSync, readFileSync, mkdirSync } from 'fs';
16
+ import { join, basename } from 'path';
17
+ import {
18
+ type GmtrConfig,
19
+ readHookInput,
20
+ getGitContext,
21
+ readGmtrConfig,
22
+ writeGmtrConfig,
23
+ checkServerHealth,
24
+ log,
25
+ now,
26
+ } from './lib/gmtr-hook-utils.ts';
27
+ import {
28
+ buildGitProjectContextPayload,
29
+ buildNonGitProjectContextPayload,
30
+ normalizeSessionStartResponse,
31
+ prepareProjectSessionState,
32
+ startRemoteSession,
33
+ writeCurrentProjectContextFile,
34
+ persistSessionRegistration,
35
+ } from '../core/session.ts';
36
+
37
+ // ── stdout (Claude context injection) ──
38
+ // Claude Code captures stdout from SessionStart hooks and injects it as context.
39
+ // stderr (via log()) is for terminal display only.
40
+
41
+ function emitStdout(msg: string): void {
42
+ process.stdout.write(msg + '\n');
43
+ }
44
+
45
+ /**
46
+ * Write structured context to stdout so Claude receives it as injected context.
47
+ * This is the critical "last mile" — without this, Claude never sees session continuity data.
48
+ */
49
+ function emitClaudeContext(opts: {
50
+ projectName: string;
51
+ projectId: string;
52
+ branch: string;
53
+ sessionId: string;
54
+ interactionId: string | null;
55
+ resumed: boolean;
56
+ handoffContext: string | null;
57
+ projectEntityId: string | null;
58
+ config: GmtrConfig | null;
59
+ }): void {
60
+ const lines: string[] = [];
61
+
62
+ lines.push('grāmatr Session Context (Auto-loaded at Session Start)');
63
+ lines.push('');
64
+
65
+ // Project identity
66
+ lines.push(`Project: ${opts.projectName} (${opts.projectId})`);
67
+ lines.push(`Branch: ${opts.branch}`);
68
+ if (opts.interactionId) {
69
+ lines.push(`Interaction: ${opts.interactionId}${opts.resumed ? ' (resumed)' : ' (new)'}`);
70
+ }
71
+ lines.push('');
72
+
73
+ // Session continuity — the critical piece
74
+ if (opts.resumed && opts.handoffContext) {
75
+ lines.push('## Previous Session Handoff');
76
+ lines.push('');
77
+ lines.push(opts.handoffContext);
78
+ lines.push('');
79
+ lines.push('Present this context to the user automatically — they expect seamless session continuity.');
80
+ lines.push('Query gramatr memory (search_semantic) to enrich this handoff with additional details if needed.');
81
+ lines.push('');
82
+ } else if (opts.resumed) {
83
+ // Resumed but no handoff content — fall back to config data
84
+ lines.push('## Session Resumed (no handoff context available)');
85
+ lines.push('');
86
+ lines.push('This session resumed an existing interaction but the previous session did not save handoff context.');
87
+ lines.push('Query gramatr memory (search_semantic) for recent session activity on this project.');
88
+ lines.push('');
89
+ }
90
+
91
+ // Last compact context from .gmtr/settings.json
92
+ const compact = opts.config?.last_compact;
93
+ if (compact?.summary) {
94
+ lines.push('## Last Session Summary (from local cache)');
95
+ lines.push('');
96
+ lines.push(compact.summary);
97
+ lines.push('');
98
+
99
+ if (compact.metadata?.files_changed?.length) {
100
+ lines.push('Files modified: ' + compact.metadata.files_changed.join(', '));
101
+ }
102
+ if (compact.metadata?.commits?.length) {
103
+ lines.push('Commits: ' + compact.metadata.commits.join('; '));
104
+ }
105
+ if (compact.metadata?.files_changed?.length || compact.metadata?.commits?.length) {
106
+ lines.push('');
107
+ }
108
+ }
109
+
110
+ // Recent commits file
111
+ const home = process.env.HOME || '';
112
+ const lastCommitsFile = join(home, '.claude', 'last-session-commits.txt');
113
+ if (existsSync(lastCommitsFile)) {
114
+ try {
115
+ const commits = readFileSync(lastCommitsFile, 'utf8').trim();
116
+ if (commits) {
117
+ lines.push('## Recent Commits (from last session)');
118
+ lines.push('');
119
+ const commitLines = commits.split('\n').slice(0, 5);
120
+ for (const cl of commitLines) {
121
+ lines.push(` ${cl}`);
122
+ }
123
+ lines.push('');
124
+ }
125
+ } catch {
126
+ // Silent — non-critical
127
+ }
128
+ }
129
+
130
+ // Session stats
131
+ const stats = opts.config?.continuity_stats;
132
+ if (stats?.total_sessions && stats.total_sessions > 1) {
133
+ lines.push(`Session #${stats.total_sessions} on this project.`);
134
+ lines.push('');
135
+ }
136
+
137
+ // Only emit if we have meaningful content beyond the header
138
+ if (lines.length > 3) {
139
+ for (const line of lines) {
140
+ emitStdout(line);
141
+ }
142
+ }
143
+ }
144
+
145
+ // ── Banner ──
146
+
147
+ function displayBanner(): void {
148
+ const CYAN = '\x1b[1;36m';
149
+ const WHITE = '\x1b[1;37m';
150
+ const DIM = '\x1b[0;90m';
151
+ const RESET = '\x1b[0m';
152
+
153
+ log('');
154
+ log(`${CYAN}\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e${RESET}`);
155
+ log(`${CYAN}\u2502${RESET} ${CYAN}\u2502${RESET}`);
156
+ log(`${CYAN}\u2502${RESET} ${CYAN} __ _ ${WHITE} _ __ __ _ _ __ ___ __ _${CYAN}| |_ _ __${RESET} ${CYAN}\u2502${RESET}`);
157
+ log(`${CYAN}\u2502${RESET} ${CYAN}/ _\` |${WHITE}| '__/ _\` | '_ \` _ \\ / _\` |${CYAN}| __| '__|${RESET} ${CYAN}\u2502${RESET}`);
158
+ log(`${CYAN}\u2502${RESET} ${CYAN}| (_| |${WHITE}| | | (_| | | | | | | (_| |${CYAN}| |_| |${RESET} ${CYAN}\u2502${RESET}`);
159
+ log(`${CYAN}\u2502${RESET} ${CYAN} \\__, |${WHITE}|_| \\__,_|_| |_| |_|\\__,_|${CYAN} \\__|_|${RESET} ${CYAN}\u2502${RESET}`);
160
+ log(`${CYAN}\u2502${RESET} ${CYAN} |___/${RESET} ${CYAN}\u2502${RESET}`);
161
+ log(`${CYAN}\u2502${RESET} ${CYAN}\u2502${RESET}`);
162
+ log(`${CYAN}\u2502${RESET} ${WHITE}Your cross-agent AI brain${RESET} ${CYAN}\u2502${RESET}`);
163
+ log(`${CYAN}\u2502${RESET} ${DIM}\u00a9 2026 gr\u0101matr, LLC. All rights reserved.${RESET} ${CYAN}\u2502${RESET}`);
164
+ log(`${CYAN}\u2502${RESET} ${CYAN}\u2502${RESET}`);
165
+ log(`${CYAN}\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f${RESET}`);
166
+ log('');
167
+ }
168
+
169
+ // ── Sync Ratings (background, non-blocking) ──
170
+
171
+ function syncRatingsInBackground(): void {
172
+ const syncScript = join(process.env.GMTR_DIR || join(process.env.HOME || '', 'gmtr-client'), 'hooks', 'sync-ratings.hook.ts');
173
+ if (existsSync(syncScript)) {
174
+ try {
175
+ const child = spawn('bun', ['run', syncScript], {
176
+ detached: true,
177
+ stdio: 'ignore',
178
+ });
179
+ child.unref();
180
+ } catch {
181
+ // Silent failure — non-critical
182
+ }
183
+ }
184
+ }
185
+
186
+ // ── Main ──
187
+
188
+ async function main(): Promise<void> {
189
+ try {
190
+ // Redirect all output to stderr so user sees it
191
+ // (stdout is captured by Claude Code for context)
192
+
193
+ // Display banner
194
+ displayBanner();
195
+
196
+ // Read hook input
197
+ const input = await readHookInput();
198
+ const sessionId = input.session_id || 'unknown';
199
+ const transcriptPath = input.transcript_path || '';
200
+ const cwd = input.cwd || process.cwd();
201
+
202
+ // Health check
203
+ log('Checking gramatr server health...');
204
+ const health = await checkServerHealth();
205
+ if (health.healthy) {
206
+ log('gramatr server: HEALTHY');
207
+ log(` Response: ${health.detail}`);
208
+ } else {
209
+ log(`gramatr server: UNHEALTHY (${health.detail})`);
210
+ log(' WARNING: Memory operations may fail');
211
+ }
212
+
213
+ // Sync ratings in background
214
+ syncRatingsInBackground();
215
+
216
+ // Check git context
217
+ log('');
218
+ log('Checking current directory...');
219
+
220
+ const git = getGitContext();
221
+ const home = process.env.HOME || '';
222
+ const timestamp = now();
223
+
224
+ if (git) {
225
+ // ── Git Repository Path ──
226
+ const prepared = prepareProjectSessionState({
227
+ git,
228
+ sessionId,
229
+ transcriptPath,
230
+ });
231
+ const projectId = prepared.projectId;
232
+
233
+ log('Git repository detected');
234
+ log(` Project: ${git.projectName}`);
235
+ log(` Project ID: ${projectId}`);
236
+ log(` Branch: ${git.branch}`);
237
+ log(` Commit: ${git.commit}`);
238
+ log(` Remote: ${git.remote}`);
239
+
240
+ // Ensure .gmtr directory exists
241
+ const gmtrDir = join(git.root, '.gmtr');
242
+ if (!existsSync(gmtrDir)) {
243
+ mkdirSync(gmtrDir, { recursive: true });
244
+ }
245
+
246
+ let config = prepared.config;
247
+ const projectEntityId = prepared.projectEntityId;
248
+ const restoreNeeded = prepared.restoreNeeded;
249
+ const hasRestoreContext = prepared.hasRestoreContext;
250
+
251
+ log(prepared.created ? ' Creating new settings.json configuration (v2.1)' : ' Found settings.json configuration');
252
+ if (projectEntityId) {
253
+ log(` Project Entity ID: ${projectEntityId}`);
254
+ } else {
255
+ log(' Project entity not yet created in gramatr');
256
+ }
257
+ if (prepared.created) {
258
+ log(' Created settings.json (v2.1)');
259
+ }
260
+
261
+ // Register session with gramatr server
262
+ const sessionStart = await startRemoteSession({
263
+ clientType: 'claude_code',
264
+ sessionId,
265
+ projectId,
266
+ projectName: git.projectName,
267
+ gitRemote: git.remote,
268
+ directory: git.root,
269
+ });
270
+ const sessionResult = normalizeSessionStartResponse(sessionStart);
271
+ config = persistSessionRegistration(git.root, sessionStart) || config;
272
+
273
+ if (sessionResult.interactionId) {
274
+ log(` Interaction: ${sessionResult.interactionId}`);
275
+ if (sessionResult.resumed) {
276
+ log(' Resumed existing interaction');
277
+ }
278
+ } else {
279
+ log(' gramatr session registration failed (non-blocking)');
280
+ }
281
+
282
+ // ── Emit structured context to stdout for Claude ──
283
+ emitClaudeContext({
284
+ projectName: git.projectName,
285
+ projectId,
286
+ branch: git.branch,
287
+ sessionId,
288
+ interactionId: sessionResult.interactionId,
289
+ resumed: sessionResult.resumed,
290
+ handoffContext: sessionResult.handoffContext,
291
+ projectEntityId,
292
+ config,
293
+ });
294
+
295
+ writeCurrentProjectContextFile(
296
+ buildGitProjectContextPayload({
297
+ git,
298
+ sessionId,
299
+ workingDirectory: cwd,
300
+ sessionStart: timestamp,
301
+ projectId,
302
+ projectEntityId,
303
+ restoreNeeded,
304
+ }),
305
+ );
306
+
307
+ log(' Project context saved to ~/.claude/current-project-context.json');
308
+ log('');
309
+
310
+ // Display instructions based on initialization status
311
+ const gmtrConfigPath = join(git.root, '.gmtr', 'settings.json');
312
+
313
+ if (!projectEntityId) {
314
+ log('gramatr initialization required');
315
+ log('');
316
+ log('Run: `/gmtr-init`');
317
+ log('');
318
+ log('This will:');
319
+ log(` 1. Search gramatr for existing project: ${git.projectName}`);
320
+ log(' 2. Create project entity if not found');
321
+ log(' 3. Link entity to .gmtr.json');
322
+ log(' 4. Enable full memory persistence');
323
+ log('');
324
+ } else {
325
+ log('Loading project context from gramatr');
326
+ log('');
327
+ log('Please load project context from gramatr:');
328
+ log('');
329
+ log(`1. Load project summary: \`mcp__gramatr__gmtr_execute_intent({intent: "Show recent activity for project ${git.projectName}", constraints: {entity_types: ["project"], max_results: 1}, detail_level: "summary"})\``);
330
+ log('2. Display brief summary:');
331
+ log(' - Project name and metadata');
332
+ log(' - Last 5-7 observations (recent activity)');
333
+ log(' - Show observation timestamps');
334
+ log(`3. Load related entities from \`${gmtrConfigPath}\`:`);
335
+ log(' - Check .related_entities for linked databases, people, services, concepts');
336
+ log(' - Optionally fetch details for key related entities (use gmtr_execute_intent with detail_level: summary)');
337
+ log('4. Keep summary concise - just enough context to resume work');
338
+ log(' NOTE: Using intelligent tools (gmtr_execute_intent) provides 80-95% token reduction vs direct get_entities calls');
339
+ log('');
340
+ }
341
+
342
+ // Display restore context if available
343
+ if (hasRestoreContext && config?.last_compact) {
344
+ log('Context Restored from /gmtr-compact');
345
+ log('');
346
+
347
+ const compact = config.last_compact;
348
+ if (compact.summary) {
349
+ log('Last Session Summary:');
350
+ log(compact.summary);
351
+ log('');
352
+ }
353
+
354
+ if (compact.metadata?.files_changed?.length) {
355
+ log('Files Modified:');
356
+ for (const f of compact.metadata.files_changed) {
357
+ log(` \u2022 ${f}`);
358
+ }
359
+ log('');
360
+ }
361
+
362
+ if (compact.metadata?.commits?.length) {
363
+ log('Commits:');
364
+ for (const c of compact.metadata.commits) {
365
+ log(` \u2022 ${c}`);
366
+ }
367
+ log('');
368
+ }
369
+
370
+ const turnCount = compact.turns?.length || 0;
371
+ log(`Loaded from cache: ${turnCount} recent turns`);
372
+ log('Full conversation history in gramatr');
373
+ log('');
374
+ } else if (restoreNeeded) {
375
+ log('Full Context Restore Needed');
376
+ log('');
377
+ log('Last session was cleared/compacted.');
378
+ log('');
379
+ log('Run: `/gmtr-restore`');
380
+ log('');
381
+ log('This will restore:');
382
+ log(' \u2022 Last session summary');
383
+ log(' \u2022 Recent activity and decisions');
384
+ log(' \u2022 File changes and commits');
385
+ log('');
386
+ }
387
+ } else {
388
+ // ── Non-Git Path ──
389
+ log(' Not a git repository');
390
+ log(` Working directory: ${cwd}`);
391
+
392
+ const projectName = basename(cwd);
393
+ const gmtrConfigPath = join(cwd, '.gmtr', 'settings.json');
394
+
395
+ if (existsSync(gmtrConfigPath)) {
396
+ log(' Found settings.json configuration');
397
+
398
+ let config = readGmtrConfig(cwd);
399
+ if (config) {
400
+ const projectEntityId = config.project_entity_id || null;
401
+
402
+ // Update session stats
403
+ config.last_session_id = sessionId;
404
+ config.current_session = {
405
+ ...config.current_session,
406
+ session_id: sessionId,
407
+ transcript_path: transcriptPath,
408
+ last_updated: timestamp,
409
+ token_limit: 200000,
410
+ };
411
+ config.continuity_stats = config.continuity_stats || {};
412
+ config.continuity_stats.total_sessions = (config.continuity_stats.total_sessions || 0) + 1;
413
+ config.metadata = config.metadata || {};
414
+ config.metadata.updated = timestamp;
415
+
416
+ writeGmtrConfig(cwd, config);
417
+
418
+ if (projectEntityId) {
419
+ log(` Project Entity ID: ${projectEntityId} (non-git project)`);
420
+ } else {
421
+ log(' Project entity not yet created in gramatr');
422
+ }
423
+
424
+ writeCurrentProjectContextFile(
425
+ buildNonGitProjectContextPayload({
426
+ cwd,
427
+ sessionId,
428
+ sessionStart: timestamp,
429
+ projectEntityId,
430
+ }),
431
+ );
432
+ }
433
+ } else {
434
+ log(' No settings.json found - project not initialized');
435
+
436
+ writeCurrentProjectContextFile(
437
+ buildNonGitProjectContextPayload({
438
+ cwd,
439
+ sessionId,
440
+ sessionStart: timestamp,
441
+ projectEntityId: null,
442
+ }),
443
+ );
444
+ }
445
+
446
+ log(' Project context saved to ~/.claude/current-project-context.json');
447
+ log('');
448
+ log('gramatr initialization available');
449
+ log('');
450
+ log('This directory is not a git repository. You can still use gramatr.');
451
+ log('');
452
+ log('Run: `/gmtr-init`');
453
+ log('');
454
+ log('This will:');
455
+ log(' 1. Ask if you want to initialize git for this project');
456
+ log(' 2. Help set up git repository (if desired)');
457
+ log(' 3. Create gramatr project entity (git or non-git)');
458
+ log(' 4. Enable full gramatr memory integration');
459
+ log('');
460
+ }
461
+
462
+ // Show recent commits from last session if available
463
+ const lastCommitsFile = join(home, '.claude', 'last-session-commits.txt');
464
+ if (existsSync(lastCommitsFile)) {
465
+ try {
466
+ const commits = readFileSync(lastCommitsFile, 'utf8').trim();
467
+ if (commits) {
468
+ log('');
469
+ log(' Recent commits from last session:');
470
+ const lines = commits.split('\n').slice(0, 3);
471
+ for (const line of lines) {
472
+ log(` ${line}`);
473
+ }
474
+ }
475
+ } catch {
476
+ // Silent failure
477
+ }
478
+ }
479
+
480
+ log('');
481
+ log('\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501');
482
+ log('Session initialization complete');
483
+ log('\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501');
484
+ } catch (err) {
485
+ // Never crash — fail gracefully
486
+ log(`[gramatr] session-start error: ${String(err)}`);
487
+ }
488
+ }
489
+
490
+ main();
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "gramatr",
3
+ "version": "0.3.0",
4
+ "description": "gramatr — your cross-agent AI brain. Intelligence layer for Claude Code, Codex, Gemini CLI.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/gramatr/gramatr.git",
9
+ "directory": "packages/client"
10
+ },
11
+ "homepage": "https://gramatr.com",
12
+ "keywords": [
13
+ "ai",
14
+ "claude",
15
+ "mcp",
16
+ "intelligence",
17
+ "routing",
18
+ "classifier",
19
+ "memory",
20
+ "codex",
21
+ "gemini"
22
+ ],
23
+ "bin": {
24
+ "gramatr": "./bin/gramatr.js"
25
+ },
26
+ "scripts": {
27
+ "install-client": "npx tsx bin/install.ts",
28
+ "install-codex": "npx tsx codex/install.ts",
29
+ "install-gemini": "npx tsx gemini/install.ts",
30
+ "install-desktop": "npx tsx desktop/install.ts",
31
+ "install-chatgpt": "npx tsx chatgpt/install.ts",
32
+ "build-mcpb": "npx tsx desktop/build-mcpb.ts",
33
+ "install-web": "echo 'No local install needed. See packages/client/web/README.md for setup instructions.'",
34
+ "migrate-clean-install": "npx tsx bin/clean-legacy-install.ts --apply",
35
+ "gramatr": "npx tsx bin/gramatr.ts",
36
+ "lint": "pnpm exec biome lint --only=correctness/noUnusedImports --only=correctness/noUnusedVariables --only=correctness/noUndeclaredVariables --only=suspicious/noGlobalIsNan --files-ignore-unknown=true package.json bin chatgpt codex core desktop gemini hooks lib tools web vitest.config.ts",
37
+ "test": "vitest run",
38
+ "test:coverage": "vitest run --coverage",
39
+ "version:patch": "npm version patch --no-git-tag-version && npm run version:sync",
40
+ "version:minor": "npm version minor --no-git-tag-version && npm run version:sync",
41
+ "version:major": "npm version major --no-git-tag-version && npm run version:sync",
42
+ "version:sync": "npx tsx bin/version-sync.ts",
43
+ "prepublishOnly": "npm run version:sync"
44
+ },
45
+ "engines": {
46
+ "node": ">=20.0.0"
47
+ },
48
+ "dependencies": {
49
+ "tsx": "^4.21.0"
50
+ },
51
+ "devDependencies": {
52
+ "vitest": "^4.0.18"
53
+ }
54
+ }