@silicaclaw/cli 1.0.0-beta.2 → 1.0.0-beta.21

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 (79) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/INSTALL.md +36 -0
  3. package/README.md +40 -0
  4. package/apps/local-console/public/index.html +81 -63
  5. package/apps/local-console/src/server.ts +41 -21
  6. package/docs/CLOUDFLARE_RELAY.md +61 -0
  7. package/package.json +6 -1
  8. package/packages/core/dist/crypto.d.ts +6 -0
  9. package/packages/core/dist/crypto.js +50 -0
  10. package/packages/core/dist/directory.d.ts +17 -0
  11. package/packages/core/dist/directory.js +145 -0
  12. package/packages/core/dist/identity.d.ts +2 -0
  13. package/packages/core/dist/identity.js +18 -0
  14. package/packages/core/dist/index.d.ts +11 -0
  15. package/packages/core/dist/index.js +27 -0
  16. package/packages/core/dist/indexing.d.ts +6 -0
  17. package/packages/core/dist/indexing.js +43 -0
  18. package/packages/core/dist/presence.d.ts +4 -0
  19. package/packages/core/dist/presence.js +23 -0
  20. package/packages/core/dist/profile.d.ts +4 -0
  21. package/packages/core/dist/profile.js +39 -0
  22. package/packages/core/dist/publicProfileSummary.d.ts +70 -0
  23. package/packages/core/dist/publicProfileSummary.js +103 -0
  24. package/packages/core/dist/socialConfig.d.ts +99 -0
  25. package/packages/core/dist/socialConfig.js +288 -0
  26. package/packages/core/dist/socialResolver.d.ts +46 -0
  27. package/packages/core/dist/socialResolver.js +237 -0
  28. package/packages/core/dist/socialTemplate.d.ts +2 -0
  29. package/packages/core/dist/socialTemplate.js +88 -0
  30. package/packages/core/dist/types.d.ts +37 -0
  31. package/packages/core/dist/types.js +2 -0
  32. package/packages/core/src/socialConfig.ts +7 -6
  33. package/packages/core/src/socialResolver.ts +17 -5
  34. package/packages/network/dist/abstractions/messageEnvelope.d.ts +28 -0
  35. package/packages/network/dist/abstractions/messageEnvelope.js +36 -0
  36. package/packages/network/dist/abstractions/peerDiscovery.d.ts +43 -0
  37. package/packages/network/dist/abstractions/peerDiscovery.js +2 -0
  38. package/packages/network/dist/abstractions/topicCodec.d.ts +4 -0
  39. package/packages/network/dist/abstractions/topicCodec.js +2 -0
  40. package/packages/network/dist/abstractions/transport.d.ts +36 -0
  41. package/packages/network/dist/abstractions/transport.js +2 -0
  42. package/packages/network/dist/codec/jsonMessageEnvelopeCodec.d.ts +5 -0
  43. package/packages/network/dist/codec/jsonMessageEnvelopeCodec.js +24 -0
  44. package/packages/network/dist/codec/jsonTopicCodec.d.ts +5 -0
  45. package/packages/network/dist/codec/jsonTopicCodec.js +12 -0
  46. package/packages/network/dist/discovery/heartbeatPeerDiscovery.d.ts +28 -0
  47. package/packages/network/dist/discovery/heartbeatPeerDiscovery.js +144 -0
  48. package/packages/network/dist/index.d.ts +14 -0
  49. package/packages/network/dist/index.js +30 -0
  50. package/packages/network/dist/localEventBus.d.ts +9 -0
  51. package/packages/network/dist/localEventBus.js +47 -0
  52. package/packages/network/dist/mock.d.ts +8 -0
  53. package/packages/network/dist/mock.js +24 -0
  54. package/packages/network/dist/realPreview.d.ts +105 -0
  55. package/packages/network/dist/realPreview.js +327 -0
  56. package/packages/network/dist/relayPreview.d.ts +133 -0
  57. package/packages/network/dist/relayPreview.js +320 -0
  58. package/packages/network/dist/transport/udpLanBroadcastTransport.d.ts +23 -0
  59. package/packages/network/dist/transport/udpLanBroadcastTransport.js +153 -0
  60. package/packages/network/dist/types.d.ts +6 -0
  61. package/packages/network/dist/types.js +2 -0
  62. package/packages/network/dist/webrtcPreview.d.ts +163 -0
  63. package/packages/network/dist/webrtcPreview.js +844 -0
  64. package/packages/network/src/index.ts +1 -0
  65. package/packages/network/src/relayPreview.ts +425 -0
  66. package/packages/storage/dist/index.d.ts +3 -0
  67. package/packages/storage/dist/index.js +19 -0
  68. package/packages/storage/dist/jsonRepo.d.ts +7 -0
  69. package/packages/storage/dist/jsonRepo.js +29 -0
  70. package/packages/storage/dist/repos.d.ts +21 -0
  71. package/packages/storage/dist/repos.js +41 -0
  72. package/packages/storage/dist/socialRuntimeRepo.d.ts +5 -0
  73. package/packages/storage/dist/socialRuntimeRepo.js +52 -0
  74. package/packages/storage/src/socialRuntimeRepo.ts +3 -3
  75. package/packages/storage/tsconfig.json +6 -1
  76. package/scripts/quickstart.sh +286 -20
  77. package/scripts/silicaclaw-cli.mjs +271 -1
  78. package/scripts/silicaclaw-gateway.mjs +411 -0
  79. package/scripts/webrtc-signaling-server.mjs +52 -1
@@ -1,9 +1,17 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
+ INVOKE_PWD="${INIT_CWD:-$PWD}"
4
5
  ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5
6
  WORK_DIR="$ROOT_DIR"
6
7
  IS_NPX_MODE=0
8
+ DEFAULT_MODE_PICK="${QUICKSTART_DEFAULT_MODE:-3}"
9
+ CONNECT_MODE="${QUICKSTART_CONNECT_MODE:-0}"
10
+
11
+ case "$DEFAULT_MODE_PICK" in
12
+ 1|2|3) ;;
13
+ *) DEFAULT_MODE_PICK="1" ;;
14
+ esac
7
15
 
8
16
  case "$ROOT_DIR" in
9
17
  *"/.npm/_npx/"*)
@@ -29,6 +37,103 @@ run_cmd() {
29
37
  eval "$cmd"
30
38
  }
31
39
 
40
+ run_cmd_may_fail() {
41
+ local cmd="$1"
42
+ printf '→ %s\n' "$cmd"
43
+ set +e
44
+ eval "$cmd"
45
+ local code=$?
46
+ set -e
47
+ return $code
48
+ }
49
+
50
+ first_writable_path_dir() {
51
+ local path_value="${PATH:-}"
52
+ local old_ifs="$IFS"
53
+ IFS=':'
54
+ for d in $path_value; do
55
+ if [ -n "$d" ] && [ -d "$d" ] && [ -w "$d" ] && [ -x "$d" ]; then
56
+ printf '%s' "$d"
57
+ IFS="$old_ifs"
58
+ return 0
59
+ fi
60
+ done
61
+ IFS="$old_ifs"
62
+ return 1
63
+ }
64
+
65
+ install_command_shim() {
66
+ local bindir="$1"
67
+ local script_path="$WORK_DIR/scripts/silicaclaw-cli.mjs"
68
+ local target="$bindir/silicaclaw"
69
+ if [ ! -f "$script_path" ]; then
70
+ echo "未找到 CLI 脚本: $script_path"
71
+ return 1
72
+ fi
73
+ cat >"$target" <<EOF
74
+ #!/usr/bin/env bash
75
+ set -euo pipefail
76
+ exec node "$script_path" "\$@"
77
+ EOF
78
+ chmod +x "$target"
79
+ return 0
80
+ }
81
+
82
+ default_system_bin_dir() {
83
+ if [ -d "/usr/local/bin" ]; then
84
+ printf '/usr/local/bin'
85
+ return 0
86
+ fi
87
+ if [ -d "/opt/homebrew/bin" ]; then
88
+ printf '/opt/homebrew/bin'
89
+ return 0
90
+ fi
91
+ printf '/usr/local/bin'
92
+ }
93
+
94
+ detect_shell_rc_file() {
95
+ local sh_name="${SHELL:-}"
96
+ case "$sh_name" in
97
+ */zsh) printf '%s' "$HOME/.zshrc" ;;
98
+ */bash) printf '%s' "$HOME/.bashrc" ;;
99
+ *)
100
+ if [ -n "${ZSH_VERSION:-}" ]; then
101
+ printf '%s' "$HOME/.zshrc"
102
+ else
103
+ printf '%s' "$HOME/.bashrc"
104
+ fi
105
+ ;;
106
+ esac
107
+ }
108
+
109
+ install_npx_alias() {
110
+ local rc_file
111
+ rc_file="$(detect_shell_rc_file)"
112
+ local begin_mark="# >>> silicaclaw npx alias >>>"
113
+ local end_mark="# <<< silicaclaw npx alias <<<"
114
+ local alias_line="alias silicaclaw='npx -y @silicaclaw/cli@beta'"
115
+
116
+ mkdir -p "$(dirname "$rc_file")"
117
+ touch "$rc_file"
118
+
119
+ if grep -Fq "$begin_mark" "$rc_file"; then
120
+ echo "已存在 silicaclaw alias 配置: $rc_file"
121
+ return 0
122
+ fi
123
+
124
+ {
125
+ echo ""
126
+ echo "$begin_mark"
127
+ echo "$alias_line"
128
+ echo "$end_mark"
129
+ } >>"$rc_file"
130
+
131
+ echo "已写入 alias 到: $rc_file"
132
+ echo "执行以下命令即可在当前 shell 立即生效:"
133
+ echo "source \"$rc_file\""
134
+ return 0
135
+ }
136
+
32
137
  ask_yes_no() {
33
138
  local prompt="$1"
34
139
  local default="${2:-Y}"
@@ -50,6 +155,40 @@ pause_continue() {
50
155
  read -r -p "按回车继续..." _ || true
51
156
  }
52
157
 
158
+ detect_public_ip() {
159
+ local ip=""
160
+ if command -v curl >/dev/null 2>&1; then
161
+ ip="$(curl -fsSL --max-time 3 https://api.ipify.org 2>/dev/null || true)"
162
+ fi
163
+ if [ -z "$ip" ] && command -v curl >/dev/null 2>&1; then
164
+ ip="$(curl -fsSL --max-time 3 https://ifconfig.me 2>/dev/null || true)"
165
+ fi
166
+ if [ -z "$ip" ] && command -v wget >/dev/null 2>&1; then
167
+ ip="$(wget -qO- --timeout=3 https://api.ipify.org 2>/dev/null || true)"
168
+ fi
169
+ ip="$(printf '%s' "$ip" | tr -d '[:space:]')"
170
+ printf '%s' "$ip"
171
+ }
172
+
173
+ url_host() {
174
+ local raw="${1:-}"
175
+ if [ -z "$raw" ]; then
176
+ printf ''
177
+ return 0
178
+ fi
179
+ node -e "try{const u=new URL(process.argv[1]); process.stdout.write(u.hostname||'');}catch{process.stdout.write('');}" "$raw"
180
+ }
181
+
182
+ url_port_or_default() {
183
+ local raw="${1:-}"
184
+ local fallback="${2:-4510}"
185
+ if [ -z "$raw" ]; then
186
+ printf '%s' "$fallback"
187
+ return 0
188
+ fi
189
+ node -e "try{const u=new URL(process.argv[1]); process.stdout.write(String(u.port || '$fallback'));}catch{process.stdout.write('$fallback');}" "$raw"
190
+ }
191
+
53
192
  title "SilicaClaw Quick Start 启动"
54
193
  echo "目录: $ROOT_DIR"
55
194
  echo "目标: 用终端一步步完成安装与启动(类似 OpenClaw Quick Start)"
@@ -57,7 +196,7 @@ pause_continue
57
196
 
58
197
  if [ "$IS_NPX_MODE" -eq 1 ]; then
59
198
  title "选择安装目录(npx 模式)"
60
- DEFAULT_TARGET_DIR="$(pwd)/silicaclaw"
199
+ DEFAULT_TARGET_DIR="$INVOKE_PWD/silicaclaw"
61
200
  TARGET_DIR_INPUT=""
62
201
  read -r -p "请输入安装目录(默认: ${DEFAULT_TARGET_DIR:-$HOME/silicaclaw}): " TARGET_DIR_INPUT || true
63
202
  TARGET_DIR="${TARGET_DIR_INPUT:-$DEFAULT_TARGET_DIR}"
@@ -117,6 +256,68 @@ else
117
256
  fi
118
257
  fi
119
258
 
259
+ title "安装系统命令(silicaclaw)"
260
+ if command -v silicaclaw >/dev/null 2>&1; then
261
+ echo "已检测到系统命令: $(command -v silicaclaw)"
262
+ echo "跳过命令安装。"
263
+ else
264
+ echo "将尝试无 sudo 安装 silicaclaw 命令到 PATH 中可写目录。"
265
+ BIN_DIR="$(first_writable_path_dir || true)"
266
+ INSTALLED=0
267
+ if [ -n "${BIN_DIR:-}" ]; then
268
+ if install_command_shim "$BIN_DIR"; then
269
+ echo "命令已安装: $BIN_DIR/silicaclaw"
270
+ hash -r || true
271
+ if command -v silicaclaw >/dev/null 2>&1; then
272
+ echo "验证成功: $(command -v silicaclaw)"
273
+ INSTALLED=1
274
+ else
275
+ echo "命令已写入,但当前 shell 未立即识别。请新开终端后运行 silicaclaw。"
276
+ fi
277
+ else
278
+ echo "命令安装失败。可继续使用: npx @silicaclaw/cli@beta <command>"
279
+ fi
280
+ else
281
+ echo "当前 PATH 中没有可写目录,无法无 sudo 安装系统命令。"
282
+ fi
283
+
284
+ if [ "$INSTALLED" != "1" ]; then
285
+ SYS_BIN_DIR="$(default_system_bin_dir)"
286
+ echo "为保证开箱即用体验,建议安装到系统目录: $SYS_BIN_DIR/silicaclaw"
287
+ if ask_yes_no "是否使用 sudo 安装系统命令?" "Y"; then
288
+ run_cmd "sudo mkdir -p \"$SYS_BIN_DIR\""
289
+ run_cmd "sudo tee \"$SYS_BIN_DIR/silicaclaw\" >/dev/null <<'EOF'
290
+ #!/usr/bin/env bash
291
+ set -euo pipefail
292
+ exec node \"$WORK_DIR/scripts/silicaclaw-cli.mjs\" \"\$@\"
293
+ EOF"
294
+ run_cmd "sudo chmod +x \"$SYS_BIN_DIR/silicaclaw\""
295
+ hash -r || true
296
+ if command -v silicaclaw >/dev/null 2>&1; then
297
+ echo "验证成功: $(command -v silicaclaw)"
298
+ INSTALLED=1
299
+ else
300
+ echo "安装完成,但当前 shell 未刷新。请新开终端后运行 silicaclaw。"
301
+ fi
302
+ else
303
+ echo "已跳过 sudo 安装。"
304
+ fi
305
+ fi
306
+
307
+ if [ "$INSTALLED" != "1" ]; then
308
+ echo "无需改 PATH/环境变量,也可一键使用 silicaclaw。"
309
+ if ask_yes_no "是否自动写入 shell alias(silicaclaw -> npx @silicaclaw/cli@beta)?" "Y"; then
310
+ if install_npx_alias; then
311
+ echo "alias 安装完成。新开终端后可直接使用 silicaclaw。"
312
+ else
313
+ echo "alias 安装失败。可继续使用: npx @silicaclaw/cli@beta <command>"
314
+ fi
315
+ else
316
+ echo "你仍可继续使用: npx @silicaclaw/cli@beta <command>"
317
+ fi
318
+ fi
319
+ fi
320
+
120
321
  title "准备 social.md"
121
322
  if [ -f "$WORK_DIR/social.md" ]; then
122
323
  echo "已存在 social.md,保留现有配置。"
@@ -132,14 +333,22 @@ fi
132
333
  title "选择网络模式"
133
334
  echo "1) local 单机预览(最快)"
134
335
  echo "2) lan 局域网预览(A/B 双机)"
135
- echo "3) global-preview 非局域网预览(WebRTC)"
136
- read -r -p "请输入模式编号 [1/2/3] (默认 1): " MODE_PICK || true
137
- MODE_PICK="${MODE_PICK:-1}"
336
+ echo "3) global-preview 互联网预览(Relay,推荐)"
337
+ echo "提示: 不确定就直接回车(默认 global-preview)。"
338
+ echo "提示: 互联网场景需要一个所有节点都可访问的 relay/signaling 地址。"
339
+ if [ "$CONNECT_MODE" = "1" ]; then
340
+ MODE_PICK="3"
341
+ echo "connect 模式:已自动选择 global-preview。"
342
+ else
343
+ read -r -p "请输入模式编号 [1/2/3] (默认 ${DEFAULT_MODE_PICK}): " MODE_PICK || true
344
+ MODE_PICK="${MODE_PICK:-$DEFAULT_MODE_PICK}"
345
+ fi
138
346
 
139
347
  NETWORK_MODE="local"
140
348
  NETWORK_ADAPTER="local-event-bus"
141
349
  WEBRTC_SIGNALING_URL_VALUE=""
142
- WEBRTC_ROOM_VALUE="silicaclaw-demo"
350
+ WEBRTC_ROOM_VALUE="silicaclaw-global-preview"
351
+ AUTO_START_SIGNALING=0
143
352
 
144
353
  case "$MODE_PICK" in
145
354
  2)
@@ -148,14 +357,40 @@ case "$MODE_PICK" in
148
357
  ;;
149
358
  3)
150
359
  NETWORK_MODE="global-preview"
151
- NETWORK_ADAPTER="webrtc-preview"
152
- read -r -p "请输入 signaling URL(例如 http://your-server:4510): " WEBRTC_SIGNALING_URL_VALUE
360
+ NETWORK_ADAPTER="relay-preview"
361
+ PUBLIC_IP="$(detect_public_ip)"
362
+ SIGNALING_DEFAULT="${WEBRTC_SIGNALING_URL:-http://localhost:4510}"
363
+ if [ -n "$PUBLIC_IP" ]; then
364
+ SIGNALING_DEFAULT="http://$PUBLIC_IP:4510"
365
+ fi
366
+ echo "提示: signaling 地址需要“所有节点可访问”。"
367
+ if [ -n "$PUBLIC_IP" ]; then
368
+ echo "已检测到本机公网 IP: $PUBLIC_IP"
369
+ echo "如果你这台机器就是 signaling 服务器,可直接回车使用默认值。"
370
+ else
371
+ echo "未检测到公网 IP,将使用默认值: $SIGNALING_DEFAULT"
372
+ echo "如果 signaling 在其他机器,请输入该机器公网地址。"
373
+ fi
374
+ read -r -p "请输入 signaling URL(默认 ${SIGNALING_DEFAULT}): " WEBRTC_SIGNALING_URL_INPUT || true
375
+ WEBRTC_SIGNALING_URL_VALUE="${WEBRTC_SIGNALING_URL_INPUT:-$SIGNALING_DEFAULT}"
153
376
  if [ -z "${WEBRTC_SIGNALING_URL_VALUE:-}" ]; then
154
- echo "global-preview 必须提供 signaling URL"
377
+ echo "global-preview 必须提供公网可达的 signaling URL"
155
378
  exit 1
156
379
  fi
157
- read -r -p "请输入 room(默认 silicaclaw-demo): " WEBRTC_ROOM_VALUE_INPUT || true
158
- WEBRTC_ROOM_VALUE="${WEBRTC_ROOM_VALUE_INPUT:-silicaclaw-demo}"
380
+
381
+ SIGNALING_HOST="$(url_host "$WEBRTC_SIGNALING_URL_VALUE")"
382
+ if [ "$SIGNALING_HOST" = "localhost" ] || [ "$SIGNALING_HOST" = "127.0.0.1" ]; then
383
+ echo "提示: 当前 signaling URL 是本机地址,仅本机可用,不适合跨网络双机演示。"
384
+ fi
385
+
386
+ if ask_yes_no "是否在当前机器自动后台启动 signaling server(用于演示)?" "Y"; then
387
+ AUTO_START_SIGNALING=1
388
+ SIGNALING_PORT_VALUE="$(url_port_or_default "$WEBRTC_SIGNALING_URL_VALUE" "4510")"
389
+ echo "将尝试以 PORT=$SIGNALING_PORT_VALUE 后台启动 signaling server"
390
+ fi
391
+
392
+ read -r -p "请输入 room(默认 silicaclaw-global-preview): " WEBRTC_ROOM_VALUE_INPUT || true
393
+ WEBRTC_ROOM_VALUE="${WEBRTC_ROOM_VALUE_INPUT:-silicaclaw-global-preview}"
159
394
  ;;
160
395
  *)
161
396
  NETWORK_MODE="local"
@@ -166,16 +401,47 @@ esac
166
401
  echo "已选择: $NETWORK_MODE ($NETWORK_ADAPTER)"
167
402
 
168
403
  title "启动 local-console"
169
- if [ "$NETWORK_MODE" = "global-preview" ]; then
170
- echo "将使用以下参数启动:"
171
- echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
172
- echo "WEBRTC_SIGNALING_URL=$WEBRTC_SIGNALING_URL_VALUE"
173
- echo "WEBRTC_ROOM=$WEBRTC_ROOM_VALUE"
174
- pause_continue
175
- run_cmd "cd \"$WORK_DIR\" && NETWORK_ADAPTER=$NETWORK_ADAPTER WEBRTC_SIGNALING_URL=$WEBRTC_SIGNALING_URL_VALUE WEBRTC_ROOM=$WEBRTC_ROOM_VALUE npm run local-console"
404
+ echo "1) gateway(推荐) 后台服务模式,可用 start/stop/restart/status 管理"
405
+ echo "2) dev watch 前台开发模式(tsx watch)"
406
+ read -r -p "请选择启动方式 [1/2] (默认 1): " START_MODE_PICK || true
407
+ START_MODE_PICK="${START_MODE_PICK:-1}"
408
+
409
+ if [ "$START_MODE_PICK" = "2" ]; then
410
+ if [ "$NETWORK_MODE" = "global-preview" ]; then
411
+ if [ "$AUTO_START_SIGNALING" -eq 1 ]; then
412
+ mkdir -p "$WORK_DIR/.silicaclaw"
413
+ SIGNALING_LOG="$WORK_DIR/.silicaclaw/signaling.log"
414
+ SIGNALING_PID_FILE="$WORK_DIR/.silicaclaw/signaling.pid"
415
+ run_cmd "cd \"$WORK_DIR\" && PORT=${SIGNALING_PORT_VALUE:-4510} nohup npm run webrtc-signaling > \"$SIGNALING_LOG\" 2>&1 & echo \$! > \"$SIGNALING_PID_FILE\""
416
+ echo "已后台启动 signaling server,日志: $SIGNALING_LOG"
417
+ echo "停止 signaling: kill \$(cat \"$SIGNALING_PID_FILE\")"
418
+ fi
419
+ echo "将使用以下参数启动(dev watch):"
420
+ echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
421
+ echo "WEBRTC_SIGNALING_URL=$WEBRTC_SIGNALING_URL_VALUE"
422
+ echo "WEBRTC_ROOM=$WEBRTC_ROOM_VALUE"
423
+ pause_continue
424
+ run_cmd "cd \"$WORK_DIR\" && NETWORK_ADAPTER=$NETWORK_ADAPTER WEBRTC_SIGNALING_URL=$WEBRTC_SIGNALING_URL_VALUE WEBRTC_ROOM=$WEBRTC_ROOM_VALUE npm run local-console"
425
+ else
426
+ echo "将使用以下参数启动(dev watch):"
427
+ echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
428
+ pause_continue
429
+ run_cmd "cd \"$WORK_DIR\" && NETWORK_ADAPTER=$NETWORK_ADAPTER npm run local-console"
430
+ fi
176
431
  else
177
- echo "将使用以下参数启动:"
178
- echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
432
+ GATEWAY_CMD="cd \"$WORK_DIR\" && npm run gateway -- start --mode=$NETWORK_MODE"
433
+ if [ "$NETWORK_MODE" = "global-preview" ]; then
434
+ GATEWAY_CMD="$GATEWAY_CMD --signaling-url=$WEBRTC_SIGNALING_URL_VALUE --room=$WEBRTC_ROOM_VALUE"
435
+ fi
436
+ echo "将使用 gateway 后台启动:"
437
+ echo "$GATEWAY_CMD"
179
438
  pause_continue
180
- run_cmd "cd \"$WORK_DIR\" && NETWORK_ADAPTER=$NETWORK_ADAPTER npm run local-console"
439
+ run_cmd "$GATEWAY_CMD"
440
+ echo ""
441
+ echo "已启动完成。常用命令:"
442
+ echo "cd \"$WORK_DIR\" && npm run gateway -- status"
443
+ echo "cd \"$WORK_DIR\" && npm run gateway -- logs local-console"
444
+ echo "cd \"$WORK_DIR\" && npm run gateway -- stop"
445
+ echo ""
446
+ echo "打开: http://localhost:4310"
181
447
  fi
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from "node:child_process";
4
- import { existsSync, readFileSync } from "node:fs";
4
+ import { accessSync, constants, cpSync, existsSync, readFileSync } from "node:fs";
5
5
  import { dirname, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
@@ -23,18 +23,254 @@ function run(cmd, args, extra = {}) {
23
23
  process.exit(result.status ?? 0);
24
24
  }
25
25
 
26
+ function runCapture(cmd, args, extra = {}) {
27
+ const result = spawnSync(cmd, args, {
28
+ cwd: ROOT_DIR,
29
+ stdio: ["ignore", "pipe", "pipe"],
30
+ encoding: "utf8",
31
+ env: process.env,
32
+ ...extra,
33
+ });
34
+ if (result.error) {
35
+ throw result.error;
36
+ }
37
+ return result;
38
+ }
39
+
40
+ function runInherit(cmd, args, extra = {}) {
41
+ const result = spawnSync(cmd, args, {
42
+ cwd: ROOT_DIR,
43
+ stdio: "inherit",
44
+ env: process.env,
45
+ ...extra,
46
+ });
47
+ if (result.error) {
48
+ throw result.error;
49
+ }
50
+ return result;
51
+ }
52
+
26
53
  function readVersion() {
27
54
  const versionFile = resolve(ROOT_DIR, "VERSION");
28
55
  if (!existsSync(versionFile)) return "unknown";
29
56
  return readFileSync(versionFile, "utf8").trim() || "unknown";
30
57
  }
31
58
 
59
+ function readPackageVersion() {
60
+ const pkgFile = resolve(ROOT_DIR, "package.json");
61
+ if (!existsSync(pkgFile)) return "unknown";
62
+ try {
63
+ const pkg = JSON.parse(readFileSync(pkgFile, "utf8"));
64
+ return String(pkg.version || "unknown");
65
+ } catch {
66
+ return "unknown";
67
+ }
68
+ }
69
+
70
+ function isNpxRun() {
71
+ return ROOT_DIR.includes("/.npm/_npx/");
72
+ }
73
+
74
+ function canWriteGlobalPrefix() {
75
+ try {
76
+ const prefixResult = runCapture("npm", ["prefix", "-g"]);
77
+ if ((prefixResult.status ?? 1) !== 0) return false;
78
+ const prefix = String(prefixResult.stdout || "").trim();
79
+ if (!prefix) return false;
80
+ const targetDir = resolve(prefix, "lib", "node_modules");
81
+ accessSync(targetDir, constants.W_OK);
82
+ return true;
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
87
+
88
+ function showUpdateGuide(current, latest, beta) {
89
+ const npxRuntime = isNpxRun();
90
+ console.log("SilicaClaw update check");
91
+ console.log(`current: ${current}`);
92
+ console.log(`latest : ${latest || "-"}`);
93
+ console.log(`beta : ${beta || "-"}`);
94
+ console.log("");
95
+
96
+ const upToDate = Boolean(beta) && current === beta;
97
+ if (upToDate) {
98
+ console.log("You are already on the latest beta.");
99
+ } else {
100
+ console.log("Update available.");
101
+ }
102
+ console.log("");
103
+ console.log("Quick next commands:");
104
+ console.log("1) Start internet gateway");
105
+ console.log(" silicaclaw start --mode=global-preview --signaling-url=https://your-relay.example");
106
+ console.log("2) Check gateway status");
107
+ console.log(" silicaclaw status");
108
+ console.log("3) npx one-shot (no alias/global install)");
109
+ console.log(" npx -y @silicaclaw/cli@beta start --mode=global-preview --signaling-url=https://your-relay.example");
110
+ console.log("");
111
+
112
+ const writableGlobal = canWriteGlobalPrefix();
113
+ if (!npxRuntime && writableGlobal) {
114
+ console.log("Optional global install:");
115
+ console.log(" npm i -g @silicaclaw/cli@beta");
116
+ console.log(" silicaclaw version");
117
+ console.log("");
118
+ } else if (!npxRuntime) {
119
+ console.log("Global install skipped: npm global directory is not writable (likely EACCES).");
120
+ console.log("");
121
+ }
122
+ if (npxRuntime) {
123
+ console.log("Detected npx runtime.");
124
+ console.log("If `silicaclaw` is unavailable in this shell, use the npx one-shot command above.");
125
+ }
126
+ }
127
+
128
+ function getGatewayStatus() {
129
+ try {
130
+ const result = runCapture("node", [resolve(ROOT_DIR, "scripts", "silicaclaw-gateway.mjs"), "status"], {
131
+ cwd: process.cwd(),
132
+ });
133
+ if ((result.status ?? 1) !== 0) return null;
134
+ const text = String(result.stdout || "").trim();
135
+ return text ? JSON.parse(text) : null;
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+
141
+ function isManagedAppDir(appDir) {
142
+ if (!appDir || !existsSync(resolve(appDir, "package.json"))) return false;
143
+ try {
144
+ const pkg = JSON.parse(readFileSync(resolve(appDir, "package.json"), "utf8"));
145
+ const name = String(pkg?.name || "");
146
+ return name === "@silicaclaw/cli" || name === "silicaclaw";
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ function syncCurrentPackageToAppDir(appDir) {
153
+ if (!appDir || resolve(appDir) === ROOT_DIR) return false;
154
+ if (!isManagedAppDir(appDir)) return false;
155
+
156
+ const entries = [
157
+ "apps/local-console",
158
+ "apps/public-explorer",
159
+ "packages/core",
160
+ "packages/network",
161
+ "packages/storage",
162
+ "scripts",
163
+ "README.md",
164
+ "INSTALL.md",
165
+ "CHANGELOG.md",
166
+ "ARCHITECTURE.md",
167
+ "ROADMAP.md",
168
+ "SOCIAL_MD_SPEC.md",
169
+ "DEMO_GUIDE.md",
170
+ "RELEASE_NOTES_v1.0.md",
171
+ "social.md.example",
172
+ "openclaw.social.md.example",
173
+ "VERSION",
174
+ "package.json",
175
+ "package-lock.json",
176
+ ];
177
+
178
+ for (const rel of entries) {
179
+ const src = resolve(ROOT_DIR, rel);
180
+ if (!existsSync(src)) continue;
181
+ const dst = resolve(appDir, rel);
182
+ cpSync(src, dst, { recursive: true, force: true });
183
+ }
184
+ return true;
185
+ }
186
+
187
+ function restartGatewayIfRunning() {
188
+ const status = getGatewayStatus();
189
+ const appDir = status?.app_dir ? String(status.app_dir) : "";
190
+ const synced = syncCurrentPackageToAppDir(appDir);
191
+ if (synced) {
192
+ console.log(`Synced runtime files to app_dir: ${appDir}`);
193
+ }
194
+
195
+ const localRunning = Boolean(status?.local_console?.running);
196
+ const signalingRunning = Boolean(status?.signaling?.running);
197
+ if (!localRunning && !signalingRunning) {
198
+ console.log("Gateway not running: no restart needed.");
199
+ return;
200
+ }
201
+
202
+ const mode = String(status?.mode || "local");
203
+ const args = [resolve(ROOT_DIR, "scripts", "silicaclaw-gateway.mjs"), "restart", `--mode=${mode}`];
204
+ if (mode === "global-preview" && status?.signaling?.url) {
205
+ args.push(`--signaling-url=${status.signaling.url}`);
206
+ }
207
+ if (mode === "global-preview" && status?.signaling?.room) {
208
+ args.push(`--room=${status.signaling.room}`);
209
+ }
210
+
211
+ console.log("Refreshing gateway services...");
212
+ runInherit("node", args, { cwd: process.cwd() });
213
+ }
214
+
215
+ function tryGlobalUpgrade(beta) {
216
+ const writableGlobal = canWriteGlobalPrefix();
217
+ if (!writableGlobal) return false;
218
+ console.log(`Installing @silicaclaw/cli@${beta} globally...`);
219
+ const result = runInherit("npm", ["i", "-g", `@silicaclaw/cli@${beta}`]);
220
+ return (result.status ?? 1) === 0;
221
+ }
222
+
223
+ function update() {
224
+ const current = readPackageVersion();
225
+ try {
226
+ const result = runCapture("npm", ["view", "@silicaclaw/cli", "dist-tags", "--json"]);
227
+ if ((result.status ?? 1) !== 0) {
228
+ console.error("Failed to query npm dist-tags.");
229
+ if (result.stderr) console.error(result.stderr.trim());
230
+ process.exit(result.status ?? 1);
231
+ }
232
+ const text = String(result.stdout || "").trim();
233
+ const tags = text ? JSON.parse(text) : {};
234
+ const latest = tags.latest ? String(tags.latest) : "";
235
+ const beta = tags.beta ? String(tags.beta) : "";
236
+ showUpdateGuide(current, latest, beta);
237
+ const hasNewBeta = Boolean(beta) && beta !== current;
238
+ const npxRuntime = isNpxRun();
239
+
240
+ if (hasNewBeta) {
241
+ if (npxRuntime) {
242
+ console.log(`New beta detected (${beta}). npx will use latest on next run.`);
243
+ } else if (tryGlobalUpgrade(beta)) {
244
+ console.log(`Global upgrade completed: ${beta}`);
245
+ } else {
246
+ console.log("Skipped global upgrade (no write permission or upgrade failed).");
247
+ }
248
+ }
249
+
250
+ restartGatewayIfRunning();
251
+ process.exit(0);
252
+ } catch (error) {
253
+ console.error(`Update check failed: ${error.message}`);
254
+ console.log("Try manually:");
255
+ console.log("npm view @silicaclaw/cli dist-tags --json");
256
+ process.exit(1);
257
+ }
258
+ }
259
+
32
260
  function help() {
33
261
  console.log(`
34
262
  SilicaClaw CLI
35
263
 
36
264
  Usage:
37
265
  silicaclaw onboard
266
+ silicaclaw connect
267
+ silicaclaw update
268
+ silicaclaw start [--mode=local|lan|global-preview]
269
+ silicaclaw stop
270
+ silicaclaw restart [--mode=local|lan|global-preview]
271
+ silicaclaw status
272
+ silicaclaw logs [local-console|signaling]
273
+ silicaclaw gateway <start|stop|restart|status|logs>
38
274
  silicaclaw local-console
39
275
  silicaclaw explorer
40
276
  silicaclaw signaling
@@ -44,6 +280,14 @@ Usage:
44
280
 
45
281
  Commands:
46
282
  onboard Interactive step-by-step onboarding (recommended)
283
+ connect Cross-network connect wizard (global-preview first)
284
+ update Check latest npm version and show upgrade commands
285
+ start Start gateway-managed background services
286
+ stop Stop gateway-managed background services
287
+ restart Restart gateway-managed background services
288
+ status Show gateway-managed service status
289
+ logs Show gateway-managed service logs
290
+ gateway Manage background services (start/stop/restart/status/logs)
47
291
  local-console Start local console (http://localhost:4310)
48
292
  explorer Start public explorer (http://localhost:4311)
49
293
  signaling Start WebRTC signaling preview server
@@ -59,6 +303,32 @@ switch (cmd) {
59
303
  case "onboard":
60
304
  run("bash", [resolve(ROOT_DIR, "scripts", "quickstart.sh")]);
61
305
  break;
306
+ case "connect":
307
+ run("bash", [resolve(ROOT_DIR, "scripts", "quickstart.sh")], {
308
+ env: {
309
+ ...process.env,
310
+ QUICKSTART_DEFAULT_MODE: "3",
311
+ QUICKSTART_CONNECT_MODE: "1",
312
+ },
313
+ });
314
+ break;
315
+ case "update":
316
+ update();
317
+ break;
318
+ case "gateway":
319
+ run("node", [resolve(ROOT_DIR, "scripts", "silicaclaw-gateway.mjs"), ...process.argv.slice(3)], {
320
+ cwd: process.cwd(),
321
+ });
322
+ break;
323
+ case "start":
324
+ case "stop":
325
+ case "restart":
326
+ case "status":
327
+ case "logs":
328
+ run("node", [resolve(ROOT_DIR, "scripts", "silicaclaw-gateway.mjs"), cmd, ...process.argv.slice(3)], {
329
+ cwd: process.cwd(),
330
+ });
331
+ break;
62
332
  case "local-console":
63
333
  case "console":
64
334
  run("npm", ["run", "local-console"]);