nostr-bray 1.9.2 → 1.10.0

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/dist/cli.js CHANGED
@@ -20,6 +20,7 @@ import { handleDuressConfigure, handleDuressActivate } from './safety/handlers.j
20
20
  import { handleZapSend, handleZapBalance, handleZapMakeInvoice, handleZapLookupInvoice, handleZapListTransactions, handleZapReceipts, handleZapDecode } from './zap/handlers.js';
21
21
  import { handleDecode, handleEncodeNpub, handleEncodeNote, handleEncodeNprofile, handleEncodeNevent, handleVerify, handleEncrypt, handleDecrypt, handleCount, handleFetch, handleKeyPublic, handleEncodeNsec, handleFilter, handleNipList, handleNipShow } from './util/handlers.js';
22
22
  import { getCommandHelp } from './help.js';
23
+ import * as fmt from './format.js';
23
24
  const args = process.argv.slice(2);
24
25
  const command = args[0];
25
26
  // No command = start MCP server
@@ -147,6 +148,10 @@ Quick examples:
147
148
  nostr-bray nips # browse official NIPs
148
149
  nostr-bray shell # interactive mode
149
150
 
151
+ Flags:
152
+ --json Output raw JSON instead of human-readable
153
+ --help Show help for a command
154
+
150
155
  Use 'nostr-bray <command> --help' for detailed help on any command.
151
156
 
152
157
  Learn more:
@@ -171,8 +176,16 @@ config.secretKey = '';
171
176
  config.nwcUri = undefined;
172
177
  const masterRelays = await nip65.loadForIdentity(ctx.activeNpub);
173
178
  pool.reconfigure(ctx.activeNpub, masterRelays);
174
- /** Helper: print JSON result */
175
- function out(data) { console.log(JSON.stringify(data, null, 2)); }
179
+ const jsonMode = args.includes('--json');
180
+ /** Print JSON (always) or human-readable (default) */
181
+ function out(data, humanFormatter) {
182
+ if (jsonMode || !humanFormatter) {
183
+ console.log(JSON.stringify(data, null, 2));
184
+ }
185
+ else {
186
+ console.log(humanFormatter(data));
187
+ }
188
+ }
176
189
  /** Parse a shell line into args, respecting quotes */
177
190
  function parseShellLine(line) {
178
191
  const result = [];
@@ -232,7 +245,7 @@ async function run(cmdArgs) {
232
245
  out(handleIdentityCreate());
233
246
  break;
234
247
  case 'list':
235
- out(handleIdentityList(ctx));
248
+ out(handleIdentityList(ctx), fmt.formatIdentityList);
236
249
  break;
237
250
  case 'derive':
238
251
  out(ctx.derive(req(1, 'derive <purpose> [index]'), parseInt(cmdArgs[2] ?? '0', 10)));
@@ -284,7 +297,7 @@ async function run(cmdArgs) {
284
297
  break;
285
298
  // === Social ===
286
299
  case 'post':
287
- out(await handleSocialPost(ctx, pool, { content: req(1, 'post "message"') }));
300
+ out(await handleSocialPost(ctx, pool, { content: req(1, 'post "message"') }), fmt.formatPost);
288
301
  break;
289
302
  case 'reply':
290
303
  out(await handleSocialReply(ctx, pool, {
@@ -301,7 +314,7 @@ async function run(cmdArgs) {
301
314
  }));
302
315
  break;
303
316
  case 'profile':
304
- out(await handleSocialProfileGet(pool, ctx.activeNpub, req(1, 'profile <pubkey-hex>')));
317
+ out(await handleSocialProfileGet(pool, ctx.activeNpub, req(1, 'profile <pubkey-hex>')), fmt.formatProfile);
305
318
  break;
306
319
  case 'profile-set': {
307
320
  const profile = JSON.parse(req(1, 'profile-set \'{"name":"..."}\''));
@@ -321,7 +334,7 @@ async function run(cmdArgs) {
321
334
  }));
322
335
  break;
323
336
  case 'contacts':
324
- out(await handleContactsGet(pool, ctx.activeNpub, req(1, 'contacts <pubkey-hex>')));
337
+ out(await handleContactsGet(pool, ctx.activeNpub, req(1, 'contacts <pubkey-hex>')), fmt.formatContacts);
325
338
  break;
326
339
  case 'follow':
327
340
  out(await handleContactsFollow(ctx, pool, {
@@ -344,10 +357,10 @@ async function run(cmdArgs) {
344
357
  }));
345
358
  break;
346
359
  case 'dm-read':
347
- out(await handleDmRead(ctx, pool));
360
+ out(await handleDmRead(ctx, pool), fmt.formatDms);
348
361
  break;
349
362
  case 'feed':
350
- out(await handleFeed(ctx, pool, { limit: parseInt(flag('limit', '20'), 10) }));
363
+ out(await handleFeed(ctx, pool, { limit: parseInt(flag('limit', '20'), 10) }), fmt.formatFeed);
351
364
  break;
352
365
  case 'nip-publish': {
353
366
  const id = req(1, 'nip-publish <identifier> <title> <content-or-file>');
@@ -370,7 +383,7 @@ async function run(cmdArgs) {
370
383
  }));
371
384
  break;
372
385
  case 'notifications':
373
- out(await handleNotifications(ctx, pool, { limit: parseInt(flag('limit', '50'), 10) }));
386
+ out(await handleNotifications(ctx, pool, { limit: parseInt(flag('limit', '50'), 10) }), fmt.formatNotifications);
374
387
  break;
375
388
  // === Trust ===
376
389
  case 'attest':
@@ -434,7 +447,7 @@ async function run(cmdArgs) {
434
447
  break;
435
448
  // === Relay ===
436
449
  case 'relay-list':
437
- out(handleRelayList(ctx, pool, flag('compare')));
450
+ out(handleRelayList(ctx, pool, flag('compare')), fmt.formatRelays);
438
451
  break;
439
452
  case 'relay-set': {
440
453
  const urls = args.slice(1).filter(a => !a.startsWith('--'));
@@ -477,7 +490,7 @@ async function run(cmdArgs) {
477
490
  }));
478
491
  break;
479
492
  case 'zap-receipts':
480
- out(await handleZapReceipts(ctx, pool, { limit: parseInt(flag('limit', '20'), 10) }));
493
+ out(await handleZapReceipts(ctx, pool, { limit: parseInt(flag('limit', '20'), 10) }), fmt.formatZapReceipts);
481
494
  break;
482
495
  case 'zap-decode':
483
496
  out(handleZapDecode(req(1, 'zap-decode <bolt11>')));
@@ -520,7 +533,7 @@ async function run(cmdArgs) {
520
533
  out(await handleGroupChat(pool, ctx.activeNpub, {
521
534
  groupId: req(1, 'group-chat <group-id>'),
522
535
  limit: parseInt(flag('limit', '20'), 10),
523
- }));
536
+ }), fmt.formatGroupChat);
524
537
  break;
525
538
  case 'group-send':
526
539
  out(await handleGroupSend(ctx, pool, {
@@ -535,7 +548,7 @@ async function run(cmdArgs) {
535
548
  break;
536
549
  // === Utility ===
537
550
  case 'decode':
538
- out(handleDecode(req(1, 'decode <nip19>')));
551
+ out(handleDecode(req(1, 'decode <nip19>')), fmt.formatDecode);
539
552
  break;
540
553
  case 'encode-npub':
541
554
  console.log(handleEncodeNpub(req(1, 'encode-npub <hex>')));
@@ -563,7 +576,7 @@ async function run(cmdArgs) {
563
576
  out(handleFilter(JSON.parse(req(1, 'filter <event-json> <filter-json>')), JSON.parse(req(2, 'filter <event-json> <filter-json>'))));
564
577
  break;
565
578
  case 'nips':
566
- out(await handleNipList());
579
+ out(await handleNipList(), fmt.formatNipList);
567
580
  break;
568
581
  case 'nip': {
569
582
  const num = parseInt(req(1, 'nip <number>'), 10);
@@ -0,0 +1,30 @@
1
+ /** Human-readable formatters for CLI output */
2
+ import type { PublicIdentity } from './types.js';
3
+ export declare function formatIdentity(id: PublicIdentity): string;
4
+ export declare function formatIdentityList(ids: PublicIdentity[]): string;
5
+ export declare function formatPost(result: any): string;
6
+ export declare function formatPublish(result: any): string;
7
+ export declare function formatProfile(profile: Record<string, unknown>): string;
8
+ export declare function formatContacts(contacts: Array<{
9
+ pubkey: string;
10
+ relay?: string;
11
+ petname?: string;
12
+ }>): string;
13
+ export declare function formatNotifications(notifications: any[]): string;
14
+ export declare function formatFeed(events: any[]): string;
15
+ export declare function formatDms(dms: any[]): string;
16
+ export declare function formatRelays(relays: {
17
+ read: string[];
18
+ write: string[];
19
+ sharedWarning?: string;
20
+ }): string;
21
+ export declare function formatZapReceipts(receipts: any[]): string;
22
+ export declare function formatGroupChat(messages: any[]): string;
23
+ export declare function formatNipList(nips: Array<{
24
+ number: number;
25
+ title: string;
26
+ }>): string;
27
+ export declare function formatDecode(result: {
28
+ type: string;
29
+ data: unknown;
30
+ }): string;
package/dist/format.js ADDED
@@ -0,0 +1,153 @@
1
+ /** Human-readable formatters for CLI output */
2
+ export function formatIdentity(id) {
3
+ let line = `${id.npub}`;
4
+ if (id.personaName)
5
+ line += ` (persona: ${id.personaName})`;
6
+ else if (id.purpose)
7
+ line += ` (${id.purpose}${id.index ? ` #${id.index}` : ''})`;
8
+ return line;
9
+ }
10
+ export function formatIdentityList(ids) {
11
+ if (ids.length === 0)
12
+ return 'No identities found.';
13
+ const lines = ids.map((id, i) => {
14
+ const marker = i === 0 ? '* ' : ' ';
15
+ return `${marker}${formatIdentity(id)}`;
16
+ });
17
+ return lines.join('\n');
18
+ }
19
+ export function formatPost(result) {
20
+ const status = result.publish?.success ? '✓' : '✗';
21
+ return `${status} ${result.event?.id?.slice(0, 12)}... published to ${result.publish?.accepted?.length ?? 0} relay(s)`;
22
+ }
23
+ export function formatPublish(result) {
24
+ const status = result.publish?.success ? '✓ Published' : '✗ Failed';
25
+ const relays = result.publish?.accepted?.length ?? 0;
26
+ return `${status} (${relays} relay${relays !== 1 ? 's' : ''})`;
27
+ }
28
+ export function formatProfile(profile) {
29
+ if (Object.keys(profile).length === 0)
30
+ return 'No profile found.';
31
+ const lines = [];
32
+ if (profile.name)
33
+ lines.push(`Name: ${profile.name}`);
34
+ if (profile.display_name)
35
+ lines.push(`Display: ${profile.display_name}`);
36
+ if (profile.about)
37
+ lines.push(`About: ${profile.about}`);
38
+ if (profile.nip05)
39
+ lines.push(`NIP-05: ${profile.nip05}`);
40
+ if (profile.lud16)
41
+ lines.push(`LN: ${profile.lud16}`);
42
+ if (profile.picture)
43
+ lines.push(`Picture: ${profile.picture}`);
44
+ if (profile.banner)
45
+ lines.push(`Banner: ${profile.banner}`);
46
+ return lines.join('\n');
47
+ }
48
+ export function formatContacts(contacts) {
49
+ if (contacts.length === 0)
50
+ return 'No contacts found.';
51
+ const lines = contacts.map(c => {
52
+ let line = c.pubkey.slice(0, 12) + '...';
53
+ if (c.petname)
54
+ line += ` ${c.petname}`;
55
+ if (c.relay)
56
+ line += ` (${c.relay})`;
57
+ return line;
58
+ });
59
+ return `${contacts.length} contacts:\n${lines.join('\n')}`;
60
+ }
61
+ export function formatNotifications(notifications) {
62
+ if (notifications.length === 0)
63
+ return 'No notifications.';
64
+ return notifications.map(n => {
65
+ const time = new Date(n.createdAt * 1000).toLocaleString();
66
+ const from = n.from?.slice(0, 12) + '...';
67
+ switch (n.type) {
68
+ case 'reply':
69
+ return `💬 ${from} replied: ${n.content?.slice(0, 80)} (${time})`;
70
+ case 'reaction':
71
+ return `${n.content === '+' ? '❤️' : n.content} ${from} reacted (${time})`;
72
+ case 'mention':
73
+ return `@ ${from} mentioned you: ${n.content?.slice(0, 80)} (${time})`;
74
+ case 'zap':
75
+ const sats = n.amountMsats ? `${Math.round(n.amountMsats / 1000)} sats` : 'zap';
76
+ return `⚡ ${n.zapSender?.slice(0, 12) ?? from}... zapped ${sats}${n.zapMessage ? `: ${n.zapMessage}` : ''} (${time})`;
77
+ default:
78
+ return `${n.type} from ${from} (${time})`;
79
+ }
80
+ }).join('\n');
81
+ }
82
+ export function formatFeed(events) {
83
+ if (events.length === 0)
84
+ return 'No posts found.';
85
+ return events.map(e => {
86
+ const time = new Date(e.createdAt * 1000).toLocaleString();
87
+ const author = e.pubkey?.slice(0, 12) + '...';
88
+ return `${author} ${time}\n ${e.content?.slice(0, 200)}\n`;
89
+ }).join('\n');
90
+ }
91
+ export function formatDms(dms) {
92
+ if (dms.length === 0)
93
+ return 'No messages.';
94
+ return dms.map(dm => {
95
+ const time = new Date(dm.createdAt * 1000).toLocaleString();
96
+ const from = dm.from?.slice(0, 12) + '...';
97
+ const proto = dm.protocol === 'nip04-deprecated' ? ' [NIP-04]' : '';
98
+ if (!dm.decrypted)
99
+ return `✗ ${from} Could not decrypt${proto} (${time})`;
100
+ return `${from}${proto} ${time}\n ${dm.content}`;
101
+ }).join('\n\n');
102
+ }
103
+ export function formatRelays(relays) {
104
+ const lines = [];
105
+ if (relays.read.length > 0) {
106
+ lines.push('Read:');
107
+ relays.read.forEach(r => lines.push(` ${r}`));
108
+ }
109
+ if (relays.write.length > 0) {
110
+ lines.push('Write:');
111
+ relays.write.forEach(r => lines.push(` ${r}`));
112
+ }
113
+ if (relays.sharedWarning)
114
+ lines.push(`\n⚠️ ${relays.sharedWarning}`);
115
+ return lines.join('\n');
116
+ }
117
+ export function formatZapReceipts(receipts) {
118
+ if (receipts.length === 0)
119
+ return 'No zap receipts.';
120
+ return receipts.map(r => {
121
+ const time = new Date(r.createdAt * 1000).toLocaleString();
122
+ const sats = r.amountMsats ? `${Math.round(r.amountMsats / 1000)} sats` : '? sats';
123
+ const from = r.sender ? r.sender.slice(0, 12) + '...' : 'unknown';
124
+ return `⚡ ${sats} from ${from}${r.message ? ` — ${r.message}` : ''} (${time})`;
125
+ }).join('\n');
126
+ }
127
+ export function formatGroupChat(messages) {
128
+ if (messages.length === 0)
129
+ return 'No messages.';
130
+ return messages.map(m => {
131
+ const time = new Date(m.createdAt * 1000).toLocaleString();
132
+ const author = m.pubkey?.slice(0, 12) + '...';
133
+ return `${author} ${time}\n ${m.content}`;
134
+ }).join('\n\n');
135
+ }
136
+ export function formatNipList(nips) {
137
+ return nips.map(n => `NIP-${String(n.number).padStart(2, '0')} ${n.title}`).join('\n');
138
+ }
139
+ export function formatDecode(result) {
140
+ const lines = [`Type: ${result.type}`];
141
+ if (typeof result.data === 'string') {
142
+ lines.push(`Data: ${result.data}`);
143
+ }
144
+ else if (typeof result.data === 'object' && result.data !== null) {
145
+ for (const [k, v] of Object.entries(result.data)) {
146
+ if (Array.isArray(v))
147
+ lines.push(`${k}: ${v.join(', ')}`);
148
+ else
149
+ lines.push(`${k}: ${v}`);
150
+ }
151
+ }
152
+ return lines.join('\n');
153
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nostr-bray",
3
- "version": "1.9.2",
3
+ "version": "1.10.0",
4
4
  "description": "MCP server giving AI agents sovereign Nostr identities — key derivation, personas, attestations, duress detection, zaps",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",