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.

55 lines · 2.7 KBRaw History
1import { spawn } from "node:child_process";
2
3/**
4 * Codex's `diffinery` MCP server, pointed at a self-hosted Diffinery by `diffinery setup`. The Codex plugin's
5 * server is https://diffinery.com/mcp (Codex has no plugin settings to change it), and a server of the same
6 * name in Codex's own config replaces it, so for another instance this adds that server with
7 * `codex mcp add`. Nothing happens for diffinery.com, or where Codex isn't installed.
8 */
9
10const DEFAULT_URL = "https://diffinery.com";
11
12function codex(env: NodeJS.ProcessEnv, args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {
13 return new Promise((resolve) => {
14 let child;
15 try {
16 child = spawn("codex", args, { env, stdio: ["ignore", "pipe", "pipe"] });
17 } catch (e) {
18 return resolve({ code: 127, stdout: "", stderr: (e as Error).message });
19 }
20 let stdout = "";
21 let stderr = "";
22 child.stdout.on("data", (d: Buffer) => (stdout += d.toString()));
23 child.stderr.on("data", (d: Buffer) => (stderr += d.toString()));
24 child.on("error", (e) => resolve({ code: 127, stdout, stderr: e.message }));
25 child.on("close", (code) => resolve({ code: code ?? 1, stdout, stderr }));
26 });
27}
28
29export interface CodexResult {
30 ok: boolean;
31 message: string;
32}
33
34/** Null when there's nothing to do: diffinery.com, DIFFINERY_NO_CODEX, or no `codex` on PATH. */
35export async function setupCodexMcp(env: NodeJS.ProcessEnv, url: string): Promise<CodexResult | null> {
36 if (url === DEFAULT_URL || env.DIFFINERY_NO_CODEX) return null;
37 const want = `${url}/mcp`;
38 // The server Codex uses now: the user's own, else the plugin's, else none (exit status 1).
39 const got = await codex(env, ["mcp", "get", "diffinery", "--json"]);
40 if (got.code === 127) return null;
41 let bearer: string | null = null;
42 if (got.code === 0) {
43 try {
44 const t = (JSON.parse(got.stdout) as { transport?: { url?: unknown; bearer_token_env_var?: unknown } }).transport ?? {};
45 if (t.url === want) return { ok: true, message: `the diffinery MCP server is ${want}` };
46 // A headless setup that sends DIFFINERY_TOKEN keeps doing so.
47 if (typeof t.bearer_token_env_var === "string") bearer = t.bearer_token_env_var;
48 } catch {
49 // An answer we can't read: add the server all the same.
50 }
51 }
52 const add = await codex(env, ["mcp", "add", "diffinery", "--url", want, ...(bearer ? ["--bearer-token-env-var", bearer] : [])]);
53 if (add.code !== 0) return { ok: false, message: `codex mcp add failed: ${(add.stderr.trim().split("\n")[0] || `exit status ${add.code}`).slice(0, 200)}` };
54 return { ok: true, message: `the diffinery MCP server is now ${want} (sign in with: codex mcp login diffinery)` };
55}