@openclaw/nostr 2026.5.2 → 2026.5.3-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.
Files changed (59) hide show
  1. package/dist/api.js +532 -0
  2. package/dist/channel-DfEqBtUh.js +1466 -0
  3. package/dist/channel-plugin-api.js +2 -0
  4. package/dist/config-schema-DIk4jlBg.js +64 -0
  5. package/dist/default-relays-DLwdWOTu.js +4 -0
  6. package/dist/inbound-direct-dm-runtime-22bZWcIW.js +2 -0
  7. package/dist/index.js +84 -0
  8. package/dist/runtime-api.js +2 -0
  9. package/dist/setup-api.js +2 -0
  10. package/dist/setup-entry.js +11 -0
  11. package/dist/setup-plugin-api.js +165 -0
  12. package/dist/setup-surface-DxAaUTyC.js +336 -0
  13. package/dist/test-api.js +2 -0
  14. package/package.json +15 -6
  15. package/api.ts +0 -10
  16. package/channel-plugin-api.ts +0 -1
  17. package/index.ts +0 -97
  18. package/runtime-api.ts +0 -6
  19. package/setup-api.ts +0 -1
  20. package/setup-entry.ts +0 -9
  21. package/setup-plugin-api.ts +0 -3
  22. package/src/channel-api.ts +0 -15
  23. package/src/channel.inbound.test.ts +0 -176
  24. package/src/channel.outbound.test.ts +0 -128
  25. package/src/channel.setup.ts +0 -231
  26. package/src/channel.test.ts +0 -519
  27. package/src/channel.ts +0 -207
  28. package/src/config-schema.ts +0 -98
  29. package/src/default-relays.ts +0 -1
  30. package/src/gateway.ts +0 -302
  31. package/src/inbound-direct-dm-runtime.ts +0 -1
  32. package/src/metrics.ts +0 -458
  33. package/src/nostr-bus.fuzz.test.ts +0 -360
  34. package/src/nostr-bus.inbound.test.ts +0 -526
  35. package/src/nostr-bus.integration.test.ts +0 -472
  36. package/src/nostr-bus.test.ts +0 -190
  37. package/src/nostr-bus.ts +0 -789
  38. package/src/nostr-key-utils.ts +0 -94
  39. package/src/nostr-profile-core.ts +0 -134
  40. package/src/nostr-profile-http-runtime.ts +0 -6
  41. package/src/nostr-profile-http.test.ts +0 -632
  42. package/src/nostr-profile-http.ts +0 -594
  43. package/src/nostr-profile-import.test.ts +0 -119
  44. package/src/nostr-profile-import.ts +0 -262
  45. package/src/nostr-profile-url-safety.ts +0 -21
  46. package/src/nostr-profile.fuzz.test.ts +0 -430
  47. package/src/nostr-profile.test.ts +0 -412
  48. package/src/nostr-profile.ts +0 -144
  49. package/src/nostr-state-store.test.ts +0 -237
  50. package/src/nostr-state-store.ts +0 -223
  51. package/src/runtime.ts +0 -9
  52. package/src/seen-tracker.ts +0 -289
  53. package/src/session-route.ts +0 -25
  54. package/src/setup-surface.ts +0 -265
  55. package/src/test-fixtures.ts +0 -45
  56. package/src/types.ts +0 -117
  57. package/test/setup.ts +0 -5
  58. package/test-api.ts +0 -1
  59. package/tsconfig.json +0 -16
@@ -0,0 +1,2 @@
1
+ import { n as nostrPlugin } from "./channel-DfEqBtUh.js";
2
+ export { nostrPlugin };
@@ -0,0 +1,64 @@
1
+ import { collectStatusIssuesFromLastError, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
2
+ import { DEFAULT_ACCOUNT_ID, buildChannelConfigSchema, formatPairingApproveHint } from "openclaw/plugin-sdk/channel-plugin-common";
3
+ import { createPreCryptoDirectDmAuthorizer, resolveInboundDirectDmAccessWithRuntime } from "openclaw/plugin-sdk/direct-dm-access";
4
+ import { AllowFromListSchema, DmPolicySchema, MarkdownConfigSchema } from "openclaw/plugin-sdk/channel-config-primitives";
5
+ import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";
6
+ import { z } from "openclaw/plugin-sdk/zod";
7
+ //#region extensions/nostr/src/config-schema.ts
8
+ /**
9
+ * Validates https:// URLs only (no javascript:, data:, file:, etc.)
10
+ */
11
+ const safeUrlSchema = z.string().url().refine((url) => {
12
+ try {
13
+ return new URL(url).protocol === "https:";
14
+ } catch {
15
+ return false;
16
+ }
17
+ }, { message: "URL must use https:// protocol" });
18
+ /**
19
+ * NIP-01 profile metadata schema
20
+ * https://github.com/nostr-protocol/nips/blob/master/01.md
21
+ */
22
+ const NostrProfileSchema = z.object({
23
+ /** Username (NIP-01: name) - max 256 chars */
24
+ name: z.string().max(256).optional(),
25
+ /** Display name (NIP-01: display_name) - max 256 chars */
26
+ displayName: z.string().max(256).optional(),
27
+ /** Bio/description (NIP-01: about) - max 2000 chars */
28
+ about: z.string().max(2e3).optional(),
29
+ /** Profile picture URL (must be https) */
30
+ picture: safeUrlSchema.optional(),
31
+ /** Banner image URL (must be https) */
32
+ banner: safeUrlSchema.optional(),
33
+ /** Website URL (must be https) */
34
+ website: safeUrlSchema.optional(),
35
+ /** NIP-05 identifier (e.g., "user@example.com") */
36
+ nip05: z.string().optional(),
37
+ /** Lightning address (LUD-16) */
38
+ lud16: z.string().optional()
39
+ });
40
+ /**
41
+ * Zod schema for channels.nostr.* configuration
42
+ */
43
+ const NostrConfigSchema = z.object({
44
+ /** Account name (optional display name) */
45
+ name: z.string().optional(),
46
+ /** Optional default account id for routing/account selection. */
47
+ defaultAccount: z.string().optional(),
48
+ /** Whether this channel is enabled */
49
+ enabled: z.boolean().optional(),
50
+ /** Markdown formatting overrides (tables). */
51
+ markdown: MarkdownConfigSchema,
52
+ /** Private key in hex or nsec bech32 format */
53
+ privateKey: buildSecretInputSchema().optional(),
54
+ /** WebSocket relay URLs to connect to */
55
+ relays: z.array(z.string()).optional(),
56
+ /** DM access policy: pairing, allowlist, open, or disabled */
57
+ dmPolicy: DmPolicySchema.optional(),
58
+ /** Allowed sender pubkeys (npub or hex format) */
59
+ allowFrom: AllowFromListSchema,
60
+ /** Profile metadata (NIP-01 kind:0 content) */
61
+ profile: NostrProfileSchema.optional()
62
+ });
63
+ //#endregion
64
+ export { collectStatusIssuesFromLastError as a, formatPairingApproveHint as c, buildChannelConfigSchema as i, resolveInboundDirectDmAccessWithRuntime as l, NostrProfileSchema as n, createDefaultChannelRuntimeState as o, DEFAULT_ACCOUNT_ID as r, createPreCryptoDirectDmAuthorizer as s, NostrConfigSchema as t };
@@ -0,0 +1,4 @@
1
+ //#region extensions/nostr/src/default-relays.ts
2
+ const DEFAULT_RELAYS = ["wss://relay.damus.io", "wss://nos.lol"];
3
+ //#endregion
4
+ export { DEFAULT_RELAYS as t };
@@ -0,0 +1,2 @@
1
+ import { dispatchInboundDirectDmWithRuntime } from "openclaw/plugin-sdk/direct-dm";
2
+ export { dispatchInboundDirectDmWithRuntime };
package/dist/index.js ADDED
@@ -0,0 +1,84 @@
1
+ import { defineBundledChannelEntry, loadBundledEntryExportSync } from "openclaw/plugin-sdk/channel-entry-contract";
2
+ //#region extensions/nostr/index.ts
3
+ function createNostrProfileHttpHandler() {
4
+ return loadBundledEntryExportSync(import.meta.url, {
5
+ specifier: "./api.js",
6
+ exportName: "createNostrProfileHttpHandler"
7
+ });
8
+ }
9
+ function getNostrRuntime() {
10
+ return loadBundledEntryExportSync(import.meta.url, {
11
+ specifier: "./api.js",
12
+ exportName: "getNostrRuntime"
13
+ })();
14
+ }
15
+ function resolveNostrAccount(params) {
16
+ return loadBundledEntryExportSync(import.meta.url, {
17
+ specifier: "./api.js",
18
+ exportName: "resolveNostrAccount"
19
+ })(params);
20
+ }
21
+ var nostr_default = defineBundledChannelEntry({
22
+ id: "nostr",
23
+ name: "Nostr",
24
+ description: "Nostr DM channel plugin via NIP-04",
25
+ importMetaUrl: import.meta.url,
26
+ plugin: {
27
+ specifier: "./channel-plugin-api.js",
28
+ exportName: "nostrPlugin"
29
+ },
30
+ runtime: {
31
+ specifier: "./api.js",
32
+ exportName: "setNostrRuntime"
33
+ },
34
+ registerFull(api) {
35
+ const httpHandler = createNostrProfileHttpHandler()({
36
+ getConfigProfile: (accountId) => {
37
+ return resolveNostrAccount({
38
+ cfg: getNostrRuntime().config.current(),
39
+ accountId
40
+ }).profile;
41
+ },
42
+ updateConfigProfile: async (accountId, profile) => {
43
+ const runtime = getNostrRuntime();
44
+ const cfg = runtime.config.current();
45
+ const channels = cfg.channels ?? {};
46
+ const nostrConfig = channels.nostr ?? {};
47
+ await runtime.config.replaceConfigFile({
48
+ nextConfig: {
49
+ ...cfg,
50
+ channels: {
51
+ ...channels,
52
+ nostr: {
53
+ ...nostrConfig,
54
+ profile
55
+ }
56
+ }
57
+ },
58
+ afterWrite: { mode: "auto" }
59
+ });
60
+ },
61
+ getAccountInfo: (accountId) => {
62
+ const account = resolveNostrAccount({
63
+ cfg: getNostrRuntime().config.current(),
64
+ accountId
65
+ });
66
+ if (!account.configured || !account.publicKey) return null;
67
+ return {
68
+ pubkey: account.publicKey,
69
+ relays: account.relays
70
+ };
71
+ },
72
+ log: api.logger
73
+ });
74
+ api.registerHttpRoute({
75
+ path: "/api/channels/nostr",
76
+ auth: "gateway",
77
+ match: "prefix",
78
+ gatewayRuntimeScopeSurface: "trusted-operator",
79
+ handler: httpHandler
80
+ });
81
+ }
82
+ });
83
+ //#endregion
84
+ export { nostr_default as default };
@@ -0,0 +1,2 @@
1
+ import { getPluginRuntimeGatewayRequestScope } from "openclaw/plugin-sdk/plugin-runtime";
2
+ export { getPluginRuntimeGatewayRequestScope };
@@ -0,0 +1,2 @@
1
+ import { n as nostrSetupWizard, t as nostrSetupAdapter } from "./setup-surface-DxAaUTyC.js";
2
+ export { nostrSetupAdapter, nostrSetupWizard };
@@ -0,0 +1,11 @@
1
+ import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract";
2
+ //#region extensions/nostr/setup-entry.ts
3
+ var setup_entry_default = defineBundledChannelSetupEntry({
4
+ importMetaUrl: import.meta.url,
5
+ plugin: {
6
+ specifier: "./setup-plugin-api.js",
7
+ exportName: "nostrSetupPlugin"
8
+ }
9
+ });
10
+ //#endregion
11
+ export { setup_entry_default as default };
@@ -0,0 +1,165 @@
1
+ import { i as buildChannelConfigSchema, t as NostrConfigSchema } from "./config-schema-DIk4jlBg.js";
2
+ import { t as DEFAULT_RELAYS } from "./default-relays-DLwdWOTu.js";
3
+ import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
4
+ import { patchTopLevelChannelConfigSection } from "openclaw/plugin-sdk/setup";
5
+ import { DEFAULT_ACCOUNT_ID, createDelegatedSetupWizardProxy, createStandardChannelSetupStatus as createStandardChannelSetupStatus$1 } from "openclaw/plugin-sdk/setup-runtime";
6
+ //#region extensions/nostr/src/channel.setup.ts
7
+ const channel = "nostr";
8
+ function getNostrConfig(cfg) {
9
+ return cfg.channels?.nostr;
10
+ }
11
+ function listSetupNostrAccountIds(cfg) {
12
+ const nostrCfg = getNostrConfig(cfg);
13
+ if (!(typeof nostrCfg?.privateKey === "string" ? nostrCfg.privateKey.trim() : "")) return [];
14
+ return [resolveDefaultSetupNostrAccountId(cfg)];
15
+ }
16
+ function resolveDefaultSetupNostrAccountId(cfg) {
17
+ const configured = getNostrConfig(cfg)?.defaultAccount;
18
+ return typeof configured === "string" && configured.trim() ? configured.trim() : DEFAULT_ACCOUNT_ID;
19
+ }
20
+ function resolveSetupNostrAccount(params) {
21
+ const nostrCfg = getNostrConfig(params.cfg);
22
+ const accountId = params.accountId?.trim() || resolveDefaultSetupNostrAccountId(params.cfg);
23
+ const privateKey = typeof nostrCfg?.privateKey === "string" ? nostrCfg.privateKey.trim() : "";
24
+ const configured = Boolean(privateKey);
25
+ return {
26
+ accountId,
27
+ name: typeof nostrCfg?.name === "string" ? nostrCfg.name : void 0,
28
+ enabled: nostrCfg?.enabled !== false,
29
+ configured,
30
+ privateKey,
31
+ publicKey: "",
32
+ relays: nostrCfg?.relays ?? DEFAULT_RELAYS,
33
+ profile: nostrCfg?.profile,
34
+ config: {
35
+ enabled: nostrCfg?.enabled,
36
+ name: nostrCfg?.name,
37
+ privateKey: nostrCfg?.privateKey,
38
+ relays: nostrCfg?.relays,
39
+ dmPolicy: nostrCfg?.dmPolicy,
40
+ allowFrom: nostrCfg?.allowFrom,
41
+ profile: nostrCfg?.profile
42
+ }
43
+ };
44
+ }
45
+ function buildNostrSetupPatch(accountId, patch) {
46
+ return {
47
+ ...accountId !== DEFAULT_ACCOUNT_ID ? { defaultAccount: accountId } : {},
48
+ ...patch
49
+ };
50
+ }
51
+ function parseRelayUrls(raw) {
52
+ const entries = raw.split(/[,\n]/).map((entry) => entry.trim()).filter(Boolean);
53
+ const relays = [];
54
+ for (const entry of entries) {
55
+ try {
56
+ const parsed = new URL(entry);
57
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") return {
58
+ relays: [],
59
+ error: `Relay must use ws:// or wss:// (${entry})`
60
+ };
61
+ } catch {
62
+ return {
63
+ relays: [],
64
+ error: `Invalid relay URL: ${entry}`
65
+ };
66
+ }
67
+ relays.push(entry);
68
+ }
69
+ return { relays: [...new Set(relays)] };
70
+ }
71
+ function looksLikeNostrPrivateKey(privateKey) {
72
+ return privateKey.startsWith("nsec1") || /^[0-9a-fA-F]{64}$/.test(privateKey);
73
+ }
74
+ const nostrSetupAdapter = {
75
+ resolveAccountId: ({ cfg, accountId }) => accountId?.trim() || resolveDefaultSetupNostrAccountId(cfg),
76
+ applyAccountName: ({ cfg, accountId, name }) => patchTopLevelChannelConfigSection({
77
+ cfg,
78
+ channel,
79
+ patch: buildNostrSetupPatch(accountId, name?.trim() ? { name: name.trim() } : {})
80
+ }),
81
+ validateInput: ({ input }) => {
82
+ const typedInput = input;
83
+ if (!typedInput.useEnv) {
84
+ const privateKey = typedInput.privateKey?.trim();
85
+ if (!privateKey) return "Nostr requires --private-key or --use-env.";
86
+ if (!looksLikeNostrPrivateKey(privateKey)) return "Nostr private key must be valid nsec or 64-character hex.";
87
+ }
88
+ if (typedInput.relayUrls?.trim()) return parseRelayUrls(typedInput.relayUrls).error ?? null;
89
+ return null;
90
+ },
91
+ applyAccountConfig: ({ cfg, accountId, input }) => {
92
+ const typedInput = input;
93
+ const relayResult = typedInput.relayUrls?.trim() ? parseRelayUrls(typedInput.relayUrls) : { relays: [] };
94
+ return patchTopLevelChannelConfigSection({
95
+ cfg,
96
+ channel,
97
+ enabled: true,
98
+ clearFields: typedInput.useEnv ? ["privateKey"] : void 0,
99
+ patch: buildNostrSetupPatch(accountId, {
100
+ ...typedInput.useEnv ? {} : { privateKey: typedInput.privateKey?.trim() },
101
+ ...relayResult.relays.length > 0 ? { relays: relayResult.relays } : {}
102
+ })
103
+ });
104
+ }
105
+ };
106
+ const nostrSetupWizard = createDelegatedSetupWizardProxy({
107
+ channel,
108
+ loadWizard: async () => (await import("./setup-surface-DxAaUTyC.js").then((n) => n.r)).nostrSetupWizard,
109
+ status: { ...createStandardChannelSetupStatus$1({
110
+ channelLabel: "Nostr",
111
+ configuredLabel: "configured",
112
+ unconfiguredLabel: "needs private key",
113
+ configuredHint: "configured",
114
+ unconfiguredHint: "needs private key",
115
+ configuredScore: 1,
116
+ unconfiguredScore: 0,
117
+ includeStatusLine: true,
118
+ resolveConfigured: ({ cfg, accountId }) => resolveSetupNostrAccount({
119
+ cfg,
120
+ accountId
121
+ }).configured,
122
+ resolveExtraStatusLines: ({ cfg }) => {
123
+ return [`Relays: ${resolveSetupNostrAccount({ cfg }).relays.length || DEFAULT_RELAYS.length}`];
124
+ }
125
+ }) },
126
+ resolveShouldPromptAccountIds: () => false,
127
+ delegatePrepare: true,
128
+ delegateFinalize: true
129
+ });
130
+ const nostrSetupPlugin = {
131
+ id: channel,
132
+ meta: {
133
+ id: channel,
134
+ label: "Nostr",
135
+ selectionLabel: "Nostr",
136
+ docsPath: "/channels/nostr",
137
+ docsLabel: "nostr",
138
+ blurb: "Decentralized DMs via Nostr relays (NIP-04)",
139
+ order: 100
140
+ },
141
+ capabilities: {
142
+ chatTypes: ["direct"],
143
+ media: false
144
+ },
145
+ reload: { configPrefixes: ["channels.nostr"] },
146
+ configSchema: buildChannelConfigSchema(NostrConfigSchema),
147
+ setup: nostrSetupAdapter,
148
+ setupWizard: nostrSetupWizard,
149
+ config: {
150
+ listAccountIds: listSetupNostrAccountIds,
151
+ resolveAccount: (cfg, accountId) => resolveSetupNostrAccount({
152
+ cfg,
153
+ accountId
154
+ }),
155
+ defaultAccountId: resolveDefaultSetupNostrAccountId,
156
+ isConfigured: (account) => account.configured,
157
+ describeAccount: (account) => describeAccountSnapshot({
158
+ account,
159
+ configured: account.configured,
160
+ extra: { publicKey: account.publicKey }
161
+ })
162
+ }
163
+ };
164
+ //#endregion
165
+ export { nostrSetupPlugin };
@@ -0,0 +1,336 @@
1
+ import { t as DEFAULT_RELAYS } from "./default-relays-DLwdWOTu.js";
2
+ import { hasConfiguredSecretInput, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
3
+ import { getPublicKey, nip19 } from "nostr-tools";
4
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId, normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-id";
5
+ import { listCombinedAccountIds, resolveListedDefaultAccountId } from "openclaw/plugin-sdk/account-resolution";
6
+ import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
7
+ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1 } from "openclaw/plugin-sdk/routing";
8
+ import { createStandardChannelSetupStatus, createTopLevelChannelDmPolicy, createTopLevelChannelParsedAllowFromPrompt, formatDocsLink, mergeAllowFromEntries, parseSetupEntriesWithParser, patchTopLevelChannelConfigSection, splitSetupEntries } from "openclaw/plugin-sdk/setup";
9
+ //#region \0rolldown/runtime.js
10
+ var __defProp = Object.defineProperty;
11
+ var __exportAll = (all, no_symbols) => {
12
+ let target = {};
13
+ for (var name in all) __defProp(target, name, {
14
+ get: all[name],
15
+ enumerable: true
16
+ });
17
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
18
+ return target;
19
+ };
20
+ //#endregion
21
+ //#region extensions/nostr/src/nostr-key-utils.ts
22
+ /**
23
+ * Validate and normalize a private key (accepts hex or nsec format)
24
+ */
25
+ function validatePrivateKey(key) {
26
+ const trimmed = key.trim();
27
+ if (trimmed.startsWith("nsec1")) {
28
+ const decoded = nip19.decode(trimmed);
29
+ if (decoded.type !== "nsec") throw new Error("Invalid nsec key: wrong type");
30
+ return decoded.data;
31
+ }
32
+ if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) throw new Error("Private key must be 64 hex characters or nsec bech32 format");
33
+ const bytes = new Uint8Array(32);
34
+ for (let i = 0; i < 32; i++) bytes[i] = Number.parseInt(trimmed.slice(i * 2, i * 2 + 2), 16);
35
+ return bytes;
36
+ }
37
+ /**
38
+ * Get public key from private key (hex or nsec format)
39
+ */
40
+ function getPublicKeyFromPrivate(privateKey) {
41
+ return getPublicKey(validatePrivateKey(privateKey));
42
+ }
43
+ /**
44
+ * Normalize a pubkey to hex format (accepts npub or hex)
45
+ */
46
+ function normalizePubkey(input) {
47
+ const trimmed = input.trim();
48
+ if (trimmed.startsWith("npub1")) {
49
+ const decoded = nip19.decode(trimmed);
50
+ if (decoded.type !== "npub") throw new Error("Invalid npub key");
51
+ return Array.from(decoded.data).map((b) => b.toString(16).padStart(2, "0")).join("");
52
+ }
53
+ if (!/^[0-9a-fA-F]{64}$/.test(trimmed)) throw new Error("Pubkey must be 64 hex characters or npub format");
54
+ return trimmed.toLowerCase();
55
+ }
56
+ //#endregion
57
+ //#region extensions/nostr/src/types.ts
58
+ function resolveConfiguredDefaultNostrAccountId(cfg) {
59
+ const nostrCfg = cfg.channels?.nostr;
60
+ return normalizeOptionalAccountId(nostrCfg?.defaultAccount);
61
+ }
62
+ /**
63
+ * List all configured Nostr account IDs
64
+ */
65
+ function listNostrAccountIds(cfg) {
66
+ const nostrCfg = cfg.channels?.nostr;
67
+ return listCombinedAccountIds({
68
+ configuredAccountIds: [],
69
+ implicitAccountId: normalizeSecretInputString(nostrCfg?.privateKey) ? resolveConfiguredDefaultNostrAccountId(cfg) ?? DEFAULT_ACCOUNT_ID : void 0
70
+ });
71
+ }
72
+ /**
73
+ * Get the default account ID
74
+ */
75
+ function resolveDefaultNostrAccountId(cfg) {
76
+ return resolveListedDefaultAccountId({
77
+ accountIds: listNostrAccountIds(cfg),
78
+ configuredDefaultAccountId: resolveConfiguredDefaultNostrAccountId(cfg)
79
+ });
80
+ }
81
+ /**
82
+ * Resolve a Nostr account from config
83
+ */
84
+ function resolveNostrAccount(opts) {
85
+ const accountId = normalizeAccountId(opts.accountId ?? resolveDefaultNostrAccountId(opts.cfg));
86
+ const nostrCfg = opts.cfg.channels?.nostr;
87
+ const baseEnabled = nostrCfg?.enabled !== false;
88
+ const privateKey = normalizeSecretInputString(nostrCfg?.privateKey) ?? "";
89
+ const configured = Boolean(privateKey);
90
+ let publicKey = "";
91
+ if (privateKey) try {
92
+ publicKey = getPublicKeyFromPrivate(privateKey);
93
+ } catch {}
94
+ return {
95
+ accountId,
96
+ name: normalizeOptionalString(nostrCfg?.name),
97
+ enabled: baseEnabled,
98
+ configured,
99
+ privateKey,
100
+ publicKey,
101
+ relays: nostrCfg?.relays ?? DEFAULT_RELAYS,
102
+ profile: nostrCfg?.profile,
103
+ config: {
104
+ enabled: nostrCfg?.enabled,
105
+ name: nostrCfg?.name,
106
+ privateKey: nostrCfg?.privateKey,
107
+ relays: nostrCfg?.relays,
108
+ dmPolicy: nostrCfg?.dmPolicy,
109
+ allowFrom: nostrCfg?.allowFrom,
110
+ profile: nostrCfg?.profile
111
+ }
112
+ };
113
+ }
114
+ //#endregion
115
+ //#region extensions/nostr/src/setup-surface.ts
116
+ var setup_surface_exports = /* @__PURE__ */ __exportAll({
117
+ nostrSetupAdapter: () => nostrSetupAdapter,
118
+ nostrSetupWizard: () => nostrSetupWizard
119
+ });
120
+ const channel = "nostr";
121
+ const NOSTR_SETUP_HELP_LINES = [
122
+ "Use a Nostr private key in nsec or 64-character hex format.",
123
+ "Relay URLs are optional. Leave blank to keep the default relay set.",
124
+ "Env vars supported: NOSTR_PRIVATE_KEY (default account only).",
125
+ `Docs: ${formatDocsLink("/channels/nostr", "channels/nostr")}`
126
+ ];
127
+ const NOSTR_ALLOW_FROM_HELP_LINES = [
128
+ "Allowlist Nostr DMs by npub or hex pubkey.",
129
+ "Examples:",
130
+ "- npub1...",
131
+ "- nostr:npub1...",
132
+ "- 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
133
+ "Multiple entries: comma-separated.",
134
+ `Docs: ${formatDocsLink("/channels/nostr", "channels/nostr")}`
135
+ ];
136
+ function buildNostrSetupPatch(accountId, patch) {
137
+ return {
138
+ ...accountId !== DEFAULT_ACCOUNT_ID$1 ? { defaultAccount: accountId } : {},
139
+ ...patch
140
+ };
141
+ }
142
+ function parseRelayUrls(raw) {
143
+ const entries = splitSetupEntries(raw);
144
+ const relays = [];
145
+ for (const entry of entries) {
146
+ try {
147
+ const parsed = new URL(entry);
148
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") return {
149
+ relays: [],
150
+ error: `Relay must use ws:// or wss:// (${entry})`
151
+ };
152
+ } catch {
153
+ return {
154
+ relays: [],
155
+ error: `Invalid relay URL: ${entry}`
156
+ };
157
+ }
158
+ relays.push(entry);
159
+ }
160
+ return { relays: [...new Set(relays)] };
161
+ }
162
+ function parseNostrAllowFrom(raw) {
163
+ return parseSetupEntriesWithParser(raw, (entry) => {
164
+ const cleaned = entry.replace(/^nostr:/i, "").trim();
165
+ try {
166
+ return { value: normalizePubkey(cleaned) };
167
+ } catch {
168
+ return { error: `Invalid Nostr pubkey: ${entry}` };
169
+ }
170
+ });
171
+ }
172
+ const nostrDmPolicy = createTopLevelChannelDmPolicy({
173
+ label: "Nostr",
174
+ channel,
175
+ policyKey: "channels.nostr.dmPolicy",
176
+ allowFromKey: "channels.nostr.allowFrom",
177
+ getCurrent: (cfg) => cfg.channels?.nostr?.dmPolicy ?? "pairing",
178
+ promptAllowFrom: createTopLevelChannelParsedAllowFromPrompt({
179
+ channel,
180
+ defaultAccountId: resolveDefaultNostrAccountId,
181
+ noteTitle: "Nostr allowlist",
182
+ noteLines: NOSTR_ALLOW_FROM_HELP_LINES,
183
+ message: "Nostr allowFrom",
184
+ placeholder: "npub1..., 0123abcd...",
185
+ parseEntries: parseNostrAllowFrom,
186
+ mergeEntries: ({ existing, parsed }) => mergeAllowFromEntries(existing, parsed)
187
+ })
188
+ });
189
+ const nostrSetupAdapter = {
190
+ resolveAccountId: ({ cfg, accountId }) => accountId?.trim() || resolveDefaultNostrAccountId(cfg),
191
+ applyAccountName: ({ cfg, accountId, name }) => patchTopLevelChannelConfigSection({
192
+ cfg,
193
+ channel,
194
+ patch: buildNostrSetupPatch(accountId, name?.trim() ? { name: name.trim() } : {})
195
+ }),
196
+ validateInput: ({ input }) => {
197
+ const typedInput = input;
198
+ if (!typedInput.useEnv) {
199
+ const privateKey = typedInput.privateKey?.trim();
200
+ if (!privateKey) return "Nostr requires --private-key or --use-env.";
201
+ try {
202
+ getPublicKeyFromPrivate(privateKey);
203
+ } catch {
204
+ return "Nostr private key must be valid nsec or 64-character hex.";
205
+ }
206
+ }
207
+ if (typedInput.relayUrls?.trim()) return parseRelayUrls(typedInput.relayUrls).error ?? null;
208
+ return null;
209
+ },
210
+ applyAccountConfig: ({ cfg, accountId, input }) => {
211
+ const typedInput = input;
212
+ const relayResult = typedInput.relayUrls?.trim() ? parseRelayUrls(typedInput.relayUrls) : { relays: [] };
213
+ return patchTopLevelChannelConfigSection({
214
+ cfg,
215
+ channel,
216
+ enabled: true,
217
+ clearFields: typedInput.useEnv ? ["privateKey"] : void 0,
218
+ patch: buildNostrSetupPatch(accountId, {
219
+ ...typedInput.useEnv ? {} : { privateKey: typedInput.privateKey?.trim() },
220
+ ...relayResult.relays.length > 0 ? { relays: relayResult.relays } : {}
221
+ })
222
+ });
223
+ }
224
+ };
225
+ const nostrSetupWizard = {
226
+ channel,
227
+ resolveAccountIdForConfigure: ({ accountOverride, defaultAccountId }) => accountOverride?.trim() || defaultAccountId,
228
+ resolveShouldPromptAccountIds: () => false,
229
+ status: createStandardChannelSetupStatus({
230
+ channelLabel: "Nostr",
231
+ configuredLabel: "configured",
232
+ unconfiguredLabel: "needs private key",
233
+ configuredHint: "configured",
234
+ unconfiguredHint: "needs private key",
235
+ configuredScore: 1,
236
+ unconfiguredScore: 0,
237
+ includeStatusLine: true,
238
+ resolveConfigured: ({ cfg }) => resolveNostrAccount({ cfg }).configured,
239
+ resolveExtraStatusLines: ({ cfg }) => {
240
+ return [`Relays: ${resolveNostrAccount({ cfg }).relays.length || DEFAULT_RELAYS.length}`];
241
+ }
242
+ }),
243
+ introNote: {
244
+ title: "Nostr setup",
245
+ lines: NOSTR_SETUP_HELP_LINES
246
+ },
247
+ envShortcut: {
248
+ prompt: "NOSTR_PRIVATE_KEY detected. Use env var?",
249
+ preferredEnvVar: "NOSTR_PRIVATE_KEY",
250
+ isAvailable: ({ cfg, accountId }) => accountId === DEFAULT_ACCOUNT_ID$1 && Boolean(process.env.NOSTR_PRIVATE_KEY?.trim()) && !hasConfiguredSecretInput(resolveNostrAccount({
251
+ cfg,
252
+ accountId
253
+ }).config.privateKey),
254
+ apply: async ({ cfg, accountId }) => patchTopLevelChannelConfigSection({
255
+ cfg,
256
+ channel,
257
+ enabled: true,
258
+ clearFields: ["privateKey"],
259
+ patch: buildNostrSetupPatch(accountId, {})
260
+ })
261
+ },
262
+ credentials: [{
263
+ inputKey: "privateKey",
264
+ providerHint: channel,
265
+ credentialLabel: "private key",
266
+ preferredEnvVar: "NOSTR_PRIVATE_KEY",
267
+ helpTitle: "Nostr private key",
268
+ helpLines: NOSTR_SETUP_HELP_LINES,
269
+ envPrompt: "NOSTR_PRIVATE_KEY detected. Use env var?",
270
+ keepPrompt: "Nostr private key already configured. Keep it?",
271
+ inputPrompt: "Nostr private key (nsec... or hex)",
272
+ allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID$1,
273
+ inspect: ({ cfg, accountId }) => {
274
+ const account = resolveNostrAccount({
275
+ cfg,
276
+ accountId
277
+ });
278
+ return {
279
+ accountConfigured: account.configured,
280
+ hasConfiguredValue: hasConfiguredSecretInput(account.config.privateKey),
281
+ resolvedValue: normalizeSecretInputString(account.config.privateKey),
282
+ envValue: process.env.NOSTR_PRIVATE_KEY?.trim()
283
+ };
284
+ },
285
+ applyUseEnv: async ({ cfg, accountId }) => patchTopLevelChannelConfigSection({
286
+ cfg,
287
+ channel,
288
+ enabled: true,
289
+ clearFields: ["privateKey"],
290
+ patch: buildNostrSetupPatch(accountId, {})
291
+ }),
292
+ applySet: async ({ cfg, accountId, resolvedValue }) => patchTopLevelChannelConfigSection({
293
+ cfg,
294
+ channel,
295
+ enabled: true,
296
+ patch: buildNostrSetupPatch(accountId, { privateKey: resolvedValue })
297
+ })
298
+ }],
299
+ textInputs: [{
300
+ inputKey: "relayUrls",
301
+ message: "Relay URLs (comma-separated, optional)",
302
+ placeholder: DEFAULT_RELAYS.join(", "),
303
+ required: false,
304
+ applyEmptyValue: true,
305
+ helpTitle: "Nostr relays",
306
+ helpLines: ["Use ws:// or wss:// relay URLs.", "Leave blank to keep the default relay set."],
307
+ currentValue: ({ cfg, accountId }) => {
308
+ const account = resolveNostrAccount({
309
+ cfg,
310
+ accountId
311
+ });
312
+ const configuredRelays = cfg.channels?.nostr?.relays;
313
+ return (configuredRelays && configuredRelays.length > 0 ? account.relays : []).join(", ");
314
+ },
315
+ keepPrompt: (value) => `Relay URLs set (${value}). Keep them?`,
316
+ validate: ({ value }) => parseRelayUrls(value).error,
317
+ applySet: async ({ cfg, accountId, value }) => {
318
+ const relayResult = parseRelayUrls(value);
319
+ return patchTopLevelChannelConfigSection({
320
+ cfg,
321
+ channel,
322
+ enabled: true,
323
+ clearFields: relayResult.relays.length > 0 ? void 0 : ["relays"],
324
+ patch: buildNostrSetupPatch(accountId, relayResult.relays.length > 0 ? { relays: relayResult.relays } : {})
325
+ });
326
+ }
327
+ }],
328
+ dmPolicy: nostrDmPolicy,
329
+ disable: (cfg) => patchTopLevelChannelConfigSection({
330
+ cfg,
331
+ channel,
332
+ patch: { enabled: false }
333
+ })
334
+ };
335
+ //#endregion
336
+ export { resolveDefaultNostrAccountId as a, validatePrivateKey as c, listNostrAccountIds as i, nostrSetupWizard as n, resolveNostrAccount as o, setup_surface_exports as r, normalizePubkey as s, nostrSetupAdapter as t };
@@ -0,0 +1,2 @@
1
+ import { n as nostrPlugin } from "./channel-DfEqBtUh.js";
2
+ export { nostrPlugin };