@silicaclaw/cli 2026.3.20-2 → 2026.3.20-4

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 (50) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/INSTALL.md +13 -7
  3. package/README.md +60 -12
  4. package/VERSION +1 -1
  5. package/apps/local-console/dist/apps/local-console/src/server.d.ts +39 -0
  6. package/apps/local-console/dist/apps/local-console/src/server.js +220 -0
  7. package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +1 -0
  8. package/apps/local-console/dist/packages/network/src/relayPreview.js +17 -0
  9. package/apps/local-console/public/app/app.js +274 -3
  10. package/apps/local-console/public/app/events.js +21 -0
  11. package/apps/local-console/public/app/network.js +111 -30
  12. package/apps/local-console/public/app/overview.js +49 -21
  13. package/apps/local-console/public/app/social.js +315 -93
  14. package/apps/local-console/public/app/styles.css +86 -0
  15. package/apps/local-console/public/app/template.js +56 -35
  16. package/apps/local-console/public/app/translations.js +394 -312
  17. package/apps/local-console/src/server.ts +251 -1
  18. package/apps/public-explorer/public/app/template.js +2 -2
  19. package/apps/public-explorer/public/app/translations.js +36 -36
  20. package/docs/NEW_USER_OPERATIONS.md +5 -5
  21. package/docs/OPENCLAW_BRIDGE.md +7 -7
  22. package/docs/OPENCLAW_BRIDGE_ZH.md +6 -6
  23. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +1 -0
  24. package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +17 -0
  25. package/node_modules/@silicaclaw/network/src/relayPreview.ts +17 -0
  26. package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +18 -0
  27. package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -1
  28. package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +2 -2
  29. package/openclaw-skills/silicaclaw-broadcast/SKILL.md +18 -0
  30. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
  31. package/openclaw-skills/silicaclaw-broadcast/manifest.json +2 -2
  32. package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
  33. package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
  34. package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
  35. package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
  36. package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
  37. package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
  38. package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
  39. package/openclaw-skills/silicaclaw-owner-push/SKILL.md +18 -0
  40. package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
  41. package/openclaw-skills/silicaclaw-owner-push/manifest.json +2 -2
  42. package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +3 -0
  43. package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +67 -8
  44. package/package.json +1 -1
  45. package/packages/network/dist/packages/network/src/relayPreview.d.ts +1 -0
  46. package/packages/network/dist/packages/network/src/relayPreview.js +17 -0
  47. package/packages/network/src/relayPreview.ts +17 -0
  48. package/scripts/silicaclaw-cli.mjs +4 -1
  49. package/scripts/silicaclaw-gateway.mjs +108 -0
  50. package/scripts/validate-openclaw-skill.mjs +19 -0
@@ -12,6 +12,7 @@ const OWNER_FORWARD_CMD = String(process.env.OPENCLAW_OWNER_FORWARD_CMD || "").t
12
12
  const STATE_PATH = resolve(
13
13
  String(process.env.OPENCLAW_OWNER_FORWARD_STATE_PATH || resolve(homedir(), ".openclaw", "workspace", "state", "silicaclaw-owner-push.json"))
14
14
  );
15
+ const LATEST_ONLY = String(process.env.OPENCLAW_FORWARD_LATEST_ONLY || "true").trim().toLowerCase() !== "false";
15
16
  const ONCE = process.argv.includes("--once");
16
17
  const VERBOSE = process.argv.includes("--verbose");
17
18
 
@@ -61,14 +62,24 @@ function loadState() {
61
62
  return {
62
63
  seen_ids: [],
63
64
  pushed_at: {},
65
+ last_pushed_created_at: 0,
66
+ last_pushed_message_id: "",
64
67
  };
65
68
  }
66
69
  try {
67
- return JSON.parse(readFileSync(STATE_PATH, "utf8"));
70
+ const parsed = JSON.parse(readFileSync(STATE_PATH, "utf8"));
71
+ return {
72
+ seen_ids: Array.isArray(parsed?.seen_ids) ? parsed.seen_ids : [],
73
+ pushed_at: parsed?.pushed_at && typeof parsed.pushed_at === "object" ? parsed.pushed_at : {},
74
+ last_pushed_created_at: Number(parsed?.last_pushed_created_at || 0) || 0,
75
+ last_pushed_message_id: String(parsed?.last_pushed_message_id || ""),
76
+ };
68
77
  } catch {
69
78
  return {
70
79
  seen_ids: [],
71
80
  pushed_at: {},
81
+ last_pushed_created_at: 0,
82
+ last_pushed_message_id: "",
72
83
  };
73
84
  }
74
85
  }
@@ -85,6 +96,22 @@ function trimState(state) {
85
96
  state.pushed_at = Object.fromEntries(pushedEntries);
86
97
  }
87
98
 
99
+ function messageCreatedAt(item) {
100
+ const createdAt = Number(item?.created_at || 0);
101
+ return Number.isFinite(createdAt) && createdAt > 0 ? createdAt : 0;
102
+ }
103
+
104
+ function isNewerThanCursor(item, state) {
105
+ const createdAt = messageCreatedAt(item);
106
+ const lastCreatedAt = Number(state.last_pushed_created_at || 0) || 0;
107
+ const messageId = String(item?.message_id || "").trim();
108
+ const lastMessageId = String(state.last_pushed_message_id || "").trim();
109
+ if (createdAt > lastCreatedAt) return true;
110
+ if (createdAt < lastCreatedAt) return false;
111
+ if (!createdAt) return !state.seen_ids.includes(messageId);
112
+ return Boolean(messageId) && messageId !== lastMessageId && !state.seen_ids.includes(messageId);
113
+ }
114
+
88
115
  function shouldWatchTopic(message) {
89
116
  if (!TOPIC_FILTERS.length) return true;
90
117
  return TOPIC_FILTERS.includes(String(message?.topic || "global").toLowerCase());
@@ -165,29 +192,60 @@ function dispatchToOwner(route, summary, message) {
165
192
  async function pollOnce(state) {
166
193
  const payload = await request(`/api/openclaw/bridge/messages?limit=${LIMIT}`);
167
194
  const items = Array.isArray(payload?.items) ? payload.items.slice().reverse() : [];
195
+ const candidates = [];
168
196
 
169
197
  for (const item of items) {
170
198
  const messageId = String(item?.message_id || "").trim();
171
199
  if (!messageId) continue;
172
- if (state.seen_ids.includes(messageId)) continue;
173
-
174
- state.seen_ids.push(messageId);
200
+ if (!isNewerThanCursor(item, state)) {
201
+ if (!state.seen_ids.includes(messageId)) {
202
+ state.seen_ids.push(messageId);
203
+ }
204
+ continue;
205
+ }
175
206
 
176
207
  if (!shouldWatchTopic(item)) {
208
+ state.seen_ids.push(messageId);
177
209
  if (VERBOSE) console.log(`skip topic: ${messageId}`);
178
210
  continue;
179
211
  }
180
212
 
181
213
  const route = scoreRoute(item);
182
214
  if (route === "ignore") {
215
+ state.seen_ids.push(messageId);
183
216
  if (VERBOSE) console.log(`ignore low-signal: ${messageId}`);
184
217
  continue;
185
218
  }
186
219
 
187
- const summary = summarizeForOwner(item);
188
- await dispatchToOwner(route, summary, item);
189
- state.pushed_at[messageId] = new Date().toISOString();
190
- if (VERBOSE) console.log(`pushed to owner: ${messageId}`);
220
+ candidates.push({ item, messageId, route, createdAt: messageCreatedAt(item) });
221
+ }
222
+
223
+ const selected = LATEST_ONLY
224
+ ? candidates.sort((left, right) => {
225
+ if (left.createdAt !== right.createdAt) return right.createdAt - left.createdAt;
226
+ return left.messageId.localeCompare(right.messageId);
227
+ })[0] || null
228
+ : null;
229
+
230
+ const toPush = LATEST_ONLY ? (selected ? [selected] : []) : candidates;
231
+
232
+ for (const candidate of toPush) {
233
+ const summary = summarizeForOwner(candidate.item);
234
+ await dispatchToOwner(candidate.route, summary, candidate.item);
235
+ state.pushed_at[candidate.messageId] = new Date().toISOString();
236
+ state.last_pushed_created_at = candidate.createdAt || Date.now();
237
+ state.last_pushed_message_id = candidate.messageId;
238
+ if (VERBOSE) console.log(`pushed to owner: ${candidate.messageId}`);
239
+ }
240
+
241
+ if (LATEST_ONLY && selected) {
242
+ for (const candidate of candidates) {
243
+ state.seen_ids.push(candidate.messageId);
244
+ }
245
+ } else {
246
+ for (const candidate of candidates) {
247
+ state.seen_ids.push(candidate.messageId);
248
+ }
191
249
  }
192
250
 
193
251
  trimState(state);
@@ -199,6 +257,7 @@ async function main() {
199
257
  if (VERBOSE) {
200
258
  console.log(`SilicaClaw owner push watching ${API_BASE}`);
201
259
  console.log(`State file: ${STATE_PATH}`);
260
+ console.log(`Latest-only mode: ${LATEST_ONLY ? "on" : "off"}`);
202
261
  }
203
262
 
204
263
  do {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silicaclaw/cli",
3
- "version": "2026.3.20-2",
3
+ "version": "2026.3.20-4",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -162,5 +162,6 @@ export declare class RelayPreviewAdapter implements NetworkAdapter {
162
162
  private requestJson;
163
163
  private updatePeersFromList;
164
164
  private scheduleNextPoll;
165
+ private ensurePollingAlive;
165
166
  }
166
167
  export {};
@@ -349,6 +349,7 @@ class RelayPreviewAdapter {
349
349
  if (!this.lastJoinAt || Date.now() - this.lastJoinAt > Math.max(45_000, this.pollIntervalMs * 6)) {
350
350
  await this.joinRoom(reason);
351
351
  }
352
+ this.ensurePollingAlive(reason);
352
353
  }
353
354
  async get(path) {
354
355
  return this.requestJson("GET", path);
@@ -444,5 +445,21 @@ class RelayPreviewAdapter {
444
445
  this.pollOnce().catch(() => { });
445
446
  }, Math.max(1000, delayMs + jitterMs));
446
447
  }
448
+ ensurePollingAlive(reason) {
449
+ if (!this.started)
450
+ return;
451
+ const pollStaleMs = Math.max(45_000, this.pollIntervalMs * 6);
452
+ const pollMissing = !this.poller;
453
+ const pollStale = Boolean(this.lastPollAt) && Date.now() - this.lastPollAt > pollStaleMs;
454
+ if (!pollMissing && !pollStale) {
455
+ return;
456
+ }
457
+ this.recordDiscovery("poll_recover_scheduled", {
458
+ endpoint: this.activeEndpoint,
459
+ detail: `${reason}:${pollMissing ? "missing" : "stale"}`,
460
+ });
461
+ this.currentPollDelayMs = this.pollIntervalMs;
462
+ this.scheduleNextPoll(0);
463
+ }
447
464
  }
448
465
  exports.RelayPreviewAdapter = RelayPreviewAdapter;
@@ -467,6 +467,7 @@ export class RelayPreviewAdapter implements NetworkAdapter {
467
467
  if (!this.lastJoinAt || Date.now() - this.lastJoinAt > Math.max(45_000, this.pollIntervalMs * 6)) {
468
468
  await this.joinRoom(reason);
469
469
  }
470
+ this.ensurePollingAlive(reason);
470
471
  }
471
472
 
472
473
  private async get(path: string): Promise<any> {
@@ -565,4 +566,20 @@ export class RelayPreviewAdapter implements NetworkAdapter {
565
566
  this.pollOnce().catch(() => {});
566
567
  }, Math.max(1000, delayMs + jitterMs));
567
568
  }
569
+
570
+ private ensurePollingAlive(reason: string): void {
571
+ if (!this.started) return;
572
+ const pollStaleMs = Math.max(45_000, this.pollIntervalMs * 6);
573
+ const pollMissing = !this.poller;
574
+ const pollStale = Boolean(this.lastPollAt) && Date.now() - this.lastPollAt > pollStaleMs;
575
+ if (!pollMissing && !pollStale) {
576
+ return;
577
+ }
578
+ this.recordDiscovery("poll_recover_scheduled", {
579
+ endpoint: this.activeEndpoint,
580
+ detail: `${reason}:${pollMissing ? "missing" : "stale"}`,
581
+ });
582
+ this.currentPollDelayMs = this.pollIntervalMs;
583
+ this.scheduleNextPoll(0);
584
+ }
568
585
  }
@@ -224,10 +224,12 @@ function shellInitTargets() {
224
224
  shell.endsWith("/bash") ||
225
225
  process.env.BASH_VERSION ||
226
226
  existsSync(resolve(home, ".bashrc")) ||
227
- existsSync(resolve(home, ".bash_profile"))
227
+ existsSync(resolve(home, ".bash_profile")) ||
228
+ existsSync(resolve(home, ".profile"))
228
229
  ) {
229
230
  add(resolve(home, ".bashrc"));
230
231
  add(resolve(home, ".bash_profile"));
232
+ add(resolve(home, ".profile"));
231
233
  }
232
234
 
233
235
  if (targets.length === 0) {
@@ -280,6 +282,7 @@ function installPersistentCommand(specifier = readPackageVersion()) {
280
282
  kv("Command", "silicaclaw");
281
283
  console.log("");
282
284
  kv("Activate", `source "${envFile}"`);
285
+ kv("Current shell", "run Activate now if `silicaclaw` is not found yet");
283
286
  if (configuredFiles.length > 0) {
284
287
  kv("Startup", "configured");
285
288
  } else {
@@ -74,6 +74,11 @@ function readJson(file) {
74
74
  }
75
75
  }
76
76
 
77
+ function readPackageVersionFrom(dir) {
78
+ const pkg = readJson(resolve(dir, "package.json"));
79
+ return String(pkg?.version || "latest").trim() || "latest";
80
+ }
81
+
77
82
  function isSilicaClawDir(dir) {
78
83
  const pkgPath = join(dir, "package.json");
79
84
  if (!existsSync(pkgPath)) return false;
@@ -144,6 +149,107 @@ function ensureStateDir() {
144
149
  mkdirSync(STATE_DIR, { recursive: true });
145
150
  }
146
151
 
152
+ function userShimDir() {
153
+ return join(homedir(), ".silicaclaw", "bin");
154
+ }
155
+
156
+ function userShimPath() {
157
+ return join(userShimDir(), "silicaclaw");
158
+ }
159
+
160
+ function userEnvFile() {
161
+ return join(homedir(), ".silicaclaw", "env.sh");
162
+ }
163
+
164
+ function userNpmCacheDir() {
165
+ return join(homedir(), ".silicaclaw", "npm-cache");
166
+ }
167
+
168
+ function preferredShellRcFile() {
169
+ const shell = String(process.env.SHELL || "");
170
+ if (shell.endsWith("/zsh")) return resolve(homedir(), ".zshrc");
171
+ if (shell.endsWith("/bash")) return resolve(homedir(), ".bashrc");
172
+ if (process.env.ZSH_VERSION) return resolve(homedir(), ".zshrc");
173
+ return resolve(homedir(), ".bashrc");
174
+ }
175
+
176
+ function shellInitTargets() {
177
+ const home = homedir();
178
+ const shell = String(process.env.SHELL || "");
179
+ const targets = [];
180
+ const add = (filePath) => {
181
+ if (!targets.includes(filePath)) targets.push(filePath);
182
+ };
183
+
184
+ if (shell.endsWith("/zsh") || process.env.ZSH_VERSION || existsSync(resolve(home, ".zshrc"))) {
185
+ add(resolve(home, ".zshrc"));
186
+ }
187
+
188
+ if (
189
+ shell.endsWith("/bash") ||
190
+ process.env.BASH_VERSION ||
191
+ existsSync(resolve(home, ".bashrc")) ||
192
+ existsSync(resolve(home, ".bash_profile")) ||
193
+ existsSync(resolve(home, ".profile"))
194
+ ) {
195
+ add(resolve(home, ".bashrc"));
196
+ add(resolve(home, ".bash_profile"));
197
+ add(resolve(home, ".profile"));
198
+ }
199
+
200
+ if (targets.length === 0) {
201
+ add(preferredShellRcFile());
202
+ }
203
+ return targets;
204
+ }
205
+
206
+ function ensureLineInFile(filePath, block) {
207
+ try {
208
+ const current = existsSync(filePath) ? readFileSync(filePath, "utf8") : "";
209
+ if (current.includes(block.trim())) return true;
210
+ const next = `${current.replace(/\s*$/, "")}\n\n${block}\n`;
211
+ mkdirSync(dirname(filePath), { recursive: true });
212
+ writeFileSync(filePath, next, "utf8");
213
+ return true;
214
+ } catch {
215
+ return false;
216
+ }
217
+ }
218
+
219
+ function shimScriptText(specifier = "latest") {
220
+ return [
221
+ "#!/usr/bin/env bash",
222
+ "set -euo pipefail",
223
+ 'export npm_config_cache="${npm_config_cache:-$HOME/.silicaclaw/npm-cache}"',
224
+ `exec npx -y @silicaclaw/cli@${specifier} "$@"`,
225
+ "",
226
+ ].join("\n");
227
+ }
228
+
229
+ function ensureUserCommandInstalled() {
230
+ const version = readPackageVersionFrom(APP_DIR);
231
+ const envBlock = [
232
+ "#!/usr/bin/env bash",
233
+ 'export PATH="$HOME/.silicaclaw/bin:$PATH"',
234
+ 'export npm_config_cache="$HOME/.silicaclaw/npm-cache"',
235
+ "",
236
+ ].join("\n");
237
+ const rcBlock = [
238
+ "# >>> silicaclaw >>>",
239
+ '[ -f "$HOME/.silicaclaw/env.sh" ] && . "$HOME/.silicaclaw/env.sh"',
240
+ "# <<< silicaclaw <<<",
241
+ ].join("\n");
242
+
243
+ mkdirSync(userShimDir(), { recursive: true });
244
+ mkdirSync(userNpmCacheDir(), { recursive: true });
245
+ writeFileSync(userEnvFile(), envBlock, { encoding: "utf8", mode: 0o755 });
246
+ writeFileSync(userShimPath(), shimScriptText(version), { encoding: "utf8", mode: 0o755 });
247
+
248
+ for (const filePath of shellInitTargets()) {
249
+ ensureLineInFile(filePath, rcBlock);
250
+ }
251
+ }
252
+
147
253
  function ensureLaunchAgentsDir() {
148
254
  mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });
149
255
  }
@@ -797,6 +903,7 @@ async function stopAll() {
797
903
 
798
904
  async function startAll() {
799
905
  ensureStateDir();
906
+ ensureUserCommandInstalled();
800
907
 
801
908
  const mode = parseMode(parseFlag("mode", process.env.NETWORK_MODE || DEFAULT_NETWORK_MODE));
802
909
  const adapter = adapterForMode(mode);
@@ -923,6 +1030,7 @@ async function startAll() {
923
1030
  }
924
1031
 
925
1032
  async function restartAll() {
1033
+ ensureUserCommandInstalled();
926
1034
  if (!isLaunchdPlatform()) {
927
1035
  await stopAll();
928
1036
  await startAll();
@@ -100,6 +100,25 @@ function main() {
100
100
  assert(dialogueCheatsheetZh.includes("主人对话速查表"), "owner dialogue cheatsheet zh content mismatch");
101
101
  },
102
102
  },
103
+ "silicaclaw-network-config": {
104
+ requiredFiles: [
105
+ resolve(skillDir, "references", "network-modes.md"),
106
+ resolve(skillDir, "references", "public-discovery.md"),
107
+ resolve(skillDir, "references", "owner-dialogue-cheatsheet-zh.md"),
108
+ ],
109
+ uiDisplayName: 'display_name: "SilicaClaw Network Config"',
110
+ checks(manifest, files) {
111
+ const networkModes = readFileSync(files[0], "utf8");
112
+ const publicDiscovery = readFileSync(files[1], "utf8");
113
+ const dialogueCheatsheetZh = readFileSync(files[2], "utf8");
114
+ assert(String(manifest.references?.network_modes || "") === "references/network-modes.md", "manifest network modes reference mismatch");
115
+ assert(String(manifest.references?.public_discovery || "") === "references/public-discovery.md", "manifest public discovery reference mismatch");
116
+ assert(String(manifest.references?.owner_dialogue_cheatsheet_zh || "") === "references/owner-dialogue-cheatsheet-zh.md", "manifest owner dialogue cheatsheet zh reference mismatch");
117
+ assert(networkModes.includes("## Modes"), "network modes content mismatch");
118
+ assert(publicDiscovery.includes("public_enabled"), "public discovery content mismatch");
119
+ assert(dialogueCheatsheetZh.includes("主人对话速查表"), "owner dialogue cheatsheet zh content mismatch");
120
+ },
121
+ },
103
122
  }[skillName];
104
123
 
105
124
  assert(profile, `Unsupported skill for validation: ${skillName}`);