@openclaw/nostr 2026.3.13 → 2026.5.1-beta.2
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/README.md +6 -0
- package/api.ts +10 -0
- package/channel-plugin-api.ts +1 -0
- package/index.ts +60 -36
- package/openclaw.plugin.json +190 -1
- package/package.json +41 -9
- package/runtime-api.ts +6 -0
- package/setup-api.ts +1 -0
- package/setup-entry.ts +9 -0
- package/setup-plugin-api.ts +3 -0
- package/src/channel-api.ts +15 -0
- package/src/channel.inbound.test.ts +176 -0
- package/src/channel.outbound.test.ts +89 -49
- package/src/channel.setup.ts +231 -0
- package/src/channel.test.ts +439 -71
- package/src/channel.ts +146 -283
- package/src/config-schema.ts +18 -12
- package/src/default-relays.ts +1 -0
- package/src/gateway.ts +302 -0
- package/src/inbound-direct-dm-runtime.ts +1 -0
- package/src/metrics.ts +6 -6
- package/src/nostr-bus.fuzz.test.ts +74 -247
- package/src/nostr-bus.inbound.test.ts +526 -0
- package/src/nostr-bus.integration.test.ts +88 -64
- package/src/nostr-bus.test.ts +22 -31
- package/src/nostr-bus.ts +206 -136
- package/src/nostr-key-utils.ts +94 -0
- package/src/nostr-profile-core.ts +134 -0
- package/src/nostr-profile-http-runtime.ts +6 -0
- package/src/nostr-profile-http.test.ts +276 -167
- package/src/nostr-profile-http.ts +51 -36
- package/src/nostr-profile-import.ts +3 -3
- package/src/nostr-profile-url-safety.ts +21 -0
- package/src/nostr-profile.fuzz.test.ts +7 -57
- package/src/nostr-profile.test.ts +16 -14
- package/src/nostr-profile.ts +13 -146
- package/src/nostr-state-store.test.ts +106 -2
- package/src/nostr-state-store.ts +46 -49
- package/src/runtime.ts +6 -3
- package/src/seen-tracker.ts +1 -1
- package/src/session-route.ts +25 -0
- package/src/setup-surface.ts +265 -0
- package/src/test-fixtures.ts +45 -0
- package/src/types.ts +26 -25
- package/test-api.ts +1 -0
- package/tsconfig.json +16 -0
- package/CHANGELOG.md +0 -116
- package/src/types.test.ts +0 -175
package/src/channel.ts
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
|
+
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
2
|
+
import {
|
|
3
|
+
createScopedDmSecurityResolver,
|
|
4
|
+
createTopLevelChannelConfigAdapter,
|
|
5
|
+
} from "openclaw/plugin-sdk/channel-config-helpers";
|
|
6
|
+
import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
7
|
+
import {
|
|
8
|
+
buildPassiveChannelStatusSummary,
|
|
9
|
+
buildTrafficStatusSummary,
|
|
10
|
+
} from "openclaw/plugin-sdk/extension-shared";
|
|
11
|
+
import { createComputedAccountStatusAdapter } from "openclaw/plugin-sdk/status-helpers";
|
|
1
12
|
import {
|
|
2
13
|
buildChannelConfigSchema,
|
|
3
14
|
collectStatusIssuesFromLastError,
|
|
4
15
|
createDefaultChannelRuntimeState,
|
|
5
16
|
DEFAULT_ACCOUNT_ID,
|
|
6
17
|
formatPairingApproveHint,
|
|
7
|
-
mapAllowFromEntries,
|
|
8
18
|
type ChannelPlugin,
|
|
9
|
-
} from "
|
|
10
|
-
import {
|
|
11
|
-
buildPassiveChannelStatusSummary,
|
|
12
|
-
buildTrafficStatusSummary,
|
|
13
|
-
} from "../../shared/channel-status-summary.js";
|
|
19
|
+
} from "./channel-api.js";
|
|
14
20
|
import type { NostrProfile } from "./config-schema.js";
|
|
15
21
|
import { NostrConfigSchema } from "./config-schema.js";
|
|
16
|
-
import
|
|
17
|
-
|
|
22
|
+
import {
|
|
23
|
+
getActiveNostrBuses,
|
|
24
|
+
nostrOutboundAdapter,
|
|
25
|
+
nostrPairingTextAdapter,
|
|
26
|
+
startNostrGatewayAccount,
|
|
27
|
+
} from "./gateway.js";
|
|
28
|
+
import { normalizePubkey } from "./nostr-key-utils.js";
|
|
18
29
|
import type { ProfilePublishResult } from "./nostr-profile.js";
|
|
19
|
-
import {
|
|
30
|
+
import { resolveNostrOutboundSessionRoute } from "./session-route.js";
|
|
31
|
+
import { nostrSetupAdapter, nostrSetupWizard } from "./setup-surface.js";
|
|
20
32
|
import {
|
|
21
33
|
listNostrAccountIds,
|
|
22
34
|
resolveDefaultNostrAccountId,
|
|
@@ -24,288 +36,139 @@ import {
|
|
|
24
36
|
type ResolvedNostrAccount,
|
|
25
37
|
} from "./types.js";
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
docsLabel: "nostr",
|
|
41
|
-
blurb: "Decentralized DMs via Nostr relays (NIP-04)",
|
|
42
|
-
order: 100,
|
|
43
|
-
},
|
|
44
|
-
capabilities: {
|
|
45
|
-
chatTypes: ["direct"], // DMs only for MVP
|
|
46
|
-
media: false, // No media for MVP
|
|
39
|
+
const resolveNostrDmPolicy = createScopedDmSecurityResolver<ResolvedNostrAccount>({
|
|
40
|
+
channelKey: "nostr",
|
|
41
|
+
resolvePolicy: (account) => account.config.dmPolicy,
|
|
42
|
+
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
43
|
+
policyPathSuffix: "dmPolicy",
|
|
44
|
+
defaultPolicy: "pairing",
|
|
45
|
+
approveHint: formatPairingApproveHint("nostr"),
|
|
46
|
+
normalizeEntry: (raw) => {
|
|
47
|
+
try {
|
|
48
|
+
return normalizePubkey(raw.trim().replace(/^nostr:/i, ""));
|
|
49
|
+
} catch {
|
|
50
|
+
return raw.trim();
|
|
51
|
+
}
|
|
47
52
|
},
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (bus) {
|
|
95
|
-
await bus.sendDm(id, "Your pairing request has been approved!");
|
|
96
|
-
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const nostrConfigAdapter = createTopLevelChannelConfigAdapter<ResolvedNostrAccount>({
|
|
56
|
+
sectionKey: "nostr",
|
|
57
|
+
resolveAccount: (cfg) => resolveNostrAccount({ cfg }),
|
|
58
|
+
listAccountIds: listNostrAccountIds,
|
|
59
|
+
defaultAccountId: resolveDefaultNostrAccountId,
|
|
60
|
+
deleteMode: "clear-fields",
|
|
61
|
+
clearBaseFields: [
|
|
62
|
+
"name",
|
|
63
|
+
"defaultAccount",
|
|
64
|
+
"privateKey",
|
|
65
|
+
"relays",
|
|
66
|
+
"dmPolicy",
|
|
67
|
+
"allowFrom",
|
|
68
|
+
"profile",
|
|
69
|
+
],
|
|
70
|
+
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
71
|
+
formatAllowFrom: (allowFrom) =>
|
|
72
|
+
allowFrom
|
|
73
|
+
.map((entry) => String(entry).trim())
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.map((entry) => {
|
|
76
|
+
if (entry === "*") {
|
|
77
|
+
return "*";
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return normalizePubkey(entry);
|
|
81
|
+
} catch {
|
|
82
|
+
return entry;
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
.filter(Boolean),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = createChatChannelPlugin({
|
|
89
|
+
base: {
|
|
90
|
+
id: "nostr",
|
|
91
|
+
meta: {
|
|
92
|
+
id: "nostr",
|
|
93
|
+
label: "Nostr",
|
|
94
|
+
selectionLabel: "Nostr",
|
|
95
|
+
docsPath: "/channels/nostr",
|
|
96
|
+
docsLabel: "nostr",
|
|
97
|
+
blurb: "Decentralized DMs via Nostr relays (NIP-04)",
|
|
98
|
+
order: 100,
|
|
97
99
|
},
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
resolveDmPolicy: ({ account }) => {
|
|
102
|
-
return {
|
|
103
|
-
policy: account.config.dmPolicy ?? "pairing",
|
|
104
|
-
allowFrom: account.config.allowFrom ?? [],
|
|
105
|
-
policyPath: "channels.nostr.dmPolicy",
|
|
106
|
-
allowFromPath: "channels.nostr.allowFrom",
|
|
107
|
-
approveHint: formatPairingApproveHint("nostr"),
|
|
108
|
-
normalizeEntry: (raw) => {
|
|
109
|
-
try {
|
|
110
|
-
return normalizePubkey(raw.replace(/^nostr:/i, "").trim());
|
|
111
|
-
} catch {
|
|
112
|
-
return raw.trim();
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
};
|
|
100
|
+
capabilities: {
|
|
101
|
+
chatTypes: ["direct"], // DMs only for MVP
|
|
102
|
+
media: false, // No media for MVP
|
|
116
103
|
},
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
104
|
+
reload: { configPrefixes: ["channels.nostr"] },
|
|
105
|
+
configSchema: buildChannelConfigSchema(NostrConfigSchema),
|
|
106
|
+
setup: nostrSetupAdapter,
|
|
107
|
+
setupWizard: nostrSetupWizard,
|
|
108
|
+
config: {
|
|
109
|
+
...nostrConfigAdapter,
|
|
110
|
+
isConfigured: (account) => account.configured,
|
|
111
|
+
describeAccount: (account) =>
|
|
112
|
+
describeAccountSnapshot({
|
|
113
|
+
account,
|
|
114
|
+
configured: account.configured,
|
|
115
|
+
extra: {
|
|
116
|
+
publicKey: account.publicKey,
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
128
119
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
120
|
+
messaging: {
|
|
121
|
+
normalizeTarget: (target) => {
|
|
122
|
+
// Strip nostr: prefix if present
|
|
123
|
+
const cleaned = target.trim().replace(/^nostr:/i, "");
|
|
124
|
+
try {
|
|
125
|
+
return normalizePubkey(cleaned);
|
|
126
|
+
} catch {
|
|
127
|
+
return cleaned;
|
|
128
|
+
}
|
|
133
129
|
},
|
|
134
|
-
|
|
130
|
+
targetResolver: {
|
|
131
|
+
looksLikeId: (input) => {
|
|
132
|
+
const trimmed = input.trim();
|
|
133
|
+
return trimmed.startsWith("npub1") || /^[0-9a-fA-F]{64}$/.test(trimmed);
|
|
134
|
+
},
|
|
135
|
+
hint: "<npub|hex pubkey|nostr:npub...>",
|
|
136
|
+
},
|
|
137
|
+
resolveOutboundSessionRoute: (params) => resolveNostrOutboundSessionRoute(params),
|
|
135
138
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
messageId: `nostr-${Date.now()}`,
|
|
160
|
-
};
|
|
139
|
+
status: {
|
|
140
|
+
...createComputedAccountStatusAdapter<ResolvedNostrAccount>({
|
|
141
|
+
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
|
|
142
|
+
collectStatusIssues: (accounts) => collectStatusIssuesFromLastError("nostr", accounts),
|
|
143
|
+
buildChannelSummary: ({ snapshot }) =>
|
|
144
|
+
buildPassiveChannelStatusSummary(snapshot, {
|
|
145
|
+
publicKey: snapshot.publicKey ?? null,
|
|
146
|
+
}),
|
|
147
|
+
resolveAccountSnapshot: ({ account, runtime }) => ({
|
|
148
|
+
accountId: account.accountId,
|
|
149
|
+
name: account.name,
|
|
150
|
+
enabled: account.enabled,
|
|
151
|
+
configured: account.configured,
|
|
152
|
+
extra: {
|
|
153
|
+
publicKey: account.publicKey,
|
|
154
|
+
profile: account.profile,
|
|
155
|
+
...buildTrafficStatusSummary(runtime),
|
|
156
|
+
},
|
|
157
|
+
}),
|
|
158
|
+
}),
|
|
159
|
+
},
|
|
160
|
+
gateway: {
|
|
161
|
+
startAccount: startNostrGatewayAccount,
|
|
161
162
|
},
|
|
162
163
|
},
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
|
|
166
|
-
collectStatusIssues: (accounts) => collectStatusIssuesFromLastError("nostr", accounts),
|
|
167
|
-
buildChannelSummary: ({ snapshot }) =>
|
|
168
|
-
buildPassiveChannelStatusSummary(snapshot, {
|
|
169
|
-
publicKey: snapshot.publicKey ?? null,
|
|
170
|
-
}),
|
|
171
|
-
buildAccountSnapshot: ({ account, runtime }) => ({
|
|
172
|
-
accountId: account.accountId,
|
|
173
|
-
name: account.name,
|
|
174
|
-
enabled: account.enabled,
|
|
175
|
-
configured: account.configured,
|
|
176
|
-
publicKey: account.publicKey,
|
|
177
|
-
profile: account.profile,
|
|
178
|
-
running: runtime?.running ?? false,
|
|
179
|
-
lastStartAt: runtime?.lastStartAt ?? null,
|
|
180
|
-
lastStopAt: runtime?.lastStopAt ?? null,
|
|
181
|
-
lastError: runtime?.lastError ?? null,
|
|
182
|
-
...buildTrafficStatusSummary(runtime),
|
|
183
|
-
}),
|
|
164
|
+
pairing: {
|
|
165
|
+
text: nostrPairingTextAdapter,
|
|
184
166
|
},
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
startAccount: async (ctx) => {
|
|
188
|
-
const account = ctx.account;
|
|
189
|
-
ctx.setStatus({
|
|
190
|
-
accountId: account.accountId,
|
|
191
|
-
publicKey: account.publicKey,
|
|
192
|
-
});
|
|
193
|
-
ctx.log?.info(
|
|
194
|
-
`[${account.accountId}] starting Nostr provider (pubkey: ${account.publicKey})`,
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
if (!account.configured) {
|
|
198
|
-
throw new Error("Nostr private key not configured");
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const runtime = getNostrRuntime();
|
|
202
|
-
|
|
203
|
-
// Track bus handle for metrics callback
|
|
204
|
-
let busHandle: NostrBusHandle | null = null;
|
|
205
|
-
|
|
206
|
-
const bus = await startNostrBus({
|
|
207
|
-
accountId: account.accountId,
|
|
208
|
-
privateKey: account.privateKey,
|
|
209
|
-
relays: account.relays,
|
|
210
|
-
onMessage: async (senderPubkey, text, reply) => {
|
|
211
|
-
ctx.log?.debug?.(
|
|
212
|
-
`[${account.accountId}] DM from ${senderPubkey}: ${text.slice(0, 50)}...`,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
// Forward to OpenClaw's message pipeline
|
|
216
|
-
await (
|
|
217
|
-
runtime.channel.reply as { handleInboundMessage?: (params: unknown) => Promise<void> }
|
|
218
|
-
).handleInboundMessage?.({
|
|
219
|
-
channel: "nostr",
|
|
220
|
-
accountId: account.accountId,
|
|
221
|
-
senderId: senderPubkey,
|
|
222
|
-
chatType: "direct",
|
|
223
|
-
chatId: senderPubkey, // For DMs, chatId is the sender's pubkey
|
|
224
|
-
text,
|
|
225
|
-
reply: async (responseText: string) => {
|
|
226
|
-
await reply(responseText);
|
|
227
|
-
},
|
|
228
|
-
});
|
|
229
|
-
},
|
|
230
|
-
onError: (error, context) => {
|
|
231
|
-
ctx.log?.error?.(`[${account.accountId}] Nostr error (${context}): ${error.message}`);
|
|
232
|
-
},
|
|
233
|
-
onConnect: (relay) => {
|
|
234
|
-
ctx.log?.debug?.(`[${account.accountId}] Connected to relay: ${relay}`);
|
|
235
|
-
},
|
|
236
|
-
onDisconnect: (relay) => {
|
|
237
|
-
ctx.log?.debug?.(`[${account.accountId}] Disconnected from relay: ${relay}`);
|
|
238
|
-
},
|
|
239
|
-
onEose: (relays) => {
|
|
240
|
-
ctx.log?.debug?.(`[${account.accountId}] EOSE received from relays: ${relays}`);
|
|
241
|
-
},
|
|
242
|
-
onMetric: (event: MetricEvent) => {
|
|
243
|
-
// Log significant metrics at appropriate levels
|
|
244
|
-
if (event.name.startsWith("event.rejected.")) {
|
|
245
|
-
ctx.log?.debug?.(
|
|
246
|
-
`[${account.accountId}] Metric: ${event.name} ${JSON.stringify(event.labels)}`,
|
|
247
|
-
);
|
|
248
|
-
} else if (event.name === "relay.circuit_breaker.open") {
|
|
249
|
-
ctx.log?.warn?.(
|
|
250
|
-
`[${account.accountId}] Circuit breaker opened for relay: ${event.labels?.relay}`,
|
|
251
|
-
);
|
|
252
|
-
} else if (event.name === "relay.circuit_breaker.close") {
|
|
253
|
-
ctx.log?.info?.(
|
|
254
|
-
`[${account.accountId}] Circuit breaker closed for relay: ${event.labels?.relay}`,
|
|
255
|
-
);
|
|
256
|
-
} else if (event.name === "relay.error") {
|
|
257
|
-
ctx.log?.debug?.(`[${account.accountId}] Relay error: ${event.labels?.relay}`);
|
|
258
|
-
}
|
|
259
|
-
// Update cached metrics snapshot
|
|
260
|
-
if (busHandle) {
|
|
261
|
-
metricsSnapshots.set(account.accountId, busHandle.getMetrics());
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
busHandle = bus;
|
|
267
|
-
|
|
268
|
-
// Store the bus handle
|
|
269
|
-
activeBuses.set(account.accountId, bus);
|
|
270
|
-
|
|
271
|
-
ctx.log?.info(
|
|
272
|
-
`[${account.accountId}] Nostr provider started, connected to ${account.relays.length} relay(s)`,
|
|
273
|
-
);
|
|
274
|
-
|
|
275
|
-
// Return cleanup function
|
|
276
|
-
return {
|
|
277
|
-
stop: () => {
|
|
278
|
-
bus.close();
|
|
279
|
-
activeBuses.delete(account.accountId);
|
|
280
|
-
metricsSnapshots.delete(account.accountId);
|
|
281
|
-
ctx.log?.info(`[${account.accountId}] Nostr provider stopped`);
|
|
282
|
-
},
|
|
283
|
-
};
|
|
284
|
-
},
|
|
167
|
+
security: {
|
|
168
|
+
resolveDmPolicy: resolveNostrDmPolicy,
|
|
285
169
|
},
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Get metrics snapshot for a Nostr account.
|
|
290
|
-
* Returns undefined if account is not running.
|
|
291
|
-
*/
|
|
292
|
-
export function getNostrMetrics(
|
|
293
|
-
accountId: string = DEFAULT_ACCOUNT_ID,
|
|
294
|
-
): MetricsSnapshot | undefined {
|
|
295
|
-
const bus = activeBuses.get(accountId);
|
|
296
|
-
if (bus) {
|
|
297
|
-
return bus.getMetrics();
|
|
298
|
-
}
|
|
299
|
-
return metricsSnapshots.get(accountId);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Get all active Nostr bus handles.
|
|
304
|
-
* Useful for debugging and status reporting.
|
|
305
|
-
*/
|
|
306
|
-
export function getActiveNostrBuses(): Map<string, NostrBusHandle> {
|
|
307
|
-
return new Map(activeBuses);
|
|
308
|
-
}
|
|
170
|
+
outbound: nostrOutboundAdapter,
|
|
171
|
+
});
|
|
309
172
|
|
|
310
173
|
/**
|
|
311
174
|
* Publish a profile (kind:0) for a Nostr account.
|
|
@@ -318,7 +181,7 @@ export async function publishNostrProfile(
|
|
|
318
181
|
accountId: string = DEFAULT_ACCOUNT_ID,
|
|
319
182
|
profile: NostrProfile,
|
|
320
183
|
): Promise<ProfilePublishResult> {
|
|
321
|
-
const bus =
|
|
184
|
+
const bus = getActiveNostrBuses().get(accountId);
|
|
322
185
|
if (!bus) {
|
|
323
186
|
throw new Error(`Nostr bus not running for account ${accountId}`);
|
|
324
187
|
}
|
|
@@ -335,7 +198,7 @@ export async function getNostrProfileState(accountId: string = DEFAULT_ACCOUNT_I
|
|
|
335
198
|
lastPublishedEventId: string | null;
|
|
336
199
|
lastPublishResults: Record<string, "ok" | "failed" | "timeout"> | null;
|
|
337
200
|
} | null> {
|
|
338
|
-
const bus =
|
|
201
|
+
const bus = getActiveNostrBuses().get(accountId);
|
|
339
202
|
if (!bus) {
|
|
340
203
|
return null;
|
|
341
204
|
}
|
package/src/config-schema.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
AllowFromListSchema,
|
|
3
|
+
DmPolicySchema,
|
|
4
|
+
MarkdownConfigSchema,
|
|
5
|
+
} from "openclaw/plugin-sdk/channel-config-primitives";
|
|
6
|
+
import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";
|
|
7
|
+
import { z } from "openclaw/plugin-sdk/zod";
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* Validates https:// URLs only (no javascript:, data:, file:, etc.)
|
|
@@ -50,7 +54,16 @@ export const NostrProfileSchema = z.object({
|
|
|
50
54
|
lud16: z.string().optional(),
|
|
51
55
|
});
|
|
52
56
|
|
|
53
|
-
export
|
|
57
|
+
export interface NostrProfile {
|
|
58
|
+
name?: string;
|
|
59
|
+
displayName?: string;
|
|
60
|
+
about?: string;
|
|
61
|
+
picture?: string;
|
|
62
|
+
banner?: string;
|
|
63
|
+
website?: string;
|
|
64
|
+
nip05?: string;
|
|
65
|
+
lud16?: string;
|
|
66
|
+
}
|
|
54
67
|
|
|
55
68
|
/**
|
|
56
69
|
* Zod schema for channels.nostr.* configuration
|
|
@@ -69,7 +82,7 @@ export const NostrConfigSchema = z.object({
|
|
|
69
82
|
markdown: MarkdownConfigSchema,
|
|
70
83
|
|
|
71
84
|
/** Private key in hex or nsec bech32 format */
|
|
72
|
-
privateKey:
|
|
85
|
+
privateKey: buildSecretInputSchema().optional(),
|
|
73
86
|
|
|
74
87
|
/** WebSocket relay URLs to connect to */
|
|
75
88
|
relays: z.array(z.string()).optional(),
|
|
@@ -83,10 +96,3 @@ export const NostrConfigSchema = z.object({
|
|
|
83
96
|
/** Profile metadata (NIP-01 kind:0 content) */
|
|
84
97
|
profile: NostrProfileSchema.optional(),
|
|
85
98
|
});
|
|
86
|
-
|
|
87
|
-
export type NostrConfig = z.infer<typeof NostrConfigSchema>;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* JSON Schema for Control UI (converted from Zod)
|
|
91
|
-
*/
|
|
92
|
-
export const nostrChannelConfigSchema = buildChannelConfigSchema(NostrConfigSchema);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_RELAYS = ["wss://relay.damus.io", "wss://nos.lol"];
|