@upturtle/wizard 0.2.1 → 0.2.2

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/README.md CHANGED
@@ -8,7 +8,7 @@ npx @upturtle/wizard
8
8
 
9
9
  The installer:
10
10
 
11
- 1. Detects which coding agent(s) you have installed (Claude Code, Cursor, Antigravity, OpenCode, Zed, Claude Desktop).
11
+ 1. Detects which coding agent(s) you have installed (Claude Code, Cursor, OpenAI Codex CLI, Antigravity, OpenCode, Zed, Claude Desktop).
12
12
  2. Shows a checkbox list — `↑`/`↓` to move, space to toggle, `a` to toggle all, enter to confirm.
13
13
  3. Opens UpTurtle in your browser to authenticate and mint a scoped personal access token.
14
14
  4. Writes the MCP server entry into each selected agent's config.
@@ -23,7 +23,7 @@ By default the wizard writes to your **user-wide** config — the MCP server is
23
23
  npx @upturtle/wizard --project
24
24
  ```
25
25
 
26
- Project scope is supported for `claude-code`, `cursor`, `opencode`, and `zed`. Antigravity and Claude Desktop don't have a project-level config format.
26
+ Project scope is supported for `claude-code`, `cursor`, `opencode`, and `zed`. Antigravity, Codex CLI, and Claude Desktop don't have a project-level config format.
27
27
 
28
28
  ## Flags
29
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upturtle/wizard",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Connects your coding agent to UpTurtle's MCP server. Detects the agent, mints a scoped PAT via browser handshake, and writes the right MCP config.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agents.mjs CHANGED
@@ -14,6 +14,13 @@ export const AGENTS = [
14
14
  writer: "claude-code",
15
15
  scopes: ["user", "project"],
16
16
  },
17
+ {
18
+ id: "codex",
19
+ label: "OpenAI Codex CLI",
20
+ detect: () => isOnPath("codex") || existsSync(codexConfigPath()),
21
+ writer: "codex",
22
+ scopes: ["user"],
23
+ },
17
24
  {
18
25
  id: "cursor",
19
26
  label: "Cursor",
@@ -78,6 +85,10 @@ export function antigravityConfigPath() {
78
85
  return join(homedir(), ".gemini", "antigravity", "mcp_config.json");
79
86
  }
80
87
 
88
+ export function codexConfigPath() {
89
+ return join(homedir(), ".codex", "config.toml");
90
+ }
91
+
81
92
  export function opencodeConfigPath() {
82
93
  return join(homedir(), ".config", "opencode", "opencode.json");
83
94
  }
package/src/index.mjs CHANGED
@@ -271,7 +271,7 @@ Options:
271
271
  --help, -h Show this message.
272
272
 
273
273
  Project scope is supported for: claude-code, cursor, opencode, zed.
274
- Antigravity and Claude Desktop only support user scope (their config formats
275
- don't have a project-level alternative).
274
+ Antigravity, Codex CLI, and Claude Desktop only support user scope
275
+ (their config formats don't have a project-level alternative).
276
276
  `);
277
277
  }
package/src/writers.mjs CHANGED
@@ -5,6 +5,7 @@ import { spawnSync } from "node:child_process";
5
5
  import {
6
6
  antigravityConfigPath,
7
7
  claudeDesktopConfigPath,
8
+ codexConfigPath,
8
9
  opencodeConfigPath,
9
10
  zedConfigPath,
10
11
  } from "./agents.mjs";
@@ -14,6 +15,7 @@ const SERVER_KEY = "upturtle";
14
15
  export const WRITERS = {
15
16
  "claude-code": writeClaudeCode,
16
17
  "cursor": writeCursor,
18
+ "codex": writeCodex,
17
19
  "antigravity": writeAntigravity,
18
20
  "opencode": writeOpenCode,
19
21
  "zed": writeZed,
@@ -135,6 +137,60 @@ function writeZed({ host, secret, scope }) {
135
137
  });
136
138
  }
137
139
 
140
+ function writeCodex({ host, secret, scope }) {
141
+ if (scope === "project") {
142
+ throw new Error(
143
+ "Codex CLI's MCP config is user-wide only (`~/.codex/config.toml`). " +
144
+ "Re-run without --scope=project for this agent.");
145
+ }
146
+ const path = codexConfigPath();
147
+ const block =
148
+ `[mcp_servers.${SERVER_KEY}]\n` +
149
+ `command = "npx"\n` +
150
+ `args = ["-y", "mcp-remote", "${host}/mcp", "--header", "Authorization: Bearer ${secret}"]\n`;
151
+
152
+ let existing = "";
153
+ if (existsSync(path)) {
154
+ existing = readFileSync(path, "utf8");
155
+ }
156
+ const updated = upsertTomlTable(existing, `mcp_servers.${SERVER_KEY}`, block);
157
+
158
+ mkdirSync(dirname(path), { recursive: true });
159
+ writeFileSync(path, updated, "utf8");
160
+ return { configPath: path };
161
+ }
162
+
163
+ // Naive TOML table upsert: replaces the `[<header>]` block (if present),
164
+ // otherwise appends. The block runs from its `[header]` line to the next
165
+ // top-level `[` or end-of-file. Good enough for our single-block writes —
166
+ // avoids pulling in a TOML parser dependency.
167
+ function upsertTomlTable(source, header, block) {
168
+ const headerLine = `[${header}]`;
169
+ const lines = source.split("\n");
170
+ const startIdx = lines.findIndex((line) => line.trim() === headerLine);
171
+
172
+ const trimmedBlock = block.endsWith("\n") ? block : block + "\n";
173
+
174
+ if (startIdx === -1) {
175
+ const prefix = source.length === 0 || source.endsWith("\n") ? source : source + "\n";
176
+ const separator = prefix.endsWith("\n\n") || prefix.length === 0 ? "" : "\n";
177
+ return prefix + separator + trimmedBlock;
178
+ }
179
+
180
+ let endIdx = lines.length;
181
+ for (let i = startIdx + 1; i < lines.length; i++) {
182
+ if (/^\s*\[/.test(lines[i])) {
183
+ endIdx = i;
184
+ break;
185
+ }
186
+ }
187
+ const before = lines.slice(0, startIdx).join("\n");
188
+ const after = lines.slice(endIdx).join("\n");
189
+ const beforePart = before.length === 0 ? "" : before.endsWith("\n") ? before : before + "\n";
190
+ const afterPart = after.length === 0 ? "" : after.startsWith("\n") ? after : "\n" + after;
191
+ return beforePart + trimmedBlock + afterPart;
192
+ }
193
+
138
194
  function writeClaudeDesktop({ host, secret, scope }) {
139
195
  if (scope === "project") {
140
196
  throw new Error(