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