nostr-bray 1.8.0 → 1.9.1

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
@@ -652,6 +652,14 @@ async function shell() {
652
652
  continue;
653
653
  }
654
654
  const shellArgs = parseShellLine(trimmed);
655
+ // Per-command help in shell
656
+ if (shellArgs.includes('--help') && shellArgs[0]) {
657
+ const h = getCommandHelp(shellArgs[0]);
658
+ if (h) {
659
+ console.log(h);
660
+ continue;
661
+ }
662
+ }
655
663
  try {
656
664
  await run(shellArgs);
657
665
  }
package/dist/context.d.ts CHANGED
@@ -10,8 +10,10 @@ export declare class IdentityContext {
10
10
  private activeEntry;
11
11
  private maxCacheSize;
12
12
  constructor(secretKey: string, format: 'nsec' | 'hex' | 'mnemonic', opts?: ContextOptions);
13
- /** Current active identity's npub */
13
+ /** Current active identity's npub (bech32) */
14
14
  get activeNpub(): string;
15
+ /** Current active identity's hex public key — use this in relay filters */
16
+ get activePublicKeyHex(): string;
15
17
  /** Derive a child identity by purpose and index */
16
18
  derive(purpose: string, index: number): PublicIdentity;
17
19
  /** Derive a named persona */
package/dist/context.js CHANGED
@@ -30,10 +30,14 @@ export class IdentityContext {
30
30
  };
31
31
  this.activeEntry = this.masterEntry;
32
32
  }
33
- /** Current active identity's npub */
33
+ /** Current active identity's npub (bech32) */
34
34
  get activeNpub() {
35
35
  return this.activeEntry.identity.npub;
36
36
  }
37
+ /** Current active identity's hex public key — use this in relay filters */
38
+ get activePublicKeyHex() {
39
+ return Buffer.from(this.activeEntry.identity.publicKey).toString('hex');
40
+ }
37
41
  /** Derive a child identity by purpose and index */
38
42
  derive(purpose, index) {
39
43
  // Return from cache if already derived
@@ -5,7 +5,7 @@ import { hexId } from '../validation.js';
5
5
  import { handleIdentityBackup, handleIdentityRestore, handleIdentityMigrate } from './migration.js';
6
6
  export function registerIdentityTools(server, deps) {
7
7
  server.registerTool('identity_create', {
8
- description: 'Generate a fresh Nostr identity with a BIP-39 mnemonic seed. Returns the master npub and mnemonic for backup.',
8
+ description: 'Generate a fresh Nostr identity with a BIP-39 mnemonic seed. Returns { npub, mnemonic }. Store the mnemonic securely — it will not be shown again. Use this when the user needs a brand new identity unrelated to the current one.',
9
9
  annotations: { readOnlyHint: false },
10
10
  }, async () => {
11
11
  const result = handleIdentityCreate();
@@ -14,7 +14,7 @@ export function registerIdentityTools(server, deps) {
14
14
  };
15
15
  });
16
16
  server.registerTool('identity_derive', {
17
- description: 'Derive a child Nostr identity from the active tree root. Returns the new npub.',
17
+ description: 'Derive a child Nostr identity from the master key by purpose and index. Deterministic — same inputs always produce the same npub. Returns { npub, purpose, index }. Use identity_switch after deriving to operate as the new identity.',
18
18
  inputSchema: {
19
19
  purpose: z.string().describe('Purpose string for derivation (e.g. "messaging", "signing")'),
20
20
  index: z.number().int().min(0).default(0).describe('Derivation index (default 0)'),
@@ -27,7 +27,7 @@ export function registerIdentityTools(server, deps) {
27
27
  };
28
28
  });
29
29
  server.registerTool('identity_derive_persona', {
30
- description: 'Derive a named persona identity (e.g. "work", "personal", "anonymous"). Returns the new npub.',
30
+ description: 'Derive a named persona (e.g. "work", "personal", "anonymous"). Like identity_derive but uses a human-readable name. Returns { npub, personaName, index }. Follow with identity_switch to activate.',
31
31
  inputSchema: {
32
32
  name: z.string().describe('Persona name (e.g. "work", "personal", "anonymous")'),
33
33
  index: z.number().int().min(0).default(0).describe('Derivation index (default 0)'),
@@ -40,7 +40,7 @@ export function registerIdentityTools(server, deps) {
40
40
  };
41
41
  });
42
42
  server.registerTool('identity_switch', {
43
- description: 'Switch the active Nostr identity. All subsequent tools operate as the new identity. Use "master" to return to root.',
43
+ description: 'Switch the active Nostr identity. ALL subsequent tool calls will sign events and query relays as the new identity. Pass "master" to return to root. Returns { npub } of the now-active identity. This is the key tool for multi-persona workflows.',
44
44
  inputSchema: {
45
45
  target: z.string().describe('Purpose, persona name, or "master" to switch to'),
46
46
  index: z.number().int().min(0).optional().describe('Derivation index (if switching by purpose)'),
@@ -53,7 +53,7 @@ export function registerIdentityTools(server, deps) {
53
53
  };
54
54
  });
55
55
  server.registerTool('identity_list', {
56
- description: 'List all known Nostr identities. Returns npub, purpose, and persona name for each. Never includes private keys.',
56
+ description: 'List all known identities (master + all derived). Returns array of { npub, purpose, index, personaName }. Never includes private keys. Use this to see what identities are available before switching.',
57
57
  annotations: { readOnlyHint: true },
58
58
  }, async () => {
59
59
  const result = handleIdentityList(deps.ctx);
@@ -62,7 +62,7 @@ export function registerIdentityTools(server, deps) {
62
62
  };
63
63
  });
64
64
  server.registerTool('identity_prove', {
65
- description: 'Create a cryptographic linkage proof between the master key and the active identity. Defaults to blind proof (no purpose/index revealed).',
65
+ description: 'Create a cryptographic linkage proof between the master key and the active identity. "blind" (default) proves they share a master without revealing how the child was derived. "full" also reveals the purpose and index. Returns a LinkageProof object that can be verified by anyone with nsec-tree. Use trust_proof_publish to make it permanent on relays.',
66
66
  inputSchema: {
67
67
  mode: z.enum(['blind', 'full']).default('blind').describe('Proof mode: "blind" hides derivation path, "full" reveals purpose and index'),
68
68
  },
@@ -74,7 +74,7 @@ export function registerIdentityTools(server, deps) {
74
74
  };
75
75
  });
76
76
  server.registerTool('identity_backup_shamir', {
77
- description: 'Split the master secret into Shamir shard files. Shard content is written to disk — never returned in the response.',
77
+ description: 'Split the active identity\'s private key into Shamir shard files using threshold secret sharing. Any "threshold" shards can reconstruct the key. Files written with 0600 permissions via atomic rename. Returns file paths only — shard content never appears in the response.',
78
78
  inputSchema: {
79
79
  threshold: z.number().int().min(2).describe('Minimum shards needed to reconstruct'),
80
80
  shares: z.number().int().min(2).describe('Total number of shards to create'),
@@ -18,7 +18,7 @@ export async function handleRelaySet(ctx, pool, args) {
18
18
  // Check for existing relay list
19
19
  const existing = await pool.query(ctx.activeNpub, {
20
20
  kinds: [10002],
21
- authors: [ctx.activeNpub],
21
+ authors: [ctx.activePublicKeyHex],
22
22
  });
23
23
  // Build r-tags
24
24
  const tags = args.relays.map(r => {
@@ -90,7 +90,7 @@ export async function handleSocialProfileSet(ctx, pool, args) {
90
90
  // Check for existing profile
91
91
  const existing = await pool.query(ctx.activeNpub, {
92
92
  kinds: [0],
93
- authors: [ctx.activeNpub], // simplified — in production, use hex pubkey
93
+ authors: [ctx.activePublicKeyHex], // simplified — in production, use hex pubkey
94
94
  });
95
95
  if (existing.length > 0 && !args.confirm) {
96
96
  // Build diff
@@ -154,7 +154,7 @@ export async function handleContactsFollow(ctx, pool, args) {
154
154
  // Fetch existing contacts
155
155
  const existing = await pool.query(ctx.activeNpub, {
156
156
  kinds: [3],
157
- authors: [ctx.activeNpub],
157
+ authors: [ctx.activePublicKeyHex],
158
158
  });
159
159
  let tags = [];
160
160
  if (existing.length > 0) {
@@ -187,7 +187,7 @@ export async function handleContactsFollow(ctx, pool, args) {
187
187
  export async function handleContactsUnfollow(ctx, pool, args) {
188
188
  const existing = await pool.query(ctx.activeNpub, {
189
189
  kinds: [3],
190
- authors: [ctx.activeNpub],
190
+ authors: [ctx.activePublicKeyHex],
191
191
  });
192
192
  let tags = [];
193
193
  if (existing.length > 0) {
@@ -1,8 +1,10 @@
1
+ import { decode } from 'nostr-tools/nip19';
1
2
  /** Fetch notifications (mentions, replies, reactions, zaps) for the active identity */
2
3
  export async function handleNotifications(ctx, pool, opts) {
4
+ const activeHex = decode(ctx.activeNpub).data;
3
5
  const events = await pool.query(ctx.activeNpub, {
4
6
  kinds: [1, 7, 9735],
5
- '#p': [ctx.activeNpub],
7
+ '#p': [activeHex],
6
8
  limit: opts?.limit ?? 50,
7
9
  ...(opts?.since ? { since: opts.since } : {}),
8
10
  });
@@ -8,7 +8,7 @@ import { handleBlossomUpload, handleBlossomList, handleBlossomDelete } from './b
8
8
  import { handleGroupInfo, handleGroupChat, handleGroupSend, handleGroupMembers } from './groups.js';
9
9
  export function registerSocialTools(server, deps) {
10
10
  server.registerTool('social_post', {
11
- description: 'Post a text note (kind 1) as the active identity. Returns the signed event.',
11
+ description: 'Post a text note (kind 1) signed by the active identity and publish to relays. Returns { id, pubkey, publish: { success, accepted, rejected } }. The most common social action.',
12
12
  inputSchema: {
13
13
  content: z.string().describe('Text content of the note'),
14
14
  },
@@ -24,7 +24,7 @@ export function registerSocialTools(server, deps) {
24
24
  };
25
25
  });
26
26
  server.registerTool('social_reply', {
27
- description: 'Reply to a Nostr event (kind 1 with e-tag and p-tag) as the active identity.',
27
+ description: 'Reply to a Nostr event (kind 1) with correct threading tags. You need the event ID and the author\'s pubkey — get these from social_feed or social_notifications.',
28
28
  inputSchema: {
29
29
  content: z.string().describe('Reply text'),
30
30
  replyTo: hexId.describe('Event ID being replied to (hex)'),
@@ -42,7 +42,7 @@ export function registerSocialTools(server, deps) {
42
42
  };
43
43
  });
44
44
  server.registerTool('social_react', {
45
- description: 'React to a Nostr event (kind 7) as the active identity. Default reaction is "+".',
45
+ description: 'React to a Nostr event (kind 7). Pass "+" for like, or any emoji (🤙, ❤️, 🔥). You need the event ID and author pubkey.',
46
46
  inputSchema: {
47
47
  eventId: hexId.describe('Event ID to react to (hex)'),
48
48
  eventPubkey: hexId.describe('Pubkey of the event author (hex)'),
@@ -135,7 +135,7 @@ export function registerSocialTools(server, deps) {
135
135
  };
136
136
  });
137
137
  server.registerTool('dm_send', {
138
- description: 'Send a direct message to a Nostr pubkey. Uses NIP-17 gift wrap by default. Set nip04: true for legacy NIP-04 (requires NIP04_ENABLED).',
138
+ description: 'Send an encrypted direct message. Default: NIP-17 gift wrap (most private — sender identity hidden behind ephemeral key). Set nip04: true for legacy NIP-04 (only if NIP04_ENABLED=1). Returns { protocol, id, publish }.',
139
139
  inputSchema: {
140
140
  recipientPubkeyHex: hexId.describe('Recipient hex pubkey'),
141
141
  message: z.string().describe('Message text'),
@@ -160,7 +160,7 @@ export function registerSocialTools(server, deps) {
160
160
  };
161
161
  });
162
162
  server.registerTool('dm_read', {
163
- description: 'Read direct messages addressed to the active identity. Decrypts NIP-17 and NIP-04 messages.',
163
+ description: 'Read direct messages addressed to the active identity. Decrypts both NIP-17 (gift wrap) and NIP-04 (legacy). Each message includes { from, content, protocol, decrypted }. Gracefully handles decryption failures without crashing.',
164
164
  annotations: { readOnlyHint: true },
165
165
  }, async () => {
166
166
  const messages = await handleDmRead(deps.ctx, deps.pool);
@@ -5,7 +5,7 @@ import { handleTrustRingProve, handleTrustRingVerify } from './ring.js';
5
5
  import { handleTrustSpokenChallenge, handleTrustSpokenVerify } from './spoken.js';
6
6
  export function registerTrustTools(server, deps) {
7
7
  server.registerTool('trust_attest', {
8
- description: 'Create and publish a kind 31000 verifiable attestation as the active identity.',
8
+ description: 'Create and publish a kind 31000 verifiable attestation (NIP-VA) as the active identity. An attestation is a signed statement about a subject (e.g. "I verify this pubkey belongs to Alice"). Warns if attesting from a derived persona instead of master. Use trust_read to find existing attestations, trust_revoke to revoke them.',
9
9
  inputSchema: {
10
10
  type: z.string().describe('Attestation type (e.g. "identity-verification", "endorsement")'),
11
11
  identifier: z.string().optional().describe('D-tag identifier (hex pubkey or context string)'),
@@ -124,7 +124,7 @@ export function registerTrustTools(server, deps) {
124
124
  };
125
125
  });
126
126
  server.registerTool('trust_ring_prove', {
127
- description: 'Create a ring signature proving anonymous membership in a group of public keys.',
127
+ description: 'Create a ring signature proving anonymous group membership. A verifier can confirm "someone in this ring signed this" but cannot determine who. The active identity must be one of the pubkeys in the ring. Returns kind 30078 event with the signature.',
128
128
  inputSchema: {
129
129
  ring: z.array(hexId).describe('Hex x-only public keys of ring members (must include active identity)'),
130
130
  attestationType: z.string().describe('Attestation type context for the canonical message'),
@@ -48,9 +48,11 @@ function decryptNwcResponse(conn, event) {
48
48
  }
49
49
  /** Parse zap receipts (kind 9735) for the active identity */
50
50
  export async function handleZapReceipts(ctx, pool, opts) {
51
+ const { decode } = await import('nostr-tools/nip19');
52
+ const activeHex = decode(ctx.activeNpub).data;
51
53
  const events = await pool.query(ctx.activeNpub, {
52
54
  kinds: [9735],
53
- '#p': [ctx.activeNpub],
55
+ '#p': [activeHex],
54
56
  limit: opts?.limit ?? 20,
55
57
  ...(opts?.since ? { since: opts.since } : {}),
56
58
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nostr-bray",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
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",