nostr-bray 1.1.0 → 1.2.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.
Files changed (2) hide show
  1. package/dist/cli.js +118 -40
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -83,6 +83,10 @@ Safety:
83
83
  safety-configure [persona-name] Configure alternative identity
84
84
  safety-activate [persona-name] Switch to alternative identity
85
85
 
86
+ Modes:
87
+ (no command) Start MCP server (stdio)
88
+ shell Interactive REPL (persistent relay connection)
89
+
86
90
  Environment:
87
91
  NOSTR_SECRET_KEY nsec, hex, or BIP-39 mnemonic
88
92
  NOSTR_SECRET_KEY_FILE Path to secret key file
@@ -109,26 +113,56 @@ const masterRelays = await nip65.loadForIdentity(ctx.activeNpub);
109
113
  pool.reconfigure(ctx.activeNpub, masterRelays);
110
114
  /** Helper: print JSON result */
111
115
  function out(data) { console.log(JSON.stringify(data, null, 2)); }
112
- /** Helper: require arg or exit */
113
- function req(index, usage) {
114
- const val = args[index];
115
- if (!val) {
116
- console.error(`Usage: nostr-bray ${usage}`);
117
- process.exit(1);
116
+ /** Parse a shell line into args, respecting quotes */
117
+ function parseShellLine(line) {
118
+ const result = [];
119
+ let current = '';
120
+ let inQuote = null;
121
+ for (const ch of line) {
122
+ if (inQuote) {
123
+ if (ch === inQuote) {
124
+ inQuote = null;
125
+ }
126
+ else {
127
+ current += ch;
128
+ }
129
+ }
130
+ else if (ch === '"' || ch === "'") {
131
+ inQuote = ch;
132
+ }
133
+ else if (ch === ' ' || ch === '\t') {
134
+ if (current) {
135
+ result.push(current);
136
+ current = '';
137
+ }
138
+ }
139
+ else {
140
+ current += ch;
141
+ }
118
142
  }
119
- return val;
143
+ if (current)
144
+ result.push(current);
145
+ return result;
120
146
  }
121
- /** Helper: parse --flag value from args */
122
- function flag(name, fallback) {
123
- const i = args.indexOf(`--${name}`);
124
- if (i === -1)
125
- return fallback;
126
- return args[i + 1] ?? fallback;
127
- }
128
- function hasFlag(name) {
129
- return args.includes(`--${name}`);
130
- }
131
- async function run() {
147
+ async function run(cmdArgs) {
148
+ const command = cmdArgs[0];
149
+ /** Require arg or throw */
150
+ function req(index, usage) {
151
+ const val = cmdArgs[index];
152
+ if (!val) {
153
+ throw new Error(`Usage: ${usage}`);
154
+ }
155
+ return val;
156
+ }
157
+ function flag(name, fallback) {
158
+ const i = cmdArgs.indexOf(`--${name}`);
159
+ if (i === -1)
160
+ return fallback;
161
+ return cmdArgs[i + 1] ?? fallback;
162
+ }
163
+ function hasFlag(name) {
164
+ return cmdArgs.includes(`--${name}`);
165
+ }
132
166
  switch (command) {
133
167
  // === Identity ===
134
168
  case 'whoami':
@@ -141,21 +175,21 @@ async function run() {
141
175
  out(handleIdentityList(ctx));
142
176
  break;
143
177
  case 'derive':
144
- out(ctx.derive(req(1, 'derive <purpose> [index]'), parseInt(args[2] ?? '0', 10)));
178
+ out(ctx.derive(req(1, 'derive <purpose> [index]'), parseInt(cmdArgs[2] ?? '0', 10)));
145
179
  break;
146
180
  case 'persona':
147
- out(ctx.derivePersona(req(1, 'persona <name> [index]'), parseInt(args[2] ?? '0', 10)));
181
+ out(ctx.derivePersona(req(1, 'persona <name> [index]'), parseInt(cmdArgs[2] ?? '0', 10)));
148
182
  break;
149
183
  case 'switch':
150
- ctx.switch(req(1, 'switch <target> [index]'), args[2] ? parseInt(args[2], 10) : undefined);
184
+ ctx.switch(req(1, 'switch <target> [index]'), cmdArgs[2] ? parseInt(cmdArgs[2], 10) : undefined);
151
185
  console.log(ctx.activeNpub);
152
186
  break;
153
187
  case 'prove':
154
- out(handleIdentityProve(ctx, { mode: (args[1] === 'full' ? 'full' : 'blind') }));
188
+ out(handleIdentityProve(ctx, { mode: (cmdArgs[1] === 'full' ? 'full' : 'blind') }));
155
189
  break;
156
190
  case 'proof-publish': {
157
191
  const r = await handleTrustProofPublish(ctx, pool, {
158
- mode: (args[1] === 'full' ? 'full' : 'blind'),
192
+ mode: (cmdArgs[1] === 'full' ? 'full' : 'blind'),
159
193
  confirm: hasFlag('confirm'),
160
194
  });
161
195
  out(r);
@@ -164,8 +198,8 @@ async function run() {
164
198
  case 'backup':
165
199
  out(handleBackupShamir({
166
200
  secret: new Uint8Array(ctx.activePrivateKey),
167
- threshold: parseInt(args[2] ?? '3', 10),
168
- shares: parseInt(args[3] ?? '5', 10),
201
+ threshold: parseInt(cmdArgs[2] ?? '3', 10),
202
+ shares: parseInt(cmdArgs[3] ?? '5', 10),
169
203
  outputDir: req(1, 'backup <dir> [threshold] [shares]'),
170
204
  }));
171
205
  break;
@@ -203,7 +237,7 @@ async function run() {
203
237
  out(await handleSocialReact(ctx, pool, {
204
238
  eventId: req(1, 'react <event-id> <pubkey> [emoji]'),
205
239
  eventPubkey: req(2, 'react <event-id> <pubkey> [emoji]'),
206
- reaction: args[3] ?? '+',
240
+ reaction: cmdArgs[3] ?? '+',
207
241
  }));
208
242
  break;
209
243
  case 'profile':
@@ -235,8 +269,8 @@ async function run() {
235
269
  case 'attest':
236
270
  out(await handleTrustAttest(ctx, pool, {
237
271
  type: req(1, 'attest <type> <identifier> [subject]'),
238
- identifier: args[2],
239
- subject: args[3],
272
+ identifier: cmdArgs[2],
273
+ subject: cmdArgs[3],
240
274
  }));
241
275
  break;
242
276
  case 'trust-read':
@@ -306,7 +340,7 @@ async function run() {
306
340
  case 'relay-add':
307
341
  out(handleRelayAdd(ctx, pool, {
308
342
  url: req(1, 'relay-add <url> [read|write]'),
309
- mode: args[2],
343
+ mode: cmdArgs[2],
310
344
  }));
311
345
  break;
312
346
  case 'relay-info':
@@ -322,7 +356,7 @@ async function run() {
322
356
  case 'zap-invoice':
323
357
  out(await handleZapMakeInvoice(ctx, pool, {
324
358
  amountMsats: parseInt(req(1, 'zap-invoice <msats> [description]'), 10),
325
- description: args[2],
359
+ description: cmdArgs[2],
326
360
  nwcUri,
327
361
  }));
328
362
  break;
@@ -343,21 +377,65 @@ async function run() {
343
377
  break;
344
378
  // === Safety ===
345
379
  case 'safety-configure':
346
- out(handleDuressConfigure(ctx, pool, { personaName: args[1] }));
380
+ out(handleDuressConfigure(ctx, pool, { personaName: cmdArgs[1] }));
347
381
  break;
348
382
  case 'safety-activate':
349
- out(handleDuressActivate(ctx, { personaName: args[1] }));
383
+ out(handleDuressActivate(ctx, { personaName: cmdArgs[1] }));
350
384
  break;
351
385
  default:
352
- console.error(`Unknown command: ${command}\n`);
353
- console.error(HELP);
354
- process.exit(1);
386
+ throw new Error(`Unknown command: ${command}. Run --help for usage.`);
355
387
  }
356
388
  }
357
- try {
358
- await run();
389
+ // === Shell mode ===
390
+ async function shell() {
391
+ const { createInterface } = await import('node:readline');
392
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
393
+ console.log(`nostr-bray shell — ${ctx.activeNpub}`);
394
+ console.log('Type a command, or "help" / "exit".\n');
395
+ const prompt = () => new Promise(resolve => {
396
+ rl.question('bray> ', resolve);
397
+ });
398
+ while (true) {
399
+ const line = await prompt();
400
+ const trimmed = line.trim();
401
+ if (!trimmed)
402
+ continue;
403
+ if (trimmed === 'exit' || trimmed === 'quit')
404
+ break;
405
+ if (trimmed === 'help') {
406
+ console.log(HELP);
407
+ continue;
408
+ }
409
+ const shellArgs = parseShellLine(trimmed);
410
+ try {
411
+ await run(shellArgs);
412
+ }
413
+ catch (e) {
414
+ console.error(e.message);
415
+ }
416
+ }
417
+ rl.close();
359
418
  }
360
- finally {
361
- ctx.destroy();
362
- pool.close();
419
+ // === Entry point ===
420
+ if (command === 'shell') {
421
+ try {
422
+ await shell();
423
+ }
424
+ finally {
425
+ ctx.destroy();
426
+ pool.close();
427
+ }
428
+ }
429
+ else {
430
+ try {
431
+ await run(args);
432
+ }
433
+ catch (e) {
434
+ console.error(e.message);
435
+ process.exit(1);
436
+ }
437
+ finally {
438
+ ctx.destroy();
439
+ pool.close();
440
+ }
363
441
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nostr-bray",
3
- "version": "1.1.0",
3
+ "version": "1.2.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",