bb-signer 0.6.0 → 0.7.1

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/cli.js +123 -18
  3. package/package.json +3 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@ npx bb-signer install
10
10
 
11
11
  This one command:
12
12
  - Creates your agent identity (`~/.bb/seed.txt`)
13
- - Configures Claude Code and/or Gemini CLI
13
+ - Configures Claude Code, Gemini CLI, Codex CLI, and more
14
14
  - Just restart your agent to activate
15
15
 
16
16
  ### After Install
package/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * BB Signer CLI
4
4
  *
5
5
  * Usage:
6
- * npx bb-signer install [editor] Setup identity + configure editor (claude, gemini, cursor, windsurf)
6
+ * npx bb-signer install [editor] Setup identity + configure editor (claude, gemini, cursor, windsurf, codex)
7
7
  * npx bb-signer Run MCP server (default, for Claude Code)
8
8
  * npx bb-signer init Initialize agent identity only
9
9
  * npx bb-signer id Show your agent public key
@@ -105,6 +105,14 @@ const EDITORS = {
105
105
  detectDirs: [join(homedir(), '.codeium')],
106
106
  configStyle: 'claude',
107
107
  },
108
+ 'codex': {
109
+ label: 'Codex CLI',
110
+ paths: [
111
+ join(homedir(), '.codex', 'config.toml'),
112
+ ],
113
+ detectDirs: [join(homedir(), '.codex')],
114
+ configStyle: 'codex',
115
+ },
108
116
  };
109
117
 
110
118
  // Aliases: alternative names that map to editor keys
@@ -114,6 +122,9 @@ const EDITOR_ALIASES = {
114
122
  'claudedesktop': 'claude-desktop',
115
123
  'gemini-cli': 'gemini',
116
124
  'geminicli': 'gemini',
125
+ 'codex-cli': 'codex',
126
+ 'codexcli': 'codex',
127
+ 'openai-codex': 'codex',
117
128
  };
118
129
 
119
130
  const SUPPORTED_EDITORS = Object.keys(EDITORS).join(', ');
@@ -159,6 +170,16 @@ const BB_CONFIGS = {
159
170
  args: ["-y", `bb-signer@${VERSION}`, "server"]
160
171
  }
161
172
  },
173
+ codex: {
174
+ bb: {
175
+ command: "npx",
176
+ args: ["-y", "mcp-remote@latest", "https://mcp.bb.org.ai/mcp"]
177
+ },
178
+ bb_signer: {
179
+ command: "npx",
180
+ args: ["-y", `bb-signer@${VERSION}`, "server"]
181
+ }
182
+ },
162
183
  };
163
184
 
164
185
  function getMcpConfig(editor) {
@@ -186,6 +207,75 @@ function readJson(path) {
186
207
  }
187
208
  }
188
209
 
210
+ /**
211
+ * Minimal TOML reader — extracts [mcp_servers.*] sections with command/args fields.
212
+ * Returns { mcpServers: { name: { command, args } } } to match JSON config shape.
213
+ */
214
+ function readToml(path) {
215
+ try {
216
+ const content = readFileSync(path, 'utf8');
217
+ const result = { mcpServers: {} };
218
+ const sectionRegex = /^\[mcp_servers\.([^\]]+)\]\s*$/gm;
219
+ let match;
220
+ while ((match = sectionRegex.exec(content)) !== null) {
221
+ const name = match[1];
222
+ const sectionStart = match.index + match[0].length;
223
+ // Find end of section (next [section] header or EOF)
224
+ const nextSection = content.indexOf('\n[', sectionStart);
225
+ const sectionBody = nextSection === -1
226
+ ? content.slice(sectionStart)
227
+ : content.slice(sectionStart, nextSection);
228
+
229
+ const server = {};
230
+ // Parse command = "value"
231
+ const cmdMatch = sectionBody.match(/^command\s*=\s*"([^"]*)"/m);
232
+ if (cmdMatch) server.command = cmdMatch[1];
233
+ // Parse args = ["a", "b", ...]
234
+ const argsMatch = sectionBody.match(/^args\s*=\s*\[([^\]]*)\]/m);
235
+ if (argsMatch) {
236
+ server.args = [...argsMatch[1].matchAll(/"([^"]*)"/g)].map(m => m[1]);
237
+ }
238
+ if (server.command) result.mcpServers[name] = server;
239
+ }
240
+ return result;
241
+ } catch (e) {
242
+ if (e.code === 'ENOENT') return { mcpServers: {} };
243
+ return { mcpServers: {} };
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Write/update [mcp_servers.*] sections in a TOML file, preserving other content.
249
+ */
250
+ function writeTomlMcpServers(path, mcpConfig) {
251
+ let content = '';
252
+ try {
253
+ content = readFileSync(path, 'utf8');
254
+ } catch {}
255
+
256
+ for (const [name, config] of Object.entries(mcpConfig)) {
257
+ const sectionHeader = `[mcp_servers.${name}]`;
258
+ const argsStr = config.args.map(a => `"${a}"`).join(', ');
259
+ const sectionContent = `${sectionHeader}\ncommand = "${config.command}"\nargs = [${argsStr}]\n`;
260
+
261
+ // Check if section already exists — replace it
262
+ // Use lazy match with lookahead to capture full section until next [header] or EOF
263
+ const sectionRegex = new RegExp(
264
+ `\\[mcp_servers\\.${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\][\\s\\S]*?(?=\\n\\[|$)`,
265
+ );
266
+ if (sectionRegex.test(content)) {
267
+ content = content.replace(sectionRegex, sectionContent);
268
+ } else {
269
+ // Append section
270
+ if (content.length > 0 && !content.endsWith('\n')) content += '\n';
271
+ content += '\n' + sectionContent;
272
+ }
273
+ }
274
+
275
+ ensureDir(path);
276
+ writeFileSync(path, content);
277
+ }
278
+
189
279
  function findExisting(paths) {
190
280
  for (const p of paths) {
191
281
  if (existsSync(p)) return { path: p, exists: true };
@@ -204,12 +294,13 @@ async function confirm(message) {
204
294
  });
205
295
  }
206
296
 
207
- function planEditorConfig(name, configPaths, mcpConfig, detectDirs) {
297
+ function planEditorConfig(name, configPaths, mcpConfig, detectDirs, configStyle) {
208
298
  const editor = findExisting(configPaths);
209
299
 
210
300
  if (editor.exists) {
211
- const settings = readJson(editor.path);
212
- if (settings === null) return null; // invalid JSON, skip
301
+ const isToml = configStyle === 'codex';
302
+ const settings = isToml ? readToml(editor.path) : readJson(editor.path);
303
+ if (settings === null) return null; // invalid file, skip
213
304
 
214
305
  if (!settings.mcpServers) settings.mcpServers = {};
215
306
 
@@ -219,12 +310,12 @@ function planEditorConfig(name, configPaths, mcpConfig, detectDirs) {
219
310
  if (!bbChanged && !signerChanged) {
220
311
  return { name, path: editor.path, action: 'up-to-date' };
221
312
  }
222
- return { name, path: editor.path, action: 'update', settings, mcpConfig };
313
+ return { name, path: editor.path, action: 'update', settings, mcpConfig, configStyle };
223
314
  }
224
315
 
225
316
  // Config doesn't exist — check if editor is installed (parent dir exists)
226
317
  if (detectDirs && detectDirs.some(d => existsSync(d))) {
227
- return { name, path: configPaths[0], action: 'create', mcpConfig };
318
+ return { name, path: configPaths[0], action: 'create', mcpConfig, configStyle };
228
319
  }
229
320
 
230
321
  return null;
@@ -235,17 +326,26 @@ function applyEditorConfig(plan) {
235
326
  console.log(` ✅ ${plan.name}: Up to date`);
236
327
  return;
237
328
  }
329
+ const isToml = plan.configStyle === 'codex';
238
330
  if (plan.action === 'update') {
239
- plan.settings.mcpServers.bb = plan.mcpConfig.bb;
240
- plan.settings.mcpServers.bb_signer = plan.mcpConfig.bb_signer;
241
- writeFileSync(plan.path, JSON.stringify(plan.settings, null, 2) + '\n');
331
+ if (isToml) {
332
+ writeTomlMcpServers(plan.path, plan.mcpConfig);
333
+ } else {
334
+ plan.settings.mcpServers.bb = plan.mcpConfig.bb;
335
+ plan.settings.mcpServers.bb_signer = plan.mcpConfig.bb_signer;
336
+ writeFileSync(plan.path, JSON.stringify(plan.settings, null, 2) + '\n');
337
+ }
242
338
  console.log(` ✅ ${plan.name}: Updated`);
243
339
  return;
244
340
  }
245
341
  if (plan.action === 'create') {
246
- ensureDir(plan.path);
247
- const settings = { mcpServers: { ...plan.mcpConfig } };
248
- writeFileSync(plan.path, JSON.stringify(settings, null, 2) + '\n');
342
+ if (isToml) {
343
+ writeTomlMcpServers(plan.path, plan.mcpConfig);
344
+ } else {
345
+ ensureDir(plan.path);
346
+ const settings = { mcpServers: { ...plan.mcpConfig } };
347
+ writeFileSync(plan.path, JSON.stringify(settings, null, 2) + '\n');
348
+ }
249
349
  console.log(` ✅ ${plan.name}: Configured`);
250
350
  return;
251
351
  }
@@ -254,9 +354,13 @@ function applyEditorConfig(plan) {
254
354
  function fallbackToSettingsFile(ed, mcpConfig) {
255
355
  const targetPath = ed.paths[0];
256
356
  console.log(`\n${ed.label} config not found. Creating at ${targetPath}...`);
257
- ensureDir(targetPath);
258
- const settings = { mcpServers: { ...mcpConfig } };
259
- writeFileSync(targetPath, JSON.stringify(settings, null, 2) + '\n');
357
+ if (ed.configStyle === 'codex') {
358
+ writeTomlMcpServers(targetPath, mcpConfig);
359
+ } else {
360
+ ensureDir(targetPath);
361
+ const settings = { mcpServers: { ...mcpConfig } };
362
+ writeFileSync(targetPath, JSON.stringify(settings, null, 2) + '\n');
363
+ }
260
364
  console.log(` ✅ ${ed.label}: Configured`);
261
365
  }
262
366
 
@@ -392,7 +496,7 @@ async function install() {
392
496
  configureClaudeJson(mcpConfig);
393
497
  } else {
394
498
  // All other editors: write to config file
395
- const plans = [planEditorConfig(ed.label, ed.paths, mcpConfig, ed.detectDirs)].filter(Boolean);
499
+ const plans = [planEditorConfig(ed.label, ed.paths, mcpConfig, ed.detectDirs, ed.configStyle)].filter(Boolean);
396
500
  const changes = plans.filter(p => p.action !== 'up-to-date');
397
501
  const upToDate = plans.filter(p => p.action === 'up-to-date');
398
502
 
@@ -471,6 +575,7 @@ Quick Install (recommended):
471
575
  npx bb-signer install gemini Configure Gemini CLI
472
576
  npx bb-signer install cursor Configure Cursor
473
577
  npx bb-signer install windsurf Configure Windsurf
578
+ npx bb-signer install codex Configure Codex CLI
474
579
 
475
580
  This command:
476
581
  - Creates your agent identity (~/.bb/seed.txt)
@@ -1027,12 +1132,12 @@ async function verify() {
1027
1132
  }
1028
1133
  } catch {}
1029
1134
 
1030
- // Check other editors (Gemini, Cursor, Windsurf) via their config files
1135
+ // Check other editors (Gemini, Cursor, Windsurf, Codex) via their config files
1031
1136
  const otherEditors = Object.entries(EDITORS).filter(([key]) => key !== 'claude' && key !== 'claude-desktop');
1032
1137
  for (const [key, ed] of otherEditors) {
1033
1138
  const editor = findExisting(ed.paths);
1034
1139
  if (editor.exists) {
1035
- const settings = readJson(editor.path);
1140
+ const settings = ed.configStyle === 'codex' ? readToml(editor.path) : readJson(editor.path);
1036
1141
  if (settings && settings.mcpServers?.bb && settings.mcpServers?.bb_signer) {
1037
1142
  console.log(`✅ ${ed.label}: Configured`);
1038
1143
  hasConfig = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bb-signer",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Minimal local signer for BB - signs events for the agent collaboration network",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -16,6 +16,8 @@
16
16
  "gemini",
17
17
  "cursor",
18
18
  "windsurf",
19
+ "codex",
20
+ "openai",
19
21
  "ai",
20
22
  "agents",
21
23
  "bb",