@gramatr/mcp 0.13.40 → 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.
- package/dist/bin/gramatr-mcp.d.ts.map +1 -1
- package/dist/bin/gramatr-mcp.js +188 -179
- package/dist/bin/gramatr-mcp.js.map +1 -1
- package/dist/bin/login.d.ts.map +1 -1
- package/dist/bin/login.js +238 -164
- package/dist/bin/login.js.map +1 -1
- package/dist/bin/setup.d.ts.map +1 -1
- package/dist/bin/setup.js +33 -32
- package/dist/bin/setup.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +1 -1
package/dist/bin/gramatr-mcp.js
CHANGED
|
@@ -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 {
|
|
58
|
-
import { fileURLToPath } from
|
|
59
|
-
import {
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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 ===
|
|
75
|
+
if (normalized === "all" || normalized === "*")
|
|
76
76
|
return detected;
|
|
77
|
-
if (normalized ===
|
|
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 ===
|
|
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(
|
|
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(
|
|
103
|
-
const answer = await rl.question(
|
|
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(
|
|
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(
|
|
214
|
+
if (cliArgs.includes("--help") || cliArgs.includes("-h")) {
|
|
212
215
|
printHelp();
|
|
213
216
|
process.exit(0);
|
|
214
217
|
}
|
|
215
|
-
if (cliArgs[0] ===
|
|
216
|
-
const { runHook } = await import(
|
|
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] ===
|
|
221
|
-
const { runStatusline } = await import(
|
|
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] ===
|
|
228
|
+
if (cliArgs[0] === "daemon") {
|
|
226
229
|
const sub = cliArgs[1];
|
|
227
|
-
if (sub ===
|
|
228
|
-
const { startDaemon } = await import(
|
|
230
|
+
if (sub === "start") {
|
|
231
|
+
const { startDaemon } = await import("../daemon/index.js");
|
|
229
232
|
await startDaemon();
|
|
230
233
|
return;
|
|
231
234
|
}
|
|
232
|
-
if (sub ===
|
|
233
|
-
const { callViaDaemon } = await import(
|
|
234
|
-
const result = await callViaDaemon(
|
|
235
|
-
if (result === (await import(
|
|
236
|
-
process.stderr.write(
|
|
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(
|
|
242
|
+
process.stderr.write("[gramatr] daemon shutdown requested\n");
|
|
240
243
|
return;
|
|
241
244
|
}
|
|
242
|
-
if (sub ===
|
|
243
|
-
const { callViaDaemon } = await import(
|
|
244
|
-
const { DAEMON_UNAVAILABLE } = await import(
|
|
245
|
-
const result = await callViaDaemon(
|
|
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(
|
|
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] ===
|
|
258
|
-
const { runAdmin } = await import(
|
|
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] ===
|
|
262
|
-
const { runBrain } = await import(
|
|
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] ===
|
|
267
|
-
const { runBrain } = await import(
|
|
268
|
-
process.exit(await runBrain([
|
|
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] ===
|
|
271
|
-
const write = (msg) => process.stderr.write(msg +
|
|
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(
|
|
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(
|
|
280
|
-
const { setupAutoInstall } = await import(
|
|
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(
|
|
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(
|
|
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(
|
|
295
|
+
const { readConfig, loginBrowser } = await import("./login.js");
|
|
293
296
|
const config = readConfig();
|
|
294
297
|
if (config.token) {
|
|
295
|
-
ok(
|
|
298
|
+
ok("Already authenticated");
|
|
296
299
|
}
|
|
297
300
|
else {
|
|
298
|
-
write(
|
|
301
|
+
write(" Authenticating...");
|
|
299
302
|
await loginBrowser();
|
|
300
|
-
ok(
|
|
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(
|
|
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(
|
|
312
|
-
const verifyResult = verifySetupInstall(
|
|
314
|
+
const { verifySetupInstall } = await import("./setup.js");
|
|
315
|
+
const verifyResult = verifySetupInstall("all", { json: false, showPrompts: false });
|
|
313
316
|
if (verifyResult === 0) {
|
|
314
|
-
ok(
|
|
317
|
+
ok("Install verified");
|
|
315
318
|
}
|
|
316
319
|
else {
|
|
317
|
-
fail(
|
|
318
|
-
write(
|
|
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(
|
|
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 ||
|
|
333
|
+
ok(`Server healthy (v${health.version || "unknown"})`);
|
|
331
334
|
}
|
|
332
335
|
else {
|
|
333
336
|
fail(`Server unreachable: ${health.error}`);
|
|
334
|
-
write(
|
|
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] ===
|
|
347
|
-
const { main: runLogin } = await import(
|
|
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] ===
|
|
352
|
-
const { main: runAddApiKey } = await import(
|
|
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] ===
|
|
356
|
-
const { main: runLogout } = await import(
|
|
358
|
+
if (cliArgs[0] === "logout") {
|
|
359
|
+
const { main: runLogout } = await import("./logout.js");
|
|
357
360
|
process.exit(runLogout());
|
|
358
361
|
}
|
|
359
|
-
if (cliArgs[0] ===
|
|
360
|
-
const { main: runClearCreds } = await import(
|
|
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] ===
|
|
364
|
-
await import(
|
|
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] ===
|
|
371
|
+
if (cliArgs[0] === "setup") {
|
|
369
372
|
const target = cliArgs[1];
|
|
370
|
-
const dryRun = cliArgs.includes(
|
|
371
|
-
const cleanInstall = cliArgs.includes(
|
|
372
|
-
const showPrompts = cliArgs.includes(
|
|
373
|
-
const json = cliArgs.includes(
|
|
374
|
-
if (target ===
|
|
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(
|
|
384
|
-
showStatusLine = answer.trim().toLowerCase() !==
|
|
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 &&
|
|
410
|
-
|
|
411
|
-
process.
|
|
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(
|
|
415
|
-
const enableAutoCompact = compactAnswer.trim().toLowerCase() !==
|
|
416
|
-
writeGmtrConfig({
|
|
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(
|
|
417
|
+
const { setupClaude } = await import("./setup.js");
|
|
424
418
|
setupClaude(dryRun, cleanInstall, showPrompts, showStatusLine);
|
|
425
419
|
return;
|
|
426
420
|
}
|
|
427
|
-
if (target ===
|
|
421
|
+
if (target === "codex") {
|
|
428
422
|
if (cleanInstall) {
|
|
429
|
-
const { runCleanInstall } = await import(
|
|
423
|
+
const { runCleanInstall } = await import("./setup.js");
|
|
430
424
|
runCleanInstall(dryRun);
|
|
431
425
|
}
|
|
432
|
-
const { setupCodex } = await import(
|
|
426
|
+
const { setupCodex } = await import("./setup.js");
|
|
433
427
|
setupCodex(dryRun, showPrompts);
|
|
434
428
|
return;
|
|
435
429
|
}
|
|
436
|
-
if (target ===
|
|
430
|
+
if (target === "claude-desktop") {
|
|
437
431
|
if (cleanInstall) {
|
|
438
|
-
const { runCleanInstall } = await import(
|
|
432
|
+
const { runCleanInstall } = await import("./setup.js");
|
|
439
433
|
runCleanInstall(dryRun);
|
|
440
434
|
}
|
|
441
|
-
const { setupClaudeDesktop } = await import(
|
|
435
|
+
const { setupClaudeDesktop } = await import("./setup.js");
|
|
442
436
|
setupClaudeDesktop(dryRun, showPrompts);
|
|
443
437
|
return;
|
|
444
438
|
}
|
|
445
|
-
if (target ===
|
|
439
|
+
if (target === "chatgpt-desktop") {
|
|
446
440
|
if (cleanInstall) {
|
|
447
|
-
const { runCleanInstall } = await import(
|
|
441
|
+
const { runCleanInstall } = await import("./setup.js");
|
|
448
442
|
runCleanInstall(dryRun);
|
|
449
443
|
}
|
|
450
|
-
const { setupChatgptDesktop } = await import(
|
|
444
|
+
const { setupChatgptDesktop } = await import("./setup.js");
|
|
451
445
|
setupChatgptDesktop(dryRun, showPrompts);
|
|
452
446
|
return;
|
|
453
447
|
}
|
|
454
|
-
if (target ===
|
|
448
|
+
if (target === "gemini") {
|
|
455
449
|
if (cleanInstall) {
|
|
456
|
-
const { runCleanInstall } = await import(
|
|
450
|
+
const { runCleanInstall } = await import("./setup.js");
|
|
457
451
|
runCleanInstall(dryRun);
|
|
458
452
|
}
|
|
459
|
-
const { setupGemini } = await import(
|
|
453
|
+
const { setupGemini } = await import("./setup.js");
|
|
460
454
|
setupGemini(dryRun, showPrompts);
|
|
461
455
|
return;
|
|
462
456
|
}
|
|
463
|
-
if (target ===
|
|
457
|
+
if (target === "cursor") {
|
|
464
458
|
if (cleanInstall) {
|
|
465
|
-
const { runCleanInstall } = await import(
|
|
459
|
+
const { runCleanInstall } = await import("./setup.js");
|
|
466
460
|
runCleanInstall(dryRun);
|
|
467
461
|
}
|
|
468
|
-
const { setupCursor } = await import(
|
|
462
|
+
const { setupCursor } = await import("./setup.js");
|
|
469
463
|
setupCursor(dryRun, showPrompts);
|
|
470
464
|
return;
|
|
471
465
|
}
|
|
472
|
-
if (target ===
|
|
466
|
+
if (target === "windsurf") {
|
|
473
467
|
if (cleanInstall) {
|
|
474
|
-
const { runCleanInstall } = await import(
|
|
468
|
+
const { runCleanInstall } = await import("./setup.js");
|
|
475
469
|
runCleanInstall(dryRun);
|
|
476
470
|
}
|
|
477
|
-
const { setupWindsurf } = await import(
|
|
471
|
+
const { setupWindsurf } = await import("./setup.js");
|
|
478
472
|
setupWindsurf(dryRun, showPrompts);
|
|
479
473
|
return;
|
|
480
474
|
}
|
|
481
|
-
if (target ===
|
|
475
|
+
if (target === "vscode") {
|
|
482
476
|
if (cleanInstall) {
|
|
483
|
-
const { runCleanInstall } = await import(
|
|
477
|
+
const { runCleanInstall } = await import("./setup.js");
|
|
484
478
|
runCleanInstall(dryRun);
|
|
485
479
|
}
|
|
486
|
-
const { setupVscode } = await import(
|
|
480
|
+
const { setupVscode } = await import("./setup.js");
|
|
487
481
|
setupVscode(dryRun, showPrompts);
|
|
488
482
|
return;
|
|
489
483
|
}
|
|
490
|
-
if (target ===
|
|
484
|
+
if (target === "web") {
|
|
491
485
|
if (cleanInstall) {
|
|
492
|
-
const { runCleanInstall } = await import(
|
|
486
|
+
const { runCleanInstall } = await import("./setup.js");
|
|
493
487
|
runCleanInstall(dryRun);
|
|
494
488
|
}
|
|
495
489
|
const webTarget = cliArgs[2];
|
|
496
|
-
const resolved = webTarget ===
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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 ===
|
|
504
|
-
const { setupAutoInstall, getAutoDetectedTargets } = await import(
|
|
505
|
-
const listOnly = cliArgs.includes(
|
|
506
|
-
const assumeYes = cliArgs.includes(
|
|
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(
|
|
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 ===
|
|
528
|
-
const { setupAutoInstall } = await import(
|
|
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 ===
|
|
539
|
-
const { setupAutoInstall } = await import(
|
|
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 ===
|
|
550
|
-
const verifyTargetRaw = cliArgs.find((arg, index) => index > 1 && !arg.startsWith(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
584
|
-
|
|
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] =
|
|
596
|
-
|
|
597
|
-
|
|
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(
|
|
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(
|
|
616
|
-
const { join } = await import(
|
|
617
|
-
const { existsSync } = await import(
|
|
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(),
|
|
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(
|
|
624
|
-
const { setupClaude } = await import(
|
|
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 {
|
|
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(
|
|
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 &&
|
|
639
|
-
process.argv[1]
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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);
|