nostr-bray 1.28.1 → 1.29.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
@@ -23,6 +23,16 @@ import { handleKeyEncrypt, handleKeyDecrypt } from './util/ncryptsec.js';
23
23
  import { getCommandHelp } from './help.js';
24
24
  import * as fmt from './format.js';
25
25
  const args = process.argv.slice(2);
26
+ // Strip global flags before command detection so `--bunker <uri> whoami` works.
27
+ // Inject into env so loadConfig() picks them up via its normal path.
28
+ for (const flag of ['--bunker', '--key']) {
29
+ const i = args.indexOf(flag);
30
+ if (i !== -1) {
31
+ const envKey = flag === '--bunker' ? 'BUNKER_URI' : 'NOSTR_SECRET_KEY';
32
+ process.env[envKey] = args[i + 1];
33
+ args.splice(i, 2);
34
+ }
35
+ }
26
36
  const command = args[0];
27
37
  // No command = start MCP server.
28
38
  //
@@ -221,6 +231,8 @@ Quick examples:
221
231
  nostr-bray shell # interactive mode
222
232
 
223
233
  Flags:
234
+ --bunker <uri> Use bunker:// URI (overrides env/config)
235
+ --key <nsec|hex|mnemonic> Use this secret key (overrides env/config)
224
236
  --json Output raw JSON (for piping/scripts)
225
237
  --human Force human-readable output
226
238
  --help Show help for a command
@@ -255,6 +267,7 @@ let ctx;
255
267
  if (config.bunkerUri) {
256
268
  const { BunkerContext } = await import('./bunker-context.js');
257
269
  ctx = await BunkerContext.connect(config.bunkerUri);
270
+ await ctx.resolvePublicKey();
258
271
  console.error(`Connected to bunker — signing as ${ctx.activeNpub}`);
259
272
  }
260
273
  else {
package/dist/context.js CHANGED
@@ -121,12 +121,27 @@ export class IdentityContext {
121
121
  return;
122
122
  }
123
123
  }
124
- // Not in cache — derive on the fly
125
- const identity = derive(this.root, purposeOrName, index ?? 0);
124
+ // Not in cache — derive on the fly.
125
+ // If no colon, treat as a persona name (nostr:persona:{name}); otherwise raw purpose.
126
+ const isPersonaName = !purposeOrName.includes(':');
127
+ let identity;
128
+ let purpose;
129
+ let personaName;
130
+ if (isPersonaName) {
131
+ const persona = derivePersona(this.root, purposeOrName, index ?? 0);
132
+ identity = persona.identity;
133
+ purpose = persona.identity.purpose;
134
+ personaName = purposeOrName;
135
+ }
136
+ else {
137
+ identity = derive(this.root, purposeOrName, index ?? 0);
138
+ purpose = purposeOrName;
139
+ }
126
140
  const entry = {
127
141
  identity,
128
- purpose: purposeOrName,
142
+ purpose,
129
143
  index: index ?? 0,
144
+ personaName,
130
145
  lastUsed: Date.now(),
131
146
  };
132
147
  this.putCache(identity.npub, entry);
@@ -240,11 +240,11 @@ const { App } = globalThis.ExtApps;
240
240
  function sendReply() {
241
241
  const message = replyInput.value.trim();
242
242
  if (!message || !partnerPubkeyHex) return;
243
- app.callServerTool("dm-send", { recipientPubkeyHex: partnerPubkeyHex, message: message });
243
+ app.callServerTool({ name: "dm-send", arguments: { recipientPubkeyHex: partnerPubkeyHex, message } });
244
244
  replyInput.value = "";
245
245
  // Refresh conversation after short delay
246
246
  setTimeout(() => {
247
- app.callServerTool("dm-thread-widget", { withPubkeyHex: partnerPubkeyHex });
247
+ app.callServerTool({ name: "dm-thread-widget", arguments: { withPubkeyHex: partnerPubkeyHex } });
248
248
  }, 1500);
249
249
  }
250
250
 
@@ -6,6 +6,7 @@ import type { SigningContext } from '../signing-context.js';
6
6
  import type { RelayPool } from '../relay-pool.js';
7
7
  export interface IdentityPickerEntry {
8
8
  npub: string;
9
+ purpose: string;
9
10
  personaName?: string;
10
11
  displayName?: string;
11
12
  pictureDataUri?: string;
@@ -25,6 +25,7 @@ export async function handleIdentityPickerWidget(ctx, pool) {
25
25
  catch { /* profile lookup may fail for new identities */ }
26
26
  entries.push({
27
27
  npub: id.npub,
28
+ purpose: id.purpose ?? 'master',
28
29
  personaName: id.personaName,
29
30
  displayName,
30
31
  pictureDataUri,
@@ -110,7 +110,7 @@ const { App } = globalThis.ExtApps;
110
110
  app.ontoolresult = ({ content }) => {
111
111
  try {
112
112
  const data = JSON.parse(content[0].text);
113
- render(data);
113
+ if (data.identities) render(data);
114
114
  } catch (e) {
115
115
  clearAndShowMessage("Failed to parse identity data.");
116
116
  }
@@ -119,6 +119,10 @@ const { App } = globalThis.ExtApps;
119
119
  await app.connect();
120
120
  const hostCtx = app.getHostContext();
121
121
  if (hostCtx) applyTheme(hostCtx.theme);
122
+ const caps = app.getHostCapabilities();
123
+ const canCallTools = !!caps?.serverTools;
124
+ console.debug("[identity-picker] hostCapabilities:", JSON.stringify(caps));
125
+ console.debug("[identity-picker] canCallTools:", canCallTools);
122
126
 
123
127
  function clearAndShowMessage(msg) {
124
128
  while (root.firstChild) root.removeChild(root.firstChild);
@@ -179,14 +183,21 @@ const { App } = globalThis.ExtApps;
179
183
  card.appendChild(badge);
180
184
  }
181
185
 
182
- card.addEventListener("click", () => {
186
+ card.addEventListener("click", async () => {
183
187
  if (id.isActive) return;
184
- const target = id.personaName || "master";
185
- app.callServerTool("identity-switch", { target: target });
186
- // Re-fetch identities after switch
187
- setTimeout(() => {
188
- app.callServerTool("identity-picker-widget", {});
189
- }, 500);
188
+ if (!canCallTools) {
189
+ clearAndShowMessage(`Host doesn't support widget tool calls. Ask the AI to switch to "${id.personaName || id.purpose}".`);
190
+ return;
191
+ }
192
+ const target = id.personaName || id.purpose;
193
+ try {
194
+ await app.callServerTool({ name: "identity-switch", arguments: { target } });
195
+ const result = await app.callServerTool({ name: "identity-picker-widget", arguments: {} });
196
+ const data = JSON.parse(result.content[0].text);
197
+ render(data);
198
+ } catch (e) {
199
+ clearAndShowMessage("Switch failed: " + (e?.message ?? e));
200
+ }
190
201
  });
191
202
 
192
203
  grid.appendChild(card);
@@ -210,7 +210,7 @@ const { App } = globalThis.ExtApps;
210
210
  loadMore.className = "load-more";
211
211
  loadMore.textContent = "Load more";
212
212
  loadMore.addEventListener("click", () => {
213
- app.callServerTool("social-feed-widget", { since: lastSince });
213
+ app.callServerTool({ name: "social-feed-widget", arguments: { since: lastSince } });
214
214
  });
215
215
  root.appendChild(loadMore);
216
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nostr-bray",
3
- "version": "1.28.1",
3
+ "version": "1.29.1",
4
4
  "mcpName": "dev.forgesworn/bray",
5
5
  "description": "Trust-aware Nostr for AI and humans. Three dimensions of trust -- verification, proximity, and access -- woven into every interaction.",
6
6
  "type": "module",