Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

71 lines · 3.9 KBRaw History
1import { spawn } from "node:child_process";
2import { appendFile, mkdir, readFile } from "node:fs/promises";
3import { homedir } from "node:os";
4import path from "node:path";
5
6/**
7 * Codex's `diffinery` MCP server, pointed at a self-hosted Diffinery by `diffinery setup`. The Codex plugin's
8 * server is https://diffinery.com/mcp (Codex has no plugin settings to change it), and a server of the same
9 * name in Codex's own config replaces it, so for another instance this adds that server to config.toml.
10 * Not with `codex mcp add`, which starts a browser sign-in for a server that offers OAuth and waits for it.
11 * Nothing happens for diffinery.com, or where Codex isn't installed.
12 */
13
14const DEFAULT_URL = "https://diffinery.com";
15
16function codex(env: NodeJS.ProcessEnv, args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {
17 return new Promise((resolve) => {
18 let child;
19 try {
20 child = spawn("codex", args, { env, stdio: ["ignore", "pipe", "pipe"] });
21 } catch (e) {
22 return resolve({ code: 127, stdout: "", stderr: (e as Error).message });
23 }
24 let stdout = "";
25 let stderr = "";
26 child.stdout.on("data", (d: Buffer) => (stdout += d.toString()));
27 child.stderr.on("data", (d: Buffer) => (stderr += d.toString()));
28 child.on("error", (e) => resolve({ code: 127, stdout, stderr: e.message }));
29 child.on("close", (code) => resolve({ code: code ?? 1, stdout, stderr }));
30 });
31}
32
33export interface CodexResult {
34 ok: boolean;
35 message: string;
36}
37
38/** Null when there's nothing to do: diffinery.com, DIFFINERY_NO_CODEX, or no `codex` on PATH. */
39export async function setupCodexMcp(env: NodeJS.ProcessEnv, url: string): Promise<CodexResult | null> {
40 if (url === DEFAULT_URL || env.DIFFINERY_NO_CODEX) return null;
41 const want = `${url}/mcp`;
42 // The server Codex uses now: the user's own, else the plugin's, else none (exit status 1).
43 const got = await codex(env, ["mcp", "get", "diffinery", "--json"]);
44 if (got.code === 127) return null;
45 let bearer: string | null = null;
46 if (got.code === 0) {
47 try {
48 const t = (JSON.parse(got.stdout) as { transport?: { url?: unknown; bearer_token_env_var?: unknown } }).transport ?? {};
49 if (t.url === want) return { ok: true, message: `the diffinery MCP server is ${want}` };
50 // A headless setup that sends DIFFINERY_TOKEN keeps doing so.
51 if (typeof t.bearer_token_env_var === "string") bearer = t.bearer_token_env_var;
52 } catch {
53 // An answer we can't read: add the server all the same.
54 }
55 }
56 const home = env.CODEX_HOME && path.isAbsolute(env.CODEX_HOME) ? env.CODEX_HOME : path.join(env.HOME || homedir(), ".codex");
57 const file = path.join(home, "config.toml");
58 const text = await readFile(file, "utf8").catch(() => "");
59 // A server of the user's own goes first (codex removes it however it's written); the plugin's stays.
60 if (/^\s*\[\s*mcp_servers\s*\.\s*"?diffinery"?\s*\]/m.test(text) || /^\s*mcp_servers\s*\.\s*"?diffinery"?\s*[.=]/m.test(text)) {
61 const rm = await codex(env, ["mcp", "remove", "diffinery"]);
62 if (rm.code !== 0) return { ok: false, message: `codex mcp remove diffinery failed: ${(rm.stderr.trim().split("\n")[0] || `exit status ${rm.code}`).slice(0, 200)}` };
63 }
64 const now = await readFile(file, "utf8").catch(() => "");
65 const table = `[mcp_servers.diffinery]\nurl = ${JSON.stringify(want)}\n${bearer ? `bearer_token_env_var = ${JSON.stringify(bearer)}\n` : ""}`;
66 await mkdir(home, { recursive: true });
67 await appendFile(file, `${now && !now.endsWith("\n") ? "\n" : ""}${now ? "\n" : ""}${table}`);
68 const check = await codex(env, ["mcp", "get", "diffinery", "--json"]);
69 if (!check.stdout.includes(JSON.stringify(want).slice(1, -1))) return { ok: false, message: `Codex doesn't read the diffinery server added to ${file}: check that file` };
70 return { ok: true, message: `the diffinery MCP server is now ${want} (sign in with: codex mcp login diffinery)` };
71}