@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.
- package/ARCHITECTURE.md +15 -0
- package/CHANGELOG.md +17 -2
- package/INSTALL.md +35 -0
- package/README.md +119 -10
- package/RELEASE_NOTES_v1.0.md +29 -2
- package/SOCIAL_MD_SPEC.md +2 -0
- package/VERSION +1 -1
- package/apps/local-console/public/index.html +2297 -231
- package/apps/local-console/src/server.ts +1120 -24
- package/apps/local-console/src/socialRoutes.ts +21 -0
- package/apps/public-explorer/public/index.html +190 -43
- package/docs/NEW_USER_OPERATIONS.md +35 -5
- package/docs/OPENCLAW_BRIDGE.md +449 -0
- package/docs/OPENCLAW_BRIDGE_ZH.md +445 -0
- package/docs/QUICK_START.md +20 -1
- package/docs/release/ANNOUNCEMENT_v1.0-beta.md +68 -0
- package/docs/release/FINAL_RELEASE_SUMMARY_v1.0-beta.md +112 -0
- package/docs/release/GITHUB_RELEASE_v1.0-beta.md +16 -16
- package/docs/release/RELEASE_COPY_v1.0-beta.md +102 -0
- package/openclaw-skills/silicaclaw-broadcast/SKILL.md +89 -0
- package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -0
- package/openclaw-skills/silicaclaw-broadcast/agents/openai.yaml +6 -0
- package/openclaw-skills/silicaclaw-broadcast/manifest.json +34 -0
- package/openclaw-skills/silicaclaw-broadcast/references/computer-control-via-openclaw.md +41 -0
- package/openclaw-skills/silicaclaw-broadcast/references/owner-dispatch-adapter.md +81 -0
- package/openclaw-skills/silicaclaw-broadcast/references/owner-forwarding-policy.md +48 -0
- package/openclaw-skills/silicaclaw-broadcast/scripts/bridge-client.mjs +59 -0
- package/openclaw-skills/silicaclaw-broadcast/scripts/owner-dispatch-adapter-demo.mjs +12 -0
- package/openclaw-skills/silicaclaw-broadcast/scripts/owner-forwarder-demo.mjs +111 -0
- package/openclaw-skills/silicaclaw-broadcast/scripts/send-to-owner-via-openclaw.mjs +69 -0
- package/openclaw.social.md.example +6 -0
- package/package.json +2 -1
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/socialConfig.d.ts +1 -0
- package/packages/core/dist/socialConfig.js +9 -1
- package/packages/core/dist/socialMessage.d.ts +19 -0
- package/packages/core/dist/socialMessage.js +69 -0
- package/packages/core/dist/socialTemplate.js +3 -1
- package/packages/core/dist/types.d.ts +22 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/socialConfig.ts +13 -1
- package/packages/core/src/socialMessage.ts +86 -0
- package/packages/core/src/socialTemplate.ts +3 -1
- package/packages/core/src/types.ts +24 -0
- package/packages/network/dist/relayPreview.js +16 -4
- package/packages/network/src/relayPreview.ts +17 -4
- package/packages/storage/dist/repos.d.ts +40 -0
- package/packages/storage/dist/repos.js +27 -1
- package/packages/storage/dist/socialRuntimeRepo.js +1 -0
- package/packages/storage/src/repos.ts +60 -0
- package/packages/storage/src/socialRuntimeRepo.ts +1 -0
- package/packages/storage/tsconfig.json +1 -1
- package/scripts/functional-check.mjs +85 -2
- package/scripts/install-openclaw-skill.mjs +54 -0
- package/scripts/openclaw-bridge-adapter.mjs +89 -0
- package/scripts/openclaw-bridge-client.mjs +223 -0
- package/scripts/openclaw-runtime-demo.mjs +202 -0
- package/scripts/pack-openclaw-skill.mjs +58 -0
- package/scripts/silicaclaw-cli.mjs +30 -0
- package/scripts/silicaclaw-gateway.mjs +215 -0
- package/scripts/validate-openclaw-skill.mjs +74 -0
- 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("&", "&")
|
|
218
|
+
.replaceAll("<", "<")
|
|
219
|
+
.replaceAll(">", ">")
|
|
220
|
+
.replaceAll('"', """)
|
|
221
|
+
.replaceAll("'", "'");
|
|
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
|