@kodelyth/nostr 2026.5.39 → 2026.5.42
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 +142 -0
- package/api.ts +10 -0
- package/channel-plugin-api.ts +1 -0
- package/dist/api.js +522 -0
- package/dist/channel-CnPQxTzj.js +1467 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/config-schema-KoL8Et_9.js +63 -0
- package/dist/default-relays-DLwdWOTu.js +4 -0
- package/dist/inbound-direct-dm-runtime-CeYGU_Fo.js +2 -0
- package/dist/index.js +81 -0
- package/dist/runtime-api.js +2 -0
- package/dist/setup-api.js +2 -0
- package/dist/setup-entry.js +11 -0
- package/dist/setup-plugin-api.js +166 -0
- package/dist/setup-surface-DFlfVW6j.js +337 -0
- package/dist/test-api.js +2 -0
- package/index.ts +95 -0
- package/klaw.plugin.json +2 -185
- package/package.json +4 -4
- 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 +11 -0
- package/src/channel.inbound.test.ts +187 -0
- package/src/channel.outbound.test.ts +163 -0
- package/src/channel.setup.ts +234 -0
- package/src/channel.test.ts +526 -0
- package/src/channel.ts +215 -0
- package/src/config-schema.ts +98 -0
- package/src/default-relays.ts +1 -0
- package/src/gateway.ts +321 -0
- package/src/inbound-direct-dm-runtime.ts +1 -0
- package/src/metrics.ts +458 -0
- package/src/nostr-bus.fuzz.test.ts +382 -0
- package/src/nostr-bus.inbound.test.ts +526 -0
- package/src/nostr-bus.integration.test.ts +477 -0
- package/src/nostr-bus.test.ts +231 -0
- package/src/nostr-bus.ts +789 -0
- 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 +632 -0
- package/src/nostr-profile-http.ts +583 -0
- package/src/nostr-profile-import.test.ts +119 -0
- package/src/nostr-profile-import.ts +262 -0
- package/src/nostr-profile-url-safety.ts +21 -0
- package/src/nostr-profile.fuzz.test.ts +430 -0
- package/src/nostr-profile.test.ts +415 -0
- package/src/nostr-profile.ts +144 -0
- package/src/nostr-state-store.test.ts +237 -0
- package/src/nostr-state-store.ts +206 -0
- package/src/runtime.ts +9 -0
- package/src/seen-tracker.ts +289 -0
- package/src/session-route.ts +25 -0
- package/src/setup-surface.ts +264 -0
- package/src/test-fixtures.ts +45 -0
- package/src/types.ts +117 -0
- package/test/setup.ts +5 -0
- package/test-api.ts +1 -0
- package/tsconfig.json +16 -0
- package/api.js +0 -7
- package/channel-plugin-api.js +0 -7
- package/index.js +0 -7
- package/runtime-api.js +0 -7
- package/setup-api.js +0 -7
- package/setup-entry.js +0 -7
- package/setup-plugin-api.js +0 -7
- package/test-api.js +0 -7
|
@@ -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 };
|
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,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 };
|
package/dist/test-api.js
ADDED