@gramatr/mcp 0.13.41 → 0.13.42

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.
@@ -54,30 +54,33 @@
54
54
  * client hooks (Claude Code, Codex, Gemini) invoke this binary directly
55
55
  * instead of shipping duplicated TypeScript in each integration package.
56
56
  */
57
- import { VERSION } from '../hooks/lib/version.js';
58
- import { fileURLToPath } from 'node:url';
59
- import { createInterface } from 'node:readline/promises';
57
+ import { createInterface } from "node:readline/promises";
58
+ import { fileURLToPath } from "node:url";
59
+ import { VERSION } from "../hooks/lib/version.js";
60
60
  const ALL_LOCAL_SETUP_TARGETS = [
61
- 'claude',
62
- 'codex',
63
- 'opencode',
64
- 'gemini',
65
- 'claude-desktop',
66
- 'chatgpt-desktop',
67
- 'cursor',
68
- 'windsurf',
69
- 'vscode',
61
+ "claude",
62
+ "codex",
63
+ "opencode",
64
+ "gemini",
65
+ "claude-desktop",
66
+ "chatgpt-desktop",
67
+ "cursor",
68
+ "windsurf",
69
+ "vscode",
70
70
  ];
71
71
  export function parseAutoTargetSelection(raw, detected) {
72
72
  const normalized = raw.trim().toLowerCase();
73
73
  if (!normalized)
74
74
  return detected;
75
- if (normalized === 'all' || normalized === '*')
75
+ if (normalized === "all" || normalized === "*")
76
76
  return detected;
77
- if (normalized === 'none' || normalized === 'cancel' || normalized === 'q' || normalized === 'quit')
77
+ if (normalized === "none" ||
78
+ normalized === "cancel" ||
79
+ normalized === "q" ||
80
+ normalized === "quit")
78
81
  return null;
79
82
  const indexes = normalized
80
- .split(',')
83
+ .split(",")
81
84
  .map((chunk) => chunk.trim())
82
85
  .filter(Boolean)
83
86
  .map((chunk) => Number.parseInt(chunk, 10));
@@ -86,7 +89,7 @@ export function parseAutoTargetSelection(raw, detected) {
86
89
  }
87
90
  const selected = indexes
88
91
  .map((n) => detected[n - 1])
89
- .filter((value) => typeof value === 'string');
92
+ .filter((value) => typeof value === "string");
90
93
  return selected.length > 0 ? Array.from(new Set(selected)) : detected;
91
94
  }
92
95
  async function promptAutoTargetSelection(detected) {
@@ -95,12 +98,12 @@ async function promptAutoTargetSelection(detected) {
95
98
  output: process.stderr,
96
99
  });
97
100
  try {
98
- process.stderr.write('\n[gramatr] Select install targets for setup auto:\n');
101
+ process.stderr.write("\n[gramatr] Select install targets for setup auto:\n");
99
102
  detected.forEach((target, index) => {
100
103
  process.stderr.write(` ${index + 1}. ${target}\n`);
101
104
  });
102
- process.stderr.write(' Enter numbers (e.g. 1,3,4), or `all`, or `none` to cancel.\n');
103
- const answer = await rl.question(' Selection [all]: ');
105
+ process.stderr.write(" Enter numbers (e.g. 1,3,4), or `all`, or `none` to cancel.\n");
106
+ const answer = await rl.question(" Selection [all]: ");
104
107
  return parseAutoTargetSelection(answer, detected);
105
108
  }
106
109
  finally {
@@ -204,47 +207,47 @@ export async function cliMain(cliArgs = process.argv.slice(2)) {
204
207
  // Handle hook subcommand — MUST be before any heavyweight imports so hook
205
208
  // invocation stays under the 250ms cold-start budget for UserPromptSubmit /
206
209
  // SessionStart. See issue #652 comment 4230136578 for the latency budget.
207
- if (cliArgs.includes('--version') || cliArgs.includes('-v')) {
210
+ if (cliArgs.includes("--version") || cliArgs.includes("-v")) {
208
211
  process.stdout.write(`${VERSION}\n`);
209
212
  process.exit(0);
210
213
  }
211
- if (cliArgs.includes('--help') || cliArgs.includes('-h')) {
214
+ if (cliArgs.includes("--help") || cliArgs.includes("-h")) {
212
215
  printHelp();
213
216
  process.exit(0);
214
217
  }
215
- if (cliArgs[0] === 'hook') {
216
- const { runHook } = await import('./hook-dispatcher.js');
218
+ if (cliArgs[0] === "hook") {
219
+ const { runHook } = await import("./hook-dispatcher.js");
217
220
  const code = await runHook(cliArgs[1], cliArgs.slice(2));
218
221
  process.exit(code);
219
222
  }
220
- if (cliArgs[0] === 'statusline') {
221
- const { runStatusline } = await import('./statusline.js');
223
+ if (cliArgs[0] === "statusline") {
224
+ const { runStatusline } = await import("./statusline.js");
222
225
  await runStatusline(cliArgs.slice(1));
223
226
  process.exit(0);
224
227
  }
225
- if (cliArgs[0] === 'daemon') {
228
+ if (cliArgs[0] === "daemon") {
226
229
  const sub = cliArgs[1];
227
- if (sub === 'start') {
228
- const { startDaemon } = await import('../daemon/index.js');
230
+ if (sub === "start") {
231
+ const { startDaemon } = await import("../daemon/index.js");
229
232
  await startDaemon();
230
233
  return;
231
234
  }
232
- if (sub === 'stop') {
233
- const { callViaDaemon } = await import('../proxy/local-client.js');
234
- const result = await callViaDaemon('daemon/shutdown', {});
235
- if (result === (await import('../daemon/ipc-protocol.js')).DAEMON_UNAVAILABLE) {
236
- process.stderr.write('[gramatr] daemon is not running\n');
235
+ if (sub === "stop") {
236
+ const { callViaDaemon } = await import("../proxy/local-client.js");
237
+ const result = await callViaDaemon("daemon/shutdown", {});
238
+ if (result === (await import("../daemon/ipc-protocol.js")).DAEMON_UNAVAILABLE) {
239
+ process.stderr.write("[gramatr] daemon is not running\n");
237
240
  process.exit(1);
238
241
  }
239
- process.stderr.write('[gramatr] daemon shutdown requested\n');
242
+ process.stderr.write("[gramatr] daemon shutdown requested\n");
240
243
  return;
241
244
  }
242
- if (sub === 'status') {
243
- const { callViaDaemon } = await import('../proxy/local-client.js');
244
- const { DAEMON_UNAVAILABLE } = await import('../daemon/ipc-protocol.js');
245
- const result = await callViaDaemon('daemon/ping', {});
245
+ if (sub === "status") {
246
+ const { callViaDaemon } = await import("../proxy/local-client.js");
247
+ const { DAEMON_UNAVAILABLE } = await import("../daemon/ipc-protocol.js");
248
+ const result = await callViaDaemon("daemon/ping", {});
246
249
  if (result === DAEMON_UNAVAILABLE) {
247
- process.stderr.write('[gramatr] daemon is not running\n');
250
+ process.stderr.write("[gramatr] daemon is not running\n");
248
251
  process.exit(1);
249
252
  }
250
253
  const info = result;
@@ -254,68 +257,68 @@ export async function cliMain(cliArgs = process.argv.slice(2)) {
254
257
  process.stderr.write(`[gramatr] Unknown daemon subcommand: ${String(sub)}\nUsage: daemon start|stop|status\n`);
255
258
  process.exit(1);
256
259
  }
257
- if (cliArgs[0] === 'admin') {
258
- const { runAdmin } = await import('./admin.js');
260
+ if (cliArgs[0] === "admin") {
261
+ const { runAdmin } = await import("./admin.js");
259
262
  process.exit(await runAdmin(cliArgs.slice(1)));
260
263
  }
261
- if (cliArgs[0] === 'brain') {
262
- const { runBrain } = await import('./brain.js');
264
+ if (cliArgs[0] === "brain") {
265
+ const { runBrain } = await import("./brain.js");
263
266
  process.exit(await runBrain(cliArgs.slice(1)));
264
267
  }
265
268
  // 'upload' is a legacy alias for 'brain upload' — kept for backward compatibility
266
- if (cliArgs[0] === 'upload') {
267
- const { runBrain } = await import('./brain.js');
268
- process.exit(await runBrain(['upload', ...cliArgs.slice(1)]));
269
+ if (cliArgs[0] === "upload") {
270
+ const { runBrain } = await import("./brain.js");
271
+ process.exit(await runBrain(["upload", ...cliArgs.slice(1)]));
269
272
  }
270
- if (cliArgs[0] === 'start') {
271
- const write = (msg) => process.stderr.write(msg + '\n');
273
+ if (cliArgs[0] === "start") {
274
+ const write = (msg) => process.stderr.write(msg + "\n");
272
275
  const ok = (msg) => write(` \x1b[32m✓\x1b[0m ${msg}`);
273
276
  const fail = (msg) => write(` \x1b[31m✗\x1b[0m ${msg}`);
274
- write('');
275
- write(' \x1b[1mgrāmatr\x1b[0m — setting up your AI middleware');
276
- write('');
277
+ write("");
278
+ write(" \x1b[1mgrāmatr\x1b[0m — setting up your AI middleware");
279
+ write("");
277
280
  // Step 1: Setup (auto, non-interactive)
278
281
  try {
279
- write(' Setting up local clients...');
280
- const { setupAutoInstall } = await import('./setup.js');
282
+ write(" Setting up local clients...");
283
+ const { setupAutoInstall } = await import("./setup.js");
281
284
  setupAutoInstall({ dryRun: false, cleanInstall: false, listOnly: false, showPrompts: false });
282
- ok('Setup complete');
285
+ ok("Setup complete");
283
286
  }
284
287
  catch (err) {
285
288
  const detail = err instanceof Error ? err.message : String(err);
286
289
  fail(`Setup failed: ${detail}`);
287
- write(' Run manually: npx @gramatr/mcp setup auto');
290
+ write(" Run manually: npx @gramatr/mcp setup auto");
288
291
  process.exit(1);
289
292
  }
290
293
  // Step 2: Authenticate (skip if already have token)
291
294
  try {
292
- const { readConfig, loginBrowser } = await import('./login.js');
295
+ const { readConfig, loginBrowser } = await import("./login.js");
293
296
  const config = readConfig();
294
297
  if (config.token) {
295
- ok('Already authenticated');
298
+ ok("Already authenticated");
296
299
  }
297
300
  else {
298
- write(' Authenticating...');
301
+ write(" Authenticating...");
299
302
  await loginBrowser();
300
- ok('Authenticated');
303
+ ok("Authenticated");
301
304
  }
302
305
  }
303
306
  catch (err) {
304
307
  const detail = err instanceof Error ? err.message : String(err);
305
308
  fail(`Authentication failed: ${detail}`);
306
- write(' Run manually: npx @gramatr/mcp login');
309
+ write(" Run manually: npx @gramatr/mcp login");
307
310
  // Non-fatal — continue to verify + doctor
308
311
  }
309
312
  // Step 3: Verify install
310
313
  try {
311
- const { verifySetupInstall } = await import('./setup.js');
312
- const verifyResult = verifySetupInstall('all', { json: false, showPrompts: false });
314
+ const { verifySetupInstall } = await import("./setup.js");
315
+ const verifyResult = verifySetupInstall("all", { json: false, showPrompts: false });
313
316
  if (verifyResult === 0) {
314
- ok('Install verified');
317
+ ok("Install verified");
315
318
  }
316
319
  else {
317
- fail('Install verification found issues');
318
- write(' Run: npx @gramatr/mcp setup verify');
320
+ fail("Install verification found issues");
321
+ write(" Run: npx @gramatr/mcp setup verify");
319
322
  }
320
323
  }
321
324
  catch (err) {
@@ -324,54 +327,54 @@ export async function cliMain(cliArgs = process.argv.slice(2)) {
324
327
  }
325
328
  // Step 4: Health check
326
329
  try {
327
- const { checkServerHealth } = await import('./login.js');
330
+ const { checkServerHealth } = await import("./login.js");
328
331
  const health = await checkServerHealth();
329
332
  if (health.ok) {
330
- ok(`Server healthy (v${health.version || 'unknown'})`);
333
+ ok(`Server healthy (v${health.version || "unknown"})`);
331
334
  }
332
335
  else {
333
336
  fail(`Server unreachable: ${health.error}`);
334
- write(' Check GRAMATR_URL or server status');
337
+ write(" Check GRAMATR_URL or server status");
335
338
  }
336
339
  }
337
340
  catch (err) {
338
341
  const detail = err instanceof Error ? err.message : String(err);
339
342
  fail(`Health check failed: ${detail}`);
340
343
  }
341
- write('');
344
+ write("");
342
345
  write(" \x1b[32mYou're ready.\x1b[0m Your next prompt will be enhanced by grāmatr.");
343
- write('');
346
+ write("");
344
347
  return;
345
348
  }
346
- if (cliArgs[0] === 'login') {
347
- const { main: runLogin } = await import('./login.js');
349
+ if (cliArgs[0] === "login") {
350
+ const { main: runLogin } = await import("./login.js");
348
351
  await runLogin();
349
352
  return;
350
353
  }
351
- if (cliArgs[0] === 'add-api-key') {
352
- const { main: runAddApiKey } = await import('./add-api-key.js');
354
+ if (cliArgs[0] === "add-api-key") {
355
+ const { main: runAddApiKey } = await import("./add-api-key.js");
353
356
  process.exit(await runAddApiKey());
354
357
  }
355
- if (cliArgs[0] === 'logout') {
356
- const { main: runLogout } = await import('./logout.js');
358
+ if (cliArgs[0] === "logout") {
359
+ const { main: runLogout } = await import("./logout.js");
357
360
  process.exit(runLogout());
358
361
  }
359
- if (cliArgs[0] === 'clear-creds') {
360
- const { main: runClearCreds } = await import('./clear-creds.js');
362
+ if (cliArgs[0] === "clear-creds") {
363
+ const { main: runClearCreds } = await import("./clear-creds.js");
361
364
  process.exit(runClearCreds());
362
365
  }
363
- if (cliArgs[0] === 'build-mcpb') {
364
- await import('./build-mcpb.js');
366
+ if (cliArgs[0] === "build-mcpb") {
367
+ await import("./build-mcpb.js");
365
368
  return;
366
369
  }
367
370
  // Handle setup subcommand
368
- if (cliArgs[0] === 'setup') {
371
+ if (cliArgs[0] === "setup") {
369
372
  const target = cliArgs[1];
370
- const dryRun = cliArgs.includes('--dry');
371
- const cleanInstall = cliArgs.includes('--clean-install') || cliArgs.includes('--clean');
372
- const showPrompts = cliArgs.includes('--show-prompts');
373
- const json = cliArgs.includes('--json');
374
- if (target === 'claude') {
373
+ const dryRun = cliArgs.includes("--dry");
374
+ const cleanInstall = cliArgs.includes("--clean-install") || cliArgs.includes("--clean");
375
+ const showPrompts = cliArgs.includes("--show-prompts");
376
+ const json = cliArgs.includes("--json");
377
+ if (target === "claude") {
375
378
  // ── Interactive prompts first — before any heavyweight imports ──
376
379
  // Deferring import('./setup.js') until after all readline questions prevents
377
380
  // the Node SQLite ExperimentalWarning (emitted when node:sqlite is first
@@ -380,137 +383,130 @@ export async function cliMain(cliArgs = process.argv.slice(2)) {
380
383
  if (!dryRun && process.stdin.isTTY && process.stderr.isTTY) {
381
384
  const rl = createInterface({ input: process.stdin, output: process.stderr });
382
385
  try {
383
- const answer = await rl.question(' Show the grāmatr status line in Claude Code? [Y/n] ');
384
- showStatusLine = answer.trim().toLowerCase() !== 'n';
386
+ const answer = await rl.question(" Show the grāmatr status line in Claude Code? [Y/n] ");
387
+ showStatusLine = answer.trim().toLowerCase() !== "n";
385
388
  }
386
389
  finally {
387
390
  rl.close();
388
391
  }
389
392
  }
390
- // Prompt for user name if not already set
391
- const { readConfig: readGmtrConfig, writeConfig: writeGmtrConfig } = await import('./login.js');
392
- const gmtrConfig = readGmtrConfig();
393
- if (!gmtrConfig.user?.name && !dryRun && process.stdin.isTTY && process.stderr.isTTY) {
394
- const rl2 = createInterface({ input: process.stdin, output: process.stderr });
395
- try {
396
- const nameAnswer = await rl2.question(' What should grāmatr call you? [Enter to skip] ');
397
- const name = nameAnswer.trim();
398
- if (name) {
399
- writeGmtrConfig({ ...gmtrConfig, user: { ...(gmtrConfig.user ?? {}), name } });
400
- }
401
- }
402
- finally {
403
- rl2.close();
404
- }
405
- }
406
393
  // Prompt for auto-compact if not already configured
394
+ const { readConfig: readGmtrConfig, writeConfig: writeGmtrConfig } = await import("./login.js");
407
395
  const latestConfig = readGmtrConfig();
408
396
  const existingAutoCompact = latestConfig?.auto_compact;
409
- if (existingAutoCompact?.auto === undefined && !dryRun && process.stdin.isTTY && process.stderr.isTTY) {
410
- process.stderr.write(' grāmatr session continuity: saves full context every 15 turns — tasks, git state,\n');
411
- process.stderr.write(' recent work. Restores automatically after /clear so nothing is lost.\n');
397
+ if (existingAutoCompact?.auto === undefined &&
398
+ !dryRun &&
399
+ process.stdin.isTTY &&
400
+ process.stderr.isTTY) {
401
+ process.stderr.write(" grāmatr session continuity: saves full context every 15 turns — tasks, git state,\n");
402
+ process.stderr.write(" recent work. Restores automatically after /clear so nothing is lost.\n");
412
403
  const rl3 = createInterface({ input: process.stdin, output: process.stderr });
413
404
  try {
414
- const compactAnswer = await rl3.question(' Enable session continuity? [Y/n] ');
415
- const enableAutoCompact = compactAnswer.trim().toLowerCase() !== 'n';
416
- writeGmtrConfig({ ...latestConfig, auto_compact: { auto: enableAutoCompact, every_turns: 15, remind_every: 5 } });
405
+ const compactAnswer = await rl3.question(" Enable session continuity? [Y/n] ");
406
+ const enableAutoCompact = compactAnswer.trim().toLowerCase() !== "n";
407
+ writeGmtrConfig({
408
+ ...latestConfig,
409
+ auto_compact: { auto: enableAutoCompact, every_turns: 15, remind_every: 5 },
410
+ });
417
411
  }
418
412
  finally {
419
413
  rl3.close();
420
414
  }
421
415
  }
422
416
  // All prompts done — now safe to import setup.js (triggers node:sqlite import)
423
- const { setupClaude } = await import('./setup.js');
417
+ const { setupClaude } = await import("./setup.js");
424
418
  setupClaude(dryRun, cleanInstall, showPrompts, showStatusLine);
425
419
  return;
426
420
  }
427
- if (target === 'codex') {
421
+ if (target === "codex") {
428
422
  if (cleanInstall) {
429
- const { runCleanInstall } = await import('./setup.js');
423
+ const { runCleanInstall } = await import("./setup.js");
430
424
  runCleanInstall(dryRun);
431
425
  }
432
- const { setupCodex } = await import('./setup.js');
426
+ const { setupCodex } = await import("./setup.js");
433
427
  setupCodex(dryRun, showPrompts);
434
428
  return;
435
429
  }
436
- if (target === 'claude-desktop') {
430
+ if (target === "claude-desktop") {
437
431
  if (cleanInstall) {
438
- const { runCleanInstall } = await import('./setup.js');
432
+ const { runCleanInstall } = await import("./setup.js");
439
433
  runCleanInstall(dryRun);
440
434
  }
441
- const { setupClaudeDesktop } = await import('./setup.js');
435
+ const { setupClaudeDesktop } = await import("./setup.js");
442
436
  setupClaudeDesktop(dryRun, showPrompts);
443
437
  return;
444
438
  }
445
- if (target === 'chatgpt-desktop') {
439
+ if (target === "chatgpt-desktop") {
446
440
  if (cleanInstall) {
447
- const { runCleanInstall } = await import('./setup.js');
441
+ const { runCleanInstall } = await import("./setup.js");
448
442
  runCleanInstall(dryRun);
449
443
  }
450
- const { setupChatgptDesktop } = await import('./setup.js');
444
+ const { setupChatgptDesktop } = await import("./setup.js");
451
445
  setupChatgptDesktop(dryRun, showPrompts);
452
446
  return;
453
447
  }
454
- if (target === 'gemini') {
448
+ if (target === "gemini") {
455
449
  if (cleanInstall) {
456
- const { runCleanInstall } = await import('./setup.js');
450
+ const { runCleanInstall } = await import("./setup.js");
457
451
  runCleanInstall(dryRun);
458
452
  }
459
- const { setupGemini } = await import('./setup.js');
453
+ const { setupGemini } = await import("./setup.js");
460
454
  setupGemini(dryRun, showPrompts);
461
455
  return;
462
456
  }
463
- if (target === 'cursor') {
457
+ if (target === "cursor") {
464
458
  if (cleanInstall) {
465
- const { runCleanInstall } = await import('./setup.js');
459
+ const { runCleanInstall } = await import("./setup.js");
466
460
  runCleanInstall(dryRun);
467
461
  }
468
- const { setupCursor } = await import('./setup.js');
462
+ const { setupCursor } = await import("./setup.js");
469
463
  setupCursor(dryRun, showPrompts);
470
464
  return;
471
465
  }
472
- if (target === 'windsurf') {
466
+ if (target === "windsurf") {
473
467
  if (cleanInstall) {
474
- const { runCleanInstall } = await import('./setup.js');
468
+ const { runCleanInstall } = await import("./setup.js");
475
469
  runCleanInstall(dryRun);
476
470
  }
477
- const { setupWindsurf } = await import('./setup.js');
471
+ const { setupWindsurf } = await import("./setup.js");
478
472
  setupWindsurf(dryRun, showPrompts);
479
473
  return;
480
474
  }
481
- if (target === 'vscode') {
475
+ if (target === "vscode") {
482
476
  if (cleanInstall) {
483
- const { runCleanInstall } = await import('./setup.js');
477
+ const { runCleanInstall } = await import("./setup.js");
484
478
  runCleanInstall(dryRun);
485
479
  }
486
- const { setupVscode } = await import('./setup.js');
480
+ const { setupVscode } = await import("./setup.js");
487
481
  setupVscode(dryRun, showPrompts);
488
482
  return;
489
483
  }
490
- if (target === 'web') {
484
+ if (target === "web") {
491
485
  if (cleanInstall) {
492
- const { runCleanInstall } = await import('./setup.js');
486
+ const { runCleanInstall } = await import("./setup.js");
493
487
  runCleanInstall(dryRun);
494
488
  }
495
489
  const webTarget = cliArgs[2];
496
- const resolved = webTarget === 'chatgpt' ? 'chatgpt-web'
497
- : webTarget === 'gemini' ? 'gemini-web'
498
- : 'claude-web';
499
- const { setupWeb } = await import('./setup.js');
490
+ const resolved = webTarget === "chatgpt"
491
+ ? "chatgpt-web"
492
+ : webTarget === "gemini"
493
+ ? "gemini-web"
494
+ : "claude-web";
495
+ const { setupWeb } = await import("./setup.js");
500
496
  await setupWeb(resolved);
501
497
  return;
502
498
  }
503
- if (target === 'auto') {
504
- const { setupAutoInstall, getAutoDetectedTargets } = await import('./setup.js');
505
- const listOnly = cliArgs.includes('--list') || cliArgs.includes('--list-only');
506
- const assumeYes = cliArgs.includes('--yes') || cliArgs.includes('-y');
499
+ if (target === "auto") {
500
+ const { setupAutoInstall, getAutoDetectedTargets } = await import("./setup.js");
501
+ const listOnly = cliArgs.includes("--list") || cliArgs.includes("--list-only");
502
+ const assumeYes = cliArgs.includes("--yes") || cliArgs.includes("-y");
507
503
  const interactiveAllowed = Boolean(process.stdin.isTTY && process.stdout.isTTY);
508
504
  let selectedTargets;
509
505
  if (!listOnly && !assumeYes && interactiveAllowed) {
510
506
  const detected = getAutoDetectedTargets();
511
507
  const selected = await promptAutoTargetSelection(detected);
512
508
  if (selected === null) {
513
- process.stderr.write('[gramatr] setup auto cancelled.\n');
509
+ process.stderr.write("[gramatr] setup auto cancelled.\n");
514
510
  process.exit(1);
515
511
  }
516
512
  selectedTargets = selected;
@@ -524,8 +520,8 @@ export async function cliMain(cliArgs = process.argv.slice(2)) {
524
520
  });
525
521
  return;
526
522
  }
527
- if (target === 'all') {
528
- const { setupAutoInstall } = await import('./setup.js');
523
+ if (target === "all") {
524
+ const { setupAutoInstall } = await import("./setup.js");
529
525
  setupAutoInstall({
530
526
  dryRun,
531
527
  cleanInstall,
@@ -535,8 +531,8 @@ export async function cliMain(cliArgs = process.argv.slice(2)) {
535
531
  });
536
532
  return;
537
533
  }
538
- if (target === 'clean-install') {
539
- const { setupAutoInstall } = await import('./setup.js');
534
+ if (target === "clean-install") {
535
+ const { setupAutoInstall } = await import("./setup.js");
540
536
  setupAutoInstall({
541
537
  dryRun,
542
538
  cleanInstall: true,
@@ -546,26 +542,26 @@ export async function cliMain(cliArgs = process.argv.slice(2)) {
546
542
  });
547
543
  return;
548
544
  }
549
- if (target === 'verify') {
550
- const verifyTargetRaw = cliArgs.find((arg, index) => index > 1 && !arg.startsWith('--')) || 'all';
545
+ if (target === "verify") {
546
+ const verifyTargetRaw = cliArgs.find((arg, index) => index > 1 && !arg.startsWith("--")) || "all";
551
547
  const verifyTarget = verifyTargetRaw;
552
- const { verifySetupInstall } = await import('./setup.js');
548
+ const { verifySetupInstall } = await import("./setup.js");
553
549
  process.exit(verifySetupInstall(verifyTarget, { json, showPrompts }));
554
550
  }
555
551
  process.stderr.write(`[gramatr] Unknown setup target: ${target}\n`);
556
- process.stderr.write(' Supported: claude | codex | claude-desktop | chatgpt-desktop | gemini | cursor | windsurf | vscode | all | auto | web | clean-install | verify\n');
552
+ process.stderr.write(" Supported: claude | codex | claude-desktop | chatgpt-desktop | gemini | cursor | windsurf | vscode | all | auto | web | clean-install | verify\n");
557
553
  process.exit(1);
558
554
  }
559
555
  // ── --mode flag: set initial privilege mode before starting the server ──
560
556
  // Usage: gramatr --mode admin
561
557
  // Sets the session's privilege mode at startup. Session-scoped only — resets on restart.
562
558
  {
563
- const modeIdx = cliArgs.indexOf('--mode');
559
+ const modeIdx = cliArgs.indexOf("--mode");
564
560
  if (modeIdx !== -1) {
565
561
  const modeValue = cliArgs[modeIdx + 1];
566
- const { isValidMode, setCurrentMode, PRIVILEGE_MODES } = await import('../proxy/tool-privilege.js');
562
+ const { isValidMode, setCurrentMode, PRIVILEGE_MODES } = await import("../proxy/tool-privilege.js");
567
563
  if (!modeValue || !isValidMode(modeValue)) {
568
- const valid = PRIVILEGE_MODES.join(' | ');
564
+ const valid = PRIVILEGE_MODES.join(" | ");
569
565
  process.stderr.write(`[gramatr] Invalid --mode value: "${String(modeValue)}". Must be one of: ${valid}\n`);
570
566
  process.exit(1);
571
567
  return;
@@ -578,10 +574,20 @@ export async function cliMain(cliArgs = process.argv.slice(2)) {
578
574
  // If the first positional arg looks like a subcommand (no leading '-') but
579
575
  // didn't match any handler above, reject it with a helpful error. Without this
580
576
  // guard, typos silently fall through and start the MCP server.
581
- if (cliArgs[0] && !cliArgs[0].startsWith('-')) {
577
+ if (cliArgs[0] && !cliArgs[0].startsWith("-")) {
582
578
  const KNOWN_SUBCOMMANDS = [
583
- 'hook', 'statusline', 'daemon', 'admin', 'brain', 'start',
584
- 'login', 'add-api-key', 'logout', 'clear-creds', 'build-mcpb', 'setup',
579
+ "hook",
580
+ "statusline",
581
+ "daemon",
582
+ "admin",
583
+ "brain",
584
+ "start",
585
+ "login",
586
+ "add-api-key",
587
+ "logout",
588
+ "clear-creds",
589
+ "build-mcpb",
590
+ "setup",
585
591
  ];
586
592
  const typed = cliArgs[0];
587
593
  process.stderr.write(`[gramatr] Unknown subcommand: ${typed}\n`);
@@ -592,57 +598,60 @@ export async function cliMain(cliArgs = process.argv.slice(2)) {
592
598
  const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
593
599
  for (let i = 1; i <= m; i++) {
594
600
  for (let j = 1; j <= n; j++) {
595
- dp[i][j] = a[i - 1] === b[j - 1]
596
- ? dp[i - 1][j - 1]
597
- : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
601
+ dp[i][j] =
602
+ a[i - 1] === b[j - 1]
603
+ ? dp[i - 1][j - 1]
604
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
598
605
  }
599
606
  }
600
607
  return dp[m][n];
601
608
  }
602
- const suggestion = KNOWN_SUBCOMMANDS
603
- .map((cmd) => ({ cmd, dist: levenshtein(typed, cmd) }))
609
+ const suggestion = KNOWN_SUBCOMMANDS.map((cmd) => ({ cmd, dist: levenshtein(typed, cmd) }))
604
610
  .filter(({ dist }) => dist <= 2)
605
611
  .sort((a, b) => a.dist - b.dist)[0];
606
612
  if (suggestion) {
607
613
  process.stderr.write(` Did you mean: gramatr ${suggestion.cmd}?\n`);
608
614
  }
609
- process.stderr.write(' Run: gramatr --help\n');
615
+ process.stderr.write(" Run: gramatr --help\n");
610
616
  process.exit(1);
611
617
  }
612
618
  // Auto-repair: if extraKnownMarketplaces.gramatr is missing from Claude settings,
613
619
  // the plugin silently fails to install. Re-run setup to fix it before starting.
614
620
  try {
615
- const { getClaudeSettingsPath, parseJson, getGramatrPluginDir } = await import('./setup-config-io.js');
616
- const { join } = await import('node:path');
617
- const { existsSync } = await import('node:fs');
621
+ const { getClaudeSettingsPath, parseJson, getGramatrPluginDir } = await import("./setup-config-io.js");
622
+ const { join } = await import("node:path");
623
+ const { existsSync } = await import("node:fs");
618
624
  const settingsPath = getClaudeSettingsPath();
619
625
  const settings = parseJson(settingsPath);
620
- const pluginJsonPath = join(getGramatrPluginDir(), '.claude-plugin', 'plugin.json');
626
+ const pluginJsonPath = join(getGramatrPluginDir(), ".claude-plugin", "plugin.json");
621
627
  const marketplaceRegistered = settings?.extraKnownMarketplaces?.gramatr;
622
628
  if (!marketplaceRegistered || !existsSync(pluginJsonPath)) {
623
- process.stderr.write('[gramatr] Plugin marketplace not registered — running setup to repair...\n');
624
- const { setupClaude } = await import('./setup.js');
629
+ process.stderr.write("[gramatr] Plugin marketplace not registered — running setup to repair...\n");
630
+ const { setupClaude } = await import("./setup.js");
625
631
  setupClaude(false, false, false);
626
632
  }
627
633
  }
628
- catch { /* non-critical — proceed to start server regardless */ }
634
+ catch {
635
+ /* non-critical — proceed to start server regardless */
636
+ }
629
637
  // Default: start the stdio server. Lazy-loaded so the `hook` and `setup`
630
638
  // paths don't pay the MCP SDK import cost.
631
- const { startServer } = await import('../server/server.js');
639
+ const { startServer } = await import("../server/server.js");
632
640
  await startServer();
633
641
  }
634
642
  // Detect if this module is the entry point. Handles both direct node invocation
635
643
  // (process.argv[1] === this file) and npm bin shims (process.argv[1] is the
636
644
  // shim wrapper that loads this file).
637
645
  const thisFile = fileURLToPath(import.meta.url);
638
- const isEntrypoint = process.argv[1] != null && (process.argv[1] === thisFile ||
639
- process.argv[1].endsWith('gramatr') ||
640
- process.argv[1].endsWith('gramatr.cmd') ||
641
- process.argv[1].endsWith('gramatr.ps1') ||
642
- process.argv[1].endsWith('gramatr-mcp') ||
643
- process.argv[1].endsWith('gramatr-mcp.js') ||
644
- process.argv[1].endsWith('gramatr-mcp.cmd') ||
645
- process.argv[1].endsWith('gramatr-mcp.ps1'));
646
+ const isEntrypoint = process.argv[1] != null &&
647
+ (process.argv[1] === thisFile ||
648
+ process.argv[1].endsWith("gramatr") ||
649
+ process.argv[1].endsWith("gramatr.cmd") ||
650
+ process.argv[1].endsWith("gramatr.ps1") ||
651
+ process.argv[1].endsWith("gramatr-mcp") ||
652
+ process.argv[1].endsWith("gramatr-mcp.js") ||
653
+ process.argv[1].endsWith("gramatr-mcp.cmd") ||
654
+ process.argv[1].endsWith("gramatr-mcp.ps1"));
646
655
  if (isEntrypoint) {
647
656
  cliMain().catch((error) => {
648
657
  const message = error instanceof Error ? error.message : String(error);