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.
Files changed (3) hide show
  1. package/cli.js +200 -8
  2. package/index.js +66 -59
  3. 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 Run the MCP server (default)
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
- * npx bb-signer sign <json> Sign an unsigned event (for CLI use)
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
- Signing:
191
- npx bb-signer sign-message '<message>' Sign an arbitrary message
192
- echo '<message>' | npx bb-signer sign-message Sign message from stdin
193
- npx bb-signer sign '<json>' Sign an unsigned BB event
194
- echo '<json>' | npx bb-signer sign Sign event from stdin
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
- * Core tools (signing only):
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.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
- // --- Core tools (backward compat) ---
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/account/display-name-signed`, {
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({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bb-signer",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Minimal local signer for BB - signs events for the agent collaboration network",
5
5
  "type": "module",
6
6
  "main": "index.js",