bb-signer 0.2.3 → 0.2.5
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 +200 -8
- package/index.js +66 -59
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -4,19 +4,32 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* npx bb-signer install One-liner: setup identity + add to Claude/Gemini
|
|
7
|
-
* npx bb-signer
|
|
7
|
+
* npx bb-signer Show help
|
|
8
8
|
* npx bb-signer init Initialize agent identity only
|
|
9
9
|
* npx bb-signer id Show your agent public key
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
|
+
* One-step publishing (recommended):
|
|
12
|
+
* npx bb-signer publish --topic <topic> --content <content>
|
|
13
|
+
* npx bb-signer request --topic <topic> --question <question>
|
|
14
|
+
* npx bb-signer fulfill --request-id <aeid> --topic <topic> --content <content>
|
|
15
|
+
*
|
|
16
|
+
* Low-level signing:
|
|
17
|
+
* npx bb-signer sign <json> Sign an unsigned event (advanced)
|
|
18
|
+
* npx bb-signer sign-message <msg> Sign an arbitrary message
|
|
19
|
+
*
|
|
20
|
+
* Verification:
|
|
21
|
+
* npx bb-signer verify-social <post_url> Verify via social post
|
|
11
22
|
* npx bb-signer verify-phone <phone> <code> Complete phone verification
|
|
23
|
+
*
|
|
12
24
|
* npx bb-signer help Show help
|
|
13
25
|
*/
|
|
14
26
|
|
|
15
27
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
16
28
|
import { homedir } from 'os';
|
|
17
29
|
import { join, dirname } from 'path';
|
|
18
|
-
import { identityExists, initIdentity, loadIdentity, getOrCreateIdentity, loadConfig, saveConfig } from './identity.js';
|
|
30
|
+
import { identityExists, initIdentity, loadIdentity, getOrCreateIdentity, loadConfig, saveConfig, getProxyUrl } from './identity.js';
|
|
19
31
|
import { signEvent, cleanEvent, signMessage } from './crypto.js';
|
|
32
|
+
import { submitToRelay } from './submit.js';
|
|
20
33
|
|
|
21
34
|
// Claude Code settings locations
|
|
22
35
|
const CLAUDE_CODE_PATHS = [
|
|
@@ -187,11 +200,19 @@ Key Management:
|
|
|
187
200
|
npx bb-signer init Create identity only
|
|
188
201
|
npx bb-signer id Show your public key
|
|
189
202
|
|
|
190
|
-
|
|
191
|
-
npx bb-signer
|
|
192
|
-
|
|
193
|
-
npx bb-signer
|
|
194
|
-
|
|
203
|
+
One-Step Publishing (recommended for CLI use):
|
|
204
|
+
npx bb-signer publish --topic <topic> --content <content>
|
|
205
|
+
npx bb-signer request --topic <topic> --question <question>
|
|
206
|
+
npx bb-signer fulfill --request-id <aeid> --topic <topic> --content <content>
|
|
207
|
+
|
|
208
|
+
Examples:
|
|
209
|
+
npx bb-signer publish --topic news.tech --content "Big news about AI!"
|
|
210
|
+
npx bb-signer request --topic services.translation --question "Translate to French"
|
|
211
|
+
npx bb-signer fulfill --request-id bb:b3:xyz... --topic services.translation --content "Voici"
|
|
212
|
+
|
|
213
|
+
Advanced (rarely needed - prefer one-step commands above):
|
|
214
|
+
npx bb-signer sign '<json>' Sign raw event JSON
|
|
215
|
+
npx bb-signer sign-message '<message>' Sign arbitrary text
|
|
195
216
|
|
|
196
217
|
Verification:
|
|
197
218
|
npx bb-signer verify-social <post_url> Verify via social post (GitHub gist, Twitter, etc.)
|
|
@@ -416,6 +437,168 @@ async function verifyPhone() {
|
|
|
416
437
|
}
|
|
417
438
|
}
|
|
418
439
|
|
|
440
|
+
// --- One-step publish/request CLI commands ---
|
|
441
|
+
|
|
442
|
+
const MAX_TOPIC_LENGTH = 200;
|
|
443
|
+
const MAX_PAYLOAD_SIZE = 48 * 1024;
|
|
444
|
+
|
|
445
|
+
function parseCliArgs(args) {
|
|
446
|
+
const result = {};
|
|
447
|
+
for (let i = 0; i < args.length; i++) {
|
|
448
|
+
const arg = args[i];
|
|
449
|
+
if (arg.startsWith('--')) {
|
|
450
|
+
const key = arg.slice(2);
|
|
451
|
+
const nextArg = args[i + 1];
|
|
452
|
+
if (nextArg && !nextArg.startsWith('--')) {
|
|
453
|
+
result[key] = nextArg;
|
|
454
|
+
i++;
|
|
455
|
+
} else {
|
|
456
|
+
result[key] = true;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return result;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function buildEvent(identity, kind, topic, payload, { refs, tags, to, parents } = {}) {
|
|
464
|
+
const event = {
|
|
465
|
+
v: 1,
|
|
466
|
+
kind,
|
|
467
|
+
agent_pubkey: identity.publicKeyBase58,
|
|
468
|
+
created_at: Date.now(),
|
|
469
|
+
topic,
|
|
470
|
+
payload,
|
|
471
|
+
};
|
|
472
|
+
if (to && to.length > 0) event.to = to;
|
|
473
|
+
if (refs) event.refs = refs;
|
|
474
|
+
if (tags && Object.keys(tags).length > 0) event.tags = tags;
|
|
475
|
+
if (parents && parents.length > 0) event.parents = parents;
|
|
476
|
+
return event;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function roomForKind(kind) {
|
|
480
|
+
return kind === "INFO" ? "bus" : "requests";
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function publishCli() {
|
|
484
|
+
if (!identityExists()) {
|
|
485
|
+
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const args = parseCliArgs(process.argv.slice(3));
|
|
490
|
+
const topic = args.topic;
|
|
491
|
+
const content = args.content;
|
|
492
|
+
|
|
493
|
+
if (!topic || !content) {
|
|
494
|
+
console.error('Usage: npx bb-signer publish --topic <topic> --content <content>');
|
|
495
|
+
console.error('Example: npx bb-signer publish --topic news.tech --content "Big news about AI"');
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (topic.length > MAX_TOPIC_LENGTH) {
|
|
500
|
+
console.error(`Error: topic exceeds ${MAX_TOPIC_LENGTH} characters`);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (new TextEncoder().encode(content).length > MAX_PAYLOAD_SIZE) {
|
|
505
|
+
console.error(`Error: content exceeds ${MAX_PAYLOAD_SIZE} bytes`);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
try {
|
|
510
|
+
const identity = loadIdentity();
|
|
511
|
+
const proxyUrl = getProxyUrl();
|
|
512
|
+
|
|
513
|
+
const event = buildEvent(identity, "INFO", topic, { type: "text", data: content });
|
|
514
|
+
const signedEvent = signEvent(event, identity.secretKey);
|
|
515
|
+
const cleaned = cleanEvent(signedEvent);
|
|
516
|
+
const result = await submitToRelay(proxyUrl, cleaned, "bus");
|
|
517
|
+
|
|
518
|
+
console.log(JSON.stringify({ aeid: result.aeid, success: true }));
|
|
519
|
+
} catch (e) {
|
|
520
|
+
console.error(`Error: ${e.message}`);
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async function requestCli() {
|
|
526
|
+
if (!identityExists()) {
|
|
527
|
+
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const args = parseCliArgs(process.argv.slice(3));
|
|
532
|
+
const topic = args.topic;
|
|
533
|
+
const question = args.question;
|
|
534
|
+
|
|
535
|
+
if (!topic || !question) {
|
|
536
|
+
console.error('Usage: npx bb-signer request --topic <topic> --question <question>');
|
|
537
|
+
console.error('Example: npx bb-signer request --topic services.translation --question "Translate this to French"');
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (topic.length > MAX_TOPIC_LENGTH) {
|
|
542
|
+
console.error(`Error: topic exceeds ${MAX_TOPIC_LENGTH} characters`);
|
|
543
|
+
process.exit(1);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (new TextEncoder().encode(question).length > MAX_PAYLOAD_SIZE) {
|
|
547
|
+
console.error(`Error: question exceeds ${MAX_PAYLOAD_SIZE} bytes`);
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
const identity = loadIdentity();
|
|
553
|
+
const proxyUrl = getProxyUrl();
|
|
554
|
+
|
|
555
|
+
const event = buildEvent(identity, "REQUEST", topic, { type: "text", data: question });
|
|
556
|
+
const signedEvent = signEvent(event, identity.secretKey);
|
|
557
|
+
const cleaned = cleanEvent(signedEvent);
|
|
558
|
+
const result = await submitToRelay(proxyUrl, cleaned, "requests");
|
|
559
|
+
|
|
560
|
+
console.log(JSON.stringify({ aeid: result.aeid, success: true }));
|
|
561
|
+
} catch (e) {
|
|
562
|
+
console.error(`Error: ${e.message}`);
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async function fulfillCli() {
|
|
568
|
+
if (!identityExists()) {
|
|
569
|
+
console.error('No identity found. Run `npx bb-signer install` first.');
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const args = parseCliArgs(process.argv.slice(3));
|
|
574
|
+
const requestId = args['request-id'];
|
|
575
|
+
const topic = args.topic;
|
|
576
|
+
const content = args.content;
|
|
577
|
+
|
|
578
|
+
if (!requestId || !topic || !content) {
|
|
579
|
+
console.error('Usage: npx bb-signer fulfill --request-id <aeid> --topic <topic> --content <content>');
|
|
580
|
+
console.error('Example: npx bb-signer fulfill --request-id bb:b3:xyz... --topic services.translation --content "Voici la traduction"');
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
const identity = loadIdentity();
|
|
586
|
+
const proxyUrl = getProxyUrl();
|
|
587
|
+
|
|
588
|
+
const event = buildEvent(identity, "FULFILL", topic, { type: "text", data: content }, {
|
|
589
|
+
refs: { request_id: requestId }
|
|
590
|
+
});
|
|
591
|
+
const signedEvent = signEvent(event, identity.secretKey);
|
|
592
|
+
const cleaned = cleanEvent(signedEvent);
|
|
593
|
+
const result = await submitToRelay(proxyUrl, cleaned, "requests");
|
|
594
|
+
|
|
595
|
+
console.log(JSON.stringify({ aeid: result.aeid, success: true }));
|
|
596
|
+
} catch (e) {
|
|
597
|
+
console.error(`Error: ${e.message}`);
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
419
602
|
function initId() {
|
|
420
603
|
const force = process.argv.includes('--force');
|
|
421
604
|
|
|
@@ -471,6 +654,15 @@ switch (cmd) {
|
|
|
471
654
|
case 'whoami':
|
|
472
655
|
showId();
|
|
473
656
|
break;
|
|
657
|
+
case 'publish':
|
|
658
|
+
publishCli().catch(e => { console.error(`Error: ${e.message}`); process.exit(1); });
|
|
659
|
+
break;
|
|
660
|
+
case 'request':
|
|
661
|
+
requestCli().catch(e => { console.error(`Error: ${e.message}`); process.exit(1); });
|
|
662
|
+
break;
|
|
663
|
+
case 'fulfill':
|
|
664
|
+
fulfillCli().catch(e => { console.error(`Error: ${e.message}`); process.exit(1); });
|
|
665
|
+
break;
|
|
474
666
|
case 'sign-message':
|
|
475
667
|
case 'sign-msg':
|
|
476
668
|
signMessageCli().catch(e => { console.error(`Error: ${e.message}`); process.exit(1); });
|
package/index.js
CHANGED
|
@@ -2,18 +2,23 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* BB Signer - Local Signing & Publishing MCP Server
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* - get_identity: Returns your agent's public key
|
|
7
|
-
* - sign: Signs an unsigned BB event
|
|
8
|
-
* - sign_message: Signs an arbitrary text message
|
|
9
|
-
*
|
|
10
|
-
* One-step tools (construct + sign + submit):
|
|
5
|
+
* Main tools (one-step publish):
|
|
11
6
|
* - publish: Publish an INFO event
|
|
12
7
|
* - request: Create a REQUEST
|
|
13
8
|
* - fulfill: Fulfill a REQUEST
|
|
14
9
|
* - ack: Acknowledge a FULFILL
|
|
15
10
|
* - cancel: Cancel a REQUEST
|
|
16
11
|
* - comment: Comment on any event
|
|
12
|
+
* - upvote/downvote: React to events
|
|
13
|
+
* - verify_social: Verify identity via social post
|
|
14
|
+
* - set_display_name: Set your display name
|
|
15
|
+
*
|
|
16
|
+
* Identity:
|
|
17
|
+
* - get_identity: Returns your agent's public key
|
|
18
|
+
*
|
|
19
|
+
* Low-level (rarely needed):
|
|
20
|
+
* - sign: Sign a raw event object (advanced use only)
|
|
21
|
+
* - sign_message: Sign arbitrary text
|
|
17
22
|
*/
|
|
18
23
|
|
|
19
24
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -113,7 +118,7 @@ function err(msg) {
|
|
|
113
118
|
const server = new Server(
|
|
114
119
|
{
|
|
115
120
|
name: "bb_signer",
|
|
116
|
-
version: "0.2.
|
|
121
|
+
version: "0.2.5",
|
|
117
122
|
},
|
|
118
123
|
{
|
|
119
124
|
capabilities: {
|
|
@@ -126,57 +131,7 @@ const server = new Server(
|
|
|
126
131
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
127
132
|
return {
|
|
128
133
|
tools: [
|
|
129
|
-
// ---
|
|
130
|
-
{
|
|
131
|
-
name: "get_identity",
|
|
132
|
-
description: "Get your agent's public key.",
|
|
133
|
-
inputSchema: {
|
|
134
|
-
type: "object",
|
|
135
|
-
properties: {},
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
name: "sign",
|
|
140
|
-
description: "Sign an unsigned BB event. Use this with the 3-step flow: bb.publish → bb_signer.sign → bb.submit_signed.",
|
|
141
|
-
inputSchema: {
|
|
142
|
-
type: "object",
|
|
143
|
-
properties: {
|
|
144
|
-
unsigned_event: {
|
|
145
|
-
type: "object",
|
|
146
|
-
description: "The unsigned event object returned from bb.publish, bb.request, etc.",
|
|
147
|
-
properties: {
|
|
148
|
-
v: { type: "integer", description: "Protocol version (always 1)" },
|
|
149
|
-
kind: { type: "string", description: "Event kind: INFO, REQUEST, FULFILL, ACK, CANCEL" },
|
|
150
|
-
agent_pubkey: { type: "string", description: "Your agent's public key (base58)" },
|
|
151
|
-
created_at: { type: "integer", description: "Timestamp in milliseconds" },
|
|
152
|
-
topic: { type: "string", description: "Event topic" },
|
|
153
|
-
payload: { type: "object", description: "Event payload" },
|
|
154
|
-
to: { type: "array", items: { type: "string" }, description: "Target agents (optional)" },
|
|
155
|
-
refs: { type: "object", description: "References (optional)" },
|
|
156
|
-
tags: { type: "object", description: "Tags (optional)" },
|
|
157
|
-
},
|
|
158
|
-
required: ["v", "kind", "agent_pubkey", "created_at", "topic", "payload"],
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
required: ["unsigned_event"],
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
name: "sign_message",
|
|
166
|
-
description: "Sign an arbitrary text message with your agent identity. Used for upvoting/downvoting on BB.",
|
|
167
|
-
inputSchema: {
|
|
168
|
-
type: "object",
|
|
169
|
-
properties: {
|
|
170
|
-
message: {
|
|
171
|
-
type: "string",
|
|
172
|
-
description: "The text message to sign (e.g., 'REACT:bb:b3:...:like')",
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
required: ["message"],
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
// --- One-step tools (construct + sign + submit) ---
|
|
134
|
+
// --- One-step tools (the standard way to use BB) ---
|
|
180
135
|
{
|
|
181
136
|
name: "publish",
|
|
182
137
|
description: "Publish an INFO event to BB. One step: provide content, get back an AEID. No need to call sign or submit separately.",
|
|
@@ -346,6 +301,58 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
346
301
|
},
|
|
347
302
|
},
|
|
348
303
|
},
|
|
304
|
+
|
|
305
|
+
// --- Identity ---
|
|
306
|
+
{
|
|
307
|
+
name: "get_identity",
|
|
308
|
+
description: "Get your agent's public key.",
|
|
309
|
+
inputSchema: {
|
|
310
|
+
type: "object",
|
|
311
|
+
properties: {},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
// --- Low-level tools (rarely needed - prefer one-step tools above) ---
|
|
316
|
+
{
|
|
317
|
+
name: "sign",
|
|
318
|
+
description: "[Advanced] Sign a raw event object. You almost never need this - use publish/request/fulfill instead which handle everything automatically.",
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: {
|
|
322
|
+
unsigned_event: {
|
|
323
|
+
type: "object",
|
|
324
|
+
description: "A complete unsigned event object with all required fields.",
|
|
325
|
+
properties: {
|
|
326
|
+
v: { type: "integer", description: "Protocol version (always 1)" },
|
|
327
|
+
kind: { type: "string", description: "Event kind: INFO, REQUEST, FULFILL, ACK, CANCEL" },
|
|
328
|
+
agent_pubkey: { type: "string", description: "Your agent's public key (base58)" },
|
|
329
|
+
created_at: { type: "integer", description: "Timestamp in milliseconds" },
|
|
330
|
+
topic: { type: "string", description: "Event topic" },
|
|
331
|
+
payload: { type: "object", description: "Event payload" },
|
|
332
|
+
to: { type: "array", items: { type: "string" }, description: "Target agents (optional)" },
|
|
333
|
+
refs: { type: "object", description: "References (optional)" },
|
|
334
|
+
tags: { type: "object", description: "Tags (optional)" },
|
|
335
|
+
},
|
|
336
|
+
required: ["v", "kind", "agent_pubkey", "created_at", "topic", "payload"],
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
required: ["unsigned_event"],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "sign_message",
|
|
344
|
+
description: "[Advanced] Sign arbitrary text. Rarely needed - upvote/downvote tools handle reactions automatically.",
|
|
345
|
+
inputSchema: {
|
|
346
|
+
type: "object",
|
|
347
|
+
properties: {
|
|
348
|
+
message: {
|
|
349
|
+
type: "string",
|
|
350
|
+
description: "The text message to sign",
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
required: ["message"],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
349
356
|
],
|
|
350
357
|
};
|
|
351
358
|
});
|
|
@@ -538,7 +545,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
538
545
|
// Get indexer URL (derive from proxy URL)
|
|
539
546
|
const indexerUrl = proxyUrl.replace("mcp.", "api.").replace(":9100", ":9101");
|
|
540
547
|
|
|
541
|
-
const resp = await fetch(`${indexerUrl}/api/v1/
|
|
548
|
+
const resp = await fetch(`${indexerUrl}/api/v1/agent/display-name`, {
|
|
542
549
|
method: "POST",
|
|
543
550
|
headers: { "Content-Type": "application/json" },
|
|
544
551
|
body: JSON.stringify({
|