@silicaclaw/cli 1.0.0-beta.0

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 (77) hide show
  1. package/ARCHITECTURE.md +137 -0
  2. package/CHANGELOG.md +411 -0
  3. package/DEMO_GUIDE.md +89 -0
  4. package/INSTALL.md +156 -0
  5. package/README.md +244 -0
  6. package/RELEASE_NOTES_v1.0.md +65 -0
  7. package/ROADMAP.md +48 -0
  8. package/SOCIAL_MD_SPEC.md +122 -0
  9. package/VERSION +1 -0
  10. package/apps/local-console/package.json +23 -0
  11. package/apps/local-console/public/assets/README.md +5 -0
  12. package/apps/local-console/public/assets/silicaclaw-logo.png +0 -0
  13. package/apps/local-console/public/index.html +1602 -0
  14. package/apps/local-console/src/server.ts +1656 -0
  15. package/apps/local-console/src/socialRoutes.ts +90 -0
  16. package/apps/local-console/tsconfig.json +7 -0
  17. package/apps/public-explorer/package.json +20 -0
  18. package/apps/public-explorer/public/assets/README.md +5 -0
  19. package/apps/public-explorer/public/assets/silicaclaw-logo.png +0 -0
  20. package/apps/public-explorer/public/index.html +483 -0
  21. package/apps/public-explorer/src/server.ts +32 -0
  22. package/apps/public-explorer/tsconfig.json +7 -0
  23. package/docs/QUICK_START.md +48 -0
  24. package/docs/assets/README.md +8 -0
  25. package/docs/assets/banner.svg +25 -0
  26. package/docs/assets/silicaclaw-logo.png +0 -0
  27. package/docs/assets/silicaclaw-og.png +0 -0
  28. package/docs/release/GITHUB_RELEASE_v1.0-beta.md +143 -0
  29. package/docs/screenshots/README.md +8 -0
  30. package/docs/screenshots/v0.3.1-explorer-search.svg +9 -0
  31. package/docs/screenshots/v0.3.1-machine-a-network.svg +9 -0
  32. package/docs/screenshots/v0.3.1-machine-b-peers.svg +9 -0
  33. package/docs/screenshots/v0.3.1-stale-transition.svg +9 -0
  34. package/openclaw.social.md.example +28 -0
  35. package/package.json +64 -0
  36. package/packages/core/package.json +13 -0
  37. package/packages/core/src/crypto.ts +55 -0
  38. package/packages/core/src/directory.ts +171 -0
  39. package/packages/core/src/identity.ts +14 -0
  40. package/packages/core/src/index.ts +11 -0
  41. package/packages/core/src/indexing.ts +42 -0
  42. package/packages/core/src/presence.ts +24 -0
  43. package/packages/core/src/profile.ts +39 -0
  44. package/packages/core/src/publicProfileSummary.ts +180 -0
  45. package/packages/core/src/socialConfig.ts +440 -0
  46. package/packages/core/src/socialResolver.ts +281 -0
  47. package/packages/core/src/socialTemplate.ts +97 -0
  48. package/packages/core/src/types.ts +43 -0
  49. package/packages/core/tsconfig.json +7 -0
  50. package/packages/network/package.json +10 -0
  51. package/packages/network/src/abstractions/messageEnvelope.ts +80 -0
  52. package/packages/network/src/abstractions/peerDiscovery.ts +49 -0
  53. package/packages/network/src/abstractions/topicCodec.ts +4 -0
  54. package/packages/network/src/abstractions/transport.ts +40 -0
  55. package/packages/network/src/codec/jsonMessageEnvelopeCodec.ts +22 -0
  56. package/packages/network/src/codec/jsonTopicCodec.ts +11 -0
  57. package/packages/network/src/discovery/heartbeatPeerDiscovery.ts +173 -0
  58. package/packages/network/src/index.ts +16 -0
  59. package/packages/network/src/localEventBus.ts +61 -0
  60. package/packages/network/src/mock.ts +27 -0
  61. package/packages/network/src/realPreview.ts +436 -0
  62. package/packages/network/src/transport/udpLanBroadcastTransport.ts +173 -0
  63. package/packages/network/src/types.ts +6 -0
  64. package/packages/network/src/webrtcPreview.ts +1052 -0
  65. package/packages/network/tsconfig.json +7 -0
  66. package/packages/storage/package.json +13 -0
  67. package/packages/storage/src/index.ts +3 -0
  68. package/packages/storage/src/jsonRepo.ts +25 -0
  69. package/packages/storage/src/repos.ts +46 -0
  70. package/packages/storage/src/socialRuntimeRepo.ts +51 -0
  71. package/packages/storage/tsconfig.json +7 -0
  72. package/scripts/functional-check.mjs +165 -0
  73. package/scripts/install-logo.sh +53 -0
  74. package/scripts/quickstart.sh +144 -0
  75. package/scripts/silicaclaw-cli.mjs +88 -0
  76. package/scripts/webrtc-signaling-server.mjs +249 -0
  77. package/social.md.example +30 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist"
5
+ },
6
+ "include": ["src"]
7
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@silicaclaw/storage",
3
+ "version": "0.1.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "tsc -p tsconfig.json",
8
+ "check": "tsc -p tsconfig.json --noEmit"
9
+ },
10
+ "dependencies": {
11
+ "@silicaclaw/core": "0.1.0"
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./jsonRepo";
2
+ export * from "./repos";
3
+ export * from "./socialRuntimeRepo";
@@ -0,0 +1,25 @@
1
+ import { mkdir, readFile, writeFile } from "fs/promises";
2
+ import { dirname } from "path";
3
+
4
+ export class JsonFileRepo<T> {
5
+ constructor(
6
+ private filePath: string,
7
+ private fallback: () => T
8
+ ) {}
9
+
10
+ async get(): Promise<T> {
11
+ try {
12
+ const raw = await readFile(this.filePath, "utf8");
13
+ return JSON.parse(raw) as T;
14
+ } catch {
15
+ const seed = this.fallback();
16
+ await this.set(seed);
17
+ return seed;
18
+ }
19
+ }
20
+
21
+ async set(value: T): Promise<void> {
22
+ await mkdir(dirname(this.filePath), { recursive: true });
23
+ await writeFile(this.filePath, JSON.stringify(value, null, 2), "utf8");
24
+ }
25
+ }
@@ -0,0 +1,46 @@
1
+ import { resolve } from "path";
2
+ import { AgentIdentity, DirectoryState, PublicProfile, createEmptyDirectoryState } from "@silicaclaw/core";
3
+ import { JsonFileRepo } from "./jsonRepo";
4
+
5
+ export type LogEntry = {
6
+ id: string;
7
+ level: "info" | "warn" | "error";
8
+ message: string;
9
+ timestamp: number;
10
+ };
11
+
12
+ export class IdentityRepo extends JsonFileRepo<AgentIdentity | null> {
13
+ constructor(rootDir = process.cwd()) {
14
+ super(resolve(rootDir, "data", "identity.json"), () => null);
15
+ }
16
+ }
17
+
18
+ export class ProfileRepo extends JsonFileRepo<PublicProfile | null> {
19
+ constructor(rootDir = process.cwd()) {
20
+ super(resolve(rootDir, "data", "profile.json"), () => null);
21
+ }
22
+ }
23
+
24
+ export class CacheRepo extends JsonFileRepo<DirectoryState> {
25
+ constructor(rootDir = process.cwd()) {
26
+ super(resolve(rootDir, "data", "cache.json"), () => createEmptyDirectoryState());
27
+ }
28
+ }
29
+
30
+ export class LogRepo extends JsonFileRepo<LogEntry[]> {
31
+ constructor(rootDir = process.cwd()) {
32
+ super(resolve(rootDir, "data", "logs.json"), () => []);
33
+ }
34
+
35
+ async append(entry: Omit<LogEntry, "id">): Promise<void> {
36
+ const current = await this.get();
37
+ const next = [
38
+ {
39
+ id: `${entry.timestamp}-${Math.random().toString(36).slice(2, 8)}`,
40
+ ...entry,
41
+ },
42
+ ...current,
43
+ ].slice(0, 50);
44
+ await this.set(next);
45
+ }
46
+ }
@@ -0,0 +1,51 @@
1
+ import { resolve } from "path";
2
+ import { SocialRuntimeConfig } from "@silicaclaw/core";
3
+ import { JsonFileRepo } from "./jsonRepo";
4
+
5
+ function emptyRuntime(): SocialRuntimeConfig {
6
+ return {
7
+ enabled: true,
8
+ public_enabled: false,
9
+ source_path: null,
10
+ last_loaded_at: 0,
11
+ social_found: false,
12
+ parse_error: null,
13
+ resolved_identity: null,
14
+ resolved_profile: null,
15
+ resolved_network: {
16
+ mode: "lan",
17
+ adapter: "real-preview",
18
+ namespace: "silicaclaw.preview",
19
+ port: null,
20
+ signaling_url: "http://localhost:4510",
21
+ signaling_urls: [],
22
+ room: "silicaclaw-room",
23
+ seed_peers: [],
24
+ bootstrap_hints: [],
25
+ bootstrap_sources: [],
26
+ },
27
+ resolved_discovery: {
28
+ discoverable: true,
29
+ allow_profile_broadcast: true,
30
+ allow_presence_broadcast: true,
31
+ },
32
+ visibility: {
33
+ show_display_name: true,
34
+ show_bio: true,
35
+ show_tags: true,
36
+ show_agent_id: true,
37
+ show_last_seen: true,
38
+ show_capabilities_summary: true,
39
+ },
40
+ openclaw: {
41
+ bind_existing_identity: true,
42
+ use_openclaw_profile_if_available: true,
43
+ },
44
+ };
45
+ }
46
+
47
+ export class SocialRuntimeRepo extends JsonFileRepo<SocialRuntimeConfig> {
48
+ constructor(rootDir = process.cwd()) {
49
+ super(resolve(rootDir, ".silicaclaw", "social.runtime.json"), emptyRuntime);
50
+ }
51
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist"
5
+ },
6
+ "include": ["src"]
7
+ }
@@ -0,0 +1,165 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import vm from 'node:vm';
3
+ import path from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+
6
+ function assert(condition, message) {
7
+ if (!condition) throw new Error(message);
8
+ }
9
+
10
+ function checkJson(filePath) {
11
+ const raw = readFileSync(filePath, 'utf8');
12
+ JSON.parse(raw);
13
+ }
14
+
15
+ function checkInlineScriptSyntax(htmlPath) {
16
+ const html = readFileSync(htmlPath, 'utf8');
17
+ const start = html.indexOf('<script>');
18
+ const end = html.lastIndexOf('</script>');
19
+ assert(start >= 0 && end > start, `Missing inline script in ${htmlPath}`);
20
+ const js = html.slice(start + '<script>'.length, end);
21
+ new vm.Script(js, { filename: htmlPath });
22
+ }
23
+
24
+ class InMemoryLoopbackTransport {
25
+ constructor() {
26
+ this.handlers = new Set();
27
+ this.started = false;
28
+ }
29
+ async start() { this.started = true; }
30
+ async stop() { this.started = false; }
31
+ async send(data) {
32
+ if (!this.started) return;
33
+ for (const h of this.handlers) {
34
+ h(data, { remote_address: '127.0.0.1', remote_port: 0, transport: 'in-memory' });
35
+ }
36
+ }
37
+ onMessage(handler) {
38
+ this.handlers.add(handler);
39
+ return () => this.handlers.delete(handler);
40
+ }
41
+ inject(data) {
42
+ for (const h of this.handlers) {
43
+ h(data, { remote_address: '192.168.1.88', remote_port: 44123, transport: 'in-memory' });
44
+ }
45
+ }
46
+ }
47
+
48
+ async function main() {
49
+ const root = process.cwd();
50
+
51
+ const criticalFiles = [
52
+ 'README.md',
53
+ 'ARCHITECTURE.md',
54
+ 'ROADMAP.md',
55
+ 'CHANGELOG.md',
56
+ 'VERSION',
57
+ 'apps/local-console/public/index.html',
58
+ 'apps/public-explorer/public/index.html',
59
+ 'data/cache.json',
60
+ 'data/profile.json',
61
+ 'data/identity.json',
62
+ 'data/logs.json',
63
+ ];
64
+
65
+ for (const rel of criticalFiles) {
66
+ const abs = path.resolve(root, rel);
67
+ assert(existsSync(abs), `Missing required file: ${rel}`);
68
+ }
69
+
70
+ // JSON file sanity
71
+ checkJson(path.resolve(root, 'data/cache.json'));
72
+ checkJson(path.resolve(root, 'data/logs.json'));
73
+ checkJson(path.resolve(root, 'data/profile.json'));
74
+ checkJson(path.resolve(root, 'data/identity.json'));
75
+
76
+ // Browser script syntax sanity
77
+ checkInlineScriptSyntax(path.resolve(root, 'apps/local-console/public/index.html'));
78
+ checkInlineScriptSyntax(path.resolve(root, 'apps/public-explorer/public/index.html'));
79
+
80
+ // Import built modules (requires npm run build)
81
+ const core = await import(pathToFileURL(path.resolve(root, 'packages/core/dist/index.js')).href);
82
+ const network = await import(pathToFileURL(path.resolve(root, 'packages/network/dist/index.js')).href);
83
+
84
+ // Core smoke: identity/profile/presence
85
+ const identity = core.createIdentity();
86
+ assert(identity.agent_id && identity.public_key && identity.private_key, 'Identity generation failed');
87
+
88
+ const profile = core.signProfile({
89
+ agent_id: identity.agent_id,
90
+ display_name: 'demo-agent',
91
+ bio: 'smoke',
92
+ tags: ['ai', 'lan'],
93
+ avatar_url: '',
94
+ public_enabled: true,
95
+ }, identity);
96
+ assert(core.verifyProfile(profile, identity.public_key), 'Profile signature verification failed');
97
+
98
+ const presence = core.signPresence(identity);
99
+ assert(core.verifyPresence(presence, identity.public_key), 'Presence signature verification failed');
100
+
101
+ // Core smoke: index + search + TTL
102
+ let state = core.createEmptyDirectoryState();
103
+ state = core.ingestProfileRecord(state, { type: 'profile', profile });
104
+ state = core.ingestPresenceRecord(state, presence);
105
+ const searchHit = core.searchDirectory(state, 'ai');
106
+ assert(searchHit.length >= 1, 'Directory search failed for tag index');
107
+
108
+ const cleaned = core.cleanupExpiredPresence(state, Date.now() + 999_999, 1000);
109
+ assert(cleaned.removed >= 1, 'Presence cleanup failed');
110
+
111
+ // Real preview adapter smoke with in-memory transport
112
+ const transport = new InMemoryLoopbackTransport();
113
+ const adapter = new network.RealNetworkAdapterPreview({
114
+ transport,
115
+ namespace: 'smoke.ns',
116
+ maxMessageBytes: 4 * 1024,
117
+ dedupeWindowMs: 60_000,
118
+ });
119
+
120
+ let receivedCount = 0;
121
+ adapter.subscribe('profile', () => {
122
+ receivedCount += 1;
123
+ });
124
+
125
+ await adapter.start();
126
+
127
+ // Self publish should not deliver (self-message filter)
128
+ await adapter.publish('profile', { hello: 'self' });
129
+
130
+ // Inject remote envelope
131
+ const codec = new network.JsonMessageEnvelopeCodec();
132
+ const remoteEnvelope = {
133
+ version: 1,
134
+ message_id: 'msg-1',
135
+ topic: 'smoke.ns:profile',
136
+ source_peer_id: 'remote-peer-1',
137
+ timestamp: Date.now(),
138
+ payload: { hello: 'remote' },
139
+ };
140
+ transport.inject(codec.encode(remoteEnvelope));
141
+ transport.inject(codec.encode(remoteEnvelope)); // duplicate
142
+
143
+ // Namespace mismatch should be dropped
144
+ transport.inject(codec.encode({ ...remoteEnvelope, message_id: 'msg-2', topic: 'other.ns:profile' }));
145
+
146
+ // Malformed should be dropped
147
+ transport.inject(Buffer.from('{not-json'));
148
+
149
+ await new Promise((r) => setTimeout(r, 20));
150
+
151
+ const diagnostics = adapter.getDiagnostics();
152
+ assert(receivedCount === 1, 'Dedup/self filter behavior unexpected');
153
+ assert(diagnostics.stats.dropped_duplicate >= 1, 'Expected duplicate drop counter');
154
+ assert(diagnostics.stats.dropped_namespace_mismatch >= 1, 'Expected namespace mismatch drop counter');
155
+ assert(diagnostics.stats.dropped_malformed >= 1, 'Expected malformed drop counter');
156
+
157
+ await adapter.stop();
158
+
159
+ console.log('Functional check passed: core + network preview + UI script syntax + data sanity');
160
+ }
161
+
162
+ main().catch((error) => {
163
+ console.error('Functional check failed:', error.message);
164
+ process.exit(1);
165
+ });
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ "${1:-}" = "" ]; then
5
+ echo "Usage: npm run logo -- /absolute/path/to/logo.png"
6
+ exit 1
7
+ fi
8
+
9
+ SRC="$1"
10
+ if [ ! -f "$SRC" ]; then
11
+ echo "Logo file not found: $SRC"
12
+ exit 1
13
+ fi
14
+
15
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
16
+ TARGETS=(
17
+ "$ROOT_DIR/apps/local-console/public/assets/silicaclaw-logo.png"
18
+ "$ROOT_DIR/apps/public-explorer/public/assets/silicaclaw-logo.png"
19
+ "$ROOT_DIR/docs/assets/silicaclaw-logo.png"
20
+ )
21
+ OG_TARGET="$ROOT_DIR/docs/assets/silicaclaw-og.png"
22
+
23
+ mkdir -p \
24
+ "$ROOT_DIR/apps/local-console/public/assets" \
25
+ "$ROOT_DIR/apps/public-explorer/public/assets" \
26
+ "$ROOT_DIR/docs/assets"
27
+
28
+ for t in "${TARGETS[@]}"; do
29
+ if [ "$SRC" -ef "$t" ] 2>/dev/null; then
30
+ echo "Skipped (same file) -> $t"
31
+ continue
32
+ fi
33
+ if [ -f "$t" ] && cmp -s "$SRC" "$t"; then
34
+ echo "Skipped (identical content) -> $t"
35
+ continue
36
+ fi
37
+ cp "$SRC" "$t"
38
+ echo "Copied logo -> $t"
39
+ done
40
+
41
+ if command -v sips >/dev/null 2>&1; then
42
+ if sips -z 630 1200 "$SRC" --out "$OG_TARGET" >/dev/null 2>&1; then
43
+ echo "Generated OG image -> $OG_TARGET (1200x630)"
44
+ else
45
+ cp "$SRC" "$OG_TARGET"
46
+ echo "OG resize failed, copied source -> $OG_TARGET"
47
+ fi
48
+ else
49
+ cp "$SRC" "$OG_TARGET"
50
+ echo "sips not found, copied source as OG -> $OG_TARGET"
51
+ fi
52
+
53
+ echo "Done. Refresh local-console/public-explorer pages to see the new logo + favicon."
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5
+ cd "$ROOT_DIR"
6
+
7
+ STEP=1
8
+
9
+ line() {
10
+ printf '\n%s\n' "------------------------------------------------------------"
11
+ }
12
+
13
+ title() {
14
+ line
15
+ printf '[Step %s] %s\n' "$STEP" "$1"
16
+ STEP=$((STEP + 1))
17
+ }
18
+
19
+ run_cmd() {
20
+ local cmd="$1"
21
+ printf '→ %s\n' "$cmd"
22
+ eval "$cmd"
23
+ }
24
+
25
+ ask_yes_no() {
26
+ local prompt="$1"
27
+ local default="${2:-Y}"
28
+ local answer
29
+ if [ "$default" = "Y" ]; then
30
+ read -r -p "$prompt [Y/n]: " answer || true
31
+ answer="${answer:-Y}"
32
+ else
33
+ read -r -p "$prompt [y/N]: " answer || true
34
+ answer="${answer:-N}"
35
+ fi
36
+ case "$answer" in
37
+ Y|y) return 0 ;;
38
+ *) return 1 ;;
39
+ esac
40
+ }
41
+
42
+ pause_continue() {
43
+ read -r -p "按回车继续..." _ || true
44
+ }
45
+
46
+ title "SilicaClaw Quick Start 启动"
47
+ echo "目录: $ROOT_DIR"
48
+ echo "目标: 用终端一步步完成安装与启动(类似 OpenClaw Quick Start)"
49
+ pause_continue
50
+
51
+ title "检查 Node.js / npm"
52
+ if ! command -v node >/dev/null 2>&1; then
53
+ echo "未找到 node,请先安装 Node.js 18+"
54
+ exit 1
55
+ fi
56
+ if ! command -v npm >/dev/null 2>&1; then
57
+ echo "未找到 npm,请先安装 npm 9+"
58
+ exit 1
59
+ fi
60
+
61
+ NODE_VER="$(node -p "process.versions.node")"
62
+ NPM_VER="$(npm -v)"
63
+ echo "node: $NODE_VER"
64
+ echo "npm : $NPM_VER"
65
+
66
+ if ! node -e "const v=process.versions.node.split('.').map(Number); if (v[0] < 18) process.exit(1)"; then
67
+ echo "Node.js 版本过低,请升级到 18+"
68
+ exit 1
69
+ fi
70
+
71
+ title "安装依赖"
72
+ if [ ! -d "$ROOT_DIR/node_modules" ]; then
73
+ run_cmd "npm install"
74
+ else
75
+ if ask_yes_no "检测到 node_modules,是否仍执行 npm install 同步依赖?" "N"; then
76
+ run_cmd "npm install"
77
+ else
78
+ echo "跳过 npm install"
79
+ fi
80
+ fi
81
+
82
+ title "准备 social.md"
83
+ if [ -f "$ROOT_DIR/social.md" ]; then
84
+ echo "已存在 social.md,保留现有配置。"
85
+ else
86
+ if ask_yes_no "未找到 social.md,是否自动从 social.md.example 生成?" "Y"; then
87
+ run_cmd "cp social.md.example social.md"
88
+ echo "已生成: $ROOT_DIR/social.md"
89
+ else
90
+ echo "跳过生成 social.md(local-console 首启也会自动生成最小模板)"
91
+ fi
92
+ fi
93
+
94
+ title "选择网络模式"
95
+ echo "1) local 单机预览(最快)"
96
+ echo "2) lan 局域网预览(A/B 双机)"
97
+ echo "3) global-preview 非局域网预览(WebRTC)"
98
+ read -r -p "请输入模式编号 [1/2/3] (默认 1): " MODE_PICK || true
99
+ MODE_PICK="${MODE_PICK:-1}"
100
+
101
+ NETWORK_MODE="local"
102
+ NETWORK_ADAPTER="local-event-bus"
103
+ WEBRTC_SIGNALING_URL_VALUE=""
104
+ WEBRTC_ROOM_VALUE="silicaclaw-demo"
105
+
106
+ case "$MODE_PICK" in
107
+ 2)
108
+ NETWORK_MODE="lan"
109
+ NETWORK_ADAPTER="real-preview"
110
+ ;;
111
+ 3)
112
+ NETWORK_MODE="global-preview"
113
+ NETWORK_ADAPTER="webrtc-preview"
114
+ read -r -p "请输入 signaling URL(例如 http://your-server:4510): " WEBRTC_SIGNALING_URL_VALUE
115
+ if [ -z "${WEBRTC_SIGNALING_URL_VALUE:-}" ]; then
116
+ echo "global-preview 必须提供 signaling URL"
117
+ exit 1
118
+ fi
119
+ read -r -p "请输入 room(默认 silicaclaw-demo): " WEBRTC_ROOM_VALUE_INPUT || true
120
+ WEBRTC_ROOM_VALUE="${WEBRTC_ROOM_VALUE_INPUT:-silicaclaw-demo}"
121
+ ;;
122
+ *)
123
+ NETWORK_MODE="local"
124
+ NETWORK_ADAPTER="local-event-bus"
125
+ ;;
126
+ esac
127
+
128
+ echo "已选择: $NETWORK_MODE ($NETWORK_ADAPTER)"
129
+
130
+ title "启动 local-console"
131
+ if [ "$NETWORK_MODE" = "global-preview" ]; then
132
+ echo "将使用以下参数启动:"
133
+ echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
134
+ echo "WEBRTC_SIGNALING_URL=$WEBRTC_SIGNALING_URL_VALUE"
135
+ echo "WEBRTC_ROOM=$WEBRTC_ROOM_VALUE"
136
+ pause_continue
137
+ run_cmd "NETWORK_ADAPTER=$NETWORK_ADAPTER WEBRTC_SIGNALING_URL=$WEBRTC_SIGNALING_URL_VALUE WEBRTC_ROOM=$WEBRTC_ROOM_VALUE npm run local-console"
138
+ else
139
+ echo "将使用以下参数启动:"
140
+ echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
141
+ pause_continue
142
+ run_cmd "NETWORK_ADAPTER=$NETWORK_ADAPTER npm run local-console"
143
+ fi
144
+
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { existsSync, readFileSync } from "node:fs";
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 run(cmd, args, extra = {}) {
13
+ const result = spawnSync(cmd, args, {
14
+ cwd: ROOT_DIR,
15
+ stdio: "inherit",
16
+ env: process.env,
17
+ ...extra,
18
+ });
19
+ if (result.error) {
20
+ console.error(`[silicaclaw] failed to run ${cmd}: ${result.error.message}`);
21
+ process.exit(1);
22
+ }
23
+ process.exit(result.status ?? 0);
24
+ }
25
+
26
+ function readVersion() {
27
+ const versionFile = resolve(ROOT_DIR, "VERSION");
28
+ if (!existsSync(versionFile)) return "unknown";
29
+ return readFileSync(versionFile, "utf8").trim() || "unknown";
30
+ }
31
+
32
+ function help() {
33
+ console.log(`
34
+ SilicaClaw CLI
35
+
36
+ Usage:
37
+ silicaclaw onboard
38
+ silicaclaw local-console
39
+ silicaclaw explorer
40
+ silicaclaw signaling
41
+ silicaclaw doctor
42
+ silicaclaw version
43
+ silicaclaw help
44
+
45
+ Commands:
46
+ onboard Interactive step-by-step onboarding (recommended)
47
+ local-console Start local console (http://localhost:4310)
48
+ explorer Start public explorer (http://localhost:4311)
49
+ signaling Start WebRTC signaling preview server
50
+ doctor Run project checks (npm run health)
51
+ version Print SilicaClaw version
52
+ help Show this help
53
+ `.trim());
54
+ }
55
+
56
+ const cmd = String(process.argv[2] || "help").trim().toLowerCase();
57
+
58
+ switch (cmd) {
59
+ case "onboard":
60
+ run("bash", [resolve(ROOT_DIR, "scripts", "quickstart.sh")]);
61
+ break;
62
+ case "local-console":
63
+ case "console":
64
+ run("npm", ["run", "local-console"]);
65
+ break;
66
+ case "explorer":
67
+ case "public-explorer":
68
+ run("npm", ["run", "public-explorer"]);
69
+ break;
70
+ case "signaling":
71
+ case "webrtc-signaling":
72
+ run("npm", ["run", "webrtc-signaling"]);
73
+ break;
74
+ case "doctor":
75
+ run("npm", ["run", "health"]);
76
+ break;
77
+ case "version":
78
+ case "-v":
79
+ case "--version":
80
+ console.log(readVersion());
81
+ break;
82
+ case "help":
83
+ case "-h":
84
+ case "--help":
85
+ default:
86
+ help();
87
+ break;
88
+ }