diff --git a/apps/cli/src/commands/install.ts b/apps/cli/src/commands/install.ts index 5476dc9..0b9238c 100644 --- a/apps/cli/src/commands/install.ts +++ b/apps/cli/src/commands/install.ts @@ -68,7 +68,6 @@ function readClaudeConfig(): Record { /** * Create a timestamped backup of ~/.claude.json before any write. - * Keeps the last 3 backups to avoid clutter. */ function backupClaudeConfig(): void { if (!existsSync(CLAUDE_CONFIG)) return; @@ -80,30 +79,52 @@ function backupClaudeConfig(): void { } /** - * Sanity-check: abort if we're about to lose MCP servers that existed - * on disk but are missing from the object we're about to write. + * Atomic read-merge-write: re-reads ~/.claude.json at write time and + * patches ONLY the `claudemesh` MCP entry. Never touches other keys. + * Returns the action taken ("added" | "updated" | "unchanged"). */ -function assertNoMcpLoss(next: Record): void { - if (!existsSync(CLAUDE_CONFIG)) return; - const prev = readClaudeConfig(); - const prevServers = Object.keys( - (prev.mcpServers as Record) ?? {}, - ); - const nextServers = Object.keys( - (next.mcpServers as Record) ?? {}, - ); - const lost = prevServers.filter((k) => !nextServers.includes(k)); - if (lost.length > 0) { - throw new Error( - `Aborting write: would lose ${lost.length} existing MCP server(s): ${lost.join(", ")}. ` + - `This is a bug — please report it. A backup was saved in ~/.claude/backups/`, - ); +function patchMcpServer(entry: McpEntry): "added" | "updated" | "unchanged" { + backupClaudeConfig(); + const cfg = readClaudeConfig(); + const servers = + ((cfg.mcpServers as Record) ?? {}); + if (!cfg.mcpServers) cfg.mcpServers = servers; + + const existing = servers[MCP_NAME]; + let action: "added" | "updated" | "unchanged"; + if (!existing) { + servers[MCP_NAME] = entry; + action = "added"; + } else if (entriesEqual(existing, entry)) { + return "unchanged"; + } else { + servers[MCP_NAME] = entry; + action = "updated"; } + + flushClaudeConfig(cfg); + return action; } -function writeClaudeConfig(obj: Record): void { +/** + * Atomic read-merge-write: re-reads ~/.claude.json at write time and + * removes ONLY the `claudemesh` MCP entry. Never touches other keys. + * Returns true if an entry was removed. + */ +function removeMcpServer(): boolean { + if (!existsSync(CLAUDE_CONFIG)) return false; backupClaudeConfig(); - assertNoMcpLoss(obj); + const cfg = readClaudeConfig(); + const servers = cfg.mcpServers as Record | undefined; + if (!servers || !(MCP_NAME in servers)) return false; + delete servers[MCP_NAME]; + cfg.mcpServers = servers; + flushClaudeConfig(cfg); + return true; +} + +/** Low-level write — callers must backup + merge first. */ +function flushClaudeConfig(obj: Record): void { mkdirSync(dirname(CLAUDE_CONFIG), { recursive: true }); writeFileSync( CLAUDE_CONFIG, @@ -117,6 +138,7 @@ function writeClaudeConfig(obj: Record): void { } } + /** Check `bun` is on PATH — OS-agnostic, node:child_process. */ function bunAvailable(): boolean { const res = @@ -269,24 +291,8 @@ export function runInstall(args: string[] = []): void { process.exit(1); } - const cfg = readClaudeConfig(); - const servers = - ((cfg.mcpServers ??= {}) as Record) ?? {}; const desired = buildMcpEntry(entry); - const existing = servers[MCP_NAME]; - let action: "added" | "updated" | "unchanged"; - if (!existing) { - servers[MCP_NAME] = desired; - action = "added"; - } else if (entriesEqual(existing, desired)) { - action = "unchanged"; - } else { - servers[MCP_NAME] = desired; - action = "updated"; - } - cfg.mcpServers = servers; - - writeClaudeConfig(cfg); + const action = patchMcpServer(desired); // Read-back verification. const verify = readClaudeConfig(); @@ -362,22 +368,11 @@ export function runUninstall(): void { console.log("claudemesh uninstall"); console.log("--------------------"); - // MCP entry - if (existsSync(CLAUDE_CONFIG)) { - const cfg = readClaudeConfig(); - const servers = cfg.mcpServers as - | Record - | undefined; - if (servers && MCP_NAME in servers) { - delete servers[MCP_NAME]; - cfg.mcpServers = servers; - writeClaudeConfig(cfg); - console.log(`✓ MCP server "${MCP_NAME}" removed`); - } else { - console.log(`· MCP server "${MCP_NAME}" not present`); - } + // MCP entry — only removes claudemesh, never touches other servers. + if (removeMcpServer()) { + console.log(`✓ MCP server "${MCP_NAME}" removed`); } else { - console.log(`· no ${CLAUDE_CONFIG} — MCP entry skipped`); + console.log(`· MCP server "${MCP_NAME}" not present`); } // Hooks diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 66fac57..8f83169 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,8 +1,5 @@ import type { NextConfig } from "next"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -const { withPayload } = require("@payloadcms/next/withPayload"); - import env from "./env.config"; const INTERNAL_PACKAGES = [ @@ -118,4 +115,4 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({ enabled: env.ANALYZE, }); -export default withPayload(withBundleAnalyzer(config)); +export default withBundleAnalyzer(config); diff --git a/apps/web/src/app/(payload)/layout.tsx b/apps/web/src/app/(payload)/layout.tsx deleted file mode 100644 index bdcfe53..0000000 --- a/apps/web/src/app/(payload)/layout.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import "@payloadcms/next/css"; -import type { ReactNode } from "react"; - -export const metadata = { - title: "CMS — claudemesh", -}; - -export default function PayloadLayout({ children }: { children: ReactNode }) { - return ( - - {children} - - ); -} diff --git a/apps/web/src/app/(payload)/payload/[[...segments]]/page.tsx b/apps/web/src/app/(payload)/payload/[[...segments]]/page.tsx deleted file mode 100644 index 2b49cdd..0000000 --- a/apps/web/src/app/(payload)/payload/[[...segments]]/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { redirect } from "next/navigation"; - -// Payload admin panel disabled in production (standalone output -// doesn't support Payload's server init). Content managed via -// local dev server or API. -export default function PayloadAdminRedirect() { - redirect("/"); -} diff --git a/apps/web/src/app/(payload)/payload/importMap.js b/apps/web/src/app/(payload)/payload/importMap.js deleted file mode 100644 index d275fa9..0000000 --- a/apps/web/src/app/(payload)/payload/importMap.js +++ /dev/null @@ -1,51 +0,0 @@ -import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc' -import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc' -import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc' -import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client' -import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc' - -export const importMap = { - "@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e, - "@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e, - "@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e, - "@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#UploadFeatureClient": UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#RelationshipFeatureClient": RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#ChecklistFeatureClient": ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#SuperscriptFeatureClient": SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#SubscriptFeatureClient": SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864, - "@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 -}