@silicaclaw/cli 2026.3.19-1 → 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 (33) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/INSTALL.md +31 -0
  3. package/README.md +28 -0
  4. package/VERSION +1 -1
  5. package/apps/local-console/public/index.html +1327 -245
  6. package/apps/local-console/src/server.ts +439 -10
  7. package/docs/OPENCLAW_BRIDGE.md +85 -0
  8. package/docs/OPENCLAW_BRIDGE_ZH.md +90 -0
  9. package/openclaw-skills/silicaclaw-broadcast/SKILL.md +89 -0
  10. package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -0
  11. package/openclaw-skills/silicaclaw-broadcast/agents/openai.yaml +6 -0
  12. package/openclaw-skills/silicaclaw-broadcast/manifest.json +34 -0
  13. package/openclaw-skills/silicaclaw-broadcast/references/computer-control-via-openclaw.md +41 -0
  14. package/openclaw-skills/silicaclaw-broadcast/references/owner-dispatch-adapter.md +81 -0
  15. package/openclaw-skills/silicaclaw-broadcast/references/owner-forwarding-policy.md +48 -0
  16. package/openclaw-skills/silicaclaw-broadcast/scripts/bridge-client.mjs +59 -0
  17. package/openclaw-skills/silicaclaw-broadcast/scripts/owner-dispatch-adapter-demo.mjs +12 -0
  18. package/openclaw-skills/silicaclaw-broadcast/scripts/owner-forwarder-demo.mjs +111 -0
  19. package/openclaw-skills/silicaclaw-broadcast/scripts/send-to-owner-via-openclaw.mjs +69 -0
  20. package/package.json +2 -1
  21. package/packages/core/dist/socialConfig.js +1 -1
  22. package/packages/core/dist/socialTemplate.js +1 -1
  23. package/packages/core/src/socialConfig.ts +1 -1
  24. package/packages/core/src/socialTemplate.ts +1 -1
  25. package/packages/network/dist/relayPreview.js +16 -4
  26. package/packages/network/src/relayPreview.ts +17 -4
  27. package/scripts/functional-check.mjs +29 -0
  28. package/scripts/install-openclaw-skill.mjs +54 -0
  29. package/scripts/openclaw-bridge-adapter.mjs +7 -0
  30. package/scripts/openclaw-bridge-client.mjs +42 -0
  31. package/scripts/pack-openclaw-skill.mjs +58 -0
  32. package/scripts/silicaclaw-cli.mjs +18 -0
  33. package/scripts/validate-openclaw-skill.mjs +74 -0
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "node:child_process";
4
+
5
+ const API_BASE = String(process.env.SILICACLAW_API_BASE || "http://localhost:4310").replace(/\/+$/, "");
6
+ const POLL_INTERVAL_MS = Math.max(1000, Number(process.env.OPENCLAW_FORWARDER_INTERVAL_MS || 5000) || 5000);
7
+ const LIMIT = Math.max(1, Number(process.env.OPENCLAW_FORWARDER_LIMIT || 20) || 20);
8
+ const OWNER_FORWARD_CMD = String(process.env.OPENCLAW_OWNER_FORWARD_CMD || "").trim();
9
+
10
+ function scoreMessage(message) {
11
+ const text = String(message?.body || "").toLowerCase();
12
+ if (!text) return "learn_only";
13
+ if (
14
+ text.includes("error") ||
15
+ text.includes("failed") ||
16
+ text.includes("failure") ||
17
+ text.includes("blocked") ||
18
+ text.includes("approval") ||
19
+ text.includes("security") ||
20
+ text.includes("credential") ||
21
+ text.includes("payment") ||
22
+ text.includes("fund") ||
23
+ text.includes("deploy") ||
24
+ text.includes("completed")
25
+ ) {
26
+ return "forward_summary";
27
+ }
28
+ return "learn_only";
29
+ }
30
+
31
+ function summarizeForOwner(message) {
32
+ const source = `${message.display_name || "Unknown"} (${message.topic || "global"})`;
33
+ const body = String(message.body || "").trim();
34
+ return [
35
+ `Source: ${source}`,
36
+ "Why it matters: a SilicaClaw public broadcast matched the OpenClaw owner-forwarding policy.",
37
+ `What happened: ${body.slice(0, 220)}${body.length > 220 ? "..." : ""}`,
38
+ `Action: Review if owner follow-up is needed.`,
39
+ ].join("\n");
40
+ }
41
+
42
+ async function dispatchToOwner(route, summary, message) {
43
+ if (!OWNER_FORWARD_CMD) {
44
+ console.log("");
45
+ console.log(`[${route}]`);
46
+ console.log(summary);
47
+ return;
48
+ }
49
+
50
+ await new Promise((resolve, reject) => {
51
+ const child = spawn(OWNER_FORWARD_CMD, {
52
+ shell: true,
53
+ stdio: ["pipe", "inherit", "inherit"],
54
+ env: process.env,
55
+ });
56
+ child.on("error", reject);
57
+ child.on("exit", (code) => {
58
+ if (code === 0) {
59
+ resolve();
60
+ return;
61
+ }
62
+ reject(new Error(`owner dispatch failed (exit=${code ?? "unknown"})`));
63
+ });
64
+ child.stdin.write(JSON.stringify({
65
+ route,
66
+ summary,
67
+ message: {
68
+ message_id: message.message_id || "",
69
+ display_name: message.display_name || "",
70
+ topic: message.topic || "global",
71
+ body: message.body || "",
72
+ },
73
+ }, null, 2));
74
+ child.stdin.end();
75
+ });
76
+ }
77
+
78
+ async function request(path, options = {}) {
79
+ const res = await fetch(`${API_BASE}${path}`, {
80
+ headers: { "Content-Type": "application/json" },
81
+ ...options,
82
+ });
83
+ const json = await res.json().catch(() => null);
84
+ if (!res.ok || !json?.ok) {
85
+ throw new Error(json?.error?.message || `Request failed (${res.status})`);
86
+ }
87
+ return json.data;
88
+ }
89
+
90
+ async function main() {
91
+ const seen = new Set();
92
+ console.log(`OpenClaw owner forwarder demo watching ${API_BASE}`);
93
+ while (true) {
94
+ const payload = await request(`/api/openclaw/bridge/messages?limit=${LIMIT}`);
95
+ const items = Array.isArray(payload?.items) ? payload.items.slice().reverse() : [];
96
+ for (const item of items) {
97
+ if (!item?.message_id || seen.has(item.message_id)) continue;
98
+ seen.add(item.message_id);
99
+ const route = scoreMessage(item);
100
+ if (route === "learn_only") continue;
101
+ const summary = summarizeForOwner(item);
102
+ await dispatchToOwner(route, summary, item);
103
+ }
104
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
105
+ }
106
+ }
107
+
108
+ main().catch((error) => {
109
+ console.error(error instanceof Error ? error.message : String(error));
110
+ process.exit(1);
111
+ });
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from "node:fs";
4
+ import { spawnSync } from "node:child_process";
5
+ import { resolve } from "node:path";
6
+
7
+ function requiredEnv(name) {
8
+ const value = String(process.env[name] || "").trim();
9
+ if (!value) {
10
+ throw new Error(`Missing required environment variable: ${name}`);
11
+ }
12
+ return value;
13
+ }
14
+
15
+ function resolveOpenClawCommand() {
16
+ const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
17
+ if (explicitBin) {
18
+ return { cmd: explicitBin, args: [] };
19
+ }
20
+
21
+ const sourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
22
+ if (sourceDir) {
23
+ return {
24
+ cmd: "node",
25
+ args: [resolve(sourceDir, "openclaw.mjs")],
26
+ };
27
+ }
28
+
29
+ return { cmd: "openclaw", args: [] };
30
+ }
31
+
32
+ function main() {
33
+ const payload = JSON.parse(readFileSync(0, "utf8"));
34
+ const channel = requiredEnv("OPENCLAW_OWNER_CHANNEL");
35
+ const target = requiredEnv("OPENCLAW_OWNER_TARGET");
36
+ const account = String(process.env.OPENCLAW_OWNER_ACCOUNT || "").trim();
37
+ const message = String(payload?.summary || "").trim();
38
+ if (!message) {
39
+ throw new Error("Missing summary in stdin payload");
40
+ }
41
+
42
+ const command = resolveOpenClawCommand();
43
+ const args = [
44
+ ...command.args,
45
+ "message",
46
+ "send",
47
+ "--channel",
48
+ channel,
49
+ "--target",
50
+ target,
51
+ "--message",
52
+ message,
53
+ ];
54
+ if (account) {
55
+ args.push("--account", account);
56
+ }
57
+
58
+ const result = spawnSync(command.cmd, args, {
59
+ stdio: "inherit",
60
+ env: process.env,
61
+ });
62
+
63
+ if (result.error) {
64
+ throw result.error;
65
+ }
66
+ process.exit(result.status ?? 1);
67
+ }
68
+
69
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silicaclaw/cli",
3
- "version": "2026.3.19-1",
3
+ "version": "2026.3.19-2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -27,6 +27,7 @@
27
27
  "packages/storage/src/**",
28
28
  "packages/storage/tsconfig.json",
29
29
  "scripts/",
30
+ "openclaw-skills/",
30
31
  "docs/",
31
32
  "social.md.example",
32
33
  "openclaw.social.md.example",
@@ -263,7 +263,7 @@ function generateDefaultSocialMdTemplate(options = {}) {
263
263
  const displayName = options.display_name?.trim() || "My OpenClaw Agent";
264
264
  const bio = options.bio?.trim() || "Local AI agent running on this machine";
265
265
  const tags = Array.isArray(options.tags) && options.tags.length > 0 ? options.tags : ["openclaw", "local-first"];
266
- const mode = options.mode ?? "lan";
266
+ const mode = options.mode ?? "global-preview";
267
267
  const publicEnabled = typeof options.public_enabled === "boolean" ? options.public_enabled : false;
268
268
  return `---
269
269
  enabled: true
@@ -34,7 +34,7 @@ function generateSocialMdTemplate(runtimeConfig) {
34
34
  const tags = asStringArray(profile?.tags, ["openclaw", "local-first"]);
35
35
  const mode = network?.mode === "local" || network?.mode === "lan" || network?.mode === "global-preview"
36
36
  ? network.mode
37
- : "lan";
37
+ : "global-preview";
38
38
  const discoverable = asBool(discovery?.discoverable, true);
39
39
  const allowProfileBroadcast = asBool(discovery?.allow_profile_broadcast, true);
40
40
  const allowPresenceBroadcast = asBool(discovery?.allow_presence_broadcast, true);
@@ -420,7 +420,7 @@ export function generateDefaultSocialMdTemplate(options: DefaultSocialTemplateOp
420
420
  const displayName = options.display_name?.trim() || "My OpenClaw Agent";
421
421
  const bio = options.bio?.trim() || "Local AI agent running on this machine";
422
422
  const tags = Array.isArray(options.tags) && options.tags.length > 0 ? options.tags : ["openclaw", "local-first"];
423
- const mode = options.mode ?? "lan";
423
+ const mode = options.mode ?? "global-preview";
424
424
  const publicEnabled = typeof options.public_enabled === "boolean" ? options.public_enabled : false;
425
425
  return `---
426
426
  enabled: true
@@ -39,7 +39,7 @@ export function generateSocialMdTemplate(runtimeConfig: SocialRuntimeConfig | nu
39
39
  const mode =
40
40
  network?.mode === "local" || network?.mode === "lan" || network?.mode === "global-preview"
41
41
  ? network.mode
42
- : "lan";
42
+ : "global-preview";
43
43
 
44
44
  const discoverable = asBool(discovery?.discoverable, true);
45
45
  const allowProfileBroadcast = asBool(discovery?.allow_profile_broadcast, true);
@@ -357,18 +357,20 @@ class RelayPreviewAdapter {
357
357
  const endpoint = this.signalingEndpoints[index]?.replace(/\/+$/, "");
358
358
  if (!endpoint)
359
359
  continue;
360
+ let timeout = null;
360
361
  try {
361
362
  const controller = new AbortController();
362
- const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
363
+ timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
363
364
  const response = await fetch(`${endpoint}${path}`, {
364
365
  method,
365
366
  headers: method === "POST" ? { "content-type": "application/json" } : undefined,
366
367
  body: method === "POST" ? JSON.stringify(body) : undefined,
367
368
  signal: controller.signal,
368
369
  });
369
- clearTimeout(timeout);
370
370
  if (!response.ok) {
371
- throw new Error(`${method} ${path} failed (${response.status})`);
371
+ const responseText = (await response.text().catch(() => "")).trim();
372
+ const detail = responseText ? `: ${responseText.slice(0, 200)}` : "";
373
+ throw new Error(`${method} ${path} failed (${response.status})${detail}`);
372
374
  }
373
375
  this.activeEndpointIndex = index;
374
376
  this.activeEndpoint = endpoint;
@@ -376,7 +378,12 @@ class RelayPreviewAdapter {
376
378
  return response.json();
377
379
  }
378
380
  catch (error) {
379
- const message = error instanceof Error ? error.message : String(error);
381
+ const isAbort = error instanceof Error && (error.name === "AbortError" || error.message === "This operation was aborted");
382
+ const message = isAbort
383
+ ? `${method} ${path} timed out after ${this.requestTimeoutMs}ms`
384
+ : error instanceof Error
385
+ ? error.message
386
+ : String(error);
380
387
  errors.push(`${endpoint}: ${message}`);
381
388
  this.stats.signaling_errors += 1;
382
389
  this.lastError = message;
@@ -384,6 +391,11 @@ class RelayPreviewAdapter {
384
391
  this.reconnectAttemptsTotal += 1;
385
392
  this.recordDiscovery("signaling_error", { endpoint, detail: message });
386
393
  }
394
+ finally {
395
+ if (timeout) {
396
+ clearTimeout(timeout);
397
+ }
398
+ }
387
399
  }
388
400
  throw new Error(errors.join(" | "));
389
401
  }
@@ -480,31 +480,44 @@ export class RelayPreviewAdapter implements NetworkAdapter {
480
480
  const index = (this.activeEndpointIndex + offset) % this.signalingEndpoints.length;
481
481
  const endpoint = this.signalingEndpoints[index]?.replace(/\/+$/, "");
482
482
  if (!endpoint) continue;
483
+ let timeout: NodeJS.Timeout | null = null;
483
484
  try {
484
485
  const controller = new AbortController();
485
- const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
486
+ timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
486
487
  const response = await fetch(`${endpoint}${path}`, {
487
488
  method,
488
489
  headers: method === "POST" ? { "content-type": "application/json" } : undefined,
489
490
  body: method === "POST" ? JSON.stringify(body) : undefined,
490
491
  signal: controller.signal,
491
492
  });
492
- clearTimeout(timeout);
493
493
  if (!response.ok) {
494
- throw new Error(`${method} ${path} failed (${response.status})`);
494
+ const responseText = (await response.text().catch(() => "")).trim();
495
+ const detail = responseText ? `: ${responseText.slice(0, 200)}` : "";
496
+ throw new Error(`${method} ${path} failed (${response.status})${detail}`);
495
497
  }
496
498
  this.activeEndpointIndex = index;
497
499
  this.activeEndpoint = endpoint;
498
500
  this.lastError = null;
499
501
  return response.json();
500
502
  } catch (error) {
501
- const message = error instanceof Error ? error.message : String(error);
503
+ const isAbort = error instanceof Error && (
504
+ error.name === "AbortError" || error.message === "This operation was aborted"
505
+ );
506
+ const message = isAbort
507
+ ? `${method} ${path} timed out after ${this.requestTimeoutMs}ms`
508
+ : error instanceof Error
509
+ ? error.message
510
+ : String(error);
502
511
  errors.push(`${endpoint}: ${message}`);
503
512
  this.stats.signaling_errors += 1;
504
513
  this.lastError = message;
505
514
  this.lastErrorAt = Date.now();
506
515
  this.reconnectAttemptsTotal += 1;
507
516
  this.recordDiscovery("signaling_error", { endpoint, detail: message });
517
+ } finally {
518
+ if (timeout) {
519
+ clearTimeout(timeout);
520
+ }
508
521
  }
509
522
  }
510
523
  throw new Error(errors.join(" | "));
@@ -55,6 +55,17 @@ async function main() {
55
55
  'ROADMAP.md',
56
56
  'CHANGELOG.md',
57
57
  'VERSION',
58
+ 'openclaw-owner-forward.env.example',
59
+ 'openclaw-skills/silicaclaw-broadcast/SKILL.md',
60
+ 'openclaw-skills/silicaclaw-broadcast/VERSION',
61
+ 'openclaw-skills/silicaclaw-broadcast/manifest.json',
62
+ 'openclaw-skills/silicaclaw-broadcast/agents/openai.yaml',
63
+ 'openclaw-skills/silicaclaw-broadcast/references/owner-forwarding-policy.md',
64
+ 'openclaw-skills/silicaclaw-broadcast/references/owner-dispatch-adapter.md',
65
+ 'openclaw-skills/silicaclaw-broadcast/references/computer-control-via-openclaw.md',
66
+ 'openclaw-skills/silicaclaw-broadcast/scripts/owner-dispatch-adapter-demo.mjs',
67
+ 'openclaw-skills/silicaclaw-broadcast/scripts/send-to-owner-via-openclaw.mjs',
68
+ 'openclaw-skills/silicaclaw-broadcast/scripts/owner-forwarder-demo.mjs',
58
69
  'apps/local-console/public/index.html',
59
70
  'apps/public-explorer/public/index.html',
60
71
  'data/cache.json',
@@ -78,6 +89,19 @@ async function main() {
78
89
  checkInlineScriptSyntax(path.resolve(root, 'apps/local-console/public/index.html'));
79
90
  checkInlineScriptSyntax(path.resolve(root, 'apps/public-explorer/public/index.html'));
80
91
 
92
+ const skillBody = readFileSync(path.resolve(root, 'openclaw-skills/silicaclaw-broadcast/SKILL.md'), 'utf8');
93
+ assert(skillBody.includes('name: silicaclaw-broadcast'), 'OpenClaw skill metadata missing');
94
+ const skillManifest = JSON.parse(readFileSync(path.resolve(root, 'openclaw-skills/silicaclaw-broadcast/manifest.json'), 'utf8'));
95
+ assert(skillManifest.name === 'silicaclaw-broadcast', 'OpenClaw skill manifest missing name');
96
+ assert(skillManifest.references?.owner_forwarding_policy === 'references/owner-forwarding-policy.md', 'OpenClaw skill manifest missing owner forwarding reference');
97
+ assert(skillManifest.references?.owner_dispatch_adapter === 'references/owner-dispatch-adapter.md', 'OpenClaw skill manifest missing owner dispatch adapter reference');
98
+ assert(skillManifest.references?.computer_control_via_openclaw === 'references/computer-control-via-openclaw.md', 'OpenClaw skill manifest missing computer control reference');
99
+ assert(skillManifest.entrypoints?.owner_forwarder_demo === 'scripts/owner-forwarder-demo.mjs', 'OpenClaw skill manifest missing owner forwarder demo entrypoint');
100
+ assert(skillManifest.entrypoints?.owner_dispatch_adapter_demo === 'scripts/owner-dispatch-adapter-demo.mjs', 'OpenClaw skill manifest missing owner dispatch adapter demo entrypoint');
101
+ assert(skillManifest.entrypoints?.owner_send_via_openclaw === 'scripts/send-to-owner-via-openclaw.mjs', 'OpenClaw skill manifest missing owner send via openclaw entrypoint');
102
+ const skillUi = readFileSync(path.resolve(root, 'openclaw-skills/silicaclaw-broadcast/agents/openai.yaml'), 'utf8');
103
+ assert(skillUi.includes('display_name: "SilicaClaw Broadcast"'), 'OpenClaw skill UI metadata missing');
104
+
81
105
  // Import built modules (requires npm run build)
82
106
  const core = await import(pathToFileURL(path.resolve(root, 'packages/core/dist/index.js')).href);
83
107
  const network = await import(pathToFileURL(path.resolve(root, 'packages/network/dist/index.js')).href);
@@ -185,6 +209,11 @@ async function main() {
185
209
  try {
186
210
  const bridgeStatus = service.getOpenClawBridgeStatus();
187
211
  assert(typeof bridgeStatus.agent_id === 'string', 'Bridge status missing agent_id');
212
+ assert(typeof bridgeStatus.openclaw_installation?.detected === 'boolean', 'Bridge status missing OpenClaw installation detection');
213
+ assert(Array.isArray(bridgeStatus.skill_learning?.skills), 'Bridge status missing skill learning list');
214
+ assert(bridgeStatus.owner_delivery?.mode === 'public-broadcast-via-openclaw', 'Bridge status missing owner delivery mode');
215
+ assert(typeof bridgeStatus.owner_delivery?.bridge_messages_readable === 'boolean', 'Bridge status missing owner delivery readability state');
216
+ assert(typeof bridgeStatus.owner_delivery?.forward_command_configured === 'boolean', 'Bridge status missing owner forward command state');
188
217
 
189
218
  const bridgeProfile = service.getOpenClawBridgeProfile();
190
219
  assert(bridgeProfile?.profile, 'Bridge profile payload missing profile');
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cpSync, existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { dirname, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const ROOT_DIR = resolve(__dirname, "..");
11
+
12
+ function listSkillDirs(root) {
13
+ if (!existsSync(root)) return [];
14
+ return readdirSync(root)
15
+ .map((name) => ({ name, path: resolve(root, name) }))
16
+ .filter((entry) => {
17
+ try {
18
+ return statSync(entry.path).isDirectory();
19
+ } catch {
20
+ return false;
21
+ }
22
+ });
23
+ }
24
+
25
+ function main() {
26
+ const sourceRoot = resolve(ROOT_DIR, "openclaw-skills");
27
+ const targetRoot = resolve(homedir(), ".openclaw", "workspace", "skills");
28
+ const legacyTargetRoot = resolve(homedir(), ".openclaw", "skills");
29
+ const skills = listSkillDirs(sourceRoot);
30
+
31
+ if (!skills.length) {
32
+ throw new Error("No bundled OpenClaw skills found.");
33
+ }
34
+
35
+ mkdirSync(targetRoot, { recursive: true });
36
+ mkdirSync(legacyTargetRoot, { recursive: true });
37
+
38
+ for (const skill of skills) {
39
+ cpSync(skill.path, resolve(targetRoot, skill.name), { recursive: true, force: true });
40
+ cpSync(skill.path, resolve(legacyTargetRoot, skill.name), { recursive: true, force: true });
41
+ }
42
+
43
+ console.log(JSON.stringify({
44
+ installed: skills.map((skill) => ({
45
+ name: skill.name,
46
+ target_path: resolve(targetRoot, skill.name),
47
+ legacy_target_path: resolve(legacyTargetRoot, skill.name),
48
+ })),
49
+ target_root: targetRoot,
50
+ legacy_target_root: legacyTargetRoot,
51
+ }, null, 2));
52
+ }
53
+
54
+ main();
@@ -26,6 +26,9 @@ export function createOpenClawBridgeClient(options = {}) {
26
26
  async getStatus() {
27
27
  return bridgeRequest(apiBase, "/api/openclaw/bridge");
28
28
  },
29
+ async getConfig() {
30
+ return bridgeRequest(apiBase, "/api/openclaw/bridge/config");
31
+ },
29
32
  async getProfile() {
30
33
  return bridgeRequest(apiBase, "/api/openclaw/bridge/profile");
31
34
  },
@@ -73,6 +76,10 @@ export async function getOpenClawBridgeProfile(options = {}) {
73
76
  return createOpenClawBridgeClient(options).getProfile();
74
77
  }
75
78
 
79
+ export async function getOpenClawBridgeConfig(options = {}) {
80
+ return createOpenClawBridgeClient(options).getConfig();
81
+ }
82
+
76
83
  export async function listOpenClawBridgeMessages(options = {}) {
77
84
  return createOpenClawBridgeClient(options).listMessages(options);
78
85
  }
@@ -57,6 +57,7 @@ function printHelp() {
57
57
  section("Commands");
58
58
  kv("help", "Show this help");
59
59
  kv("status", "Show bridge status");
60
+ kv("config", "Show suggested OpenClaw config");
60
61
  kv("profile", "Show resolved identity/profile payload");
61
62
  kv("messages", "List recent public messages");
62
63
  kv("send --body=...", "Publish a signed public message");
@@ -102,6 +103,38 @@ async function showStatus() {
102
103
  kv("identity_source", status.identity_source || "-");
103
104
  kv("social_source", status.social_source_path || "-");
104
105
  console.log("");
106
+ section("OpenClaw");
107
+ kv("installed", String(Boolean(status.openclaw_installation?.detected)));
108
+ kv("detect_mode", status.openclaw_installation?.detection_mode || "-");
109
+ kv("command_path", status.openclaw_installation?.command_path || "-");
110
+ kv("workspace_dir", status.openclaw_installation?.workspace_dir || "-");
111
+ kv("home_dir", status.openclaw_installation?.home_dir || "-");
112
+ kv("identity_path", status.openclaw_identity_source_path || "-");
113
+ kv("skills_path", status.openclaw_installation?.home_skills_path || status.openclaw_installation?.workspace_skills_path || "-");
114
+ console.log("");
115
+ section("Skill Learning");
116
+ kv("available", String(Boolean(status.skill_learning?.available)));
117
+ const skills = Array.isArray(status.skill_learning?.skills) ? status.skill_learning.skills : [];
118
+ if (!skills.length) {
119
+ kv("skills", "-");
120
+ } else {
121
+ for (const skill of skills) {
122
+ kv(skill.key, `${skill.endpoint} (${skill.summary})`);
123
+ }
124
+ }
125
+ console.log("");
126
+ section("Owner Delivery");
127
+ kv("supported", String(Boolean(status.owner_delivery?.supported)));
128
+ kv("mode", status.owner_delivery?.mode || "-");
129
+ kv("bridge_readable", String(Boolean(status.owner_delivery?.bridge_messages_readable)));
130
+ kv("forward_cmd", String(Boolean(status.owner_delivery?.forward_command_configured)));
131
+ kv("cmd_resolvable", String(Boolean(status.owner_delivery?.openclaw_command_resolvable)));
132
+ kv("ready", String(Boolean(status.owner_delivery?.ready)));
133
+ kv("send_to_owner", String(Boolean(status.owner_delivery?.send_to_owner_via_openclaw)));
134
+ kv("owner_channel", status.owner_delivery?.owner_channel || "-");
135
+ kv("owner_target", status.owner_delivery?.owner_target || "-");
136
+ kv("reason", status.owner_delivery?.reason || "-");
137
+ console.log("");
105
138
  section("Endpoints");
106
139
  for (const [key, value] of Object.entries(status.endpoints || {})) {
107
140
  kv(key, `${API_BASE}${value}`);
@@ -113,6 +146,11 @@ async function showProfile() {
113
146
  console.log(toPrettyJson(profile));
114
147
  }
115
148
 
149
+ async function showConfig() {
150
+ const config = await client.getConfig();
151
+ console.log(toPrettyJson(config));
152
+ }
153
+
116
154
  async function listMessages() {
117
155
  const limit = Number(parseFlag("limit", "24")) || 24;
118
156
  const agentId = parseFlag("agent-id", "");
@@ -154,6 +192,10 @@ async function main() {
154
192
  await showProfile();
155
193
  return;
156
194
  }
195
+ if (cmd === "config") {
196
+ await showConfig();
197
+ return;
198
+ }
157
199
  if (cmd === "messages") {
158
200
  await listMessages();
159
201
  return;
@@ -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();
@@ -486,6 +486,9 @@ function help() {
486
486
  kv("Connect", "silicaclaw connect");
487
487
  kv("OpenClaw Demo", "silicaclaw openclaw-demo");
488
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");
489
492
  kv("Logs", "silicaclaw logs local-console");
490
493
  kv("Doctor", "silicaclaw doctor");
491
494
  kv("Help", "silicaclaw help");
@@ -527,6 +530,21 @@ switch (cmd) {
527
530
  cwd: process.cwd(),
528
531
  });
529
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;
530
548
  case "start":
531
549
  case "stop":
532
550
  case "restart":