gnosys 5.11.2 → 5.11.4

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/cli.js CHANGED
@@ -974,7 +974,14 @@ setupCmd
974
974
  setupCmd
975
975
  .command("ides")
976
976
  .description("Configure IDE MCP integrations (Claude Code/Desktop, Cursor, Codex, Grok Build, Gemini CLI, Antigravity)")
977
- .action(async () => {
977
+ .option("--all", "Configure MCP for all supported IDEs (non-interactive)")
978
+ .action(async (opts) => {
979
+ if (opts.all) {
980
+ const { runIdesSetupAll } = await import("./lib/setup/sections/ides.js");
981
+ const { configured, errors } = await runIdesSetupAll(process.cwd());
982
+ console.log(`\n${configured} ides configured · ${errors} errors`);
983
+ return;
984
+ }
978
985
  const readline = await import("readline/promises");
979
986
  const { runIdesSetup } = await import("./lib/setup/sections/ides.js");
980
987
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
package/dist/index.js CHANGED
File without changes
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared stdio MCP install helpers for IDE setup (`gnosys setup ides`).
3
+ */
4
+ /** IDE keys handled by `setupIDE()`. */
5
+ export declare const SUPPORTED_IDE_KEYS: readonly ["claude", "claude-desktop", "cursor", "codex", "gemini-cli", "antigravity", "grok-build"];
6
+ export type SupportedIde = (typeof SUPPORTED_IDE_KEYS)[number];
7
+ /** User-facing aliases → canonical IDE key. */
8
+ export declare const IDE_ALIASES: Record<string, SupportedIde>;
9
+ export declare function normalizeIdeKey(ide: string): SupportedIde | null;
10
+ /** Absolute path to `gnosys-mcp` when on PATH, else bare name. */
11
+ export declare function resolveGnosysMcpCommand(): string;
12
+ export declare function gnosysStdioMcpEntry(): {
13
+ command: string;
14
+ args: string[];
15
+ };
16
+ /** True when an existing JSON MCP entry still points at broken `gnosys serve`. */
17
+ export declare function isStaleGnosysMcpEntry(entry: unknown): boolean;
18
+ /** Merge (or replace) the gnosys stdio entry in a JSON MCP config file. */
19
+ export declare function installStdioMcpJson(file: string): Promise<void>;
20
+ /** Project + user-global Cursor MCP paths. */
21
+ export declare function cursorMcpPaths(projectDir: string): {
22
+ project: string;
23
+ user: string;
24
+ };
25
+ /** Run a CLI with argv (safe for paths containing spaces). */
26
+ export declare function runCli(command: string, args: string[], opts?: {
27
+ allowFailure?: boolean;
28
+ }): string;
29
+ /** Remove a `[section]` block from hand-rolled TOML text. */
30
+ export declare function removeTomlSection(existing: string, sectionHeader: string): string;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Shared stdio MCP install helpers for IDE setup (`gnosys setup ides`).
3
+ */
4
+ import { execFileSync, execSync } from "child_process";
5
+ import path from "path";
6
+ import os from "os";
7
+ import { mergeJsonMcpServer } from "./mcpClientConfig.js";
8
+ /** IDE keys handled by `setupIDE()`. */
9
+ export const SUPPORTED_IDE_KEYS = [
10
+ "claude",
11
+ "claude-desktop",
12
+ "cursor",
13
+ "codex",
14
+ "gemini-cli",
15
+ "antigravity",
16
+ "grok-build",
17
+ ];
18
+ /** User-facing aliases → canonical IDE key. */
19
+ export const IDE_ALIASES = {
20
+ grok: "grok-build",
21
+ "grok-build": "grok-build",
22
+ };
23
+ export function normalizeIdeKey(ide) {
24
+ const key = ide.toLowerCase();
25
+ if (SUPPORTED_IDE_KEYS.includes(key)) {
26
+ return key;
27
+ }
28
+ return IDE_ALIASES[key] ?? null;
29
+ }
30
+ /** Absolute path to `gnosys-mcp` when on PATH, else bare name. */
31
+ export function resolveGnosysMcpCommand() {
32
+ try {
33
+ const p = execSync("command -v gnosys-mcp", { encoding: "utf-8" }).trim();
34
+ if (p)
35
+ return p;
36
+ }
37
+ catch {
38
+ // Fall back to bare name on PATH.
39
+ }
40
+ return "gnosys-mcp";
41
+ }
42
+ export function gnosysStdioMcpEntry() {
43
+ return { command: resolveGnosysMcpCommand(), args: [] };
44
+ }
45
+ /** True when an existing JSON MCP entry still points at broken `gnosys serve`. */
46
+ export function isStaleGnosysMcpEntry(entry) {
47
+ if (!entry || typeof entry !== "object")
48
+ return false;
49
+ const e = entry;
50
+ const cmd = String(e.command ?? "");
51
+ const args = Array.isArray(e.args) ? e.args.map(String) : [];
52
+ if (cmd.includes("gnosys-mcp"))
53
+ return false;
54
+ if (cmd.endsWith("/gnosys") || cmd === "gnosys") {
55
+ return args.includes("serve") || args.length === 0;
56
+ }
57
+ return false;
58
+ }
59
+ /** Merge (or replace) the gnosys stdio entry in a JSON MCP config file. */
60
+ export async function installStdioMcpJson(file) {
61
+ await mergeJsonMcpServer(file, gnosysStdioMcpEntry());
62
+ }
63
+ /** Project + user-global Cursor MCP paths. */
64
+ export function cursorMcpPaths(projectDir) {
65
+ return {
66
+ project: path.join(projectDir, ".cursor", "mcp.json"),
67
+ user: path.join(os.homedir(), ".cursor", "mcp.json"),
68
+ };
69
+ }
70
+ /** Run a CLI with argv (safe for paths containing spaces). */
71
+ export function runCli(command, args, opts) {
72
+ try {
73
+ return execFileSync(command, args, {
74
+ encoding: "utf-8",
75
+ stdio: ["pipe", "pipe", "pipe"],
76
+ });
77
+ }
78
+ catch (err) {
79
+ if (opts?.allowFailure) {
80
+ return err instanceof Error && "stdout" in err ? String(err.stdout ?? "") : "";
81
+ }
82
+ throw err;
83
+ }
84
+ }
85
+ /** Remove a `[section]` block from hand-rolled TOML text. */
86
+ export function removeTomlSection(existing, sectionHeader) {
87
+ const lines = existing.split("\n");
88
+ const headerIdx = lines.findIndex((line) => line.trim() === sectionHeader);
89
+ if (headerIdx === -1)
90
+ return existing;
91
+ let endIdx = lines.length;
92
+ for (let i = headerIdx + 1; i < lines.length; i++) {
93
+ if (/^\s*\[/.test(lines[i])) {
94
+ endIdx = i;
95
+ break;
96
+ }
97
+ }
98
+ const before = lines.slice(0, headerIdx).join("\n");
99
+ const after = lines.slice(endIdx).join("\n");
100
+ const merged = [before, after].filter((s) => s.length > 0).join("\n");
101
+ return merged.length > 0 ? `${merged.replace(/\n{3,}/g, "\n\n")}\n` : "";
102
+ }
@@ -25,3 +25,10 @@ export interface IdesSetupOptions {
25
25
  * Returns true if at least one IDE config was written.
26
26
  */
27
27
  export declare function runIdesSetup(opts: IdesSetupOptions): Promise<boolean>;
28
+ /**
29
+ * Non-interactive: wire MCP for every supported IDE (user-level + project cursor).
30
+ */
31
+ export declare function runIdesSetupAll(directory: string): Promise<{
32
+ configured: number;
33
+ errors: number;
34
+ }>;
@@ -9,6 +9,7 @@
9
9
  import fs from "fs/promises";
10
10
  import path from "path";
11
11
  import { detectIDEs, setupIDE } from "../../setup.js";
12
+ import { SUPPORTED_IDE_KEYS } from "../../ideMcpInstall.js";
12
13
  import { safeQuestion } from "../ui/safePrompt.js";
13
14
  import { renderTable } from "../ui/table.js";
14
15
  const DIM = "\x1b[2m";
@@ -18,7 +19,7 @@ const RESET = "\x1b[0m";
18
19
  const CHECK = `${GREEN}✓${RESET}`;
19
20
  const CROSS = `${RED}✗${RESET}`;
20
21
  const IDE_LABELS = {
21
- claude: "Claude Code",
22
+ claude: "Claude Code + Desktop",
22
23
  "claude-desktop": "Claude Desktop",
23
24
  cursor: "Cursor",
24
25
  codex: "Codex",
@@ -35,10 +36,10 @@ const IDE_TARGET_DISPLAY = {
35
36
  claude: "claude mcp add (CLI)",
36
37
  "claude-desktop": "~/Library/.../claude_desktop_config.json",
37
38
  cursor: ".cursor/mcp.json",
38
- codex: ".codex/mcp.json",
39
+ codex: "codex mcp add (CLI registry)",
39
40
  "gemini-cli": "~/.gemini/settings.json",
40
41
  antigravity: "~/.gemini/antigravity/mcp_config.json",
41
- "grok-build": "~/.grok/config.toml",
42
+ "grok-build": "~/.grok/config.toml ([mcp_servers.gnosys])",
42
43
  };
43
44
  function ideTarget(ide) {
44
45
  return IDE_TARGET_DISPLAY[ide] ?? `.${ide}/mcp.json`;
@@ -178,3 +179,24 @@ export async function runIdesSetup(opts) {
178
179
  console.log(` ${color(c.textDim, `${configured} ides configured · ${errors} errors`)}`);
179
180
  return configured > 0;
180
181
  }
182
+ /**
183
+ * Non-interactive: wire MCP for every supported IDE (user-level + project cursor).
184
+ */
185
+ export async function runIdesSetupAll(directory) {
186
+ let configured = 0;
187
+ let errors = 0;
188
+ // Claude setup also wires Claude Desktop — skip duplicate pass.
189
+ const idesToRun = SUPPORTED_IDE_KEYS.filter((k) => k !== "claude-desktop");
190
+ for (const ide of idesToRun) {
191
+ const result = await setupIDE(ide, directory);
192
+ if (result.success) {
193
+ console.log(` ${CHECK} ${IDE_LABELS[ide] ?? ide}: ${result.message}`);
194
+ configured++;
195
+ }
196
+ else {
197
+ console.log(` ${CROSS} ${IDE_LABELS[ide] ?? ide}: ${result.message}`);
198
+ errors++;
199
+ }
200
+ }
201
+ return { configured, errors };
202
+ }
@@ -56,14 +56,11 @@ export declare function writeApiKey(provider: string, key: string): Promise<void
56
56
  */
57
57
  export declare function detectIDEs(projectDir: string): Promise<string[]>;
58
58
  /**
59
- * Replace (or append) a `[mcp.<name>]` block inside the TOML text for
60
- * Grok Build's config file. Preserves every line outside that block
61
- * deci-046 read-then-merge rule. We can't pull in a TOML dependency
62
- * without adding to package.json, so we ship a minimal hand-rolled
63
- * updater scoped exactly to the `[mcp.gnosys]` use case.
59
+ * Replace (or append) a `[mcp_servers.<name>]` block inside Grok Build's
60
+ * `~/.grok/config.toml`. Preserves every line outside that block (deci-046).
64
61
  *
65
- * Spec assumption: TOML headers we touch are simple `[a.b]` lines with
66
- * no inline tables or nested arrays. Any other content is left alone.
62
+ * Grok Build reads `mcp_servers.*` only legacy `[mcp.*]` blocks are ignored
63
+ * by `grok mcp list` / the agent runtime (see ~/.grok/README.md).
67
64
  *
68
65
  * Exported for tests.
69
66
  */
package/dist/lib/setup.js CHANGED
@@ -19,6 +19,7 @@ import { validateModel } from "./modelValidation.js";
19
19
  import { resolveActiveStorePath, ensureActiveStorePath } from "./setup/storePath.js";
20
20
  import { safeQuestion } from "./setup/ui/safePrompt.js";
21
21
  import { getClaudeDesktopConfigPath, getApiKeySkipHints } from "./platform.js";
22
+ import { gnosysStdioMcpEntry, installStdioMcpJson, normalizeIdeKey, resolveGnosysMcpCommand, cursorMcpPaths, runCli, removeTomlSection, } from "./ideMcpInstall.js";
22
23
  // ─── ANSI Colors ────────────────────────────────────────────────────────────
23
24
  const BOLD = "\x1b[1m";
24
25
  const DIM = "\x1b[2m";
@@ -568,32 +569,29 @@ export async function detectIDEs(projectDir) {
568
569
  return detected;
569
570
  }
570
571
  /**
571
- * Replace (or append) a `[mcp.<name>]` block inside the TOML text for
572
- * Grok Build's config file. Preserves every line outside that block
573
- * deci-046 read-then-merge rule. We can't pull in a TOML dependency
574
- * without adding to package.json, so we ship a minimal hand-rolled
575
- * updater scoped exactly to the `[mcp.gnosys]` use case.
572
+ * Replace (or append) a `[mcp_servers.<name>]` block inside Grok Build's
573
+ * `~/.grok/config.toml`. Preserves every line outside that block (deci-046).
576
574
  *
577
- * Spec assumption: TOML headers we touch are simple `[a.b]` lines with
578
- * no inline tables or nested arrays. Any other content is left alone.
575
+ * Grok Build reads `mcp_servers.*` only legacy `[mcp.*]` blocks are ignored
576
+ * by `grok mcp list` / the agent runtime (see ~/.grok/README.md).
579
577
  *
580
578
  * Exported for tests.
581
579
  */
582
580
  export function upsertGrokMcpBlock(existing, name, entry) {
583
- const sectionHeader = `[mcp.${name}]`;
584
- const lines = existing.split("\n");
581
+ // Drop mistaken v5.9.4 `[mcp.<name>]` sections so we don't leave dead config.
582
+ let content = removeTomlSection(existing, `[mcp.${name}]`);
583
+ const sectionHeader = `[mcp_servers.${name}]`;
584
+ const lines = content.split("\n");
585
585
  const headerIdx = lines.findIndex((line) => line.trim() === sectionHeader);
586
586
  const blockBody = renderGrokMcpBlock(entry);
587
587
  if (headerIdx === -1) {
588
- // Append a fresh block, separated by a blank line if the file has content.
589
- const prefix = existing.length === 0 || existing.endsWith("\n\n")
590
- ? existing
591
- : existing.endsWith("\n") ? `${existing}\n` : `${existing}\n\n`;
588
+ const prefix = content.length === 0 || content.endsWith("\n\n")
589
+ ? content
590
+ : content.endsWith("\n")
591
+ ? `${content}\n`
592
+ : `${content}\n\n`;
592
593
  return `${prefix}${sectionHeader}\n${blockBody}`;
593
594
  }
594
- // Replace the existing block — everything from sectionHeader up to the
595
- // next `[` header (or EOF). Count blank lines immediately after the block
596
- // so we can preserve the original spacing before the next section.
597
595
  let endIdx = lines.length;
598
596
  for (let i = headerIdx + 1; i < lines.length; i++) {
599
597
  if (/^\s*\[/.test(lines[i])) {
@@ -611,32 +609,12 @@ export function upsertGrokMcpBlock(existing, name, entry) {
611
609
  const beforeBlock = lines.slice(0, headerIdx).join("\n");
612
610
  const head = beforeBlock.length > 0 ? `${beforeBlock}\n` : "";
613
611
  if (!hasFollowingSection) {
614
- // No following section — drop trailing blank lines and end with a single \n.
615
612
  return `${head}${sectionHeader}\n${blockBody}`;
616
613
  }
617
614
  const gap = "\n".repeat(Math.max(1, trailingBlankBeforeNext));
618
615
  const afterBlock = afterLines.join("\n");
619
616
  return `${head}${sectionHeader}\n${blockBody}${gap}${afterBlock}`;
620
617
  }
621
- /**
622
- * Absolute path to the `gnosys-mcp` stdio entry (dist/index.js).
623
- * Prefer this over `gnosys serve` — v5.11.0 `gnosys serve` imported index.js but
624
- * did not call startMcpServer(), so MCP hosts saw "connection closed" on init.
625
- */
626
- function resolveGnosysMcpCommand() {
627
- try {
628
- const p = execSync("command -v gnosys-mcp", { encoding: "utf-8" }).trim();
629
- if (p)
630
- return p;
631
- }
632
- catch {
633
- // Fall back to bare name on PATH.
634
- }
635
- return "gnosys-mcp";
636
- }
637
- function gnosysMcpServerEntry() {
638
- return { command: resolveGnosysMcpCommand(), args: [] };
639
- }
640
618
  function renderGrokMcpBlock(entry) {
641
619
  const argsStr = `[${entry.args.map((a) => JSON.stringify(a)).join(", ")}]`;
642
620
  const lines = [
@@ -652,48 +630,45 @@ function renderGrokMcpBlock(entry) {
652
630
  * Set up Gnosys MCP integration for a specific IDE.
653
631
  */
654
632
  export async function setupIDE(ide, projectDir) {
633
+ const canonical = normalizeIdeKey(ide);
634
+ if (!canonical) {
635
+ return { success: false, message: `Unknown IDE: ${ide}` };
636
+ }
655
637
  try {
656
- switch (ide) {
638
+ switch (canonical) {
657
639
  case "claude": {
658
640
  const mcpCmd = resolveGnosysMcpCommand();
641
+ let codeMsg = `Claude Code MCP registered (${mcpCmd})`;
659
642
  try {
660
- try {
661
- execSync("claude mcp remove gnosys", { stdio: "pipe" });
662
- }
663
- catch {
664
- // Not registered yet — fine.
665
- }
666
- execSync(`claude mcp add -s user gnosys -- ${mcpCmd}`, {
667
- stdio: "pipe",
668
- });
643
+ runCli("claude", ["mcp", "remove", "gnosys"], { allowFailure: true });
644
+ runCli("claude", ["mcp", "add", "-s", "user", "gnosys", "--", mcpCmd]);
669
645
  }
670
646
  catch (e) {
671
647
  const msg = e instanceof Error ? e.message : String(e);
672
648
  if (msg.includes("already exists")) {
673
- return { success: true, message: "Claude Code MCP server already configured" };
649
+ codeMsg = "Claude Code MCP server already configured";
650
+ }
651
+ else {
652
+ codeMsg = `Claude Code MCP skipped (is \`claude\` on PATH?): ${msg}`;
674
653
  }
675
- throw e;
676
654
  }
677
- return { success: true, message: `Claude Code MCP server registered (${mcpCmd})` };
655
+ const desktop = await setupIDE("claude-desktop", projectDir);
656
+ return {
657
+ success: desktop.success,
658
+ message: desktop.success
659
+ ? `${codeMsg}; ${desktop.message}`
660
+ : `${codeMsg}; Claude Desktop: ${desktop.message}`,
661
+ };
678
662
  }
679
663
  case "cursor": {
680
- const cursorDir = path.join(projectDir, ".cursor");
681
- const mcpPath = path.join(cursorDir, "mcp.json");
682
- await fs.mkdir(cursorDir, { recursive: true });
683
- let config = {};
684
- try {
685
- const existing = await fs.readFile(mcpPath, "utf-8");
686
- config = JSON.parse(existing);
687
- }
688
- catch {
689
- // File doesn't exist or is invalid — start fresh
690
- }
691
- // Merge gnosys entry
692
- const servers = (config.mcpServers ?? {});
693
- servers.gnosys = gnosysMcpServerEntry();
694
- config.mcpServers = servers;
695
- await fs.writeFile(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
696
- return { success: true, message: "Cursor MCP config updated (.cursor/mcp.json)" };
664
+ const paths = cursorMcpPaths(projectDir);
665
+ await fs.mkdir(path.dirname(paths.project), { recursive: true });
666
+ await installStdioMcpJson(paths.project);
667
+ await installStdioMcpJson(paths.user);
668
+ return {
669
+ success: true,
670
+ message: "Cursor MCP updated (project .cursor/mcp.json and ~/.cursor/mcp.json)",
671
+ };
697
672
  }
698
673
  case "codex": {
699
674
  // v5.8.5: register via `codex mcp add` (the real Codex CLI registration
@@ -705,19 +680,13 @@ export async function setupIDE(ide, projectDir) {
705
680
  //
706
681
  // We also migrate away from those legacy blocks in
707
682
  // `~/.codex/config.toml` so users on stale configs get cleaned up.
708
- const os = await import("os");
709
- // 1. Migrate (strip) legacy hand-written blocks in ~/.codex/config.toml.
683
+ // 1. Strip legacy hand-written sections in ~/.codex/config.toml.
710
684
  const userCodexConfig = path.join(os.homedir(), ".codex", "config.toml");
711
685
  try {
712
686
  let existing = await fs.readFile(userCodexConfig, "utf-8");
713
- const before = existing;
714
- // Old shape (pre-v5.8.4): [gnosys] command/args
715
- existing = existing.replace(/\n?\[gnosys\][^[]*?command\s*=\s*"gnosys"[^[]*?args\s*=\s*\[[^\]]*\]\s*\n?/, "\n");
716
- // v5.8.4 shape: [mcp.gnosys] type/command
717
- existing = existing.replace(/\n?\[mcp\.gnosys\][^[]*?type\s*=\s*"local"[^[]*?command\s*=\s*\[[^\]]*\]\s*\n?/, "\n");
718
- if (existing !== before) {
719
- existing = existing.replace(/\n{3,}/g, "\n\n");
720
- await fs.writeFile(userCodexConfig, existing, "utf-8");
687
+ const cleaned = removeTomlSection(removeTomlSection(existing, "[mcp.gnosys]"), "[gnosys]");
688
+ if (cleaned !== existing) {
689
+ await fs.writeFile(userCodexConfig, cleaned, "utf-8");
721
690
  }
722
691
  }
723
692
  catch {
@@ -730,25 +699,16 @@ export async function setupIDE(ide, projectDir) {
730
699
  // remove and re-add.
731
700
  let alreadyCorrect = false;
732
701
  try {
733
- const existing = execSync("codex mcp get gnosys 2>/dev/null", {
734
- encoding: "utf-8",
735
- });
702
+ const existing = runCli("codex", ["mcp", "get", "gnosys"], { allowFailure: true });
736
703
  if (existing && existing.includes(gnosysCmd) && !existing.includes(" serve")) {
737
704
  alreadyCorrect = true;
738
705
  }
739
706
  else if (existing) {
740
- // Different command — remove so we can re-add with the right one.
741
- try {
742
- execSync("codex mcp remove gnosys", { stdio: "pipe" });
743
- }
744
- catch {
745
- // Non-fatal — `mcp add` below will overwrite or fail loudly.
746
- }
707
+ runCli("codex", ["mcp", "remove", "gnosys"], { allowFailure: true });
747
708
  }
748
709
  }
749
710
  catch {
750
- // `codex mcp get` returns non-zero when the server isn't registered —
751
- // that's the common case on first install; proceed to add.
711
+ // `codex mcp get` failed proceed to add.
752
712
  }
753
713
  if (alreadyCorrect) {
754
714
  return {
@@ -758,7 +718,7 @@ export async function setupIDE(ide, projectDir) {
758
718
  }
759
719
  // 4. Register via the canonical Codex CLI command.
760
720
  try {
761
- execSync(`codex mcp add gnosys -- ${gnosysCmd}`, { stdio: "pipe" });
721
+ runCli("codex", ["mcp", "add", "gnosys", "--", gnosysCmd]);
762
722
  }
763
723
  catch (err) {
764
724
  const msg = err instanceof Error ? err.message : String(err);
@@ -775,49 +735,24 @@ export async function setupIDE(ide, projectDir) {
775
735
  };
776
736
  }
777
737
  case "gemini-cli": {
778
- // Gemini CLI reads MCP servers from ~/.gemini/settings.json (user-level)
779
- const geminiDir = path.join(os.homedir(), ".gemini");
780
- const settingsPath = path.join(geminiDir, "settings.json");
781
- await fs.mkdir(geminiDir, { recursive: true });
782
- let config = {};
783
- try {
784
- const existing = await fs.readFile(settingsPath, "utf-8");
785
- config = JSON.parse(existing);
786
- }
787
- catch {
788
- // File doesn't exist or is invalid — start fresh
789
- }
790
- const servers = (config.mcpServers ?? {});
791
- servers.gnosys = gnosysMcpServerEntry();
792
- config.mcpServers = servers;
793
- await fs.writeFile(settingsPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
738
+ const settingsPath = path.join(os.homedir(), ".gemini", "settings.json");
739
+ await fs.mkdir(path.dirname(settingsPath), { recursive: true });
740
+ await installStdioMcpJson(settingsPath);
794
741
  return { success: true, message: "Gemini CLI MCP config updated (~/.gemini/settings.json)" };
795
742
  }
796
743
  case "antigravity": {
797
- // Antigravity reads MCP servers from ~/.gemini/antigravity/mcp_config.json
798
- // (separate file from Gemini CLI's settings.json, even though they share the parent dir)
799
- const antigravityDir = path.join(os.homedir(), ".gemini", "antigravity");
800
- const configPath = path.join(antigravityDir, "mcp_config.json");
801
- await fs.mkdir(antigravityDir, { recursive: true });
802
- let config = {};
803
- try {
804
- const existing = await fs.readFile(configPath, "utf-8");
805
- config = JSON.parse(existing);
806
- }
807
- catch {
808
- // File doesn't exist or is invalid — start fresh
809
- }
810
- const servers = (config.mcpServers ?? {});
811
- servers.gnosys = gnosysMcpServerEntry();
812
- config.mcpServers = servers;
813
- await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
814
- return { success: true, message: "Antigravity MCP config updated (~/.gemini/antigravity/mcp_config.json)" };
744
+ const configPath = path.join(os.homedir(), ".gemini", "antigravity", "mcp_config.json");
745
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
746
+ await installStdioMcpJson(configPath);
747
+ return {
748
+ success: true,
749
+ message: "Antigravity MCP config updated (~/.gemini/antigravity/mcp_config.json)",
750
+ };
815
751
  }
816
752
  case "grok-build": {
817
- // v5.9.4 Bug 12 — Grok Build reads its MCP servers from a
818
- // `[mcp.<name>]` block in ~/.grok/config.toml. We never clobber
819
- // unrelated TOML content (per deci-046 read-then-merge rule); the
820
- // helper preserves every line outside the `[mcp.gnosys]` block.
753
+ // v5.9.4 Bug 12 — Grok Build reads MCP from `[mcp_servers.<name>]`
754
+ // in ~/.grok/config.toml (not `[mcp.<name>]`). We never clobber
755
+ // unrelated TOML content (per deci-046 read-then-merge rule).
821
756
  const grokDir = path.join(os.homedir(), ".grok");
822
757
  const configPath = path.join(grokDir, "config.toml");
823
758
  await fs.mkdir(grokDir, { recursive: true });
@@ -829,32 +764,16 @@ export async function setupIDE(ide, projectDir) {
829
764
  // File doesn't exist yet — start fresh
830
765
  }
831
766
  const updated = upsertGrokMcpBlock(existing, "gnosys", {
832
- ...gnosysMcpServerEntry(),
767
+ ...gnosysStdioMcpEntry(),
833
768
  startup_timeout_sec: 90,
834
769
  });
835
770
  await fs.writeFile(configPath, updated, "utf-8");
836
771
  return { success: true, message: "Grok Build MCP config updated (~/.grok/config.toml)" };
837
772
  }
838
773
  case "claude-desktop": {
839
- // Claude Desktop reads MCP servers from claude_desktop_config.json
840
- // in a platform-specific app data directory. Distinct from Claude
841
- // Code CLI which uses `claude mcp add`.
842
774
  const configPath = getClaudeDesktopConfigPath();
843
- const configDir = path.dirname(configPath);
844
- await fs.mkdir(configDir, { recursive: true });
845
- let config = {};
846
- try {
847
- const existing = await fs.readFile(configPath, "utf-8");
848
- config = JSON.parse(existing);
849
- }
850
- catch {
851
- // File doesn't exist or is invalid — start fresh
852
- }
853
- const servers = (config.mcpServers ?? {});
854
- servers.gnosys = gnosysMcpServerEntry();
855
- config.mcpServers = servers;
856
- await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
857
- // Display path with ~ prefix when inside HOME for clarity
775
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
776
+ await installStdioMcpJson(configPath);
858
777
  const home = os.homedir();
859
778
  const displayPath = configPath.startsWith(home)
860
779
  ? configPath.replace(home, "~")
@@ -864,9 +783,8 @@ export async function setupIDE(ide, projectDir) {
864
783
  message: `Claude Desktop MCP config updated (${displayPath}). Restart Claude Desktop for the change to take effect.`,
865
784
  };
866
785
  }
867
- default:
868
- return { success: false, message: `Unknown IDE: ${ide}` };
869
786
  }
787
+ return { success: false, message: `Unhandled IDE: ${canonical}` };
870
788
  }
871
789
  catch (err) {
872
790
  const msg = err instanceof Error ? err.message : String(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnosys",
3
- "version": "5.11.2",
3
+ "version": "5.11.4",
4
4
  "description": "Gnosys — Persistent Memory for AI Agents. Sandbox-first runtime, central SQLite brain, federated search, Dream Mode, Web Knowledge Base, Obsidian export.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -34,7 +34,7 @@
34
34
  "docs:mcp-tools": "node scripts/gen-mcp-tools.mjs --write",
35
35
  "docs:cli": "node scripts/gen-cli-docs.mjs --write",
36
36
  "postinstall": "node dist/postinstall.js || true",
37
- "prepublishOnly": "npm run build:publish"
37
+ "prepublishOnly": "npm run build:publish && node -e \"const fs=require('fs');for (const f of ['dist/cli.js','dist/index.js']) fs.chmodSync(f,0o755)\""
38
38
  },
39
39
  "dependencies": {
40
40
  "@anthropic-ai/sdk": "^0.78.0",