nostr-bray 1.28.1 → 1.29.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 CHANGED
@@ -221,6 +221,8 @@ Quick examples:
221
221
  nostr-bray shell # interactive mode
222
222
 
223
223
  Flags:
224
+ --bunker <uri> Use bunker:// URI (overrides env/config)
225
+ --key <nsec|hex|mnemonic> Use this secret key (overrides env/config)
224
226
  --json Output raw JSON (for piping/scripts)
225
227
  --human Force human-readable output
226
228
  --help Show help for a command
@@ -235,6 +237,20 @@ if (command === 'help' || command === '--help' || command === '-h') {
235
237
  console.log(HELP);
236
238
  process.exit(0);
237
239
  }
240
+ // Allow key/bunker overrides on the command line
241
+ // These are injected into env before loadConfig() so all loading logic stays centralised.
242
+ // Note: loadConfig() deletes BUNKER_URI / NOSTR_SECRET_KEY from env after reading them.
243
+ if (args.includes('--bunker')) {
244
+ process.env.BUNKER_URI = args[args.indexOf('--bunker') + 1];
245
+ // Remove from args so commands don't see it as a positional argument
246
+ const i = args.indexOf('--bunker');
247
+ args.splice(i, 2);
248
+ }
249
+ if (args.includes('--key')) {
250
+ process.env.NOSTR_SECRET_KEY = args[args.indexOf('--key') + 1];
251
+ const i = args.indexOf('--key');
252
+ args.splice(i, 2);
253
+ }
238
254
  // Commands that work purely offline — no relay connection needed
239
255
  const OFFLINE_COMMANDS = new Set([
240
256
  'whoami', 'create', 'list', 'derive', 'persona', 'switch', 'prove',
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.0",
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",