nostr-bray 1.9.2 → 1.11.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 +41 -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
|
|
@@ -137,6 +138,7 @@ Environment:
|
|
|
137
138
|
NOSTR_RELAYS Comma-separated relay URLs
|
|
138
139
|
NWC_URI / NWC_URI_FILE Nostr Wallet Connect URI
|
|
139
140
|
TOR_PROXY SOCKS5h proxy URL
|
|
141
|
+
NOSTR_BRAY_OUTPUT Default output: "human" (default) or "json"
|
|
140
142
|
|
|
141
143
|
Quick examples:
|
|
142
144
|
nostr-bray whoami # show your npub
|
|
@@ -147,6 +149,11 @@ Quick examples:
|
|
|
147
149
|
nostr-bray nips # browse official NIPs
|
|
148
150
|
nostr-bray shell # interactive mode
|
|
149
151
|
|
|
152
|
+
Flags:
|
|
153
|
+
--json Output raw JSON (for piping/scripts)
|
|
154
|
+
--human Force human-readable output
|
|
155
|
+
--help Show help for a command
|
|
156
|
+
|
|
150
157
|
Use 'nostr-bray <command> --help' for detailed help on any command.
|
|
151
158
|
|
|
152
159
|
Learn more:
|
|
@@ -171,8 +178,27 @@ config.secretKey = '';
|
|
|
171
178
|
config.nwcUri = undefined;
|
|
172
179
|
const masterRelays = await nip65.loadForIdentity(ctx.activeNpub);
|
|
173
180
|
pool.reconfigure(ctx.activeNpub, masterRelays);
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
// Default output mode from env (CLI-level --json/--human handled per-command)
|
|
182
|
+
const envDefault = process.env.NOSTR_BRAY_OUTPUT === 'json' ? 'json' : 'human';
|
|
183
|
+
/** Resolve output mode for a given set of args */
|
|
184
|
+
function resolveOutputMode(cmdArgs) {
|
|
185
|
+
if (cmdArgs.includes('--json'))
|
|
186
|
+
return 'json';
|
|
187
|
+
if (cmdArgs.includes('--human'))
|
|
188
|
+
return 'human';
|
|
189
|
+
return envDefault;
|
|
190
|
+
}
|
|
191
|
+
// Top-level output mode (for single-command invocations)
|
|
192
|
+
let currentOutputMode = resolveOutputMode(args);
|
|
193
|
+
/** Print JSON or human-readable depending on output mode */
|
|
194
|
+
function out(data, humanFormatter) {
|
|
195
|
+
if (currentOutputMode === 'json' || !humanFormatter) {
|
|
196
|
+
console.log(JSON.stringify(data, null, 2));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(humanFormatter(data));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
176
202
|
/** Parse a shell line into args, respecting quotes */
|
|
177
203
|
function parseShellLine(line) {
|
|
178
204
|
const result = [];
|
|
@@ -205,6 +231,7 @@ function parseShellLine(line) {
|
|
|
205
231
|
return result;
|
|
206
232
|
}
|
|
207
233
|
async function run(cmdArgs) {
|
|
234
|
+
currentOutputMode = resolveOutputMode(cmdArgs);
|
|
208
235
|
const command = cmdArgs[0];
|
|
209
236
|
/** Require arg or throw */
|
|
210
237
|
function req(index, usage) {
|
|
@@ -232,7 +259,7 @@ async function run(cmdArgs) {
|
|
|
232
259
|
out(handleIdentityCreate());
|
|
233
260
|
break;
|
|
234
261
|
case 'list':
|
|
235
|
-
out(handleIdentityList(ctx));
|
|
262
|
+
out(handleIdentityList(ctx), fmt.formatIdentityList);
|
|
236
263
|
break;
|
|
237
264
|
case 'derive':
|
|
238
265
|
out(ctx.derive(req(1, 'derive <purpose> [index]'), parseInt(cmdArgs[2] ?? '0', 10)));
|
|
@@ -284,7 +311,7 @@ async function run(cmdArgs) {
|
|
|
284
311
|
break;
|
|
285
312
|
// === Social ===
|
|
286
313
|
case 'post':
|
|
287
|
-
out(await handleSocialPost(ctx, pool, { content: req(1, 'post "message"') }));
|
|
314
|
+
out(await handleSocialPost(ctx, pool, { content: req(1, 'post "message"') }), fmt.formatPost);
|
|
288
315
|
break;
|
|
289
316
|
case 'reply':
|
|
290
317
|
out(await handleSocialReply(ctx, pool, {
|
|
@@ -301,7 +328,7 @@ async function run(cmdArgs) {
|
|
|
301
328
|
}));
|
|
302
329
|
break;
|
|
303
330
|
case 'profile':
|
|
304
|
-
out(await handleSocialProfileGet(pool, ctx.activeNpub, req(1, 'profile <pubkey-hex>')));
|
|
331
|
+
out(await handleSocialProfileGet(pool, ctx.activeNpub, req(1, 'profile <pubkey-hex>')), fmt.formatProfile);
|
|
305
332
|
break;
|
|
306
333
|
case 'profile-set': {
|
|
307
334
|
const profile = JSON.parse(req(1, 'profile-set \'{"name":"..."}\''));
|
|
@@ -321,7 +348,7 @@ async function run(cmdArgs) {
|
|
|
321
348
|
}));
|
|
322
349
|
break;
|
|
323
350
|
case 'contacts':
|
|
324
|
-
out(await handleContactsGet(pool, ctx.activeNpub, req(1, 'contacts <pubkey-hex>')));
|
|
351
|
+
out(await handleContactsGet(pool, ctx.activeNpub, req(1, 'contacts <pubkey-hex>')), fmt.formatContacts);
|
|
325
352
|
break;
|
|
326
353
|
case 'follow':
|
|
327
354
|
out(await handleContactsFollow(ctx, pool, {
|
|
@@ -344,10 +371,10 @@ async function run(cmdArgs) {
|
|
|
344
371
|
}));
|
|
345
372
|
break;
|
|
346
373
|
case 'dm-read':
|
|
347
|
-
out(await handleDmRead(ctx, pool));
|
|
374
|
+
out(await handleDmRead(ctx, pool), fmt.formatDms);
|
|
348
375
|
break;
|
|
349
376
|
case 'feed':
|
|
350
|
-
out(await handleFeed(ctx, pool, { limit: parseInt(flag('limit', '20'), 10) }));
|
|
377
|
+
out(await handleFeed(ctx, pool, { limit: parseInt(flag('limit', '20'), 10) }), fmt.formatFeed);
|
|
351
378
|
break;
|
|
352
379
|
case 'nip-publish': {
|
|
353
380
|
const id = req(1, 'nip-publish <identifier> <title> <content-or-file>');
|
|
@@ -370,7 +397,7 @@ async function run(cmdArgs) {
|
|
|
370
397
|
}));
|
|
371
398
|
break;
|
|
372
399
|
case 'notifications':
|
|
373
|
-
out(await handleNotifications(ctx, pool, { limit: parseInt(flag('limit', '50'), 10) }));
|
|
400
|
+
out(await handleNotifications(ctx, pool, { limit: parseInt(flag('limit', '50'), 10) }), fmt.formatNotifications);
|
|
374
401
|
break;
|
|
375
402
|
// === Trust ===
|
|
376
403
|
case 'attest':
|
|
@@ -434,7 +461,7 @@ async function run(cmdArgs) {
|
|
|
434
461
|
break;
|
|
435
462
|
// === Relay ===
|
|
436
463
|
case 'relay-list':
|
|
437
|
-
out(handleRelayList(ctx, pool, flag('compare')));
|
|
464
|
+
out(handleRelayList(ctx, pool, flag('compare')), fmt.formatRelays);
|
|
438
465
|
break;
|
|
439
466
|
case 'relay-set': {
|
|
440
467
|
const urls = args.slice(1).filter(a => !a.startsWith('--'));
|
|
@@ -477,7 +504,7 @@ async function run(cmdArgs) {
|
|
|
477
504
|
}));
|
|
478
505
|
break;
|
|
479
506
|
case 'zap-receipts':
|
|
480
|
-
out(await handleZapReceipts(ctx, pool, { limit: parseInt(flag('limit', '20'), 10) }));
|
|
507
|
+
out(await handleZapReceipts(ctx, pool, { limit: parseInt(flag('limit', '20'), 10) }), fmt.formatZapReceipts);
|
|
481
508
|
break;
|
|
482
509
|
case 'zap-decode':
|
|
483
510
|
out(handleZapDecode(req(1, 'zap-decode <bolt11>')));
|
|
@@ -520,7 +547,7 @@ async function run(cmdArgs) {
|
|
|
520
547
|
out(await handleGroupChat(pool, ctx.activeNpub, {
|
|
521
548
|
groupId: req(1, 'group-chat <group-id>'),
|
|
522
549
|
limit: parseInt(flag('limit', '20'), 10),
|
|
523
|
-
}));
|
|
550
|
+
}), fmt.formatGroupChat);
|
|
524
551
|
break;
|
|
525
552
|
case 'group-send':
|
|
526
553
|
out(await handleGroupSend(ctx, pool, {
|
|
@@ -535,7 +562,7 @@ async function run(cmdArgs) {
|
|
|
535
562
|
break;
|
|
536
563
|
// === Utility ===
|
|
537
564
|
case 'decode':
|
|
538
|
-
out(handleDecode(req(1, 'decode <nip19>')));
|
|
565
|
+
out(handleDecode(req(1, 'decode <nip19>')), fmt.formatDecode);
|
|
539
566
|
break;
|
|
540
567
|
case 'encode-npub':
|
|
541
568
|
console.log(handleEncodeNpub(req(1, 'encode-npub <hex>')));
|
|
@@ -563,7 +590,7 @@ async function run(cmdArgs) {
|
|
|
563
590
|
out(handleFilter(JSON.parse(req(1, 'filter <event-json> <filter-json>')), JSON.parse(req(2, 'filter <event-json> <filter-json>'))));
|
|
564
591
|
break;
|
|
565
592
|
case 'nips':
|
|
566
|
-
out(await handleNipList());
|
|
593
|
+
out(await handleNipList(), fmt.formatNipList);
|
|
567
594
|
break;
|
|
568
595
|
case 'nip': {
|
|
569
596
|
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