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.
- package/cli.js +321 -114
- package/crypto.js +10 -2
- package/index.js +32 -5
- 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
|
|
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
|
-
//
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
//
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
95
|
-
|
|
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
|
|
116
|
-
|
|
265
|
+
// Step 3: Detect and plan config changes
|
|
266
|
+
const autoYes = process.argv.includes('--yes') || process.argv.includes('-y');
|
|
117
267
|
|
|
118
|
-
//
|
|
119
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
console.log(`
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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 (!
|
|
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
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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(`
|
|
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⚠️
|
|
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
|
-
|
|
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('
|
|
174
|
-
console.log('
|
|
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
|
-
|
|
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
|
-
-
|
|
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('❌
|
|
642
|
-
|
|
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
|
-
|
|
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(`⚠️
|
|
866
|
+
console.log(`⚠️ BB Proxy: unreachable (${e.cause?.code || e.message})`);
|
|
867
|
+
warnings++;
|
|
671
868
|
}
|
|
672
869
|
|
|
673
|
-
// Check 4: Can
|
|
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
|
|
880
|
+
console.log(`⚠️ BB API: returned ${response.status}`);
|
|
881
|
+
warnings++;
|
|
684
882
|
}
|
|
685
|
-
} catch
|
|
686
|
-
console.log(`⚠️ BB API
|
|
883
|
+
} catch {
|
|
884
|
+
console.log(`⚠️ BB API: unreachable`);
|
|
885
|
+
warnings++;
|
|
687
886
|
}
|
|
688
887
|
|
|
689
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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:
|
|
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
|
|
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": {
|