bb-signer 0.2.8 → 0.3.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.
Files changed (4) hide show
  1. package/cli.js +321 -114
  2. package/crypto.js +10 -2
  3. package/index.js +32 -5
  4. package/package.json +4 -2
package/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * BB Signer CLI
4
4
  *
5
5
  * Usage:
6
- * npx bb-signer install One-liner: setup identity + add to Claude/Gemini
6
+ * npx bb-signer install [editor] Setup identity + configure editor (claude, gemini, cursor, windsurf)
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
@@ -25,50 +25,106 @@
25
25
  */
26
26
 
27
27
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
28
+ import { createInterface } from 'readline';
28
29
  import { homedir } from 'os';
29
30
  import { join, dirname } from 'path';
31
+ import { fileURLToPath } from 'url';
30
32
  import { identityExists, initIdentity, loadIdentity, getOrCreateIdentity, loadConfig, saveConfig, getProxyUrl } from './identity.js';
31
33
  import { signEvent, cleanEvent, signMessage } from './crypto.js';
32
34
  import { submitToRelay } from './submit.js';
33
35
 
34
- // Claude Code settings locations
35
- const CLAUDE_CODE_PATHS = [
36
- join(homedir(), '.claude', 'settings.json'),
37
- join(homedir(), '.config', 'claude', 'settings.json'),
38
- ];
39
-
40
- // Gemini CLI config locations
41
- const GEMINI_CLI_PATHS = [
42
- join(homedir(), '.gemini', 'settings.json'),
43
- join(homedir(), '.config', 'gemini', 'settings.json'),
44
- ];
45
-
46
- // BB MCP config for Claude Code
47
- const BB_CONFIG_CLAUDE = {
48
- bb: {
49
- command: "npx",
50
- args: ["-y", "mcp-remote@latest", "https://mcp.bb.org.ai/mcp"]
36
+ // Read version from package.json
37
+ const __dirname = dirname(fileURLToPath(import.meta.url));
38
+ const VERSION = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8')).version;
39
+
40
+ // Editor definitions: paths, MCP config template, and detection directories
41
+ const EDITORS = {
42
+ 'claude': {
43
+ label: 'Claude Code',
44
+ paths: [
45
+ join(homedir(), '.claude', 'settings.json'),
46
+ join(homedir(), '.config', 'claude', 'settings.json'),
47
+ ],
48
+ detectDirs: [join(homedir(), '.claude')],
49
+ configStyle: 'claude',
50
+ },
51
+ 'claude-desktop': {
52
+ label: 'Claude Desktop',
53
+ paths: [
54
+ join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
55
+ join(homedir(), '.config', 'claude-desktop', 'claude_desktop_config.json'),
56
+ ],
57
+ detectDirs: null,
58
+ configStyle: 'claude',
59
+ },
60
+ 'gemini': {
61
+ label: 'Gemini CLI',
62
+ paths: [
63
+ join(homedir(), '.gemini', 'settings.json'),
64
+ join(homedir(), '.config', 'gemini', 'settings.json'),
65
+ ],
66
+ detectDirs: [join(homedir(), '.gemini')],
67
+ configStyle: 'gemini',
68
+ },
69
+ 'cursor': {
70
+ label: 'Cursor',
71
+ paths: [
72
+ join(homedir(), '.cursor', 'mcp.json'),
73
+ ],
74
+ detectDirs: [join(homedir(), '.cursor')],
75
+ configStyle: 'claude',
76
+ },
77
+ 'windsurf': {
78
+ label: 'Windsurf',
79
+ paths: [
80
+ join(homedir(), '.codeium', 'windsurf', 'mcp_config.json'),
81
+ ],
82
+ detectDirs: [join(homedir(), '.codeium')],
83
+ configStyle: 'claude',
51
84
  },
52
- bb_signer: {
53
- command: "npx",
54
- args: ["-y", "bb-signer@latest", "server"]
55
- }
56
85
  };
57
86
 
58
- // BB MCP config for Gemini CLI
59
- const BB_CONFIG_GEMINI = {
60
- bb: {
61
- transportType: "stdio",
62
- command: "npx",
63
- args: ["-y", "mcp-remote@latest", "https://mcp.bb.org.ai/mcp"]
87
+ // Aliases: alternative names that map to editor keys
88
+ const EDITOR_ALIASES = {
89
+ 'claude-code': 'claude',
90
+ 'claudecode': 'claude',
91
+ 'claudedesktop': 'claude-desktop',
92
+ 'gemini-cli': 'gemini',
93
+ 'geminicli': 'gemini',
94
+ };
95
+
96
+ const SUPPORTED_EDITORS = Object.keys(EDITORS).join(', ');
97
+
98
+ // BB MCP config templates by style
99
+ const BB_CONFIGS = {
100
+ claude: {
101
+ bb: {
102
+ command: "npx",
103
+ args: ["-y", "mcp-remote@latest", "https://mcp.bb.org.ai/mcp"]
104
+ },
105
+ bb_signer: {
106
+ command: "npx",
107
+ args: ["-y", `bb-signer@${VERSION}`, "server"]
108
+ }
109
+ },
110
+ gemini: {
111
+ bb: {
112
+ transportType: "stdio",
113
+ command: "npx",
114
+ args: ["-y", "mcp-remote@latest", "https://mcp.bb.org.ai/mcp"]
115
+ },
116
+ bb_signer: {
117
+ transportType: "stdio",
118
+ command: "npx",
119
+ args: ["-y", `bb-signer@${VERSION}`, "server"]
120
+ }
64
121
  },
65
- bb_signer: {
66
- transportType: "stdio",
67
- command: "npx",
68
- args: ["-y", "bb-signer@latest", "server"]
69
- }
70
122
  };
71
123
 
124
+ function getMcpConfig(editor) {
125
+ return BB_CONFIGS[editor.configStyle];
126
+ }
127
+
72
128
  function ensureDir(filePath) {
73
129
  const dir = dirname(filePath);
74
130
  if (!existsSync(dir)) {
@@ -79,7 +135,13 @@ function ensureDir(filePath) {
79
135
  function readJson(path) {
80
136
  try {
81
137
  return JSON.parse(readFileSync(path, 'utf8'));
82
- } catch {
138
+ } catch (e) {
139
+ if (e.code === 'ENOENT') return {};
140
+ if (e instanceof SyntaxError) {
141
+ console.warn(` ⚠️ ${path} has invalid JSON — skipping to avoid data loss.`);
142
+ console.warn(` Fix the JSON manually, then re-run: npx bb-signer install`);
143
+ return null;
144
+ }
83
145
  return {};
84
146
  }
85
147
  }
@@ -91,19 +153,107 @@ function findExisting(paths) {
91
153
  return { path: paths[0], exists: false };
92
154
  }
93
155
 
94
- function install() {
95
- console.log('Installing BB for your AI agent...\n');
156
+ async function confirm(message) {
157
+ if (!process.stdin.isTTY) return true; // non-interactive: default yes
158
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
159
+ return new Promise((resolve) => {
160
+ rl.question(message, (answer) => {
161
+ rl.close();
162
+ resolve(answer.trim().toLowerCase() !== 'n');
163
+ });
164
+ });
165
+ }
166
+
167
+ function planEditorConfig(name, configPaths, mcpConfig, detectDirs) {
168
+ const editor = findExisting(configPaths);
169
+
170
+ if (editor.exists) {
171
+ const settings = readJson(editor.path);
172
+ if (settings === null) return null; // invalid JSON, skip
173
+
174
+ if (!settings.mcpServers) settings.mcpServers = {};
175
+
176
+ const bbChanged = JSON.stringify(settings.mcpServers.bb) !== JSON.stringify(mcpConfig.bb);
177
+ const signerChanged = JSON.stringify(settings.mcpServers.bb_signer) !== JSON.stringify(mcpConfig.bb_signer);
178
+
179
+ if (!bbChanged && !signerChanged) {
180
+ return { name, path: editor.path, action: 'up-to-date' };
181
+ }
182
+ return { name, path: editor.path, action: 'update', settings, mcpConfig };
183
+ }
184
+
185
+ // Config doesn't exist — check if editor is installed (parent dir exists)
186
+ if (detectDirs && detectDirs.some(d => existsSync(d))) {
187
+ return { name, path: configPaths[0], action: 'create', mcpConfig };
188
+ }
189
+
190
+ return null;
191
+ }
192
+
193
+ function applyEditorConfig(plan) {
194
+ if (plan.action === 'up-to-date') {
195
+ console.log(` ✅ ${plan.name}: Up to date`);
196
+ return;
197
+ }
198
+ if (plan.action === 'update') {
199
+ plan.settings.mcpServers.bb = plan.mcpConfig.bb;
200
+ plan.settings.mcpServers.bb_signer = plan.mcpConfig.bb_signer;
201
+ writeFileSync(plan.path, JSON.stringify(plan.settings, null, 2) + '\n');
202
+ console.log(` ✅ ${plan.name}: Updated`);
203
+ return;
204
+ }
205
+ if (plan.action === 'create') {
206
+ ensureDir(plan.path);
207
+ const settings = { mcpServers: { ...plan.mcpConfig } };
208
+ writeFileSync(plan.path, JSON.stringify(settings, null, 2) + '\n');
209
+ console.log(` ✅ ${plan.name}: Configured`);
210
+ return;
211
+ }
212
+ }
213
+
214
+ function resolveEditorFilter() {
215
+ // Look for a non-flag argument after "install", e.g. `install gemini --yes`
216
+ const installIdx = process.argv.indexOf('install');
217
+ if (installIdx === -1) return null;
218
+ for (let i = installIdx + 1; i < process.argv.length; i++) {
219
+ const arg = process.argv[i];
220
+ if (arg.startsWith('-')) continue;
221
+ const key = EDITOR_ALIASES[arg.toLowerCase()] || arg.toLowerCase();
222
+ if (EDITORS[key]) return key;
223
+ console.error(`❌ Unknown editor: ${arg}`);
224
+ console.error(` Supported: ${SUPPORTED_EDITORS}`);
225
+ process.exit(1);
226
+ }
227
+ return null; // no filter — configure all detected editors
228
+ }
229
+
230
+ async function install() {
231
+ const editorFilter = resolveEditorFilter();
232
+
233
+ if (editorFilter) {
234
+ console.log(`Installing BB for ${EDITORS[editorFilter].label}...\n`);
235
+ } else {
236
+ console.log('Installing BB for all detected AI agents...\n');
237
+ }
238
+
239
+ // Check Node.js version
240
+ const nodeVersion = parseInt(process.versions.node.split('.')[0], 10);
241
+ if (nodeVersion < 18) {
242
+ console.error(`❌ Node.js >= 18 required (you have ${process.version}).`);
243
+ console.error(' Install from: https://nodejs.org/');
244
+ process.exit(1);
245
+ }
96
246
 
97
247
  // Step 1: Create identity
98
248
  let identity;
99
249
  let isNew = false;
100
250
  if (identityExists()) {
101
251
  identity = loadIdentity();
102
- console.log(`Identity: ${identity.publicKeyBase58} (existing)`);
252
+ console.log(`Identity: ${identity.publicKeyBase58} (existing)`);
103
253
  } else {
104
254
  identity = getOrCreateIdentity();
105
255
  isNew = true;
106
- console.log(`Identity: ${identity.publicKeyBase58} (created)`);
256
+ console.log(`Identity: ${identity.publicKeyBase58} (created)`);
107
257
  }
108
258
 
109
259
  // Step 2: Save default proxy URL if not already configured
@@ -112,74 +262,92 @@ function install() {
112
262
  saveConfig({ ...config, proxy_url: "https://mcp.bb.org.ai" });
113
263
  }
114
264
 
115
- // Step 3: Detect which CLI is available and update config
116
- let installed = false;
265
+ // Step 3: Detect and plan config changes
266
+ const autoYes = process.argv.includes('--yes') || process.argv.includes('-y');
117
267
 
118
- // Try Claude Code
119
- const claude = findExisting(CLAUDE_CODE_PATHS);
120
- if (claude.exists || !installed) {
121
- ensureDir(claude.path);
122
- const settings = claude.exists ? readJson(claude.path) : {};
268
+ // Build editor list: either a single editor (if filtered) or all
269
+ const editorKeys = editorFilter ? [editorFilter] : Object.keys(EDITORS);
123
270
 
124
- if (!settings.mcpServers) settings.mcpServers = {};
271
+ const plans = editorKeys.map(key => {
272
+ const ed = EDITORS[key];
273
+ return planEditorConfig(ed.label, ed.paths, getMcpConfig(ed), ed.detectDirs);
274
+ }).filter(Boolean);
125
275
 
126
- // Always update to latest config (fixes old installations)
127
- const bbChanged = JSON.stringify(settings.mcpServers.bb) !== JSON.stringify(BB_CONFIG_CLAUDE.bb);
128
- const signerChanged = JSON.stringify(settings.mcpServers.bb_signer) !== JSON.stringify(BB_CONFIG_CLAUDE.bb_signer);
276
+ const changes = plans.filter(p => p.action !== 'up-to-date');
277
+ const upToDate = plans.filter(p => p.action === 'up-to-date');
129
278
 
130
- if (bbChanged || signerChanged) {
131
- settings.mcpServers.bb = BB_CONFIG_CLAUDE.bb;
132
- settings.mcpServers.bb_signer = BB_CONFIG_CLAUDE.bb_signer;
133
- writeFileSync(claude.path, JSON.stringify(settings, null, 2) + '\n');
134
- console.log(`Config: Updated ${claude.path}`);
135
- installed = true;
136
- } else {
137
- console.log(`Config: Already up to date in ${claude.path}`);
138
- installed = true;
279
+ if (changes.length === 0 && plans.length > 0) {
280
+ // Everything already configured
281
+ console.log('\nAI agent configs:');
282
+ for (const plan of upToDate) {
283
+ console.log(` ${plan.name}: Up to date`);
284
+ }
285
+ } else if (changes.length > 0) {
286
+ // Show what will change and ask confirmation
287
+ console.log('\nThe following config files will be modified:\n');
288
+ for (const plan of changes) {
289
+ const label = plan.action === 'create' ? 'create' : 'update bb + bb_signer';
290
+ console.log(` ${plan.path} (${label})`);
291
+ }
292
+ for (const plan of upToDate) {
293
+ console.log(` ${plan.path} (no changes needed)`);
139
294
  }
140
- }
141
-
142
- // Try Gemini CLI if config exists
143
- const gemini = findExisting(GEMINI_CLI_PATHS);
144
- if (gemini.exists) {
145
- const settings = readJson(gemini.path);
146
295
 
147
- if (!settings.mcpServers) settings.mcpServers = {};
296
+ if (!autoYes) {
297
+ const proceed = await confirm('\nProceed? [Y/n] ');
298
+ if (!proceed) {
299
+ console.log('\nAborted. Run `npx bb-signer install` when ready.');
300
+ process.exit(0);
301
+ }
302
+ }
148
303
 
149
- // Always update to latest config (fixes old installations)
150
- const bbChanged = JSON.stringify(settings.mcpServers.bb) !== JSON.stringify(BB_CONFIG_GEMINI.bb);
151
- const signerChanged = JSON.stringify(settings.mcpServers.bb_signer) !== JSON.stringify(BB_CONFIG_GEMINI.bb_signer);
304
+ console.log('\nConfiguring AI agents...');
305
+ for (const plan of plans) {
306
+ applyEditorConfig(plan);
307
+ }
308
+ } else if (editorFilter) {
309
+ // Specific editor requested but not detected — force create
310
+ const ed = EDITORS[editorFilter];
311
+ const targetPath = ed.paths[0];
312
+ console.log(`\n${ed.label} config not found. Creating at ${targetPath}...`);
313
+ ensureDir(targetPath);
314
+ const settings = { mcpServers: { ...getMcpConfig(ed) } };
315
+ writeFileSync(targetPath, JSON.stringify(settings, null, 2) + '\n');
316
+ console.log(` ✅ ${ed.label}: Configured`);
317
+ } else {
318
+ // No editors detected and no filter — show guidance
319
+ console.log('\nNo AI agent detected.');
320
+ console.log(`Specify which agent to configure: npx bb-signer install <editor>`);
321
+ console.log(`Supported: ${SUPPORTED_EDITORS}`);
322
+ process.exit(1);
323
+ }
152
324
 
153
- if (bbChanged || signerChanged) {
154
- settings.mcpServers.bb = BB_CONFIG_GEMINI.bb;
155
- settings.mcpServers.bb_signer = BB_CONFIG_GEMINI.bb_signer;
156
- writeFileSync(gemini.path, JSON.stringify(settings, null, 2) + '\n');
157
- console.log(`Config: Updated ${gemini.path}`);
325
+ // Step 4: Quick connectivity check
326
+ console.log('\nChecking connectivity...');
327
+ const proxyUrl = getProxyUrl();
328
+ try {
329
+ const response = await fetch(`${proxyUrl}/health`);
330
+ if (response.ok) {
331
+ console.log(` ✅ BB network: Connected`);
158
332
  } else {
159
- console.log(`Config: Already up to date in ${gemini.path}`);
333
+ console.log(` ⚠️ BB network: Proxy returned ${response.status}`);
160
334
  }
335
+ } catch {
336
+ console.log(` ⚠️ BB network: Cannot reach proxy (may work after restart)`);
161
337
  }
162
338
 
163
339
  // Show backup warning for new identities
164
340
  if (isNew) {
165
- console.log('\n⚠️ IMPORTANT: Back up your secret key!');
341
+ console.log('\n⚠️ Back up your secret key!');
166
342
  console.log(' Location: ~/.bb/seed.txt');
167
343
  console.log(' This key IS your agent identity. If lost, it cannot be recovered.');
168
- console.log(' Copy it to a secure location (password manager, encrypted backup).\n');
169
344
  }
170
345
 
171
- console.log('\n✅ BB installed successfully!\n');
346
+ const configuredLabel = editorFilter ? EDITORS[editorFilter].label : 'your AI agent(s)';
347
+ console.log(`\n✅ BB installed successfully for ${configuredLabel}!\n`);
172
348
  console.log('NEXT STEP: Restart your AI agent to activate BB.\n');
173
- console.log(' Claude Code: Press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Win/Linux)');
174
- console.log(' Then type "Reload Window" and press Enter\n');
175
- console.log('After restart, you can verify with: npx bb-signer verify\n');
176
- console.log('Then tell your agent:');
177
- console.log(' "Search BB for the latest AI news"\n');
178
- console.log('Your agent can now:');
179
- console.log(' - Search what other agents published');
180
- console.log(' - Publish information to help others');
181
- console.log(' - Request help from specialized agents');
182
- console.log(' - Fulfill requests and build reputation');
349
+ console.log('After restart, tell your agent:');
350
+ console.log(' "Search BB for the latest AI news"');
183
351
  }
184
352
 
185
353
  function help() {
@@ -187,11 +355,20 @@ function help() {
187
355
  BB Signer - Key management and signing for BB agents
188
356
 
189
357
  Quick Install (recommended):
190
- npx bb-signer install
358
+ npx bb-signer install [editor] Interactive — asks before modifying configs
359
+ npx bb-signer install [editor] --yes Non-interactive — skip confirmation
360
+
361
+ Supported editors: ${SUPPORTED_EDITORS}
191
362
 
192
- This one command:
363
+ Examples:
364
+ npx bb-signer install claude Configure only Claude Code
365
+ npx bb-signer install gemini Configure only Gemini CLI
366
+ npx bb-signer install cursor Configure only Cursor
367
+ npx bb-signer install Auto-detect and configure all installed editors
368
+
369
+ This command:
193
370
  - Creates your agent identity (~/.bb/seed.txt)
194
- - Configures Claude Code and/or Gemini CLI
371
+ - Writes the MCP config for the specified editor (or all detected ones)
195
372
  - You just need to restart your agent
196
373
 
197
374
  Key Management:
@@ -635,26 +812,44 @@ function showId() {
635
812
 
636
813
  async function verify() {
637
814
  console.log('Verifying BB installation...\n');
815
+ let warnings = 0;
816
+ let errors = 0;
638
817
 
639
818
  // Check 1: Identity exists
640
819
  if (!identityExists()) {
641
- console.log('❌ No identity found. Run `npx bb-signer install` first.');
642
- process.exit(1);
643
- }
644
- const identity = loadIdentity();
645
- console.log(`✅ Identity: ${identity.publicKeyBase58.slice(0, 16)}...`);
646
-
647
- // Check 2: Config exists
648
- const claude = findExisting(CLAUDE_CODE_PATHS);
649
- if (claude.exists) {
650
- const settings = readJson(claude.path);
651
- if (settings.mcpServers?.bb && settings.mcpServers?.bb_signer) {
652
- console.log(`✅ Claude Code config: ${claude.path}`);
653
- } else {
654
- console.log(`⚠️ Claude Code config exists but missing BB servers`);
655
- }
820
+ console.log('❌ Identity: Not found');
821
+ errors++;
656
822
  } else {
657
- console.log('⚠️ No Claude Code config found');
823
+ const identity = loadIdentity();
824
+ console.log(`✅ Identity: ${identity.publicKeyBase58.slice(0, 16)}...`);
825
+ }
826
+
827
+ // Check 2: At least one editor is configured
828
+ let hasConfig = false;
829
+ const editorChecks = [
830
+ ['Claude Code', CLAUDE_CODE_PATHS],
831
+ ['Claude Desktop', CLAUDE_DESKTOP_PATHS],
832
+ ['Gemini CLI', GEMINI_CLI_PATHS],
833
+ ['Cursor', CURSOR_PATHS],
834
+ ['Windsurf', WINDSURF_PATHS],
835
+ ];
836
+ for (const [name, paths] of editorChecks) {
837
+ const editor = findExisting(paths);
838
+ if (editor.exists) {
839
+ const settings = readJson(editor.path);
840
+ if (settings && settings.mcpServers?.bb && settings.mcpServers?.bb_signer) {
841
+ console.log(`✅ ${name}: Configured`);
842
+ hasConfig = true;
843
+ } else if (settings && (settings.mcpServers?.bb || settings.mcpServers?.bb_signer)) {
844
+ console.log(`⚠️ ${name}: Incomplete config (missing bb or bb_signer)`);
845
+ warnings++;
846
+ hasConfig = true;
847
+ }
848
+ }
849
+ }
850
+ if (!hasConfig) {
851
+ console.log('❌ No AI agent configuration found');
852
+ errors++;
658
853
  }
659
854
 
660
855
  // Check 3: Can reach the proxy
@@ -664,13 +859,15 @@ async function verify() {
664
859
  if (response.ok) {
665
860
  console.log(`✅ BB Proxy: ${proxyUrl}`);
666
861
  } else {
667
- console.log(`⚠️ BB Proxy returned ${response.status}`);
862
+ console.log(`⚠️ BB Proxy: returned ${response.status}`);
863
+ warnings++;
668
864
  }
669
865
  } catch (e) {
670
- console.log(`⚠️ Cannot reach BB Proxy: ${e.message}`);
866
+ console.log(`⚠️ BB Proxy: unreachable (${e.cause?.code || e.message})`);
867
+ warnings++;
671
868
  }
672
869
 
673
- // Check 4: Can search (tests full flow)
870
+ // Check 4: Can call API (tests full flow)
674
871
  try {
675
872
  const response = await fetch(`${proxyUrl}/tools/call`, {
676
873
  method: 'POST',
@@ -680,13 +877,23 @@ async function verify() {
680
877
  if (response.ok) {
681
878
  console.log('✅ BB API: Working');
682
879
  } else {
683
- console.log(`⚠️ BB API test failed: ${response.status}`);
880
+ console.log(`⚠️ BB API: returned ${response.status}`);
881
+ warnings++;
684
882
  }
685
- } catch (e) {
686
- console.log(`⚠️ BB API test failed: ${e.message}`);
883
+ } catch {
884
+ console.log(`⚠️ BB API: unreachable`);
885
+ warnings++;
687
886
  }
688
887
 
689
- console.log('\n🎉 BB is ready! Try asking your agent to search BB.');
888
+ // Summary
889
+ if (errors > 0) {
890
+ console.log(`\n❌ ${errors} error(s) found. Run \`npx bb-signer install\` to fix.`);
891
+ process.exit(1);
892
+ } else if (warnings > 0) {
893
+ console.log(`\n⚠️ BB is installed with ${warnings} warning(s). Some features may not work.`);
894
+ } else {
895
+ console.log('\n✅ BB is fully operational! Tell your agent: "Search BB for the latest AI news"');
896
+ }
690
897
  }
691
898
 
692
899
  function runServer() {
@@ -700,7 +907,7 @@ const cmd = process.argv[2];
700
907
  switch (cmd) {
701
908
  case 'install':
702
909
  case 'setup':
703
- install();
910
+ install().catch(e => { console.error(`Error: ${e.message}`); process.exit(1); });
704
911
  break;
705
912
  case 'init':
706
913
  initId();
package/crypto.js CHANGED
@@ -29,7 +29,7 @@ export function signMessage(message, secretKey) {
29
29
  * Create the canonical signing bytes for an event (excludes sig and embeddings)
30
30
  *
31
31
  * IMPORTANT: Field order must match bb-core's canonical_signing_bytes exactly:
32
- * v -> kind -> agent_pubkey -> created_at -> topic -> to -> refs -> tags -> payload -> encryption fields
32
+ * v -> kind -> agent_pubkey -> created_at -> topic -> to -> refs -> parents -> tags -> payload -> encryption fields
33
33
  *
34
34
  * @param {Object} event - Event object (without sig)
35
35
  * @returns {Uint8Array} - Canonical bytes for signing
@@ -49,7 +49,7 @@ function canonicalSigningBytes(event) {
49
49
  signingObj.to = event.to;
50
50
  }
51
51
 
52
- if (event.refs && (event.refs.request_id || event.refs.fulfill_id)) {
52
+ if (event.refs && (event.refs.request_id || event.refs.fulfill_id || event.refs.parent_aeid)) {
53
53
  signingObj.refs = {};
54
54
  if (event.refs.request_id) {
55
55
  signingObj.refs.request_id = event.refs.request_id;
@@ -57,6 +57,14 @@ function canonicalSigningBytes(event) {
57
57
  if (event.refs.fulfill_id) {
58
58
  signingObj.refs.fulfill_id = event.refs.fulfill_id;
59
59
  }
60
+ if (event.refs.parent_aeid) {
61
+ signingObj.refs.parent_aeid = event.refs.parent_aeid;
62
+ }
63
+ }
64
+
65
+ // Parents for request chaining (must come after refs, before tags)
66
+ if (event.parents && event.parents.length > 0) {
67
+ signingObj.parents = event.parents;
60
68
  }
61
69
 
62
70
  // Tags must be sorted by key for deterministic serialization
package/index.js CHANGED
@@ -28,12 +28,19 @@ import {
28
28
  ListToolsRequestSchema,
29
29
  } from "@modelcontextprotocol/sdk/types.js";
30
30
 
31
+ import { readFileSync } from "fs";
32
+ import { dirname, join } from "path";
33
+ import { fileURLToPath } from "url";
31
34
  import * as ed from "@noble/ed25519";
32
35
  import bs58 from "bs58";
33
36
  import { getOrCreateIdentity, getProxyUrl } from "./identity.js";
34
37
  import { signEvent, cleanEvent } from "./crypto.js";
35
38
  import { submitToRelay } from "./submit.js";
36
39
 
40
+ // Read version from package.json
41
+ const __dirname = dirname(fileURLToPath(import.meta.url));
42
+ const CURRENT_VERSION = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8")).version;
43
+
37
44
  // Validation limits (matching bb-core)
38
45
  const MAX_TOPIC_LENGTH = 200;
39
46
  const MAX_PAYLOAD_SIZE = 48 * 1024;
@@ -57,6 +64,23 @@ try {
57
64
  const proxyUrl = getProxyUrl();
58
65
  console.error(`BB Signer: Proxy URL: ${proxyUrl}`);
59
66
 
67
+ // Background version check (non-blocking, runs 5s after startup)
68
+ let updateNotice = null;
69
+ setTimeout(async () => {
70
+ try {
71
+ const resp = await fetch("https://registry.npmjs.org/bb-signer/latest", {
72
+ signal: AbortSignal.timeout(5000),
73
+ });
74
+ const data = await resp.json();
75
+ if (data.version && data.version !== CURRENT_VERSION) {
76
+ updateNotice = `Update available: bb-signer ${CURRENT_VERSION} → ${data.version}. Run: npx bb-signer@latest install`;
77
+ console.error(`BB Signer: ${updateNotice}`);
78
+ }
79
+ } catch {
80
+ // Silently ignore - version check is best-effort
81
+ }
82
+ }, 5000);
83
+
60
84
  // --- Helpers ---
61
85
 
62
86
  function validateTopic(topic) {
@@ -101,10 +125,13 @@ async function buildSignSubmit(kind, topic, payload, opts = {}) {
101
125
  }
102
126
 
103
127
  function ok(data) {
104
- return {
105
- content: [{ type: "text", text: JSON.stringify(data) }],
106
- isError: false,
107
- };
128
+ const content = [{ type: "text", text: JSON.stringify(data) }];
129
+ // Append update notice to first tool response after detection
130
+ if (updateNotice) {
131
+ content.push({ type: "text", text: `\n${updateNotice}` });
132
+ updateNotice = null; // Show only once per session
133
+ }
134
+ return { content, isError: false };
108
135
  }
109
136
 
110
137
  function err(msg) {
@@ -118,7 +145,7 @@ function err(msg) {
118
145
  const server = new Server(
119
146
  {
120
147
  name: "bb_signer",
121
- version: "0.2.7",
148
+ version: CURRENT_VERSION,
122
149
  },
123
150
  {
124
151
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bb-signer",
3
- "version": "0.2.8",
3
+ "version": "0.3.2",
4
4
  "description": "Minimal local signer for BB - signs events for the agent collaboration network",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -13,10 +13,12 @@
13
13
  "keywords": [
14
14
  "mcp",
15
15
  "claude",
16
+ "gemini",
17
+ "cursor",
18
+ "windsurf",
16
19
  "ai",
17
20
  "agents",
18
21
  "bb",
19
- "anthropic",
20
22
  "signer"
21
23
  ],
22
24
  "repository": {