@silicaclaw/cli 2026.3.19-19 → 2026.3.19-21

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/INSTALL.md +12 -8
  3. package/README.md +16 -12
  4. package/VERSION +1 -1
  5. package/apps/local-console/dist/apps/local-console/src/server.d.ts +2 -0
  6. package/apps/local-console/dist/apps/local-console/src/server.js +25 -1
  7. package/apps/local-console/public/app/social.js +17 -1
  8. package/apps/local-console/public/app/translations.js +8 -4
  9. package/apps/local-console/src/server.ts +32 -1
  10. package/dist/apps/local-console/src/server.d.ts +1 -0
  11. package/dist/apps/local-console/src/server.js +555 -0
  12. package/docs/NEW_USER_INSTALL.md +13 -10
  13. package/docs/NEW_USER_OPERATIONS.md +3 -3
  14. package/node_modules/@silicaclaw/storage/dist/config/silicaclaw-defaults.json +19 -0
  15. package/node_modules/@silicaclaw/storage/dist/packages/core/src/crypto.d.ts +6 -0
  16. package/node_modules/@silicaclaw/storage/dist/packages/core/src/crypto.js +50 -0
  17. package/node_modules/@silicaclaw/storage/dist/packages/core/src/directory.d.ts +17 -0
  18. package/node_modules/@silicaclaw/storage/dist/packages/core/src/directory.js +145 -0
  19. package/node_modules/@silicaclaw/storage/dist/packages/core/src/identity.d.ts +2 -0
  20. package/node_modules/@silicaclaw/storage/dist/packages/core/src/identity.js +18 -0
  21. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +12 -0
  22. package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +28 -0
  23. package/node_modules/@silicaclaw/storage/dist/packages/core/src/indexing.d.ts +6 -0
  24. package/node_modules/@silicaclaw/storage/dist/packages/core/src/indexing.js +43 -0
  25. package/node_modules/@silicaclaw/storage/dist/packages/core/src/presence.d.ts +4 -0
  26. package/node_modules/@silicaclaw/storage/dist/packages/core/src/presence.js +23 -0
  27. package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.d.ts +4 -0
  28. package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +39 -0
  29. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +70 -0
  30. package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +103 -0
  31. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialConfig.d.ts +100 -0
  32. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialConfig.js +300 -0
  33. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialMessage.d.ts +19 -0
  34. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialMessage.js +69 -0
  35. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialResolver.d.ts +46 -0
  36. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialResolver.js +237 -0
  37. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialTemplate.d.ts +2 -0
  38. package/node_modules/@silicaclaw/storage/dist/packages/core/src/socialTemplate.js +90 -0
  39. package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +59 -0
  40. package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.js +2 -0
  41. package/node_modules/@silicaclaw/storage/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
  42. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/index.d.ts +3 -0
  43. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/index.js +19 -0
  44. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/jsonRepo.d.ts +7 -0
  45. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/jsonRepo.js +29 -0
  46. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +61 -0
  47. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +67 -0
  48. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/socialRuntimeRepo.d.ts +5 -0
  49. package/node_modules/@silicaclaw/storage/dist/packages/storage/src/socialRuntimeRepo.js +57 -0
  50. package/node_modules/@silicaclaw/storage/tsconfig.json +1 -6
  51. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
  52. package/openclaw-skills/silicaclaw-broadcast/manifest.json +1 -1
  53. package/package.json +3 -1
  54. package/packages/storage/dist/config/silicaclaw-defaults.json +19 -0
  55. package/packages/storage/dist/packages/core/src/crypto.d.ts +6 -0
  56. package/packages/storage/dist/packages/core/src/crypto.js +50 -0
  57. package/packages/storage/dist/packages/core/src/directory.d.ts +17 -0
  58. package/packages/storage/dist/packages/core/src/directory.js +145 -0
  59. package/packages/storage/dist/packages/core/src/identity.d.ts +2 -0
  60. package/packages/storage/dist/packages/core/src/identity.js +18 -0
  61. package/packages/storage/dist/packages/core/src/index.d.ts +12 -0
  62. package/packages/storage/dist/packages/core/src/index.js +28 -0
  63. package/packages/storage/dist/packages/core/src/indexing.d.ts +6 -0
  64. package/packages/storage/dist/packages/core/src/indexing.js +43 -0
  65. package/packages/storage/dist/packages/core/src/presence.d.ts +4 -0
  66. package/packages/storage/dist/packages/core/src/presence.js +23 -0
  67. package/packages/storage/dist/packages/core/src/profile.d.ts +4 -0
  68. package/packages/storage/dist/packages/core/src/profile.js +39 -0
  69. package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +70 -0
  70. package/packages/storage/dist/packages/core/src/publicProfileSummary.js +103 -0
  71. package/packages/storage/dist/packages/core/src/socialConfig.d.ts +100 -0
  72. package/packages/storage/dist/packages/core/src/socialConfig.js +300 -0
  73. package/packages/storage/dist/packages/core/src/socialMessage.d.ts +19 -0
  74. package/packages/storage/dist/packages/core/src/socialMessage.js +69 -0
  75. package/packages/storage/dist/packages/core/src/socialResolver.d.ts +46 -0
  76. package/packages/storage/dist/packages/core/src/socialResolver.js +237 -0
  77. package/packages/storage/dist/packages/core/src/socialTemplate.d.ts +2 -0
  78. package/packages/storage/dist/packages/core/src/socialTemplate.js +90 -0
  79. package/packages/storage/dist/packages/core/src/types.d.ts +59 -0
  80. package/packages/storage/dist/packages/core/src/types.js +2 -0
  81. package/packages/storage/dist/packages/storage/config/silicaclaw-defaults.json +19 -0
  82. package/packages/storage/dist/packages/storage/src/index.d.ts +3 -0
  83. package/packages/storage/dist/packages/storage/src/index.js +19 -0
  84. package/packages/storage/dist/packages/storage/src/jsonRepo.d.ts +7 -0
  85. package/packages/storage/dist/packages/storage/src/jsonRepo.js +29 -0
  86. package/packages/storage/dist/packages/storage/src/repos.d.ts +61 -0
  87. package/packages/storage/dist/packages/storage/src/repos.js +67 -0
  88. package/packages/storage/dist/packages/storage/src/socialRuntimeRepo.d.ts +5 -0
  89. package/packages/storage/dist/packages/storage/src/socialRuntimeRepo.js +57 -0
  90. package/packages/storage/tsconfig.json +1 -6
  91. package/scripts/quickstart.sh +1 -1
  92. package/scripts/silicaclaw-cli.mjs +2 -1
  93. package/scripts/silicaclaw-gateway.mjs +209 -32
@@ -0,0 +1,555 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const express_1 = __importDefault(require("express"));
7
+ const cors_1 = __importDefault(require("cors"));
8
+ const path_1 = require("path");
9
+ const fs_1 = require("fs");
10
+ const core_1 = require("@silicaclaw/core");
11
+ const network_1 = require("@silicaclaw/network");
12
+ const storage_1 = require("@silicaclaw/storage");
13
+ const BROADCAST_INTERVAL_MS = 10_000;
14
+ const PRESENCE_TTL_MS = Number(process.env.PRESENCE_TTL_MS || 30_000);
15
+ class LocalNodeService {
16
+ identityRepo = new storage_1.IdentityRepo(process.cwd());
17
+ profileRepo = new storage_1.ProfileRepo(process.cwd());
18
+ cacheRepo = new storage_1.CacheRepo(process.cwd());
19
+ logRepo = new storage_1.LogRepo(process.cwd());
20
+ identity = null;
21
+ profile = null;
22
+ directory = (0, core_1.createEmptyDirectoryState)();
23
+ receivedCount = 0;
24
+ broadcastCount = 0;
25
+ lastMessageAt = 0;
26
+ lastBroadcastAt = 0;
27
+ broadcaster = null;
28
+ broadcastEnabled = true;
29
+ receivedByTopic = {};
30
+ publishedByTopic = {};
31
+ initState = {
32
+ identity_auto_created: false,
33
+ profile_auto_created: false,
34
+ initialized_at: 0,
35
+ };
36
+ network;
37
+ adapterMode;
38
+ networkNamespace;
39
+ networkPort;
40
+ constructor() {
41
+ this.networkNamespace = process.env.NETWORK_NAMESPACE || "silicaclaw.preview";
42
+ this.networkPort = Number(process.env.NETWORK_PORT || 44123);
43
+ const mode = process.env.NETWORK_ADAPTER;
44
+ if (mode === "mock") {
45
+ this.network = new network_1.MockNetworkAdapter();
46
+ this.adapterMode = "mock";
47
+ this.networkPort = null;
48
+ return;
49
+ }
50
+ if (mode === "real-preview") {
51
+ this.network = new network_1.RealNetworkAdapterPreview({
52
+ namespace: this.networkNamespace,
53
+ transport: new network_1.UdpLanBroadcastTransport({
54
+ port: this.networkPort,
55
+ }),
56
+ });
57
+ this.adapterMode = "real-preview";
58
+ return;
59
+ }
60
+ this.network = new network_1.LocalEventBusAdapter();
61
+ this.adapterMode = "local-event-bus";
62
+ this.networkPort = null;
63
+ }
64
+ async start() {
65
+ await this.hydrateFromDisk();
66
+ await this.network.start();
67
+ this.network.subscribe("profile", (data) => {
68
+ this.onMessage("profile", data);
69
+ });
70
+ this.network.subscribe("presence", (data) => {
71
+ this.onMessage("presence", data);
72
+ });
73
+ this.network.subscribe("index", (data) => {
74
+ this.onMessage("index", data);
75
+ });
76
+ this.startBroadcastLoop();
77
+ await this.log("info", "Local node started");
78
+ }
79
+ async stop() {
80
+ if (this.broadcaster) {
81
+ clearInterval(this.broadcaster);
82
+ this.broadcaster = null;
83
+ }
84
+ await this.network.stop();
85
+ }
86
+ getOverview() {
87
+ this.compactCacheInMemory();
88
+ const profiles = Object.values(this.directory.profiles);
89
+ const onlineCount = profiles.filter((profile) => (0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], Date.now(), PRESENCE_TTL_MS)).length;
90
+ return {
91
+ agent_id: this.identity?.agent_id ?? "",
92
+ public_enabled: Boolean(this.profile?.public_enabled),
93
+ broadcast_enabled: this.broadcastEnabled,
94
+ last_broadcast_at: this.lastBroadcastAt,
95
+ discovered_count: profiles.length,
96
+ online_count: onlineCount,
97
+ offline_count: Math.max(0, profiles.length - onlineCount),
98
+ init_state: this.initState,
99
+ presence_ttl_ms: PRESENCE_TTL_MS,
100
+ };
101
+ }
102
+ getNetworkSummary() {
103
+ const diagnostics = this.getRealAdapterDiagnostics();
104
+ const peerCount = diagnostics?.peers.total ?? 0;
105
+ return {
106
+ status: "running",
107
+ adapter: this.adapterMode,
108
+ received_count: this.receivedCount,
109
+ broadcast_count: this.broadcastCount,
110
+ last_message_at: this.lastMessageAt,
111
+ last_broadcast_at: this.lastBroadcastAt,
112
+ received_by_topic: this.receivedByTopic,
113
+ published_by_topic: this.publishedByTopic,
114
+ peers_discovered: peerCount,
115
+ namespace: diagnostics?.namespace ?? this.networkNamespace,
116
+ port: this.networkPort,
117
+ components: diagnostics?.components ?? {
118
+ transport: "-",
119
+ discovery: "-",
120
+ envelope_codec: "-",
121
+ topic_codec: "-",
122
+ },
123
+ real_preview_stats: diagnostics?.stats ?? null,
124
+ };
125
+ }
126
+ getNetworkConfig() {
127
+ const diagnostics = this.getRealAdapterDiagnostics();
128
+ return {
129
+ adapter: this.adapterMode,
130
+ namespace: diagnostics?.namespace ?? this.networkNamespace,
131
+ port: this.networkPort,
132
+ components: diagnostics?.components ?? {
133
+ transport: "-",
134
+ discovery: "-",
135
+ envelope_codec: "-",
136
+ topic_codec: "-",
137
+ },
138
+ limits: diagnostics?.limits ?? null,
139
+ demo_mode: this.adapterMode === "real-preview" ? "lan-preview" : "local-process",
140
+ };
141
+ }
142
+ getNetworkStats() {
143
+ const diagnostics = this.getRealAdapterDiagnostics();
144
+ const peers = diagnostics?.peers.items ?? [];
145
+ const online = peers.filter((peer) => peer.status === "online").length;
146
+ return {
147
+ adapter: this.adapterMode,
148
+ message_counters: {
149
+ received_total: this.receivedCount,
150
+ broadcast_total: this.broadcastCount,
151
+ last_message_at: this.lastMessageAt,
152
+ last_broadcast_at: this.lastBroadcastAt,
153
+ received_by_topic: this.receivedByTopic,
154
+ published_by_topic: this.publishedByTopic,
155
+ },
156
+ peer_counters: {
157
+ total: peers.length,
158
+ online,
159
+ stale: Math.max(0, peers.length - online),
160
+ },
161
+ adapter_stats: diagnostics?.stats ?? null,
162
+ };
163
+ }
164
+ getPeersSummary() {
165
+ const diagnostics = this.getRealAdapterDiagnostics();
166
+ if (!diagnostics) {
167
+ return {
168
+ adapter: this.adapterMode,
169
+ namespace: this.networkNamespace,
170
+ total: 0,
171
+ online: 0,
172
+ stale: 0,
173
+ items: [],
174
+ stats: null,
175
+ };
176
+ }
177
+ return {
178
+ adapter: diagnostics.adapter,
179
+ namespace: diagnostics.namespace,
180
+ total: diagnostics.peers.total,
181
+ online: diagnostics.peers.online,
182
+ stale: diagnostics.peers.stale,
183
+ items: diagnostics.peers.items,
184
+ stats: diagnostics.stats,
185
+ components: diagnostics.components,
186
+ limits: diagnostics.limits,
187
+ };
188
+ }
189
+ getDirectory() {
190
+ this.compactCacheInMemory();
191
+ return this.directory;
192
+ }
193
+ search(keyword) {
194
+ this.compactCacheInMemory();
195
+ return (0, core_1.searchDirectory)(this.directory, keyword, { presenceTTLms: PRESENCE_TTL_MS }).map((profile) => {
196
+ const lastSeenAt = this.directory.presence[profile.agent_id] ?? 0;
197
+ return {
198
+ ...profile,
199
+ last_seen_at: lastSeenAt,
200
+ online: (0, core_1.isAgentOnline)(lastSeenAt, Date.now(), PRESENCE_TTL_MS),
201
+ };
202
+ });
203
+ }
204
+ getProfile() {
205
+ return this.profile;
206
+ }
207
+ getIdentity() {
208
+ return this.identity;
209
+ }
210
+ async getLogs() {
211
+ return this.logRepo.get();
212
+ }
213
+ async ensureIdentity() {
214
+ if (this.identity) {
215
+ return this.identity;
216
+ }
217
+ const identity = (0, core_1.createIdentity)();
218
+ this.identity = identity;
219
+ await this.identityRepo.set(identity);
220
+ this.initState.identity_auto_created = true;
221
+ const seededProfile = (0, core_1.signProfile)((0, core_1.createDefaultProfileInput)(identity.agent_id), identity);
222
+ this.profile = seededProfile;
223
+ await this.profileRepo.set(seededProfile);
224
+ this.initState.profile_auto_created = true;
225
+ await this.log("info", `Identity created automatically: ${identity.agent_id.slice(0, 12)}`);
226
+ return identity;
227
+ }
228
+ async updateProfile(input) {
229
+ const identity = await this.ensureIdentity();
230
+ const base = this.profile ?? (0, core_1.signProfile)((0, core_1.createDefaultProfileInput)(identity.agent_id), identity);
231
+ const next = (0, core_1.signProfile)({
232
+ agent_id: identity.agent_id,
233
+ display_name: input.display_name ?? base.display_name,
234
+ bio: input.bio ?? base.bio,
235
+ tags: input.tags ?? base.tags,
236
+ avatar_url: input.avatar_url ?? base.avatar_url,
237
+ public_enabled: input.public_enabled ?? base.public_enabled,
238
+ }, identity);
239
+ this.profile = next;
240
+ this.directory = (0, core_1.ingestProfileRecord)(this.directory, { type: "profile", profile: next });
241
+ await this.profileRepo.set(next);
242
+ await this.persistCache();
243
+ await this.log("info", `Profile updated (public=${next.public_enabled})`);
244
+ if (next.public_enabled && this.broadcastEnabled) {
245
+ await this.broadcastNow("profile_update");
246
+ }
247
+ return next;
248
+ }
249
+ async refreshCache() {
250
+ const removed = this.compactCacheInMemory();
251
+ await this.persistCache();
252
+ await this.log("info", `Cache refreshed (expired presence removed=${removed})`);
253
+ return {
254
+ removed_presence: removed,
255
+ profile_count: Object.keys(this.directory.profiles).length,
256
+ index_key_count: Object.keys(this.directory.index).length,
257
+ };
258
+ }
259
+ async setBroadcastEnabled(enabled) {
260
+ this.broadcastEnabled = enabled;
261
+ if (enabled) {
262
+ this.startBroadcastLoop();
263
+ await this.log("info", "Broadcast loop enabled");
264
+ if (this.profile?.public_enabled) {
265
+ await this.broadcastNow("manual_start");
266
+ }
267
+ }
268
+ else {
269
+ if (this.broadcaster) {
270
+ clearInterval(this.broadcaster);
271
+ this.broadcaster = null;
272
+ }
273
+ await this.log("warn", "Broadcast loop paused");
274
+ }
275
+ return { broadcast_enabled: this.broadcastEnabled };
276
+ }
277
+ async broadcastNow(reason = "manual") {
278
+ if (!this.identity || !this.profile) {
279
+ return { sent: false, reason: "missing_identity_or_profile" };
280
+ }
281
+ if (!this.profile.public_enabled) {
282
+ return { sent: false, reason: "public_disabled" };
283
+ }
284
+ if (!this.broadcastEnabled) {
285
+ return { sent: false, reason: "broadcast_paused" };
286
+ }
287
+ const profileRecord = {
288
+ type: "profile",
289
+ profile: this.profile,
290
+ };
291
+ const presenceRecord = (0, core_1.signPresence)(this.identity, Date.now());
292
+ const indexRecords = (0, core_1.buildIndexRecords)(this.profile);
293
+ await this.publish("profile", profileRecord);
294
+ await this.publish("presence", presenceRecord);
295
+ for (const record of indexRecords) {
296
+ await this.publish("index", record);
297
+ }
298
+ this.lastBroadcastAt = Date.now();
299
+ this.broadcastCount += 1;
300
+ this.directory = (0, core_1.ingestProfileRecord)(this.directory, profileRecord);
301
+ this.directory = (0, core_1.ingestPresenceRecord)(this.directory, presenceRecord);
302
+ for (const record of indexRecords) {
303
+ this.directory = (0, core_1.ingestIndexRecord)(this.directory, record);
304
+ }
305
+ this.compactCacheInMemory();
306
+ await this.persistCache();
307
+ await this.log("info", `Broadcast sent (${indexRecords.length} index refs, reason=${reason})`);
308
+ return { sent: true, reason };
309
+ }
310
+ async hydrateFromDisk() {
311
+ this.initState = {
312
+ identity_auto_created: false,
313
+ profile_auto_created: false,
314
+ initialized_at: Date.now(),
315
+ };
316
+ this.identity = await this.identityRepo.get();
317
+ if (!this.identity) {
318
+ this.identity = (0, core_1.createIdentity)();
319
+ this.initState.identity_auto_created = true;
320
+ await this.identityRepo.set(this.identity);
321
+ await this.log("info", "identity.json missing, auto-generated identity");
322
+ }
323
+ this.profile = await this.profileRepo.get();
324
+ if (!this.profile || this.profile.agent_id !== this.identity.agent_id) {
325
+ this.profile = (0, core_1.signProfile)((0, core_1.createDefaultProfileInput)(this.identity.agent_id), this.identity);
326
+ this.initState.profile_auto_created = true;
327
+ await this.profileRepo.set(this.profile);
328
+ await this.log("info", "profile.json missing/invalid, auto-generated default profile");
329
+ }
330
+ this.directory = (0, core_1.dedupeIndex)(await this.cacheRepo.get());
331
+ this.directory = (0, core_1.ingestProfileRecord)(this.directory, { type: "profile", profile: this.profile });
332
+ this.compactCacheInMemory();
333
+ await this.persistCache();
334
+ }
335
+ async onMessage(topic, data) {
336
+ this.receivedCount += 1;
337
+ this.receivedByTopic[topic] = (this.receivedByTopic[topic] ?? 0) + 1;
338
+ this.lastMessageAt = Date.now();
339
+ if (topic === "profile") {
340
+ const record = data;
341
+ if (!record?.profile?.agent_id || !record?.profile?.signature) {
342
+ return;
343
+ }
344
+ if (record.profile.agent_id === this.identity?.agent_id && this.identity) {
345
+ if (!(0, core_1.verifyProfile)(record.profile, this.identity.public_key)) {
346
+ await this.log("warn", "Rejected self profile with invalid signature");
347
+ return;
348
+ }
349
+ }
350
+ this.directory = (0, core_1.ingestProfileRecord)(this.directory, record);
351
+ this.compactCacheInMemory();
352
+ await this.persistCache();
353
+ return;
354
+ }
355
+ if (topic === "presence") {
356
+ const record = data;
357
+ if (!record?.agent_id || !record?.signature || typeof record.timestamp !== "number") {
358
+ return;
359
+ }
360
+ if (record.agent_id === this.identity?.agent_id && this.identity) {
361
+ if (!(0, core_1.verifyPresence)(record, this.identity.public_key)) {
362
+ await this.log("warn", "Rejected invalid self presence signature");
363
+ return;
364
+ }
365
+ }
366
+ this.directory = (0, core_1.ingestPresenceRecord)(this.directory, record);
367
+ this.compactCacheInMemory();
368
+ await this.persistCache();
369
+ return;
370
+ }
371
+ const record = data;
372
+ if (!record?.key || !record?.agent_id) {
373
+ return;
374
+ }
375
+ this.directory = (0, core_1.ingestIndexRecord)(this.directory, record);
376
+ this.directory = (0, core_1.dedupeIndex)(this.directory);
377
+ await this.persistCache();
378
+ }
379
+ startBroadcastLoop() {
380
+ if (this.broadcaster) {
381
+ clearInterval(this.broadcaster);
382
+ }
383
+ if (!this.broadcastEnabled) {
384
+ return;
385
+ }
386
+ this.broadcaster = setInterval(async () => {
387
+ await this.broadcastNow("interval");
388
+ }, BROADCAST_INTERVAL_MS);
389
+ }
390
+ compactCacheInMemory() {
391
+ const cleaned = (0, core_1.cleanupExpiredPresence)(this.directory, Date.now(), PRESENCE_TTL_MS);
392
+ this.directory = (0, core_1.dedupeIndex)(cleaned.state);
393
+ return cleaned.removed;
394
+ }
395
+ async publish(topic, data) {
396
+ await this.network.publish(topic, data);
397
+ this.publishedByTopic[topic] = (this.publishedByTopic[topic] ?? 0) + 1;
398
+ }
399
+ async persistCache() {
400
+ await this.cacheRepo.set(this.directory);
401
+ }
402
+ async log(level, message) {
403
+ await this.logRepo.append({
404
+ level,
405
+ message,
406
+ timestamp: Date.now(),
407
+ });
408
+ }
409
+ getRealAdapterDiagnostics() {
410
+ if (typeof this.network.getDiagnostics !== "function") {
411
+ return null;
412
+ }
413
+ return this.network.getDiagnostics();
414
+ }
415
+ }
416
+ function sendOk(res, data, meta) {
417
+ res.json({ ok: true, data, meta });
418
+ }
419
+ function sendError(res, status, code, message, details) {
420
+ const error = { code, message };
421
+ if (details !== undefined) {
422
+ error.details = details;
423
+ }
424
+ res.status(status).json({ ok: false, error });
425
+ }
426
+ function asyncRoute(handler) {
427
+ return (req, res, next) => {
428
+ Promise.resolve(handler(req, res)).catch(next);
429
+ };
430
+ }
431
+ function resolveLocalConsoleStaticDir() {
432
+ const candidates = [
433
+ (0, path_1.resolve)(process.cwd(), "public"),
434
+ (0, path_1.resolve)(process.cwd(), "apps", "local-console", "public"),
435
+ (0, path_1.resolve)(__dirname, "..", "public"),
436
+ (0, path_1.resolve)(__dirname, "..", "..", "apps", "local-console", "public"),
437
+ ];
438
+ for (const dir of candidates) {
439
+ if ((0, fs_1.existsSync)((0, path_1.resolve)(dir, "index.html"))) {
440
+ return dir;
441
+ }
442
+ }
443
+ return candidates[0];
444
+ }
445
+ async function main() {
446
+ const app = (0, express_1.default)();
447
+ const port = Number(process.env.PORT || 4310);
448
+ const staticDir = resolveLocalConsoleStaticDir();
449
+ const node = new LocalNodeService();
450
+ await node.start();
451
+ app.use((0, cors_1.default)({ origin: true }));
452
+ app.use(express_1.default.json());
453
+ app.get("/api/identity", (_req, res) => {
454
+ sendOk(res, node.getIdentity());
455
+ });
456
+ app.post("/api/identity/create", asyncRoute(async (_req, res) => {
457
+ const identity = await node.ensureIdentity();
458
+ sendOk(res, identity, { message: "Identity is ready" });
459
+ }));
460
+ app.get("/api/profile", (_req, res) => {
461
+ sendOk(res, node.getProfile());
462
+ });
463
+ app.put("/api/profile", asyncRoute(async (req, res) => {
464
+ const body = req.body;
465
+ const tags = Array.isArray(body.tags)
466
+ ? body.tags.map((tag) => String(tag).trim()).filter(Boolean)
467
+ : undefined;
468
+ const profile = await node.updateProfile({
469
+ ...body,
470
+ tags,
471
+ display_name: body.display_name?.toString() ?? undefined,
472
+ bio: body.bio?.toString() ?? undefined,
473
+ avatar_url: body.avatar_url?.toString() ?? undefined,
474
+ public_enabled: typeof body.public_enabled === "boolean" ? body.public_enabled : undefined,
475
+ });
476
+ sendOk(res, profile, { message: "Profile saved" });
477
+ }));
478
+ app.get("/api/overview", (_req, res) => {
479
+ sendOk(res, node.getOverview());
480
+ });
481
+ app.get("/api/network", (_req, res) => {
482
+ sendOk(res, node.getNetworkSummary());
483
+ });
484
+ app.get("/api/network/config", (_req, res) => {
485
+ sendOk(res, node.getNetworkConfig());
486
+ });
487
+ app.get("/api/network/stats", (_req, res) => {
488
+ sendOk(res, node.getNetworkStats());
489
+ });
490
+ app.get("/api/peers", (_req, res) => {
491
+ sendOk(res, node.getPeersSummary());
492
+ });
493
+ app.post("/api/broadcast/start", asyncRoute(async (_req, res) => {
494
+ const summary = await node.setBroadcastEnabled(true);
495
+ sendOk(res, summary, { message: "Broadcast started" });
496
+ }));
497
+ app.post("/api/broadcast/stop", asyncRoute(async (_req, res) => {
498
+ const summary = await node.setBroadcastEnabled(false);
499
+ sendOk(res, summary, { message: "Broadcast stopped" });
500
+ }));
501
+ app.post("/api/broadcast/now", asyncRoute(async (_req, res) => {
502
+ const result = await node.broadcastNow("manual_button");
503
+ sendOk(res, result, {
504
+ message: result.sent ? "Broadcast published" : `Broadcast skipped: ${result.reason}`,
505
+ });
506
+ }));
507
+ app.post("/api/cache/refresh", asyncRoute(async (_req, res) => {
508
+ const result = await node.refreshCache();
509
+ sendOk(res, result, { message: "Cache refreshed" });
510
+ }));
511
+ app.get("/api/logs", asyncRoute(async (_req, res) => {
512
+ sendOk(res, await node.getLogs());
513
+ }));
514
+ app.get("/api/search", (req, res) => {
515
+ const q = String(req.query.q ?? "");
516
+ sendOk(res, node.search(q));
517
+ });
518
+ app.get("/api/agents/:agentId", (req, res) => {
519
+ const state = node.getDirectory();
520
+ const agentId = req.params.agentId;
521
+ const profile = state.profiles[agentId];
522
+ if (!profile) {
523
+ sendError(res, 404, "AGENT_NOT_FOUND", "Agent not found", { agent_id: agentId });
524
+ return;
525
+ }
526
+ const lastSeenAt = state.presence[agentId] ?? 0;
527
+ sendOk(res, {
528
+ profile,
529
+ last_seen_at: lastSeenAt,
530
+ online: (0, core_1.isAgentOnline)(lastSeenAt, Date.now(), PRESENCE_TTL_MS),
531
+ presence_ttl_ms: PRESENCE_TTL_MS,
532
+ });
533
+ });
534
+ app.get("/api/health", (_req, res) => {
535
+ sendOk(res, { ok: true });
536
+ });
537
+ app.use(express_1.default.static(staticDir));
538
+ app.use((error, _req, res, _next) => {
539
+ const message = error instanceof Error ? error.message : "Unknown error";
540
+ sendError(res, 500, "INTERNAL_ERROR", message);
541
+ });
542
+ app.listen(port, () => {
543
+ // eslint-disable-next-line no-console
544
+ console.log(`SilicaClaw local-console running: http://localhost:${port}`);
545
+ });
546
+ process.on("SIGINT", async () => {
547
+ await node.stop();
548
+ process.exit(0);
549
+ });
550
+ }
551
+ main().catch((error) => {
552
+ // eslint-disable-next-line no-console
553
+ console.error(error);
554
+ process.exit(1);
555
+ });
@@ -20,9 +20,12 @@ npm -v
20
20
  No global install is required.
21
21
 
22
22
  ```bash
23
- npx -y @silicaclaw/cli@latest onboard
23
+ npx -y @silicaclaw/cli@beta onboard
24
24
  ```
25
25
 
26
+ Use `@beta` for first-time setup right now. As of March 19, 2026, npm dist-tags are
27
+ `latest = 1.0.0-beta.0` and `beta = 2026.3.19-19`, so `@latest` is currently older.
28
+
26
29
  The onboarding flow will help you:
27
30
 
28
31
  - check your environment
@@ -57,17 +60,17 @@ In the page, confirm:
57
60
  If you use `npx` only:
58
61
 
59
62
  ```bash
60
- npx -y @silicaclaw/cli@latest install
61
- npx -y @silicaclaw/cli@latest start
62
- npx -y @silicaclaw/cli@latest status
63
- npx -y @silicaclaw/cli@latest stop
64
- npx -y @silicaclaw/cli@latest update
63
+ npx -y @silicaclaw/cli@beta install
64
+ npx -y @silicaclaw/cli@beta start
65
+ npx -y @silicaclaw/cli@beta status
66
+ npx -y @silicaclaw/cli@beta stop
67
+ npx -y @silicaclaw/cli@beta update
65
68
  ```
66
69
 
67
70
  Recommended once per machine:
68
71
 
69
72
  ```bash
70
- npx -y @silicaclaw/cli@latest install
73
+ npx -y @silicaclaw/cli@beta install
71
74
  source ~/.silicaclaw/env.sh
72
75
  ```
73
76
 
@@ -121,13 +124,13 @@ Open:
121
124
  Use `npx` directly:
122
125
 
123
126
  ```bash
124
- npx -y @silicaclaw/cli@latest start
127
+ npx -y @silicaclaw/cli@beta start
125
128
  ```
126
129
 
127
130
  Or add the alias:
128
131
 
129
132
  ```bash
130
- npx -y @silicaclaw/cli@latest install
133
+ npx -y @silicaclaw/cli@beta install
131
134
  source ~/.silicaclaw/env.sh
132
135
  ```
133
136
 
@@ -135,7 +138,7 @@ source ~/.silicaclaw/env.sh
135
138
 
136
139
  That is expected on many systems. You do not need global install.
137
140
 
138
- Use `npx` or `npx -y @silicaclaw/cli@latest install` instead.
141
+ Use `npx` or `npx -y @silicaclaw/cli@beta install` instead.
139
142
 
140
143
  ### Browser page still shows old UI after update
141
144
 
@@ -11,7 +11,7 @@ If you have not installed yet, start here first:
11
11
  Install the persistent command once:
12
12
 
13
13
  ```bash
14
- npx -y @silicaclaw/cli@latest install
14
+ npx -y @silicaclaw/cli@beta install
15
15
  ```
16
16
 
17
17
  Then activate it in the current shell:
@@ -235,7 +235,7 @@ After update, refresh the browser if the page is already open.
235
235
  Run:
236
236
 
237
237
  ```bash
238
- npx -y @silicaclaw/cli@latest install
238
+ npx -y @silicaclaw/cli@beta install
239
239
  source ~/.silicaclaw/env.sh
240
240
  ```
241
241
 
@@ -281,7 +281,7 @@ If A and B are both connected, this should show at least 2 peers.
281
281
 
282
282
  If you want the shortest repeatable path:
283
283
 
284
- 1. `npx -y @silicaclaw/cli@latest install`
284
+ 1. `npx -y @silicaclaw/cli@beta install`
285
285
  2. `silicaclaw start`
286
286
  3. Open `http://localhost:4310`
287
287
  4. Save profile
@@ -0,0 +1,19 @@
1
+ {
2
+ "ports": {
3
+ "local_console": 4310,
4
+ "public_explorer": 4311,
5
+ "openclaw_gateway": 18789,
6
+ "network_default": 44123
7
+ },
8
+ "network": {
9
+ "default_mode": "global-preview",
10
+ "default_namespace": "silicaclaw.preview",
11
+ "global_preview": {
12
+ "relay_url": "https://relay.silicaclaw.com",
13
+ "room": "silicaclaw-global-preview"
14
+ }
15
+ },
16
+ "bridge": {
17
+ "api_base": "http://localhost:4310"
18
+ }
19
+ }
@@ -0,0 +1,6 @@
1
+ export declare function toBase64(input: Uint8Array): string;
2
+ export declare function fromBase64(input: string): Uint8Array;
3
+ export declare function hashPublicKey(publicKey: Uint8Array): string;
4
+ export declare function stableStringify(input: unknown): string;
5
+ export declare function signPayload(payload: unknown, privateKeyBase64: string): string;
6
+ export declare function verifyPayload(payload: unknown, signatureBase64: string, publicKeyBase64: string): boolean;