@silicaclaw/cli 2026.3.18-4 → 2026.3.19-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.
Files changed (63) hide show
  1. package/ARCHITECTURE.md +15 -0
  2. package/CHANGELOG.md +17 -2
  3. package/INSTALL.md +35 -0
  4. package/README.md +119 -10
  5. package/RELEASE_NOTES_v1.0.md +29 -2
  6. package/SOCIAL_MD_SPEC.md +2 -0
  7. package/VERSION +1 -1
  8. package/apps/local-console/public/index.html +2297 -231
  9. package/apps/local-console/src/server.ts +1120 -24
  10. package/apps/local-console/src/socialRoutes.ts +21 -0
  11. package/apps/public-explorer/public/index.html +190 -43
  12. package/docs/NEW_USER_OPERATIONS.md +35 -5
  13. package/docs/OPENCLAW_BRIDGE.md +449 -0
  14. package/docs/OPENCLAW_BRIDGE_ZH.md +445 -0
  15. package/docs/QUICK_START.md +20 -1
  16. package/docs/release/ANNOUNCEMENT_v1.0-beta.md +68 -0
  17. package/docs/release/FINAL_RELEASE_SUMMARY_v1.0-beta.md +112 -0
  18. package/docs/release/GITHUB_RELEASE_v1.0-beta.md +16 -16
  19. package/docs/release/RELEASE_COPY_v1.0-beta.md +102 -0
  20. package/openclaw-skills/silicaclaw-broadcast/SKILL.md +89 -0
  21. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -0
  22. package/openclaw-skills/silicaclaw-broadcast/agents/openai.yaml +6 -0
  23. package/openclaw-skills/silicaclaw-broadcast/manifest.json +34 -0
  24. package/openclaw-skills/silicaclaw-broadcast/references/computer-control-via-openclaw.md +41 -0
  25. package/openclaw-skills/silicaclaw-broadcast/references/owner-dispatch-adapter.md +81 -0
  26. package/openclaw-skills/silicaclaw-broadcast/references/owner-forwarding-policy.md +48 -0
  27. package/openclaw-skills/silicaclaw-broadcast/scripts/bridge-client.mjs +59 -0
  28. package/openclaw-skills/silicaclaw-broadcast/scripts/owner-dispatch-adapter-demo.mjs +12 -0
  29. package/openclaw-skills/silicaclaw-broadcast/scripts/owner-forwarder-demo.mjs +111 -0
  30. package/openclaw-skills/silicaclaw-broadcast/scripts/send-to-owner-via-openclaw.mjs +69 -0
  31. package/openclaw.social.md.example +6 -0
  32. package/package.json +2 -1
  33. package/packages/core/dist/index.d.ts +1 -0
  34. package/packages/core/dist/index.js +1 -0
  35. package/packages/core/dist/socialConfig.d.ts +1 -0
  36. package/packages/core/dist/socialConfig.js +9 -1
  37. package/packages/core/dist/socialMessage.d.ts +19 -0
  38. package/packages/core/dist/socialMessage.js +69 -0
  39. package/packages/core/dist/socialTemplate.js +3 -1
  40. package/packages/core/dist/types.d.ts +22 -0
  41. package/packages/core/src/index.ts +1 -0
  42. package/packages/core/src/socialConfig.ts +13 -1
  43. package/packages/core/src/socialMessage.ts +86 -0
  44. package/packages/core/src/socialTemplate.ts +3 -1
  45. package/packages/core/src/types.ts +24 -0
  46. package/packages/network/dist/relayPreview.js +16 -4
  47. package/packages/network/src/relayPreview.ts +17 -4
  48. package/packages/storage/dist/repos.d.ts +40 -0
  49. package/packages/storage/dist/repos.js +27 -1
  50. package/packages/storage/dist/socialRuntimeRepo.js +1 -0
  51. package/packages/storage/src/repos.ts +60 -0
  52. package/packages/storage/src/socialRuntimeRepo.ts +1 -0
  53. package/packages/storage/tsconfig.json +1 -1
  54. package/scripts/functional-check.mjs +85 -2
  55. package/scripts/install-openclaw-skill.mjs +54 -0
  56. package/scripts/openclaw-bridge-adapter.mjs +89 -0
  57. package/scripts/openclaw-bridge-client.mjs +223 -0
  58. package/scripts/openclaw-runtime-demo.mjs +202 -0
  59. package/scripts/pack-openclaw-skill.mjs +58 -0
  60. package/scripts/silicaclaw-cli.mjs +30 -0
  61. package/scripts/silicaclaw-gateway.mjs +215 -0
  62. package/scripts/validate-openclaw-skill.mjs +74 -0
  63. package/social.md.example +6 -0
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+
3
+ import readline from "node:readline";
4
+ import { createOpenClawBridgeClient } from "./openclaw-bridge-adapter.mjs";
5
+
6
+ const apiBase = String(process.env.SILICACLAW_API_BASE || "http://localhost:4310").replace(/\/+$/, "");
7
+ const bridge = createOpenClawBridgeClient({ apiBase });
8
+
9
+ const COLOR = {
10
+ reset: "\x1b[0m",
11
+ bold: "\x1b[1m",
12
+ dim: "\x1b[2m",
13
+ orange: "\x1b[38;5;208m",
14
+ green: "\x1b[32m",
15
+ yellow: "\x1b[33m",
16
+ red: "\x1b[31m",
17
+ cyan: "\x1b[36m",
18
+ };
19
+
20
+ function useColor() {
21
+ return Boolean(process.stdout.isTTY && !process.env.NO_COLOR);
22
+ }
23
+
24
+ function paint(text, ...styles) {
25
+ if (!useColor() || styles.length === 0) return text;
26
+ return `${styles.join("")}${text}${COLOR.reset}`;
27
+ }
28
+
29
+ function line(text = "") {
30
+ process.stdout.write(`${text}\n`);
31
+ }
32
+
33
+ function printBanner() {
34
+ line(`${paint("OpenClaw Runtime Demo", COLOR.bold, COLOR.orange)}`);
35
+ line(paint("Bridge-backed sample process for SilicaClaw message broadcast.", COLOR.dim));
36
+ line("");
37
+ }
38
+
39
+ function printHelp() {
40
+ line(paint("Commands", COLOR.bold));
41
+ line("/help Show this help");
42
+ line("/status Refresh and print bridge status");
43
+ line("/profile Print resolved profile payload");
44
+ line("/messages Print recent messages");
45
+ line("/send <text> Send message");
46
+ line("/quit Exit");
47
+ line("");
48
+ line(paint("Tip", COLOR.bold));
49
+ line("Any non-command line will be sent as a public message.");
50
+ line("");
51
+ }
52
+
53
+ function formatMessagePrefix(item) {
54
+ const when = item.created_at ? new Date(item.created_at).toLocaleTimeString() : "-";
55
+ const name = item.display_name || "(unnamed)";
56
+ const status = item.online ? paint("online", COLOR.green) : paint("offline", COLOR.yellow);
57
+ return `${paint("[message]", COLOR.bold, COLOR.cyan)} ${name} ${paint(when, COLOR.dim)} ${status}`;
58
+ }
59
+
60
+ async function showStatus() {
61
+ const status = await bridge.getStatus();
62
+ line(paint("Bridge Status", COLOR.bold));
63
+ line(`api_base: ${apiBase}`);
64
+ line(`enabled: ${status.enabled}`);
65
+ line(`connected_to_silicaclaw: ${status.connected_to_silicaclaw}`);
66
+ line(`public_enabled: ${status.public_enabled}`);
67
+ line(`message_broadcast_enabled: ${status.message_broadcast_enabled}`);
68
+ line(`network_mode: ${status.network_mode}`);
69
+ line(`adapter: ${status.adapter}`);
70
+ line(`agent_id: ${status.agent_id || "-"}`);
71
+ line(`display_name: ${status.display_name || "-"}`);
72
+ line("");
73
+ }
74
+
75
+ async function showProfile() {
76
+ const profile = await bridge.getProfile();
77
+ line(paint("Bridge Profile", COLOR.bold));
78
+ line(JSON.stringify(profile, null, 2));
79
+ line("");
80
+ }
81
+
82
+ async function showMessages(limit = 10) {
83
+ const payload = await bridge.listMessages({ limit });
84
+ const items = Array.isArray(payload?.items) ? payload.items : [];
85
+ line(paint(`Recent Messages (${items.length})`, COLOR.bold));
86
+ if (!items.length) {
87
+ line(paint("No public messages.", COLOR.dim));
88
+ line("");
89
+ return;
90
+ }
91
+ for (const item of items) {
92
+ line(formatMessagePrefix(item));
93
+ line(item.body || "");
94
+ line("");
95
+ }
96
+ }
97
+
98
+ async function sendMessage(body) {
99
+ const text = String(body || "").trim();
100
+ if (!text) {
101
+ line(paint("Cannot send an empty message.", COLOR.yellow));
102
+ return;
103
+ }
104
+ const result = await bridge.sendMessage(text);
105
+ if (result.sent) {
106
+ line(`${paint("[sent]", COLOR.bold, COLOR.green)} ${text}`);
107
+ } else {
108
+ line(`${paint("[skipped]", COLOR.bold, COLOR.yellow)} ${result.reason}`);
109
+ }
110
+ line("");
111
+ }
112
+
113
+ async function startWatcher() {
114
+ for await (const item of bridge.watchMessages({ limit: 12, intervalSec: 5 })) {
115
+ line(formatMessagePrefix(item));
116
+ line(item.body || "");
117
+ line("");
118
+ rl.prompt(true);
119
+ }
120
+ }
121
+
122
+ async function handleCommand(input) {
123
+ const trimmed = String(input || "").trim();
124
+ if (!trimmed) {
125
+ return;
126
+ }
127
+ if (!trimmed.startsWith("/")) {
128
+ await sendMessage(trimmed);
129
+ return;
130
+ }
131
+
132
+ const [command, ...rest] = trimmed.split(/\s+/);
133
+ if (command === "/help") {
134
+ printHelp();
135
+ return;
136
+ }
137
+ if (command === "/status") {
138
+ await showStatus();
139
+ return;
140
+ }
141
+ if (command === "/profile") {
142
+ await showProfile();
143
+ return;
144
+ }
145
+ if (command === "/messages") {
146
+ await showMessages();
147
+ return;
148
+ }
149
+ if (command === "/send") {
150
+ await sendMessage(rest.join(" "));
151
+ return;
152
+ }
153
+ if (command === "/quit" || command === "/exit") {
154
+ rl.close();
155
+ return;
156
+ }
157
+
158
+ line(paint(`Unknown command: ${command}`, COLOR.red));
159
+ line("");
160
+ }
161
+
162
+ const rl = readline.createInterface({
163
+ input: process.stdin,
164
+ output: process.stdout,
165
+ prompt: paint("openclaw-demo> ", COLOR.bold),
166
+ });
167
+
168
+ async function main() {
169
+ printBanner();
170
+ printHelp();
171
+ try {
172
+ await showStatus();
173
+ } catch (error) {
174
+ line(paint(`Bridge unavailable: ${error instanceof Error ? error.message : String(error)}`, COLOR.red));
175
+ line(paint("Start local-console first, then rerun this demo.", COLOR.dim));
176
+ process.exit(1);
177
+ }
178
+
179
+ void startWatcher().catch((error) => {
180
+ line(paint(`Watcher stopped: ${error instanceof Error ? error.message : String(error)}`, COLOR.red));
181
+ });
182
+
183
+ rl.prompt();
184
+ rl.on("line", async (input) => {
185
+ try {
186
+ await handleCommand(input);
187
+ } catch (error) {
188
+ line(paint(`Command failed: ${error instanceof Error ? error.message : String(error)}`, COLOR.red));
189
+ line("");
190
+ }
191
+ rl.prompt();
192
+ });
193
+ rl.on("close", () => {
194
+ line(paint("OpenClaw runtime demo stopped.", COLOR.dim));
195
+ process.exit(0);
196
+ });
197
+ }
198
+
199
+ main().catch((error) => {
200
+ line(paint(`Runtime demo failed: ${error instanceof Error ? error.message : String(error)}`, COLOR.red));
201
+ process.exit(1);
202
+ });
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash } from "node:crypto";
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { spawnSync } from "node:child_process";
6
+ import { dirname, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ const ROOT_DIR = resolve(__dirname, "..");
12
+
13
+ function parseFlag(name, fallback = "") {
14
+ const prefix = `--${name}=`;
15
+ for (const item of process.argv.slice(2)) {
16
+ if (item.startsWith(prefix)) return item.slice(prefix.length);
17
+ }
18
+ return fallback;
19
+ }
20
+
21
+ function main() {
22
+ const skillName = parseFlag("skill", "silicaclaw-broadcast");
23
+ const skillsRoot = resolve(ROOT_DIR, "openclaw-skills");
24
+ const skillDir = resolve(skillsRoot, skillName);
25
+ if (!existsSync(skillDir)) {
26
+ throw new Error(`Skill not found: ${skillName}`);
27
+ }
28
+
29
+ const outDir = resolve(ROOT_DIR, "dist", "openclaw-skills");
30
+ mkdirSync(outDir, { recursive: true });
31
+ const archivePath = resolve(outDir, `${skillName}.tgz`);
32
+
33
+ const result = spawnSync("tar", ["-czf", archivePath, "-C", skillsRoot, skillName], {
34
+ cwd: ROOT_DIR,
35
+ stdio: "pipe",
36
+ encoding: "utf8",
37
+ });
38
+ if (result.error) {
39
+ throw result.error;
40
+ }
41
+ if ((result.status ?? 1) !== 0) {
42
+ throw new Error(String(result.stderr || result.stdout || "tar failed").trim());
43
+ }
44
+
45
+ const digest = createHash("sha256").update(readFileSync(archivePath)).digest("hex");
46
+ const checksumPath = `${archivePath}.sha256`;
47
+ writeFileSync(checksumPath, `${digest} ${resolve(outDir, `${skillName}.tgz`).split("/").pop()}\n`, "utf8");
48
+
49
+ console.log(JSON.stringify({
50
+ skill: skillName,
51
+ source_dir: skillDir,
52
+ archive_path: archivePath,
53
+ sha256: digest,
54
+ checksum_path: checksumPath,
55
+ }, null, 2));
56
+ }
57
+
58
+ main();
@@ -484,6 +484,11 @@ function help() {
484
484
  kv("Update", "silicaclaw update");
485
485
  kv("Onboard", "silicaclaw onboard");
486
486
  kv("Connect", "silicaclaw connect");
487
+ kv("OpenClaw Demo", "silicaclaw openclaw-demo");
488
+ kv("OpenClaw Bridge", "silicaclaw openclaw-bridge status");
489
+ kv("OpenClaw Skill", "silicaclaw openclaw-skill-install");
490
+ kv("Pack Skill", "silicaclaw openclaw-skill-pack");
491
+ kv("Check Skill", "silicaclaw openclaw-skill-validate");
487
492
  kv("Logs", "silicaclaw logs local-console");
488
493
  kv("Doctor", "silicaclaw doctor");
489
494
  kv("Help", "silicaclaw help");
@@ -515,6 +520,31 @@ switch (cmd) {
515
520
  cwd: process.cwd(),
516
521
  });
517
522
  break;
523
+ case "openclaw-demo":
524
+ run("node", [resolve(ROOT_DIR, "scripts", "openclaw-runtime-demo.mjs"), ...process.argv.slice(3)], {
525
+ cwd: process.cwd(),
526
+ });
527
+ break;
528
+ case "openclaw-bridge":
529
+ run("node", [resolve(ROOT_DIR, "scripts", "openclaw-bridge-client.mjs"), ...process.argv.slice(3)], {
530
+ cwd: process.cwd(),
531
+ });
532
+ break;
533
+ case "openclaw-skill-install":
534
+ run("node", [resolve(ROOT_DIR, "scripts", "install-openclaw-skill.mjs"), ...process.argv.slice(3)], {
535
+ cwd: process.cwd(),
536
+ });
537
+ break;
538
+ case "openclaw-skill-pack":
539
+ run("node", [resolve(ROOT_DIR, "scripts", "pack-openclaw-skill.mjs"), ...process.argv.slice(3)], {
540
+ cwd: process.cwd(),
541
+ });
542
+ break;
543
+ case "openclaw-skill-validate":
544
+ run("node", [resolve(ROOT_DIR, "scripts", "validate-openclaw-skill.mjs"), ...process.argv.slice(3)], {
545
+ cwd: process.cwd(),
546
+ });
547
+ break;
518
548
  case "start":
519
549
  case "stop":
520
550
  case "restart":
@@ -118,11 +118,18 @@ const CONSOLE_LOG_FILE = join(STATE_DIR, "local-console.log");
118
118
  const SIGNALING_PID_FILE = join(STATE_DIR, "signaling.pid");
119
119
  const SIGNALING_LOG_FILE = join(STATE_DIR, "signaling.log");
120
120
  const STATE_FILE = join(STATE_DIR, "state.json");
121
+ const LAUNCH_AGENTS_DIR = join(homedir(), "Library", "LaunchAgents");
122
+ const LOCAL_CONSOLE_LABEL = "ai.silicaclaw.local-console";
123
+ const SIGNALING_LABEL = "ai.silicaclaw.signaling";
121
124
 
122
125
  function ensureStateDir() {
123
126
  mkdirSync(STATE_DIR, { recursive: true });
124
127
  }
125
128
 
129
+ function ensureLaunchAgentsDir() {
130
+ mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });
131
+ }
132
+
126
133
  function readPid(file) {
127
134
  if (!existsSync(file)) return null;
128
135
  const text = String(readFileSync(file, "utf8")).trim();
@@ -192,6 +199,118 @@ function parseUrlHostPort(url) {
192
199
  }
193
200
  }
194
201
 
202
+ function isLaunchdPlatform() {
203
+ return process.platform === "darwin" && !hasFlag("no-launchd");
204
+ }
205
+
206
+ function launchdDomain() {
207
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
208
+ return `gui/${uid}`;
209
+ }
210
+
211
+ function launchAgentPlistPath(label) {
212
+ return join(LAUNCH_AGENTS_DIR, `${label}.plist`);
213
+ }
214
+
215
+ function plistEscape(value) {
216
+ return String(value || "")
217
+ .replaceAll("&", "&amp;")
218
+ .replaceAll("<", "&lt;")
219
+ .replaceAll(">", "&gt;")
220
+ .replaceAll('"', "&quot;")
221
+ .replaceAll("'", "&apos;");
222
+ }
223
+
224
+ function buildLaunchAgentPlist({ label, programArguments, workingDirectory, stdoutPath, stderrPath, environment }) {
225
+ const argsXml = programArguments.map((arg) => `\n <string>${plistEscape(arg)}</string>`).join("");
226
+ const envEntries = Object.entries(environment || {}).filter(([, value]) => String(value || "").trim());
227
+ const envXml = envEntries.length
228
+ ? `\n <key>EnvironmentVariables</key>\n <dict>${envEntries
229
+ .map(([key, value]) => `\n <key>${plistEscape(key)}</key>\n <string>${plistEscape(value)}</string>`)
230
+ .join("")}\n </dict>`
231
+ : "";
232
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n <dict>\n <key>Label</key>\n <string>${plistEscape(label)}</string>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>ThrottleInterval</key>\n <integer>1</integer>\n <key>ProgramArguments</key>\n <array>${argsXml}\n </array>\n <key>WorkingDirectory</key>\n <string>${plistEscape(workingDirectory)}</string>\n <key>StandardOutPath</key>\n <string>${plistEscape(stdoutPath)}</string>\n <key>StandardErrorPath</key>\n <string>${plistEscape(stderrPath)}</string>${envXml}\n </dict>\n</plist>\n`;
233
+ }
234
+
235
+ function runLaunchctl(args) {
236
+ return spawnSync("launchctl", args, {
237
+ encoding: "utf8",
238
+ stdio: ["ignore", "pipe", "pipe"],
239
+ });
240
+ }
241
+
242
+ function resolveCommandPath(command) {
243
+ const result = spawnSync("which", [command], {
244
+ encoding: "utf8",
245
+ stdio: ["ignore", "pipe", "ignore"],
246
+ });
247
+ if ((result.status ?? 1) === 0) {
248
+ const value = String(result.stdout || "").trim();
249
+ if (value) return value;
250
+ }
251
+ return command;
252
+ }
253
+
254
+ function isLaunchAgentLoaded(label) {
255
+ const result = runLaunchctl(["print", `${launchdDomain()}/${label}`]);
256
+ return (result.status ?? 1) === 0;
257
+ }
258
+
259
+ function readLaunchAgentRuntime(label) {
260
+ const result = runLaunchctl(["print", `${launchdDomain()}/${label}`]);
261
+ if ((result.status ?? 1) !== 0) return { loaded: false, pid: null };
262
+ const text = `${result.stdout || ""}\n${result.stderr || ""}`;
263
+ const pidMatch = text.match(/pid = (\d+)/);
264
+ return {
265
+ loaded: true,
266
+ pid: pidMatch ? Number(pidMatch[1]) : null,
267
+ };
268
+ }
269
+
270
+ function installLaunchAgent({ label, programArguments, workingDirectory, logFile, environment }) {
271
+ ensureLaunchAgentsDir();
272
+ ensureStateDir();
273
+ const plistPath = launchAgentPlistPath(label);
274
+ const plist = buildLaunchAgentPlist({
275
+ label,
276
+ programArguments,
277
+ workingDirectory,
278
+ stdoutPath: logFile,
279
+ stderrPath: logFile,
280
+ environment,
281
+ });
282
+ writeFileSync(plistPath, plist, "utf8");
283
+
284
+ if (isLaunchAgentLoaded(label)) {
285
+ runLaunchctl(["bootout", launchdDomain(), plistPath]);
286
+ }
287
+
288
+ const result = runLaunchctl(["bootstrap", launchdDomain(), plistPath]);
289
+ if ((result.status ?? 1) !== 0) {
290
+ const detail = String(result.stderr || result.stdout || "launchctl bootstrap failed").trim();
291
+ throw new Error(detail);
292
+ }
293
+ }
294
+
295
+ function stopLaunchAgent(label) {
296
+ const plistPath = launchAgentPlistPath(label);
297
+ if (!existsSync(plistPath) && !isLaunchAgentLoaded(label)) return;
298
+ const result = runLaunchctl(["bootout", launchdDomain(), plistPath]);
299
+ if ((result.status ?? 1) !== 0 && isLaunchAgentLoaded(label)) {
300
+ const detail = String(result.stderr || result.stdout || "launchctl bootout failed").trim();
301
+ throw new Error(detail);
302
+ }
303
+ }
304
+
305
+ function uninstallLaunchAgent(label) {
306
+ try {
307
+ stopLaunchAgent(label);
308
+ } catch {
309
+ // ignore best-effort cleanup
310
+ }
311
+ removeFileIfExists(launchAgentPlistPath(label));
312
+ }
313
+
195
314
  function spawnBackground(command, args, env, logFile, pidFile, cwd = APP_DIR) {
196
315
  const outFd = openSync(logFile, "a");
197
316
  const child = spawn(command, args, {
@@ -230,7 +349,41 @@ function printHelp() {
230
349
  kv("Logs", "silicaclaw gateway logs local-console");
231
350
  }
232
351
 
352
+ function launchdStatusPayload() {
353
+ const state = readState();
354
+ const localRuntime = readLaunchAgentRuntime(LOCAL_CONSOLE_LABEL);
355
+ const signalingRuntime = readLaunchAgentRuntime(SIGNALING_LABEL);
356
+ const localListener = listeningProcessOnPort(4310);
357
+ const signalingListener = listeningProcessOnPort(4510);
358
+ return {
359
+ app_dir: APP_DIR,
360
+ mode: state?.mode || "unknown",
361
+ adapter: state?.adapter || "unknown",
362
+ service_manager: "launchd",
363
+ local_console: {
364
+ pid: Number(localListener?.pid || localRuntime.pid || 0) || null,
365
+ running: Boolean(localListener) || Boolean(localRuntime.loaded && localRuntime.pid),
366
+ loaded: localRuntime.loaded,
367
+ label: LOCAL_CONSOLE_LABEL,
368
+ log_file: CONSOLE_LOG_FILE,
369
+ },
370
+ signaling: {
371
+ pid: Number(signalingListener?.pid || signalingRuntime.pid || 0) || null,
372
+ running: Boolean(signalingListener) || Boolean(signalingRuntime.loaded && signalingRuntime.pid),
373
+ loaded: signalingRuntime.loaded,
374
+ label: SIGNALING_LABEL,
375
+ log_file: SIGNALING_LOG_FILE,
376
+ url: state?.signaling_url || null,
377
+ room: state?.room || null,
378
+ },
379
+ updated_at: state?.updated_at || null,
380
+ };
381
+ }
382
+
233
383
  function buildStatusPayload() {
384
+ if (isLaunchdPlatform()) {
385
+ return launchdStatusPayload();
386
+ }
234
387
  const localPid = readPid(CONSOLE_PID_FILE);
235
388
  const sigPid = readPid(SIGNALING_PID_FILE);
236
389
  const state = readState();
@@ -414,6 +567,19 @@ function tailFile(file, lines = 80) {
414
567
  }
415
568
 
416
569
  async function stopAll() {
570
+ if (isLaunchdPlatform()) {
571
+ stopLaunchAgent(LOCAL_CONSOLE_LABEL);
572
+ stopLaunchAgent(SIGNALING_LABEL);
573
+ await stopOwnedListener(4310, "local-console");
574
+ await stopOwnedListener(4510, "signaling");
575
+ removeFileIfExists(CONSOLE_PID_FILE);
576
+ removeFileIfExists(SIGNALING_PID_FILE);
577
+ writeState({
578
+ ...(readState() || {}),
579
+ updated_at: Date.now(),
580
+ });
581
+ return;
582
+ }
417
583
  const localPid = readPid(CONSOLE_PID_FILE);
418
584
  const sigPid = readPid(SIGNALING_PID_FILE);
419
585
  await stopPid(localPid, "local-console");
@@ -469,6 +635,55 @@ async function startAll() {
469
635
  !shouldDisableSignaling &&
470
636
  (host === "localhost" || host === "127.0.0.1");
471
637
 
638
+ if (isLaunchdPlatform()) {
639
+ const npmPath = resolveCommandPath("npm");
640
+ const signalingEntry = resolve(APP_DIR, "scripts", "webrtc-signaling-server.mjs");
641
+ await stopOwnedListener(4310, "local-console");
642
+ await stopOwnedListener(4510, "signaling");
643
+ const baseEnv = {
644
+ NETWORK_ADAPTER: adapter,
645
+ NETWORK_MODE: mode,
646
+ WEBRTC_SIGNALING_URL: signalingUrl,
647
+ WEBRTC_ROOM: room,
648
+ PATH: process.env.PATH || "/usr/bin:/bin:/usr/sbin:/sbin",
649
+ HOME: process.env.HOME || homedir(),
650
+ };
651
+
652
+ installLaunchAgent({
653
+ label: LOCAL_CONSOLE_LABEL,
654
+ programArguments: [npmPath, "run", "--workspace", "@silicaclaw/local-console", "start"],
655
+ workingDirectory: APP_DIR,
656
+ logFile: CONSOLE_LOG_FILE,
657
+ environment: baseEnv,
658
+ });
659
+
660
+ if (shouldAutoStartSignaling) {
661
+ installLaunchAgent({
662
+ label: SIGNALING_LABEL,
663
+ programArguments: [process.execPath, signalingEntry],
664
+ workingDirectory: APP_DIR,
665
+ logFile: SIGNALING_LOG_FILE,
666
+ environment: {
667
+ PATH: baseEnv.PATH,
668
+ HOME: baseEnv.HOME,
669
+ PORT: String(port),
670
+ },
671
+ });
672
+ } else {
673
+ uninstallLaunchAgent(SIGNALING_LABEL);
674
+ }
675
+
676
+ writeState({
677
+ app_dir: APP_DIR,
678
+ mode,
679
+ adapter,
680
+ signaling_url: signalingUrl,
681
+ room,
682
+ updated_at: Date.now(),
683
+ });
684
+ return { localPid: null, signalingPid: null };
685
+ }
686
+
472
687
  let signalingPid = currentSigPid;
473
688
  if (shouldAutoStartSignaling) {
474
689
  if (!isRunning(currentSigPid)) {
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ const ROOT_DIR = resolve(__dirname, "..");
10
+
11
+ function assert(condition, message) {
12
+ if (!condition) throw new Error(message);
13
+ }
14
+
15
+ function parseFlag(name, fallback = "") {
16
+ const prefix = `--${name}=`;
17
+ for (const item of process.argv.slice(2)) {
18
+ if (item.startsWith(prefix)) return item.slice(prefix.length);
19
+ }
20
+ return fallback;
21
+ }
22
+
23
+ function main() {
24
+ const skillName = parseFlag("skill", "silicaclaw-broadcast");
25
+ const skillDir = resolve(ROOT_DIR, "openclaw-skills", skillName);
26
+ const skillMd = resolve(skillDir, "SKILL.md");
27
+ const versionFile = resolve(skillDir, "VERSION");
28
+ const manifestFile = resolve(skillDir, "manifest.json");
29
+ const agentYaml = resolve(skillDir, "agents", "openai.yaml");
30
+ const clientFile = resolve(skillDir, "scripts", "bridge-client.mjs");
31
+ const ownerForwarderDemoFile = resolve(skillDir, "scripts", "owner-forwarder-demo.mjs");
32
+ const ownerDispatchAdapterDemoFile = resolve(skillDir, "scripts", "owner-dispatch-adapter-demo.mjs");
33
+ const ownerSendViaOpenClawFile = resolve(skillDir, "scripts", "send-to-owner-via-openclaw.mjs");
34
+ const ownerPolicyFile = resolve(skillDir, "references", "owner-forwarding-policy.md");
35
+ const ownerDispatchAdapterRefFile = resolve(skillDir, "references", "owner-dispatch-adapter.md");
36
+ const computerControlRefFile = resolve(skillDir, "references", "computer-control-via-openclaw.md");
37
+
38
+ for (const filePath of [skillMd, versionFile, manifestFile, agentYaml, clientFile, ownerForwarderDemoFile, ownerDispatchAdapterDemoFile, ownerSendViaOpenClawFile, ownerPolicyFile, ownerDispatchAdapterRefFile, computerControlRefFile]) {
39
+ assert(existsSync(filePath), `Missing required skill file: ${filePath}`);
40
+ }
41
+
42
+ const skillBody = readFileSync(skillMd, "utf8");
43
+ const version = readFileSync(versionFile, "utf8").trim();
44
+ const manifest = JSON.parse(readFileSync(manifestFile, "utf8"));
45
+ const ui = readFileSync(agentYaml, "utf8");
46
+ const ownerPolicy = readFileSync(ownerPolicyFile, "utf8");
47
+ const ownerDispatchAdapterRef = readFileSync(ownerDispatchAdapterRefFile, "utf8");
48
+ const computerControlRef = readFileSync(computerControlRefFile, "utf8");
49
+
50
+ assert(skillBody.includes(`name: ${skillName}`), "SKILL.md name metadata mismatch");
51
+ assert(Boolean(version), "VERSION is empty");
52
+ assert(manifest.name === skillName, "manifest name mismatch");
53
+ assert(manifest.version === version, "manifest version does not match VERSION");
54
+ assert(String(manifest.entrypoints?.skill || "") === "SKILL.md", "manifest skill entrypoint mismatch");
55
+ assert(String(manifest.entrypoints?.owner_forwarder_demo || "") === "scripts/owner-forwarder-demo.mjs", "manifest owner forwarder demo entrypoint mismatch");
56
+ assert(String(manifest.entrypoints?.owner_dispatch_adapter_demo || "") === "scripts/owner-dispatch-adapter-demo.mjs", "manifest owner dispatch adapter demo entrypoint mismatch");
57
+ assert(String(manifest.entrypoints?.owner_send_via_openclaw || "") === "scripts/send-to-owner-via-openclaw.mjs", "manifest owner send via openclaw entrypoint mismatch");
58
+ assert(String(manifest.references?.owner_forwarding_policy || "") === "references/owner-forwarding-policy.md", "manifest owner forwarding reference mismatch");
59
+ assert(String(manifest.references?.owner_dispatch_adapter || "") === "references/owner-dispatch-adapter.md", "manifest owner dispatch adapter reference mismatch");
60
+ assert(String(manifest.references?.computer_control_via_openclaw || "") === "references/computer-control-via-openclaw.md", "manifest computer control reference mismatch");
61
+ assert(ui.includes('display_name: "SilicaClaw Broadcast"'), "openai.yaml display_name mismatch");
62
+ assert(ownerPolicy.includes("Default routing modes"), "owner forwarding policy content mismatch");
63
+ assert(ownerDispatchAdapterRef.includes("OPENCLAW_OWNER_FORWARD_CMD"), "owner dispatch adapter reference content mismatch");
64
+ assert(computerControlRef.includes("node.invoke"), "computer control reference content mismatch");
65
+
66
+ console.log(JSON.stringify({
67
+ valid: true,
68
+ skill: skillName,
69
+ version,
70
+ skill_dir: skillDir,
71
+ }, null, 2));
72
+ }
73
+
74
+ main();
package/social.md.example CHANGED
@@ -12,6 +12,12 @@ identity:
12
12
  network:
13
13
  mode: "lan"
14
14
 
15
+ discovery:
16
+ discoverable: true
17
+ allow_profile_broadcast: true
18
+ allow_presence_broadcast: true
19
+ allow_message_broadcast: true
20
+
15
21
  openclaw:
16
22
  bind_existing_identity: true
17
23
  use_openclaw_profile_if_available: true