@openclaw/nostr 2026.7.2-beta.7 → 2026.8.1-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 CHANGED
@@ -1,7 +1,7 @@
1
- import { s as resolveNostrAccount } from "./setup-surface-BJCFpe02.js";
1
+ import { s as resolveNostrAccount } from "./setup-surface-DAhyNOcb.js";
2
2
  import { getPluginRuntimeGatewayRequestScope } from "./runtime-api.js";
3
- import { n as NostrProfileSchema } from "./config-schema-CelPzg1N.js";
4
- import { a as getNostrRuntime, i as contentToProfile, n as nostrPlugin, o as setNostrRuntime, r as publishNostrProfile, t as getNostrProfileState } from "./channel-O6-jXoT5.js";
3
+ import { n as NostrProfileSchema } from "./config-schema-CkYd8ACn.js";
4
+ import { a as getNostrRuntime, i as contentToProfile, n as nostrPlugin, o as setNostrRuntime, r as publishNostrProfile, t as getNostrProfileState } from "./channel-BifOvAwp.js";
5
5
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  import { z } from "zod";
7
7
  import { SimplePool } from "nostr-tools";
@@ -1,6 +1,6 @@
1
- import { a as listNostrAccountIds, c as normalizePubkey, l as validatePrivateKey, n as nostrSetupContract, o as resolveDefaultNostrAccountId, r as nostrSetupWizard, s as resolveNostrAccount, t as nostrSetupAdapter } from "./setup-surface-BJCFpe02.js";
2
- import { a as collectStatusIssuesFromLastError, i as buildChannelConfigSchema, n as NostrProfileSchema, o as createDefaultChannelRuntimeState, r as DEFAULT_ACCOUNT_ID, s as formatPairingApproveHint, t as NostrConfigSchema } from "./config-schema-CelPzg1N.js";
3
- import { a as DEFAULT_RELAYS } from "./setup-adapter-BjKmSwdW.js";
1
+ import { a as listNostrAccountIds, c as normalizePubkey, l as validatePrivateKey, n as nostrSetupContract, o as resolveDefaultNostrAccountId, r as nostrSetupWizard, s as resolveNostrAccount } from "./setup-surface-DAhyNOcb.js";
2
+ import { a as collectStatusIssuesFromLastError, i as buildChannelConfigSchema, n as NostrProfileSchema, o as createDefaultChannelRuntimeState, r as DEFAULT_ACCOUNT_ID, s as formatPairingApproveHint, t as NostrConfigSchema } from "./config-schema-CkYd8ACn.js";
3
+ import { s as DEFAULT_RELAYS } from "./setup-adapter-DLthHa2q.js";
4
4
  import { t as normalizeNostrStateAccountId } from "./state-account-id-CvBZ9s6P.js";
5
5
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
6
6
  import { createScopedDmSecurityResolver, createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
@@ -9,10 +9,11 @@ import { missingTargetError } from "openclaw/plugin-sdk/channel-feedback";
9
9
  import { DEFAULT_INGRESS_ADOPTION_STALL_MS, bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageAdapterFromOutbound, runPassiveAccountLifecycle } from "openclaw/plugin-sdk/channel-outbound";
10
10
  import { buildPassiveChannelStatusSummary, buildTrafficStatusSummary } from "openclaw/plugin-sdk/extension-shared";
11
11
  import { createComputedAccountStatusAdapter } from "openclaw/plugin-sdk/status-helpers";
12
- import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
12
+ import { isRecord as isNostrIngressRecord, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
13
13
  import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
14
14
  import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
15
15
  import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
16
+ import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
16
17
  import { chunkTextForOutbound, sanitizeAssistantVisibleText, stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
17
18
  import { SimplePool, finalizeEvent, getPublicKey, verifyEvent } from "nostr-tools";
18
19
  import { decrypt, encrypt } from "nostr-tools/nip04";
@@ -382,9 +383,6 @@ var NostrIngressPermanentError = class extends Error {
382
383
  this.name = "NostrIngressPermanentError";
383
384
  }
384
385
  };
385
- function isNostrIngressRecord(value) {
386
- return typeof value === "object" && value !== null && !Array.isArray(value);
387
- }
388
386
  function requiredString(value, field) {
389
387
  if (typeof value === "string" && value.trim()) return value;
390
388
  throw new NostrIngressPermanentError("invalid-event", `Nostr event is missing ${field}.`);
@@ -1361,6 +1359,9 @@ async function sendEncryptedDm(pool, sk, toPubkey, text, relays, metrics, circui
1361
1359
  const activeBuses = /* @__PURE__ */ new Map();
1362
1360
  const metricsSnapshots = /* @__PURE__ */ new Map();
1363
1361
  const ACCESS_GROUP_PREFIX = "accessGroup:";
1362
+ function normalizeRelayLifecycleKey(relay) {
1363
+ return new URL(relay).toString();
1364
+ }
1364
1365
  function parseNostrAccessGroupAllowFromEntry(entry) {
1365
1366
  const trimmed = entry.trim();
1366
1367
  if (!trimmed.startsWith(ACCESS_GROUP_PREFIX)) return null;
@@ -1396,17 +1397,20 @@ const startNostrGatewayAccount = async (ctx) => {
1396
1397
  const account = ctx.account;
1397
1398
  ctx.setStatus({
1398
1399
  accountId: account.accountId,
1399
- publicKey: account.publicKey
1400
+ publicKey: account.publicKey,
1401
+ lifecycle: "starting"
1400
1402
  });
1401
1403
  ctx.log?.info?.(`[${account.accountId}] starting Nostr provider (pubkey: ${account.publicKey})`);
1402
1404
  if (!account.configured) throw new Error("Nostr private key not configured");
1405
+ const channelRuntime = ctx.channelRuntime;
1406
+ if (!channelRuntime?.inbound?.buildContext) throw new Error("Nostr requires its registered channel runtime context builder");
1403
1407
  const runtime = getNostrRuntime();
1404
1408
  const pairing = createChannelPairingController({
1405
1409
  core: runtime,
1406
1410
  channel: "nostr",
1407
1411
  accountId: account.accountId
1408
1412
  });
1409
- const resolveInboundAccess = async (senderPubkey, rawBody) => await resolveStableChannelMessageIngress({
1413
+ const resolveInboundAccess = async (senderPubkey, rawBody, contextBinding) => await resolveStableChannelMessageIngress({
1410
1414
  channelId: "nostr",
1411
1415
  accountId: account.accountId,
1412
1416
  identity: nostrIngressIdentity,
@@ -1417,11 +1421,13 @@ const startNostrGatewayAccount = async (ctx) => {
1417
1421
  kind: "direct",
1418
1422
  id: senderPubkey
1419
1423
  },
1424
+ contextBinding,
1420
1425
  dmPolicy: account.config.dmPolicy ?? "pairing",
1421
1426
  allowFrom: account.config.allowFrom,
1422
1427
  command: runtime.channel.commands.shouldComputeCommandAuthorized(rawBody, ctx.cfg) ? { modeWhenAccessGroupsOff: "configured" } : void 0
1423
1428
  });
1424
1429
  let busHandle = null;
1430
+ const connectedRelays = /* @__PURE__ */ new Set();
1425
1431
  const authorizeSender = async (input) => {
1426
1432
  const resolved = await resolveInboundAccess(input.senderId, "");
1427
1433
  if (resolved.senderAccess.decision === "allow") return "allow";
@@ -1461,6 +1467,12 @@ const startNostrGatewayAccount = async (ctx) => {
1461
1467
  }
1462
1468
  const { dispatchInboundDirectDm } = await import("./inbound-direct-dm-runtime-DHn9NoYN.js");
1463
1469
  await dispatchInboundDirectDm({
1470
+ channelRuntime,
1471
+ resolveChannelIngress: async (contextBinding) => {
1472
+ const exactAccess = await resolveInboundAccess(senderPubkey, text, contextBinding);
1473
+ if (!exactAccess.senderAccess.allowed) throw new Error(`Nostr sender authorization changed before dispatch (${senderPubkey})`);
1474
+ return exactAccess;
1475
+ },
1464
1476
  cfg: ctx.cfg,
1465
1477
  channel: "nostr",
1466
1478
  channelLabel: "Nostr",
@@ -1501,9 +1513,17 @@ const startNostrGatewayAccount = async (ctx) => {
1501
1513
  ctx.log?.error?.(`[${account.accountId}] Nostr error (${context}): ${error.message}`);
1502
1514
  },
1503
1515
  onConnect: (relay) => {
1516
+ connectedRelays.add(normalizeRelayLifecycleKey(relay));
1517
+ ctx.setStatus(channelReadyPatch({ accountId: account.accountId }));
1504
1518
  ctx.log?.debug?.(`[${account.accountId}] Connected to relay: ${relay}`);
1505
1519
  },
1506
1520
  onDisconnect: (relay) => {
1521
+ connectedRelays.delete(normalizeRelayLifecycleKey(relay));
1522
+ if (connectedRelays.size === 0) ctx.setStatus({
1523
+ accountId: account.accountId,
1524
+ connected: false,
1525
+ lifecycle: "recovering"
1526
+ });
1507
1527
  ctx.log?.debug?.(`[${account.accountId}] Disconnected from relay: ${relay}`);
1508
1528
  },
1509
1529
  onEose: (relays) => {
@@ -1618,6 +1638,14 @@ function normalizeNostrTarget(target) {
1618
1638
  return target.trim();
1619
1639
  }
1620
1640
  }
1641
+ function inferNostrTargetChatType(target) {
1642
+ try {
1643
+ normalizePubkey(stripNostrTargetPrefix(target));
1644
+ return "direct";
1645
+ } catch {
1646
+ return;
1647
+ }
1648
+ }
1621
1649
  const resolveNostrDmPolicy = createScopedDmSecurityResolver({
1622
1650
  channelKey: "nostr",
1623
1651
  resolvePolicy: (account) => account.config.dmPolicy,
@@ -1692,7 +1720,6 @@ const nostrPlugin = createChatChannelPlugin({
1692
1720
  },
1693
1721
  reload: { configPrefixes: ["channels.nostr"] },
1694
1722
  configSchema: buildChannelConfigSchema(NostrConfigSchema),
1695
- setup: nostrSetupAdapter,
1696
1723
  setupContract: nostrSetupContract,
1697
1724
  setupWizard: nostrSetupWizard,
1698
1725
  config: {
@@ -1707,6 +1734,7 @@ const nostrPlugin = createChatChannelPlugin({
1707
1734
  messaging: {
1708
1735
  targetPrefixes: ["nostr"],
1709
1736
  normalizeTarget: normalizeNostrTarget,
1737
+ inferTargetChatType: ({ to }) => inferNostrTargetChatType(to),
1710
1738
  targetResolver: {
1711
1739
  looksLikeId: (input, normalized) => {
1712
1740
  const trimmed = normalized?.trim() || stripNostrTargetPrefix(input);
@@ -1,2 +1,2 @@
1
- import { n as nostrPlugin } from "./channel-O6-jXoT5.js";
1
+ import { n as nostrPlugin } from "./channel-BifOvAwp.js";
2
2
  export { nostrPlugin };
@@ -46,6 +46,7 @@ const NostrConfigSchema = z.object({
46
46
  defaultAccount: z.string().optional(),
47
47
  /** Whether this channel is enabled */
48
48
  enabled: z.boolean().optional(),
49
+ configWrites: z.boolean().optional(),
49
50
  /** Markdown formatting overrides (tables). */
50
51
  markdown: MarkdownConfigSchema,
51
52
  /** Private key in hex or nsec bech32 format */
@@ -1,22 +1,20 @@
1
1
  import { t as normalizeNostrStateAccountId } from "./state-account-id-CvBZ9s6P.js";
2
+ import { asFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
2
3
  import fs from "node:fs/promises";
3
4
  import path from "node:path";
4
- import { archiveLegacyStateSource } from "openclaw/plugin-sdk/runtime-doctor";
5
+ import { archiveLegacyStateSource } from "openclaw/plugin-sdk/runtime-doctor-migrations";
5
6
  //#region extensions/nostr/doctor-contract-api.ts
6
7
  const BUS_STATE_NAMESPACE = "bus-state";
7
8
  const PROFILE_STATE_NAMESPACE = "profile-state";
8
9
  const MAX_NOSTR_STATE_ENTRIES = 256;
9
- function finiteNumberOrNull(value) {
10
- return typeof value === "number" && Number.isFinite(value) ? value : null;
11
- }
12
10
  function parseBusState(value) {
13
11
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
14
12
  const parsed = value;
15
13
  if (parsed.version !== 1 && parsed.version !== 2) return null;
16
14
  return {
17
15
  version: 2,
18
- lastProcessedAt: finiteNumberOrNull(parsed.lastProcessedAt),
19
- gatewayStartedAt: finiteNumberOrNull(parsed.gatewayStartedAt),
16
+ lastProcessedAt: asFiniteNumber(parsed.lastProcessedAt) ?? null,
17
+ gatewayStartedAt: asFiniteNumber(parsed.gatewayStartedAt) ?? null,
20
18
  recentEventIds: parsed.version === 2 && Array.isArray(parsed.recentEventIds) ? parsed.recentEventIds.filter((entry) => typeof entry === "string") : []
21
19
  };
22
20
  }
@@ -31,7 +29,7 @@ function parseProfileState(value) {
31
29
  }
32
30
  return {
33
31
  version: 1,
34
- lastPublishedAt: finiteNumberOrNull(parsed.lastPublishedAt),
32
+ lastPublishedAt: asFiniteNumber(parsed.lastPublishedAt) ?? null,
35
33
  lastPublishedEventId: typeof parsed.lastPublishedEventId === "string" ? parsed.lastPublishedEventId : null,
36
34
  lastPublishResults: rawResults === null || Object.keys(lastPublishResults).length === 0 ? null : lastPublishResults
37
35
  };
@@ -1,10 +1,19 @@
1
1
  import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
2
+ import { hasConfiguredSecretInput, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
2
3
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing";
3
- import { patchTopLevelChannelConfigSection, splitSetupEntries } from "openclaw/plugin-sdk/setup";
4
+ import { createSetupTranslator, createStandardChannelSetupStatus, patchTopLevelChannelConfigSection, splitSetupEntries } from "openclaw/plugin-sdk/setup";
4
5
  import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
5
6
  //#region extensions/nostr/src/default-relays.ts
6
7
  const DEFAULT_RELAYS = ["wss://relay.damus.io", "wss://nos.lol"];
7
8
  //#endregion
9
+ //#region extensions/nostr/src/private-key.ts
10
+ const NOSTR_PRIVATE_KEY_ENV_VAR = "NOSTR_PRIVATE_KEY";
11
+ function resolveNostrPrivateKey(value) {
12
+ const configured = normalizeSecretInputString(value);
13
+ if (configured || hasConfiguredSecretInput(value)) return configured ?? "";
14
+ return process.env["NOSTR_PRIVATE_KEY"]?.trim() ?? "";
15
+ }
16
+ //#endregion
8
17
  //#region extensions/nostr/src/setup-adapter.ts
9
18
  const channel = "nostr";
10
19
  function buildNostrSetupPatch(accountId, patch) {
@@ -41,25 +50,23 @@ function createNostrSetupAdapter(params) {
41
50
  patch: buildNostrSetupPatch(accountId, name?.trim() ? { name: name.trim() } : {})
42
51
  }),
43
52
  validateInput: ({ input }) => {
44
- const typedInput = input;
45
- if (!typedInput.useEnv) {
46
- const privateKey = typedInput.privateKey?.trim();
53
+ if (!input.useEnv) {
54
+ const privateKey = input.privateKey?.trim();
47
55
  if (!privateKey) return "Nostr requires --private-key or --use-env.";
48
56
  if (!params.validatePrivateKey(privateKey)) return "Nostr private key must be valid nsec or 64-character hex.";
49
57
  }
50
- if (typedInput.relayUrls?.trim()) return parseRelayUrls(typedInput.relayUrls).error ?? null;
58
+ if (input.relayUrls?.trim()) return parseRelayUrls(input.relayUrls).error ?? null;
51
59
  return null;
52
60
  },
53
61
  applyAccountConfig: ({ cfg, accountId, input }) => {
54
- const typedInput = input;
55
- const relayResult = typedInput.relayUrls?.trim() ? parseRelayUrls(typedInput.relayUrls) : { relays: [] };
62
+ const relayResult = input.relayUrls?.trim() ? parseRelayUrls(input.relayUrls) : { relays: [] };
56
63
  return patchTopLevelChannelConfigSection({
57
64
  cfg,
58
65
  channel,
59
66
  enabled: true,
60
- clearFields: typedInput.useEnv ? ["privateKey"] : void 0,
67
+ clearFields: input.useEnv ? ["privateKey"] : void 0,
61
68
  patch: buildNostrSetupPatch(accountId, {
62
- ...typedInput.useEnv ? {} : { privateKey: typedInput.privateKey?.trim() },
69
+ ...input.useEnv ? {} : { privateKey: input.privateKey?.trim() },
63
70
  ...relayResult.relays.length > 0 ? { relays: relayResult.relays } : {}
64
71
  })
65
72
  });
@@ -89,11 +96,32 @@ function createNostrSetupContract(adapter) {
89
96
  cli: {
90
97
  flags: "--use-env",
91
98
  description: "Use NOSTR_PRIVATE_KEY"
92
- }
99
+ },
100
+ envVars: [NOSTR_PRIVATE_KEY_ENV_VAR]
93
101
  }
94
102
  },
95
- legacyAdapter: adapter
103
+ adapter
104
+ });
105
+ }
106
+ function createNostrSetupStatus(resolveAccount) {
107
+ const t = createSetupTranslator();
108
+ return createStandardChannelSetupStatus({
109
+ channelLabel: "Nostr",
110
+ configuredLabel: t("wizard.channels.statusConfigured"),
111
+ unconfiguredLabel: t("wizard.channels.statusNeedsPrivateKey"),
112
+ configuredHint: t("wizard.channels.statusConfigured"),
113
+ unconfiguredHint: t("wizard.channels.statusNeedsPrivateKey"),
114
+ configuredScore: 1,
115
+ unconfiguredScore: 0,
116
+ includeStatusLine: true,
117
+ resolveConfigured: ({ cfg, accountId }) => resolveAccount({
118
+ cfg,
119
+ accountId
120
+ }).configured,
121
+ resolveExtraStatusLines: ({ cfg }) => {
122
+ return [`Relays: ${resolveAccount({ cfg }).relays.length || DEFAULT_RELAYS.length}`];
123
+ }
96
124
  });
97
125
  }
98
126
  //#endregion
99
- export { DEFAULT_RELAYS as a, parseRelayUrls as i, createNostrSetupAdapter as n, createNostrSetupContract as r, buildNostrSetupPatch as t };
127
+ export { parseRelayUrls as a, createNostrSetupStatus as i, createNostrSetupAdapter as n, resolveNostrPrivateKey as o, createNostrSetupContract as r, DEFAULT_RELAYS as s, buildNostrSetupPatch as t };
package/dist/setup-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { r as nostrSetupWizard, t as nostrSetupAdapter } from "./setup-surface-BJCFpe02.js";
1
+ import { r as nostrSetupWizard, t as nostrSetupAdapter } from "./setup-surface-DAhyNOcb.js";
2
2
  export { nostrSetupAdapter, nostrSetupWizard };
@@ -1,18 +1,12 @@
1
- import { i as buildChannelConfigSchema, t as NostrConfigSchema } from "./config-schema-CelPzg1N.js";
2
- import { a as DEFAULT_RELAYS, n as createNostrSetupAdapter, r as createNostrSetupContract } from "./setup-adapter-BjKmSwdW.js";
1
+ import { i as buildChannelConfigSchema, t as NostrConfigSchema } from "./config-schema-CkYd8ACn.js";
2
+ import { i as createNostrSetupStatus, n as createNostrSetupAdapter, o as resolveNostrPrivateKey, r as createNostrSetupContract, s as DEFAULT_RELAYS } from "./setup-adapter-DLthHa2q.js";
3
3
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
4
- import { DEFAULT_ACCOUNT_ID, createDelegatedSetupWizardProxy, createSetupTranslator, createStandardChannelSetupStatus } from "openclaw/plugin-sdk/setup-runtime";
4
+ import { DEFAULT_ACCOUNT_ID, createDelegatedSetupWizardProxy } from "openclaw/plugin-sdk/setup-runtime";
5
5
  //#region extensions/nostr/src/channel.setup.ts
6
- const t = createSetupTranslator();
7
6
  const channel = "nostr";
8
7
  function getNostrConfig(cfg) {
9
8
  return cfg.channels?.nostr;
10
9
  }
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
10
  function resolveDefaultSetupNostrAccountId(cfg) {
17
11
  const configured = getNostrConfig(cfg)?.defaultAccount;
18
12
  return typeof configured === "string" && configured.trim() ? configured.trim() : DEFAULT_ACCOUNT_ID;
@@ -20,7 +14,7 @@ function resolveDefaultSetupNostrAccountId(cfg) {
20
14
  function resolveSetupNostrAccount(params) {
21
15
  const nostrCfg = getNostrConfig(params.cfg);
22
16
  const accountId = params.accountId?.trim() || resolveDefaultSetupNostrAccountId(params.cfg);
23
- const privateKey = typeof nostrCfg?.privateKey === "string" ? nostrCfg.privateKey.trim() : "";
17
+ const privateKey = resolveNostrPrivateKey(nostrCfg?.privateKey);
24
18
  const configured = Boolean(privateKey);
25
19
  return {
26
20
  accountId,
@@ -42,34 +36,10 @@ function resolveSetupNostrAccount(params) {
42
36
  }
43
37
  };
44
38
  }
45
- function looksLikeNostrPrivateKey(privateKey) {
46
- return privateKey.startsWith("nsec1") || privateKey.startsWith("NSEC1") || /^[0-9a-fA-F]{64}$/.test(privateKey);
47
- }
48
- const nostrSetupAdapter = createNostrSetupAdapter({
49
- resolveAccountId: (cfg, accountId) => accountId?.trim() || resolveDefaultSetupNostrAccountId(cfg),
50
- validatePrivateKey: looksLikeNostrPrivateKey
51
- });
52
- const nostrSetupContract = createNostrSetupContract(nostrSetupAdapter);
53
39
  const nostrSetupWizard = createDelegatedSetupWizardProxy({
54
40
  channel,
55
- loadWizard: async () => (await import("./setup-surface-BJCFpe02.js").then((n) => n.i)).nostrSetupWizard,
56
- status: { ...createStandardChannelSetupStatus({
57
- channelLabel: "Nostr",
58
- configuredLabel: t("wizard.channels.statusConfigured"),
59
- unconfiguredLabel: t("wizard.channels.statusNeedsPrivateKey"),
60
- configuredHint: t("wizard.channels.statusConfigured"),
61
- unconfiguredHint: t("wizard.channels.statusNeedsPrivateKey"),
62
- configuredScore: 1,
63
- unconfiguredScore: 0,
64
- includeStatusLine: true,
65
- resolveConfigured: ({ cfg, accountId }) => resolveSetupNostrAccount({
66
- cfg,
67
- accountId
68
- }).configured,
69
- resolveExtraStatusLines: ({ cfg }) => {
70
- return [`Relays: ${resolveSetupNostrAccount({ cfg }).relays.length || DEFAULT_RELAYS.length}`];
71
- }
72
- }) },
41
+ loadWizard: async () => (await import("./setup-surface-DAhyNOcb.js").then((n) => n.i)).nostrSetupWizard,
42
+ status: createNostrSetupStatus(resolveSetupNostrAccount),
73
43
  resolveShouldPromptAccountIds: () => false,
74
44
  delegatePrepare: true,
75
45
  delegateFinalize: true
@@ -91,11 +61,13 @@ const nostrSetupPlugin = {
91
61
  },
92
62
  reload: { configPrefixes: ["channels.nostr"] },
93
63
  configSchema: buildChannelConfigSchema(NostrConfigSchema),
94
- setup: nostrSetupAdapter,
95
- setupContract: nostrSetupContract,
64
+ setupContract: createNostrSetupContract(createNostrSetupAdapter({
65
+ resolveAccountId: (cfg, accountId) => accountId?.trim() || resolveDefaultSetupNostrAccountId(cfg),
66
+ validatePrivateKey: (privateKey) => /^(?:nsec1|NSEC1)|^[0-9a-fA-F]{64}$/u.test(privateKey)
67
+ })),
96
68
  setupWizard: nostrSetupWizard,
97
69
  config: {
98
- listAccountIds: listSetupNostrAccountIds,
70
+ listAccountIds: (cfg) => resolveSetupNostrAccount({ cfg }).configured ? [resolveDefaultSetupNostrAccountId(cfg)] : [],
99
71
  resolveAccount: (cfg, accountId) => resolveSetupNostrAccount({
100
72
  cfg,
101
73
  accountId
@@ -1,11 +1,11 @@
1
- import { a as DEFAULT_RELAYS, i as parseRelayUrls, n as createNostrSetupAdapter, r as createNostrSetupContract, t as buildNostrSetupPatch } from "./setup-adapter-BjKmSwdW.js";
1
+ import { a as parseRelayUrls, i as createNostrSetupStatus, n as createNostrSetupAdapter, o as resolveNostrPrivateKey, r as createNostrSetupContract, s as DEFAULT_RELAYS, t as buildNostrSetupPatch } from "./setup-adapter-DLthHa2q.js";
2
2
  import { createAccountListHelpers } from "openclaw/plugin-sdk/account-helpers";
3
3
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import { hasConfiguredSecretInput, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
5
5
  import { getPublicKey, nip19 } from "nostr-tools";
6
6
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId, normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-id";
7
7
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1 } from "openclaw/plugin-sdk/routing";
8
- import { createSetupTranslator, createStandardChannelSetupStatus, createTopLevelChannelDmPolicy, createTopLevelChannelParsedAllowFromPrompt, defineTokenCredential, formatDocsLink, mergeAllowFromEntries, parseSetupEntriesWithParser, patchTopLevelChannelConfigSection, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
8
+ import { createSetupTranslator, createTopLevelChannelDmPolicy, createTopLevelChannelParsedAllowFromPrompt, defineTokenCredential, formatDocsLink, mergeAllowFromEntries, parseSetupEntriesWithParser, patchTopLevelChannelConfigSection, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
9
9
  //#region \0rolldown/runtime.js
10
10
  var __defProp = Object.defineProperty;
11
11
  var __exportAll = (all, no_symbols) => {
@@ -59,7 +59,7 @@ const { listAccountIds: listNostrAccountIds, resolveDefaultAccountId: resolveDef
59
59
  fallbackAccountIdWhenEmpty: false,
60
60
  resolveImplicitAccountId: (cfg) => {
61
61
  const account = cfg.channels?.nostr;
62
- return normalizeSecretInputString(account?.privateKey) ? normalizeOptionalAccountId(account?.defaultAccount) ?? DEFAULT_ACCOUNT_ID : void 0;
62
+ return resolveNostrPrivateKey(account?.privateKey) ? normalizeOptionalAccountId(account?.defaultAccount) ?? DEFAULT_ACCOUNT_ID : void 0;
63
63
  }
64
64
  });
65
65
  /**
@@ -69,7 +69,7 @@ function resolveNostrAccount(opts) {
69
69
  const accountId = normalizeAccountId(opts.accountId ?? resolveDefaultNostrAccountId(opts.cfg));
70
70
  const nostrCfg = opts.cfg.channels?.nostr;
71
71
  const baseEnabled = nostrCfg?.enabled !== false;
72
- const privateKey = normalizeSecretInputString(nostrCfg?.privateKey) ?? "";
72
+ const privateKey = resolveNostrPrivateKey(nostrCfg?.privateKey);
73
73
  const configured = Boolean(privateKey);
74
74
  let publicKey = "";
75
75
  if (privateKey) try {
@@ -162,20 +162,7 @@ const nostrSetupWizard = {
162
162
  channel,
163
163
  resolveAccountIdForConfigure: ({ accountOverride, defaultAccountId }) => accountOverride?.trim() || defaultAccountId,
164
164
  resolveShouldPromptAccountIds: () => false,
165
- status: createStandardChannelSetupStatus({
166
- channelLabel: "Nostr",
167
- configuredLabel: t("wizard.channels.statusConfigured"),
168
- unconfiguredLabel: t("wizard.channels.statusNeedsPrivateKey"),
169
- configuredHint: t("wizard.channels.statusConfigured"),
170
- unconfiguredHint: t("wizard.channels.statusNeedsPrivateKey"),
171
- configuredScore: 1,
172
- unconfiguredScore: 0,
173
- includeStatusLine: true,
174
- resolveConfigured: ({ cfg }) => resolveNostrAccount({ cfg }).configured,
175
- resolveExtraStatusLines: ({ cfg }) => {
176
- return [`Relays: ${resolveNostrAccount({ cfg }).relays.length || DEFAULT_RELAYS.length}`];
177
- }
178
- }),
165
+ status: createNostrSetupStatus(resolveNostrAccount),
179
166
  introNote: {
180
167
  title: t("wizard.nostr.setupTitle"),
181
168
  lines: NOSTR_SETUP_HELP_LINES
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "id": "nostr",
3
+ "doctorContract": {
4
+ "stateMigrations": true
5
+ },
3
6
  "name": "Nostr",
4
7
  "description": "OpenClaw Nostr channel plugin for NIP-04 encrypted direct messages.",
5
8
  "activation": {
@@ -28,6 +31,9 @@
28
31
  "enabled": {
29
32
  "type": "boolean"
30
33
  },
34
+ "configWrites": {
35
+ "type": "boolean"
36
+ },
31
37
  "markdown": {
32
38
  "type": "object",
33
39
  "properties": {
@@ -73,6 +79,29 @@
73
79
  ],
74
80
  "additionalProperties": false
75
81
  },
82
+ {
83
+ "type": "object",
84
+ "properties": {
85
+ "source": {
86
+ "type": "string",
87
+ "const": "store"
88
+ },
89
+ "provider": {
90
+ "type": "string",
91
+ "pattern": "^[a-z][a-z0-9_-]{0,63}$"
92
+ },
93
+ "id": {
94
+ "type": "string",
95
+ "pattern": "^[A-Z][A-Z0-9_]{0,127}$"
96
+ }
97
+ },
98
+ "required": [
99
+ "source",
100
+ "provider",
101
+ "id"
102
+ ],
103
+ "additionalProperties": false
104
+ },
76
105
  {
77
106
  "type": "object",
78
107
  "properties": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/nostr",
3
- "version": "2026.7.2-beta.7",
3
+ "version": "2026.8.1-beta.2",
4
4
  "description": "OpenClaw Nostr channel plugin for NIP-04 encrypted direct messages.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,7 +12,7 @@
12
12
  "zod": "4.4.3"
13
13
  },
14
14
  "peerDependencies": {
15
- "openclaw": ">=2026.7.2-beta.7"
15
+ "openclaw": ">=2026.8.1-beta.2"
16
16
  },
17
17
  "peerDependenciesMeta": {
18
18
  "openclaw": {
@@ -65,7 +65,10 @@
65
65
  "cli": {
66
66
  "flags": "--use-env",
67
67
  "description": "Use NOSTR_PRIVATE_KEY"
68
- }
68
+ },
69
+ "envVars": [
70
+ "NOSTR_PRIVATE_KEY"
71
+ ]
69
72
  }
70
73
  ]
71
74
  }
@@ -76,10 +79,10 @@
76
79
  "minHostVersion": ">=2026.4.10"
77
80
  },
78
81
  "compat": {
79
- "pluginApi": ">=2026.7.2-beta.7"
82
+ "pluginApi": ">=2026.8.1-beta.2"
80
83
  },
81
84
  "build": {
82
- "openclawVersion": "2026.7.2-beta.7"
85
+ "openclawVersion": "2026.8.1-beta.2"
83
86
  },
84
87
  "release": {
85
88
  "publishToClawHub": true,