@openclaw/nostr 2026.3.13 → 2026.5.2-beta.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/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 +147 -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,140 @@ 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
|
+
targetPrefixes: ["nostr"],
|
|
122
|
+
normalizeTarget: (target) => {
|
|
123
|
+
// Strip nostr: prefix if present
|
|
124
|
+
const cleaned = target.trim().replace(/^nostr:/i, "");
|
|
125
|
+
try {
|
|
126
|
+
return normalizePubkey(cleaned);
|
|
127
|
+
} catch {
|
|
128
|
+
return cleaned;
|
|
129
|
+
}
|
|
133
130
|
},
|
|
134
|
-
|
|
131
|
+
targetResolver: {
|
|
132
|
+
looksLikeId: (input) => {
|
|
133
|
+
const trimmed = input.trim();
|
|
134
|
+
return trimmed.startsWith("npub1") || /^[0-9a-fA-F]{64}$/.test(trimmed);
|
|
135
|
+
},
|
|
136
|
+
hint: "<npub|hex pubkey|nostr:npub...>",
|
|
137
|
+
},
|
|
138
|
+
resolveOutboundSessionRoute: (params) => resolveNostrOutboundSessionRoute(params),
|
|
135
139
|
},
|
|
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
|
-
};
|
|
140
|
+
status: {
|
|
141
|
+
...createComputedAccountStatusAdapter<ResolvedNostrAccount>({
|
|
142
|
+
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
|
|
143
|
+
collectStatusIssues: (accounts) => collectStatusIssuesFromLastError("nostr", accounts),
|
|
144
|
+
buildChannelSummary: ({ snapshot }) =>
|
|
145
|
+
buildPassiveChannelStatusSummary(snapshot, {
|
|
146
|
+
publicKey: snapshot.publicKey ?? null,
|
|
147
|
+
}),
|
|
148
|
+
resolveAccountSnapshot: ({ account, runtime }) => ({
|
|
149
|
+
accountId: account.accountId,
|
|
150
|
+
name: account.name,
|
|
151
|
+
enabled: account.enabled,
|
|
152
|
+
configured: account.configured,
|
|
153
|
+
extra: {
|
|
154
|
+
publicKey: account.publicKey,
|
|
155
|
+
profile: account.profile,
|
|
156
|
+
...buildTrafficStatusSummary(runtime),
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
}),
|
|
160
|
+
},
|
|
161
|
+
gateway: {
|
|
162
|
+
startAccount: startNostrGatewayAccount,
|
|
161
163
|
},
|
|
162
164
|
},
|
|
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
|
-
}),
|
|
165
|
+
pairing: {
|
|
166
|
+
text: nostrPairingTextAdapter,
|
|
184
167
|
},
|
|
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
|
-
},
|
|
168
|
+
security: {
|
|
169
|
+
resolveDmPolicy: resolveNostrDmPolicy,
|
|
285
170
|
},
|
|
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
|
-
}
|
|
171
|
+
outbound: nostrOutboundAdapter,
|
|
172
|
+
});
|
|
309
173
|
|
|
310
174
|
/**
|
|
311
175
|
* Publish a profile (kind:0) for a Nostr account.
|
|
@@ -318,7 +182,7 @@ export async function publishNostrProfile(
|
|
|
318
182
|
accountId: string = DEFAULT_ACCOUNT_ID,
|
|
319
183
|
profile: NostrProfile,
|
|
320
184
|
): Promise<ProfilePublishResult> {
|
|
321
|
-
const bus =
|
|
185
|
+
const bus = getActiveNostrBuses().get(accountId);
|
|
322
186
|
if (!bus) {
|
|
323
187
|
throw new Error(`Nostr bus not running for account ${accountId}`);
|
|
324
188
|
}
|
|
@@ -335,7 +199,7 @@ export async function getNostrProfileState(accountId: string = DEFAULT_ACCOUNT_I
|
|
|
335
199
|
lastPublishedEventId: string | null;
|
|
336
200
|
lastPublishResults: Record<string, "ok" | "failed" | "timeout"> | null;
|
|
337
201
|
} | null> {
|
|
338
|
-
const bus =
|
|
202
|
+
const bus = getActiveNostrBuses().get(accountId);
|
|
339
203
|
if (!bus) {
|
|
340
204
|
return null;
|
|
341
205
|
}
|
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"];
|