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 +27 -14
- package/dist/format.d.ts +30 -0
- package/dist/format.js +153 -0
- package/package.json +1 -1
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
|
-
|
|
175
|
-
|
|
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);
|
package/dist/format.d.ts
ADDED
|
@@ -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