@openclaw/nostr 2026.1.29 → 2026.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,14 +49,30 @@ export function profileToContent(profile: NostrProfile): ProfileContent {
49
49
 
50
50
  const content: ProfileContent = {};
51
51
 
52
- if (validated.name !== undefined) content.name = validated.name;
53
- if (validated.displayName !== undefined) content.display_name = validated.displayName;
54
- if (validated.about !== undefined) content.about = validated.about;
55
- if (validated.picture !== undefined) content.picture = validated.picture;
56
- if (validated.banner !== undefined) content.banner = validated.banner;
57
- if (validated.website !== undefined) content.website = validated.website;
58
- if (validated.nip05 !== undefined) content.nip05 = validated.nip05;
59
- if (validated.lud16 !== undefined) content.lud16 = validated.lud16;
52
+ if (validated.name !== undefined) {
53
+ content.name = validated.name;
54
+ }
55
+ if (validated.displayName !== undefined) {
56
+ content.display_name = validated.displayName;
57
+ }
58
+ if (validated.about !== undefined) {
59
+ content.about = validated.about;
60
+ }
61
+ if (validated.picture !== undefined) {
62
+ content.picture = validated.picture;
63
+ }
64
+ if (validated.banner !== undefined) {
65
+ content.banner = validated.banner;
66
+ }
67
+ if (validated.website !== undefined) {
68
+ content.website = validated.website;
69
+ }
70
+ if (validated.nip05 !== undefined) {
71
+ content.nip05 = validated.nip05;
72
+ }
73
+ if (validated.lud16 !== undefined) {
74
+ content.lud16 = validated.lud16;
75
+ }
60
76
 
61
77
  return content;
62
78
  }
@@ -68,14 +84,30 @@ export function profileToContent(profile: NostrProfile): ProfileContent {
68
84
  export function contentToProfile(content: ProfileContent): NostrProfile {
69
85
  const profile: NostrProfile = {};
70
86
 
71
- if (content.name !== undefined) profile.name = content.name;
72
- if (content.display_name !== undefined) profile.displayName = content.display_name;
73
- if (content.about !== undefined) profile.about = content.about;
74
- if (content.picture !== undefined) profile.picture = content.picture;
75
- if (content.banner !== undefined) profile.banner = content.banner;
76
- if (content.website !== undefined) profile.website = content.website;
77
- if (content.nip05 !== undefined) profile.nip05 = content.nip05;
78
- if (content.lud16 !== undefined) profile.lud16 = content.lud16;
87
+ if (content.name !== undefined) {
88
+ profile.name = content.name;
89
+ }
90
+ if (content.display_name !== undefined) {
91
+ profile.displayName = content.display_name;
92
+ }
93
+ if (content.about !== undefined) {
94
+ profile.about = content.about;
95
+ }
96
+ if (content.picture !== undefined) {
97
+ profile.picture = content.picture;
98
+ }
99
+ if (content.banner !== undefined) {
100
+ profile.banner = content.banner;
101
+ }
102
+ if (content.website !== undefined) {
103
+ profile.website = content.website;
104
+ }
105
+ if (content.nip05 !== undefined) {
106
+ profile.nip05 = content.nip05;
107
+ }
108
+ if (content.lud16 !== undefined) {
109
+ profile.lud16 = content.lud16;
110
+ }
79
111
 
80
112
  return profile;
81
113
  }
@@ -95,7 +127,7 @@ export function contentToProfile(content: ProfileContent): NostrProfile {
95
127
  export function createProfileEvent(
96
128
  sk: Uint8Array,
97
129
  profile: NostrProfile,
98
- lastPublishedAt?: number
130
+ lastPublishedAt?: number,
99
131
  ): Event {
100
132
  const content = profileToContent(profile);
101
133
  const contentJson = JSON.stringify(content);
@@ -111,7 +143,7 @@ export function createProfileEvent(
111
143
  tags: [],
112
144
  created_at: createdAt,
113
145
  },
114
- sk
146
+ sk,
115
147
  );
116
148
 
117
149
  return event;
@@ -138,7 +170,7 @@ const RELAY_PUBLISH_TIMEOUT_MS = 5000;
138
170
  export async function publishProfileEvent(
139
171
  pool: SimplePool,
140
172
  relays: string[],
141
- event: Event
173
+ event: Event,
142
174
  ): Promise<ProfilePublishResult> {
143
175
  const successes: string[] = [];
144
176
  const failures: Array<{ relay: string; error: string }> = [];
@@ -150,6 +182,7 @@ export async function publishProfileEvent(
150
182
  setTimeout(() => reject(new Error("timeout")), RELAY_PUBLISH_TIMEOUT_MS);
151
183
  });
152
184
 
185
+ // oxlint-disable-next-line typescript/no-floating-promises
153
186
  await Promise.race([pool.publish([relay], event), timeoutPromise]);
154
187
 
155
188
  successes.push(relay);
@@ -184,7 +217,7 @@ export async function publishProfile(
184
217
  sk: Uint8Array,
185
218
  relays: string[],
186
219
  profile: NostrProfile,
187
- lastPublishedAt?: number
220
+ lastPublishedAt?: number,
188
221
  ): Promise<ProfilePublishResult> {
189
222
  const event = createProfileEvent(sk, profile, lastPublishedAt);
190
223
  return publishProfileEvent(pool, relays, event);
@@ -220,7 +253,9 @@ export function validateProfile(profile: unknown): {
220
253
  */
221
254
  export function sanitizeProfileForDisplay(profile: NostrProfile): NostrProfile {
222
255
  const escapeHtml = (str: string | undefined): string | undefined => {
223
- if (str === undefined) return undefined;
256
+ if (str === undefined) {
257
+ return undefined;
258
+ }
224
259
  return str
225
260
  .replace(/&/g, "&amp;")
226
261
  .replace(/</g, "&lt;")
@@ -1,10 +1,8 @@
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk";
1
2
  import fs from "node:fs/promises";
2
3
  import os from "node:os";
3
4
  import path from "node:path";
4
-
5
5
  import { describe, expect, it } from "vitest";
6
- import type { PluginRuntime } from "openclaw/plugin-sdk";
7
-
8
6
  import {
9
7
  readNostrBusState,
10
8
  writeNostrBusState,
@@ -19,9 +17,10 @@ async function withTempStateDir<T>(fn: (dir: string) => Promise<T>) {
19
17
  setNostrRuntime({
20
18
  state: {
21
19
  resolveStateDir: (env, homedir) => {
22
- const override =
23
- env.OPENCLAW_STATE_DIR?.trim() || env.OPENCLAW_STATE_DIR?.trim();
24
- if (override) return override;
20
+ const override = env.OPENCLAW_STATE_DIR?.trim() || env.OPENCLAW_STATE_DIR?.trim();
21
+ if (override) {
22
+ return override;
23
+ }
25
24
  return path.join(homedir(), ".openclaw");
26
25
  },
27
26
  },
@@ -29,8 +28,11 @@ async function withTempStateDir<T>(fn: (dir: string) => Promise<T>) {
29
28
  try {
30
29
  return await fn(dir);
31
30
  } finally {
32
- if (previous === undefined) delete process.env.OPENCLAW_STATE_DIR;
33
- else process.env.OPENCLAW_STATE_DIR = previous;
31
+ if (previous === undefined) {
32
+ delete process.env.OPENCLAW_STATE_DIR;
33
+ } else {
34
+ process.env.OPENCLAW_STATE_DIR = previous;
35
+ }
34
36
  await fs.rm(dir, { recursive: true, force: true });
35
37
  }
36
38
  }
@@ -2,7 +2,6 @@ import crypto from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
-
6
5
  import { getNostrRuntime } from "./runtime.js";
7
6
 
8
7
  const STORE_VERSION = 2;
@@ -39,14 +38,13 @@ export type NostrProfileState = {
39
38
 
40
39
  function normalizeAccountId(accountId?: string): string {
41
40
  const trimmed = accountId?.trim();
42
- if (!trimmed) return "default";
41
+ if (!trimmed) {
42
+ return "default";
43
+ }
43
44
  return trimmed.replace(/[^a-z0-9._-]+/gi, "_");
44
45
  }
45
46
 
46
- function resolveNostrStatePath(
47
- accountId?: string,
48
- env: NodeJS.ProcessEnv = process.env
49
- ): string {
47
+ function resolveNostrStatePath(accountId?: string, env: NodeJS.ProcessEnv = process.env): string {
50
48
  const stateDir = getNostrRuntime().state.resolveStateDir(env, os.homedir);
51
49
  const normalized = normalizeAccountId(accountId);
52
50
  return path.join(stateDir, "nostr", `bus-state-${normalized}.json`);
@@ -54,7 +52,7 @@ function resolveNostrStatePath(
54
52
 
55
53
  function resolveNostrProfileStatePath(
56
54
  accountId?: string,
57
- env: NodeJS.ProcessEnv = process.env
55
+ env: NodeJS.ProcessEnv = process.env,
58
56
  ): string {
59
57
  const stateDir = getNostrRuntime().state.resolveStateDir(env, os.homedir);
60
58
  const normalized = normalizeAccountId(accountId);
@@ -69,7 +67,8 @@ function safeParseState(raw: string): NostrBusState | null {
69
67
  return {
70
68
  version: 2,
71
69
  lastProcessedAt: typeof parsed.lastProcessedAt === "number" ? parsed.lastProcessedAt : null,
72
- gatewayStartedAt: typeof parsed.gatewayStartedAt === "number" ? parsed.gatewayStartedAt : null,
70
+ gatewayStartedAt:
71
+ typeof parsed.gatewayStartedAt === "number" ? parsed.gatewayStartedAt : null,
73
72
  recentEventIds: Array.isArray(parsed.recentEventIds)
74
73
  ? parsed.recentEventIds.filter((x): x is string => typeof x === "string")
75
74
  : [],
@@ -81,7 +80,8 @@ function safeParseState(raw: string): NostrBusState | null {
81
80
  return {
82
81
  version: 2,
83
82
  lastProcessedAt: typeof parsed.lastProcessedAt === "number" ? parsed.lastProcessedAt : null,
84
- gatewayStartedAt: typeof parsed.gatewayStartedAt === "number" ? parsed.gatewayStartedAt : null,
83
+ gatewayStartedAt:
84
+ typeof parsed.gatewayStartedAt === "number" ? parsed.gatewayStartedAt : null,
85
85
  recentEventIds: [],
86
86
  };
87
87
  }
@@ -102,7 +102,9 @@ export async function readNostrBusState(params: {
102
102
  return safeParseState(raw);
103
103
  } catch (err) {
104
104
  const code = (err as { code?: string }).code;
105
- if (code === "ENOENT") return null;
105
+ if (code === "ENOENT") {
106
+ return null;
107
+ }
106
108
  return null;
107
109
  }
108
110
  }
@@ -117,10 +119,7 @@ export async function writeNostrBusState(params: {
117
119
  const filePath = resolveNostrStatePath(params.accountId, params.env);
118
120
  const dir = path.dirname(filePath);
119
121
  await fs.mkdir(dir, { recursive: true, mode: 0o700 });
120
- const tmp = path.join(
121
- dir,
122
- `${path.basename(filePath)}.${crypto.randomUUID()}.tmp`
123
- );
122
+ const tmp = path.join(dir, `${path.basename(filePath)}.${crypto.randomUUID()}.tmp`);
124
123
  const payload: NostrBusState = {
125
124
  version: STORE_VERSION,
126
125
  lastProcessedAt: params.lastProcessedAt,
@@ -141,17 +140,20 @@ export async function writeNostrBusState(params: {
141
140
  */
142
141
  export function computeSinceTimestamp(
143
142
  state: NostrBusState | null,
144
- nowSec: number = Math.floor(Date.now() / 1000)
143
+ nowSec: number = Math.floor(Date.now() / 1000),
145
144
  ): number {
146
- if (!state) return nowSec;
145
+ if (!state) {
146
+ return nowSec;
147
+ }
147
148
 
148
149
  // Use the most recent timestamp we have
149
- const candidates = [
150
- state.lastProcessedAt,
151
- state.gatewayStartedAt,
152
- ].filter((t): t is number => t !== null && t > 0);
150
+ const candidates = [state.lastProcessedAt, state.gatewayStartedAt].filter(
151
+ (t): t is number => t !== null && t > 0,
152
+ );
153
153
 
154
- if (candidates.length === 0) return nowSec;
154
+ if (candidates.length === 0) {
155
+ return nowSec;
156
+ }
155
157
  return Math.max(...candidates);
156
158
  }
157
159
 
@@ -166,13 +168,12 @@ function safeParseProfileState(raw: string): NostrProfileState | null {
166
168
  if (parsed?.version === 1) {
167
169
  return {
168
170
  version: 1,
169
- lastPublishedAt:
170
- typeof parsed.lastPublishedAt === "number" ? parsed.lastPublishedAt : null,
171
+ lastPublishedAt: typeof parsed.lastPublishedAt === "number" ? parsed.lastPublishedAt : null,
171
172
  lastPublishedEventId:
172
173
  typeof parsed.lastPublishedEventId === "string" ? parsed.lastPublishedEventId : null,
173
174
  lastPublishResults:
174
175
  parsed.lastPublishResults && typeof parsed.lastPublishResults === "object"
175
- ? (parsed.lastPublishResults as Record<string, "ok" | "failed" | "timeout">)
176
+ ? parsed.lastPublishResults
176
177
  : null,
177
178
  };
178
179
  }
@@ -193,7 +194,9 @@ export async function readNostrProfileState(params: {
193
194
  return safeParseProfileState(raw);
194
195
  } catch (err) {
195
196
  const code = (err as { code?: string }).code;
196
- if (code === "ENOENT") return null;
197
+ if (code === "ENOENT") {
198
+ return null;
199
+ }
197
200
  return null;
198
201
  }
199
202
  }
@@ -208,10 +211,7 @@ export async function writeNostrProfileState(params: {
208
211
  const filePath = resolveNostrProfileStatePath(params.accountId, params.env);
209
212
  const dir = path.dirname(filePath);
210
213
  await fs.mkdir(dir, { recursive: true, mode: 0o700 });
211
- const tmp = path.join(
212
- dir,
213
- `${path.basename(filePath)}.${crypto.randomUUID()}.tmp`
214
- );
214
+ const tmp = path.join(dir, `${path.basename(filePath)}.${crypto.randomUUID()}.tmp`);
215
215
  const payload: NostrProfileState = {
216
216
  version: PROFILE_STATE_VERSION,
217
217
  lastPublishedAt: params.lastPublishedAt,
@@ -56,19 +56,27 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
56
56
  // Move an entry to the front (most recently used)
57
57
  function moveToFront(id: string): void {
58
58
  const entry = entries.get(id);
59
- if (!entry) return;
59
+ if (!entry) {
60
+ return;
61
+ }
60
62
 
61
63
  // Already at front
62
- if (head === id) return;
64
+ if (head === id) {
65
+ return;
66
+ }
63
67
 
64
68
  // Remove from current position
65
69
  if (entry.prev) {
66
70
  const prevEntry = entries.get(entry.prev);
67
- if (prevEntry) prevEntry.next = entry.next;
71
+ if (prevEntry) {
72
+ prevEntry.next = entry.next;
73
+ }
68
74
  }
69
75
  if (entry.next) {
70
76
  const nextEntry = entries.get(entry.next);
71
- if (nextEntry) nextEntry.prev = entry.prev;
77
+ if (nextEntry) {
78
+ nextEntry.prev = entry.prev;
79
+ }
72
80
  }
73
81
 
74
82
  // Update tail if this was the tail
@@ -81,29 +89,39 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
81
89
  entry.next = head;
82
90
  if (head) {
83
91
  const headEntry = entries.get(head);
84
- if (headEntry) headEntry.prev = id;
92
+ if (headEntry) {
93
+ headEntry.prev = id;
94
+ }
85
95
  }
86
96
  head = id;
87
97
 
88
98
  // If no tail, this is also the tail
89
- if (!tail) tail = id;
99
+ if (!tail) {
100
+ tail = id;
101
+ }
90
102
  }
91
103
 
92
104
  // Remove an entry from the linked list
93
105
  function removeFromList(id: string): void {
94
106
  const entry = entries.get(id);
95
- if (!entry) return;
107
+ if (!entry) {
108
+ return;
109
+ }
96
110
 
97
111
  if (entry.prev) {
98
112
  const prevEntry = entries.get(entry.prev);
99
- if (prevEntry) prevEntry.next = entry.next;
113
+ if (prevEntry) {
114
+ prevEntry.next = entry.next;
115
+ }
100
116
  } else {
101
117
  head = entry.next;
102
118
  }
103
119
 
104
120
  if (entry.next) {
105
121
  const nextEntry = entries.get(entry.next);
106
- if (nextEntry) nextEntry.prev = entry.prev;
122
+ if (nextEntry) {
123
+ nextEntry.prev = entry.prev;
124
+ }
107
125
  } else {
108
126
  tail = entry.prev;
109
127
  }
@@ -111,7 +129,9 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
111
129
 
112
130
  // Evict the least recently used entry
113
131
  function evictLRU(): void {
114
- if (!tail) return;
132
+ if (!tail) {
133
+ return;
134
+ }
115
135
  const idToEvict = tail;
116
136
  removeFromList(idToEvict);
117
137
  entries.delete(idToEvict);
@@ -139,7 +159,9 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
139
159
  if (pruneIntervalMs > 0) {
140
160
  pruneTimer = setInterval(pruneExpired, pruneIntervalMs);
141
161
  // Don't keep process alive just for pruning
142
- if (pruneTimer.unref) pruneTimer.unref();
162
+ if (pruneTimer.unref) {
163
+ pruneTimer.unref();
164
+ }
143
165
  }
144
166
 
145
167
  function add(id: string): void {
@@ -167,12 +189,16 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
167
189
 
168
190
  if (head) {
169
191
  const headEntry = entries.get(head);
170
- if (headEntry) headEntry.prev = id;
192
+ if (headEntry) {
193
+ headEntry.prev = id;
194
+ }
171
195
  }
172
196
 
173
197
  entries.set(id, newEntry);
174
198
  head = id;
175
- if (!tail) tail = id;
199
+ if (!tail) {
200
+ tail = id;
201
+ }
176
202
  }
177
203
 
178
204
  function has(id: string): boolean {
@@ -198,7 +224,9 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
198
224
 
199
225
  function peek(id: string): boolean {
200
226
  const entry = entries.get(id);
201
- if (!entry) return false;
227
+ if (!entry) {
228
+ return false;
229
+ }
202
230
 
203
231
  // Check if expired
204
232
  if (Date.now() - entry.seenAt > ttlMs) {
@@ -248,12 +276,16 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
248
276
 
249
277
  if (head) {
250
278
  const headEntry = entries.get(head);
251
- if (headEntry) headEntry.prev = id;
279
+ if (headEntry) {
280
+ headEntry.prev = id;
281
+ }
252
282
  }
253
283
 
254
284
  entries.set(id, newEntry);
255
285
  head = id;
256
- if (!tail) tail = id;
286
+ if (!tail) {
287
+ tail = id;
288
+ }
257
289
  }
258
290
  }
259
291
  }
package/src/types.test.ts CHANGED
@@ -1,9 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import {
3
- listNostrAccountIds,
4
- resolveDefaultNostrAccountId,
5
- resolveNostrAccount,
6
- } from "./types.js";
2
+ import { listNostrAccountIds, resolveDefaultNostrAccountId, resolveNostrAccount } from "./types.js";
7
3
 
8
4
  const TEST_PRIVATE_KEY = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
9
5
 
package/src/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { OpenClawConfig } from "openclaw/plugin-sdk";
2
+ import type { NostrProfile } from "./config-schema.js";
2
3
  import { getPublicKeyFromPrivate } from "./nostr-bus.js";
3
4
  import { DEFAULT_RELAYS } from "./nostr-bus.js";
4
- import type { NostrProfile } from "./config-schema.js";
5
5
 
6
6
  export interface NostrAccountConfig {
7
7
  enabled?: boolean;
@@ -48,7 +48,9 @@ export function listNostrAccountIds(cfg: OpenClawConfig): string[] {
48
48
  */
49
49
  export function resolveDefaultNostrAccountId(cfg: OpenClawConfig): string {
50
50
  const ids = listNostrAccountIds(cfg);
51
- if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
51
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
52
+ return DEFAULT_ACCOUNT_ID;
53
+ }
52
54
  return ids[0] ?? DEFAULT_ACCOUNT_ID;
53
55
  }
54
56