@openclaw/nostr 2026.5.2 → 2026.5.3-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +532 -0
- package/dist/channel-DfEqBtUh.js +1466 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/config-schema-DIk4jlBg.js +64 -0
- package/dist/default-relays-DLwdWOTu.js +4 -0
- package/dist/inbound-direct-dm-runtime-22bZWcIW.js +2 -0
- package/dist/index.js +84 -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 +165 -0
- package/dist/setup-surface-DxAaUTyC.js +336 -0
- package/dist/test-api.js +2 -0
- package/package.json +15 -6
- package/api.ts +0 -10
- package/channel-plugin-api.ts +0 -1
- package/index.ts +0 -97
- package/runtime-api.ts +0 -6
- package/setup-api.ts +0 -1
- package/setup-entry.ts +0 -9
- package/setup-plugin-api.ts +0 -3
- package/src/channel-api.ts +0 -15
- package/src/channel.inbound.test.ts +0 -176
- package/src/channel.outbound.test.ts +0 -128
- package/src/channel.setup.ts +0 -231
- package/src/channel.test.ts +0 -519
- package/src/channel.ts +0 -207
- package/src/config-schema.ts +0 -98
- package/src/default-relays.ts +0 -1
- package/src/gateway.ts +0 -302
- package/src/inbound-direct-dm-runtime.ts +0 -1
- package/src/metrics.ts +0 -458
- package/src/nostr-bus.fuzz.test.ts +0 -360
- package/src/nostr-bus.inbound.test.ts +0 -526
- package/src/nostr-bus.integration.test.ts +0 -472
- package/src/nostr-bus.test.ts +0 -190
- package/src/nostr-bus.ts +0 -789
- package/src/nostr-key-utils.ts +0 -94
- package/src/nostr-profile-core.ts +0 -134
- package/src/nostr-profile-http-runtime.ts +0 -6
- package/src/nostr-profile-http.test.ts +0 -632
- package/src/nostr-profile-http.ts +0 -594
- package/src/nostr-profile-import.test.ts +0 -119
- package/src/nostr-profile-import.ts +0 -262
- package/src/nostr-profile-url-safety.ts +0 -21
- package/src/nostr-profile.fuzz.test.ts +0 -430
- package/src/nostr-profile.test.ts +0 -412
- package/src/nostr-profile.ts +0 -144
- package/src/nostr-state-store.test.ts +0 -237
- package/src/nostr-state-store.ts +0 -223
- package/src/runtime.ts +0 -9
- package/src/seen-tracker.ts +0 -289
- package/src/session-route.ts +0 -25
- package/src/setup-surface.ts +0 -265
- package/src/test-fixtures.ts +0 -45
- package/src/types.ts +0 -117
- package/test/setup.ts +0 -5
- package/test-api.ts +0 -1
- package/tsconfig.json +0 -16
|
@@ -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 };
|
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,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 };
|
package/dist/test-api.js
ADDED