@silicaclaw/cli 1.0.0-beta.0 → 1.0.0-beta.10

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 (67) hide show
  1. package/INSTALL.md +31 -4
  2. package/README.md +36 -5
  3. package/package.json +6 -1
  4. package/packages/core/dist/crypto.d.ts +6 -0
  5. package/packages/core/dist/crypto.js +50 -0
  6. package/packages/core/dist/directory.d.ts +17 -0
  7. package/packages/core/dist/directory.js +145 -0
  8. package/packages/core/dist/identity.d.ts +2 -0
  9. package/packages/core/dist/identity.js +18 -0
  10. package/packages/core/dist/index.d.ts +11 -0
  11. package/packages/core/dist/index.js +27 -0
  12. package/packages/core/dist/indexing.d.ts +6 -0
  13. package/packages/core/dist/indexing.js +43 -0
  14. package/packages/core/dist/presence.d.ts +4 -0
  15. package/packages/core/dist/presence.js +23 -0
  16. package/packages/core/dist/profile.d.ts +4 -0
  17. package/packages/core/dist/profile.js +39 -0
  18. package/packages/core/dist/publicProfileSummary.d.ts +70 -0
  19. package/packages/core/dist/publicProfileSummary.js +103 -0
  20. package/packages/core/dist/socialConfig.d.ts +99 -0
  21. package/packages/core/dist/socialConfig.js +287 -0
  22. package/packages/core/dist/socialResolver.d.ts +46 -0
  23. package/packages/core/dist/socialResolver.js +227 -0
  24. package/packages/core/dist/socialTemplate.d.ts +2 -0
  25. package/packages/core/dist/socialTemplate.js +88 -0
  26. package/packages/core/dist/types.d.ts +37 -0
  27. package/packages/core/dist/types.js +2 -0
  28. package/packages/network/dist/abstractions/messageEnvelope.d.ts +28 -0
  29. package/packages/network/dist/abstractions/messageEnvelope.js +36 -0
  30. package/packages/network/dist/abstractions/peerDiscovery.d.ts +43 -0
  31. package/packages/network/dist/abstractions/peerDiscovery.js +2 -0
  32. package/packages/network/dist/abstractions/topicCodec.d.ts +4 -0
  33. package/packages/network/dist/abstractions/topicCodec.js +2 -0
  34. package/packages/network/dist/abstractions/transport.d.ts +36 -0
  35. package/packages/network/dist/abstractions/transport.js +2 -0
  36. package/packages/network/dist/codec/jsonMessageEnvelopeCodec.d.ts +5 -0
  37. package/packages/network/dist/codec/jsonMessageEnvelopeCodec.js +24 -0
  38. package/packages/network/dist/codec/jsonTopicCodec.d.ts +5 -0
  39. package/packages/network/dist/codec/jsonTopicCodec.js +12 -0
  40. package/packages/network/dist/discovery/heartbeatPeerDiscovery.d.ts +28 -0
  41. package/packages/network/dist/discovery/heartbeatPeerDiscovery.js +144 -0
  42. package/packages/network/dist/index.d.ts +13 -0
  43. package/packages/network/dist/index.js +29 -0
  44. package/packages/network/dist/localEventBus.d.ts +9 -0
  45. package/packages/network/dist/localEventBus.js +47 -0
  46. package/packages/network/dist/mock.d.ts +8 -0
  47. package/packages/network/dist/mock.js +24 -0
  48. package/packages/network/dist/realPreview.d.ts +105 -0
  49. package/packages/network/dist/realPreview.js +327 -0
  50. package/packages/network/dist/transport/udpLanBroadcastTransport.d.ts +23 -0
  51. package/packages/network/dist/transport/udpLanBroadcastTransport.js +153 -0
  52. package/packages/network/dist/types.d.ts +6 -0
  53. package/packages/network/dist/types.js +2 -0
  54. package/packages/network/dist/webrtcPreview.d.ts +163 -0
  55. package/packages/network/dist/webrtcPreview.js +844 -0
  56. package/packages/storage/dist/index.d.ts +3 -0
  57. package/packages/storage/dist/index.js +19 -0
  58. package/packages/storage/dist/jsonRepo.d.ts +7 -0
  59. package/packages/storage/dist/jsonRepo.js +29 -0
  60. package/packages/storage/dist/repos.d.ts +21 -0
  61. package/packages/storage/dist/repos.js +41 -0
  62. package/packages/storage/dist/socialRuntimeRepo.d.ts +5 -0
  63. package/packages/storage/dist/socialRuntimeRepo.js +52 -0
  64. package/packages/storage/tsconfig.json +6 -1
  65. package/scripts/quickstart.sh +198 -21
  66. package/scripts/silicaclaw-cli.mjs +103 -0
  67. package/scripts/silicaclaw-gateway.mjs +321 -0
@@ -0,0 +1,3 @@
1
+ export * from "./jsonRepo";
2
+ export * from "./repos";
3
+ export * from "./socialRuntimeRepo";
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./jsonRepo"), exports);
18
+ __exportStar(require("./repos"), exports);
19
+ __exportStar(require("./socialRuntimeRepo"), exports);
@@ -0,0 +1,7 @@
1
+ export declare class JsonFileRepo<T> {
2
+ private filePath;
3
+ private fallback;
4
+ constructor(filePath: string, fallback: () => T);
5
+ get(): Promise<T>;
6
+ set(value: T): Promise<void>;
7
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JsonFileRepo = void 0;
4
+ const promises_1 = require("fs/promises");
5
+ const path_1 = require("path");
6
+ class JsonFileRepo {
7
+ filePath;
8
+ fallback;
9
+ constructor(filePath, fallback) {
10
+ this.filePath = filePath;
11
+ this.fallback = fallback;
12
+ }
13
+ async get() {
14
+ try {
15
+ const raw = await (0, promises_1.readFile)(this.filePath, "utf8");
16
+ return JSON.parse(raw);
17
+ }
18
+ catch {
19
+ const seed = this.fallback();
20
+ await this.set(seed);
21
+ return seed;
22
+ }
23
+ }
24
+ async set(value) {
25
+ await (0, promises_1.mkdir)((0, path_1.dirname)(this.filePath), { recursive: true });
26
+ await (0, promises_1.writeFile)(this.filePath, JSON.stringify(value, null, 2), "utf8");
27
+ }
28
+ }
29
+ exports.JsonFileRepo = JsonFileRepo;
@@ -0,0 +1,21 @@
1
+ import { AgentIdentity, DirectoryState, PublicProfile } from "@silicaclaw/core";
2
+ import { JsonFileRepo } from "./jsonRepo";
3
+ export type LogEntry = {
4
+ id: string;
5
+ level: "info" | "warn" | "error";
6
+ message: string;
7
+ timestamp: number;
8
+ };
9
+ export declare class IdentityRepo extends JsonFileRepo<AgentIdentity | null> {
10
+ constructor(rootDir?: string);
11
+ }
12
+ export declare class ProfileRepo extends JsonFileRepo<PublicProfile | null> {
13
+ constructor(rootDir?: string);
14
+ }
15
+ export declare class CacheRepo extends JsonFileRepo<DirectoryState> {
16
+ constructor(rootDir?: string);
17
+ }
18
+ export declare class LogRepo extends JsonFileRepo<LogEntry[]> {
19
+ constructor(rootDir?: string);
20
+ append(entry: Omit<LogEntry, "id">): Promise<void>;
21
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LogRepo = exports.CacheRepo = exports.ProfileRepo = exports.IdentityRepo = void 0;
4
+ const path_1 = require("path");
5
+ const core_1 = require("@silicaclaw/core");
6
+ const jsonRepo_1 = require("./jsonRepo");
7
+ class IdentityRepo extends jsonRepo_1.JsonFileRepo {
8
+ constructor(rootDir = process.cwd()) {
9
+ super((0, path_1.resolve)(rootDir, "data", "identity.json"), () => null);
10
+ }
11
+ }
12
+ exports.IdentityRepo = IdentityRepo;
13
+ class ProfileRepo extends jsonRepo_1.JsonFileRepo {
14
+ constructor(rootDir = process.cwd()) {
15
+ super((0, path_1.resolve)(rootDir, "data", "profile.json"), () => null);
16
+ }
17
+ }
18
+ exports.ProfileRepo = ProfileRepo;
19
+ class CacheRepo extends jsonRepo_1.JsonFileRepo {
20
+ constructor(rootDir = process.cwd()) {
21
+ super((0, path_1.resolve)(rootDir, "data", "cache.json"), () => (0, core_1.createEmptyDirectoryState)());
22
+ }
23
+ }
24
+ exports.CacheRepo = CacheRepo;
25
+ class LogRepo extends jsonRepo_1.JsonFileRepo {
26
+ constructor(rootDir = process.cwd()) {
27
+ super((0, path_1.resolve)(rootDir, "data", "logs.json"), () => []);
28
+ }
29
+ async append(entry) {
30
+ const current = await this.get();
31
+ const next = [
32
+ {
33
+ id: `${entry.timestamp}-${Math.random().toString(36).slice(2, 8)}`,
34
+ ...entry,
35
+ },
36
+ ...current,
37
+ ].slice(0, 50);
38
+ await this.set(next);
39
+ }
40
+ }
41
+ exports.LogRepo = LogRepo;
@@ -0,0 +1,5 @@
1
+ import { SocialRuntimeConfig } from "@silicaclaw/core";
2
+ import { JsonFileRepo } from "./jsonRepo";
3
+ export declare class SocialRuntimeRepo extends JsonFileRepo<SocialRuntimeConfig> {
4
+ constructor(rootDir?: string);
5
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SocialRuntimeRepo = void 0;
4
+ const path_1 = require("path");
5
+ const jsonRepo_1 = require("./jsonRepo");
6
+ function emptyRuntime() {
7
+ return {
8
+ enabled: true,
9
+ public_enabled: false,
10
+ source_path: null,
11
+ last_loaded_at: 0,
12
+ social_found: false,
13
+ parse_error: null,
14
+ resolved_identity: null,
15
+ resolved_profile: null,
16
+ resolved_network: {
17
+ mode: "lan",
18
+ adapter: "real-preview",
19
+ namespace: "silicaclaw.preview",
20
+ port: null,
21
+ signaling_url: "http://localhost:4510",
22
+ signaling_urls: [],
23
+ room: "silicaclaw-room",
24
+ seed_peers: [],
25
+ bootstrap_hints: [],
26
+ bootstrap_sources: [],
27
+ },
28
+ resolved_discovery: {
29
+ discoverable: true,
30
+ allow_profile_broadcast: true,
31
+ allow_presence_broadcast: true,
32
+ },
33
+ visibility: {
34
+ show_display_name: true,
35
+ show_bio: true,
36
+ show_tags: true,
37
+ show_agent_id: true,
38
+ show_last_seen: true,
39
+ show_capabilities_summary: true,
40
+ },
41
+ openclaw: {
42
+ bind_existing_identity: true,
43
+ use_openclaw_profile_if_available: true,
44
+ },
45
+ };
46
+ }
47
+ class SocialRuntimeRepo extends jsonRepo_1.JsonFileRepo {
48
+ constructor(rootDir = process.cwd()) {
49
+ super((0, path_1.resolve)(rootDir, ".silicaclaw", "social.runtime.json"), emptyRuntime);
50
+ }
51
+ }
52
+ exports.SocialRuntimeRepo = SocialRuntimeRepo;
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "extends": "../../tsconfig.base.json",
3
3
  "compilerOptions": {
4
- "outDir": "./dist"
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "baseUrl": ".",
7
+ "paths": {
8
+ "@silicaclaw/core": ["../../node_modules/@silicaclaw/core/dist/index.d.ts"]
9
+ }
5
10
  },
6
11
  "include": ["src"]
7
12
  }
@@ -1,8 +1,23 @@
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
- cd "$ROOT_DIR"
6
+ WORK_DIR="$ROOT_DIR"
7
+ IS_NPX_MODE=0
8
+ DEFAULT_MODE_PICK="${QUICKSTART_DEFAULT_MODE:-1}"
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
15
+
16
+ case "$ROOT_DIR" in
17
+ *"/.npm/_npx/"*)
18
+ IS_NPX_MODE=1
19
+ ;;
20
+ esac
6
21
 
7
22
  STEP=1
8
23
 
@@ -22,6 +37,16 @@ run_cmd() {
22
37
  eval "$cmd"
23
38
  }
24
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
+
25
50
  ask_yes_no() {
26
51
  local prompt="$1"
27
52
  local default="${2:-Y}"
@@ -43,11 +68,76 @@ pause_continue() {
43
68
  read -r -p "按回车继续..." _ || true
44
69
  }
45
70
 
71
+ detect_public_ip() {
72
+ local ip=""
73
+ if command -v curl >/dev/null 2>&1; then
74
+ ip="$(curl -fsSL --max-time 3 https://api.ipify.org 2>/dev/null || true)"
75
+ fi
76
+ if [ -z "$ip" ] && command -v curl >/dev/null 2>&1; then
77
+ ip="$(curl -fsSL --max-time 3 https://ifconfig.me 2>/dev/null || true)"
78
+ fi
79
+ if [ -z "$ip" ] && command -v wget >/dev/null 2>&1; then
80
+ ip="$(wget -qO- --timeout=3 https://api.ipify.org 2>/dev/null || true)"
81
+ fi
82
+ ip="$(printf '%s' "$ip" | tr -d '[:space:]')"
83
+ printf '%s' "$ip"
84
+ }
85
+
86
+ url_host() {
87
+ local raw="${1:-}"
88
+ if [ -z "$raw" ]; then
89
+ printf ''
90
+ return 0
91
+ fi
92
+ node -e "try{const u=new URL(process.argv[1]); process.stdout.write(u.hostname||'');}catch{process.stdout.write('');}" "$raw"
93
+ }
94
+
95
+ url_port_or_default() {
96
+ local raw="${1:-}"
97
+ local fallback="${2:-4510}"
98
+ if [ -z "$raw" ]; then
99
+ printf '%s' "$fallback"
100
+ return 0
101
+ fi
102
+ node -e "try{const u=new URL(process.argv[1]); process.stdout.write(String(u.port || '$fallback'));}catch{process.stdout.write('$fallback');}" "$raw"
103
+ }
104
+
46
105
  title "SilicaClaw Quick Start 启动"
47
106
  echo "目录: $ROOT_DIR"
48
107
  echo "目标: 用终端一步步完成安装与启动(类似 OpenClaw Quick Start)"
49
108
  pause_continue
50
109
 
110
+ if [ "$IS_NPX_MODE" -eq 1 ]; then
111
+ title "选择安装目录(npx 模式)"
112
+ DEFAULT_TARGET_DIR="$INVOKE_PWD/silicaclaw"
113
+ TARGET_DIR_INPUT=""
114
+ read -r -p "请输入安装目录(默认: ${DEFAULT_TARGET_DIR:-$HOME/silicaclaw}): " TARGET_DIR_INPUT || true
115
+ TARGET_DIR="${TARGET_DIR_INPUT:-$DEFAULT_TARGET_DIR}"
116
+ TARGET_DIR="${TARGET_DIR/#\~/$HOME}"
117
+
118
+ if [ -e "$TARGET_DIR" ] && [ ! -d "$TARGET_DIR" ]; then
119
+ echo "目标路径存在且不是目录: $TARGET_DIR"
120
+ exit 1
121
+ fi
122
+
123
+ mkdir -p "$TARGET_DIR"
124
+ echo "正在复制项目文件到: $TARGET_DIR"
125
+ if command -v rsync >/dev/null 2>&1; then
126
+ rsync -a --delete \
127
+ --exclude '.git/' \
128
+ --exclude 'node_modules/' \
129
+ --exclude '.npm-cache/' \
130
+ --exclude '*.tgz' \
131
+ "$ROOT_DIR/" "$TARGET_DIR/"
132
+ else
133
+ cp -R "$ROOT_DIR/." "$TARGET_DIR/"
134
+ rm -rf "$TARGET_DIR/.git" "$TARGET_DIR/node_modules" "$TARGET_DIR/.npm-cache"
135
+ fi
136
+ WORK_DIR="$TARGET_DIR"
137
+ echo "工作目录已切换到: $WORK_DIR"
138
+ pause_continue
139
+ fi
140
+
51
141
  title "检查 Node.js / npm"
52
142
  if ! command -v node >/dev/null 2>&1; then
53
143
  echo "未找到 node,请先安装 Node.js 18+"
@@ -69,23 +159,46 @@ if ! node -e "const v=process.versions.node.split('.').map(Number); if (v[0] < 1
69
159
  fi
70
160
 
71
161
  title "安装依赖"
72
- if [ ! -d "$ROOT_DIR/node_modules" ]; then
73
- run_cmd "npm install"
162
+ if [ ! -d "$WORK_DIR/node_modules" ]; then
163
+ run_cmd "cd \"$WORK_DIR\" && npm_config_cache=\"$WORK_DIR/.npm-cache\" npm install"
74
164
  else
75
165
  if ask_yes_no "检测到 node_modules,是否仍执行 npm install 同步依赖?" "N"; then
76
- run_cmd "npm install"
166
+ run_cmd "cd \"$WORK_DIR\" && npm_config_cache=\"$WORK_DIR/.npm-cache\" npm install"
77
167
  else
78
168
  echo "跳过 npm install"
79
169
  fi
80
170
  fi
81
171
 
172
+ title "安装系统命令(silicaclaw)"
173
+ if command -v silicaclaw >/dev/null 2>&1; then
174
+ echo "已检测到系统命令: $(command -v silicaclaw)"
175
+ echo "跳过全局安装。"
176
+ else
177
+ echo "为了获得生产体验(可直接运行 silicaclaw ...),建议安装全局命令。"
178
+ if ask_yes_no "是否现在安装全局命令 @silicaclaw/cli@beta?" "Y"; then
179
+ if run_cmd_may_fail "npm_config_cache=\"$WORK_DIR/.npm-cache\" npm i -g @silicaclaw/cli@beta"; then
180
+ echo "全局命令安装成功。"
181
+ else
182
+ echo "普通权限安装失败(常见于 macOS /usr/local 权限)。"
183
+ if ask_yes_no "是否使用 sudo 重试安装全局命令?" "Y"; then
184
+ run_cmd "npm_config_cache=\"$WORK_DIR/.npm-cache\" sudo npm i -g @silicaclaw/cli@beta"
185
+ echo "全局命令安装成功。"
186
+ else
187
+ echo "已跳过全局安装。你仍可使用: npx @silicaclaw/cli@beta <command>"
188
+ fi
189
+ fi
190
+ else
191
+ echo "已跳过全局安装。你仍可使用: npx @silicaclaw/cli@beta <command>"
192
+ fi
193
+ fi
194
+
82
195
  title "准备 social.md"
83
- if [ -f "$ROOT_DIR/social.md" ]; then
196
+ if [ -f "$WORK_DIR/social.md" ]; then
84
197
  echo "已存在 social.md,保留现有配置。"
85
198
  else
86
199
  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"
200
+ run_cmd "cd \"$WORK_DIR\" && cp social.md.example social.md"
201
+ echo "已生成: $WORK_DIR/social.md"
89
202
  else
90
203
  echo "跳过生成 social.md(local-console 首启也会自动生成最小模板)"
91
204
  fi
@@ -95,13 +208,21 @@ title "选择网络模式"
95
208
  echo "1) local 单机预览(最快)"
96
209
  echo "2) lan 局域网预览(A/B 双机)"
97
210
  echo "3) global-preview 非局域网预览(WebRTC)"
98
- read -r -p "请输入模式编号 [1/2/3] (默认 1): " MODE_PICK || true
99
- MODE_PICK="${MODE_PICK:-1}"
211
+ echo "提示: 不确定就直接回车(默认 local)。"
212
+ echo "提示: 只有在你已有可达 signaling 地址时,才选择 3。"
213
+ if [ "$CONNECT_MODE" = "1" ]; then
214
+ MODE_PICK="3"
215
+ echo "connect 模式:已自动选择 global-preview。"
216
+ else
217
+ read -r -p "请输入模式编号 [1/2/3] (默认 ${DEFAULT_MODE_PICK}): " MODE_PICK || true
218
+ MODE_PICK="${MODE_PICK:-$DEFAULT_MODE_PICK}"
219
+ fi
100
220
 
101
221
  NETWORK_MODE="local"
102
222
  NETWORK_ADAPTER="local-event-bus"
103
223
  WEBRTC_SIGNALING_URL_VALUE=""
104
224
  WEBRTC_ROOM_VALUE="silicaclaw-demo"
225
+ AUTO_START_SIGNALING=0
105
226
 
106
227
  case "$MODE_PICK" in
107
228
  2)
@@ -111,11 +232,37 @@ case "$MODE_PICK" in
111
232
  3)
112
233
  NETWORK_MODE="global-preview"
113
234
  NETWORK_ADAPTER="webrtc-preview"
114
- read -r -p "请输入 signaling URL(例如 http://your-server:4510): " WEBRTC_SIGNALING_URL_VALUE
235
+ PUBLIC_IP="$(detect_public_ip)"
236
+ SIGNALING_DEFAULT="${WEBRTC_SIGNALING_URL:-http://localhost:4510}"
237
+ if [ -n "$PUBLIC_IP" ]; then
238
+ SIGNALING_DEFAULT="http://$PUBLIC_IP:4510"
239
+ fi
240
+ echo "提示: signaling 地址需要“所有节点可访问”。"
241
+ if [ -n "$PUBLIC_IP" ]; then
242
+ echo "已检测到本机公网 IP: $PUBLIC_IP"
243
+ echo "如果你这台机器就是 signaling 服务器,可直接回车使用默认值。"
244
+ else
245
+ echo "未检测到公网 IP,将使用默认值: $SIGNALING_DEFAULT"
246
+ echo "如果 signaling 在其他机器,请输入该机器公网地址。"
247
+ fi
248
+ read -r -p "请输入 signaling URL(默认 ${SIGNALING_DEFAULT}): " WEBRTC_SIGNALING_URL_INPUT || true
249
+ WEBRTC_SIGNALING_URL_VALUE="${WEBRTC_SIGNALING_URL_INPUT:-$SIGNALING_DEFAULT}"
115
250
  if [ -z "${WEBRTC_SIGNALING_URL_VALUE:-}" ]; then
116
251
  echo "global-preview 必须提供 signaling URL"
117
252
  exit 1
118
253
  fi
254
+
255
+ SIGNALING_HOST="$(url_host "$WEBRTC_SIGNALING_URL_VALUE")"
256
+ if [ "$SIGNALING_HOST" = "localhost" ] || [ "$SIGNALING_HOST" = "127.0.0.1" ]; then
257
+ echo "提示: 当前 signaling URL 是本机地址,仅本机可用,不适合跨网络双机演示。"
258
+ fi
259
+
260
+ if ask_yes_no "是否在当前机器自动后台启动 signaling server(用于演示)?" "Y"; then
261
+ AUTO_START_SIGNALING=1
262
+ SIGNALING_PORT_VALUE="$(url_port_or_default "$WEBRTC_SIGNALING_URL_VALUE" "4510")"
263
+ echo "将尝试以 PORT=$SIGNALING_PORT_VALUE 后台启动 signaling server"
264
+ fi
265
+
119
266
  read -r -p "请输入 room(默认 silicaclaw-demo): " WEBRTC_ROOM_VALUE_INPUT || true
120
267
  WEBRTC_ROOM_VALUE="${WEBRTC_ROOM_VALUE_INPUT:-silicaclaw-demo}"
121
268
  ;;
@@ -128,17 +275,47 @@ esac
128
275
  echo "已选择: $NETWORK_MODE ($NETWORK_ADAPTER)"
129
276
 
130
277
  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"
278
+ echo "1) gateway(推荐) 后台服务模式,可用 start/stop/restart/status 管理"
279
+ echo "2) dev watch 前台开发模式(tsx watch)"
280
+ read -r -p "请选择启动方式 [1/2] (默认 1): " START_MODE_PICK || true
281
+ START_MODE_PICK="${START_MODE_PICK:-1}"
282
+
283
+ if [ "$START_MODE_PICK" = "2" ]; then
284
+ if [ "$NETWORK_MODE" = "global-preview" ]; then
285
+ if [ "$AUTO_START_SIGNALING" -eq 1 ]; then
286
+ mkdir -p "$WORK_DIR/.silicaclaw"
287
+ SIGNALING_LOG="$WORK_DIR/.silicaclaw/signaling.log"
288
+ SIGNALING_PID_FILE="$WORK_DIR/.silicaclaw/signaling.pid"
289
+ run_cmd "cd \"$WORK_DIR\" && PORT=${SIGNALING_PORT_VALUE:-4510} nohup npm run webrtc-signaling > \"$SIGNALING_LOG\" 2>&1 & echo \$! > \"$SIGNALING_PID_FILE\""
290
+ echo "已后台启动 signaling server,日志: $SIGNALING_LOG"
291
+ echo "停止 signaling: kill \$(cat \"$SIGNALING_PID_FILE\")"
292
+ fi
293
+ echo "将使用以下参数启动(dev watch):"
294
+ echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
295
+ echo "WEBRTC_SIGNALING_URL=$WEBRTC_SIGNALING_URL_VALUE"
296
+ echo "WEBRTC_ROOM=$WEBRTC_ROOM_VALUE"
297
+ pause_continue
298
+ 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"
299
+ else
300
+ echo "将使用以下参数启动(dev watch):"
301
+ echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
302
+ pause_continue
303
+ run_cmd "cd \"$WORK_DIR\" && NETWORK_ADAPTER=$NETWORK_ADAPTER npm run local-console"
304
+ fi
138
305
  else
139
- echo "将使用以下参数启动:"
140
- echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
306
+ GATEWAY_CMD="cd \"$WORK_DIR\" && npm run gateway -- start --mode=$NETWORK_MODE"
307
+ if [ "$NETWORK_MODE" = "global-preview" ]; then
308
+ GATEWAY_CMD="$GATEWAY_CMD --signaling-url=$WEBRTC_SIGNALING_URL_VALUE --room=$WEBRTC_ROOM_VALUE"
309
+ fi
310
+ echo "将使用 gateway 后台启动:"
311
+ echo "$GATEWAY_CMD"
141
312
  pause_continue
142
- run_cmd "NETWORK_ADAPTER=$NETWORK_ADAPTER npm run local-console"
313
+ run_cmd "$GATEWAY_CMD"
314
+ echo ""
315
+ echo "已启动完成。常用命令:"
316
+ echo "cd \"$WORK_DIR\" && npm run gateway -- status"
317
+ echo "cd \"$WORK_DIR\" && npm run gateway -- logs local-console"
318
+ echo "cd \"$WORK_DIR\" && npm run gateway -- stop"
319
+ echo ""
320
+ echo "打开: http://localhost:4310"
143
321
  fi
144
-
@@ -23,18 +23,101 @@ 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
+
26
40
  function readVersion() {
27
41
  const versionFile = resolve(ROOT_DIR, "VERSION");
28
42
  if (!existsSync(versionFile)) return "unknown";
29
43
  return readFileSync(versionFile, "utf8").trim() || "unknown";
30
44
  }
31
45
 
46
+ function readPackageVersion() {
47
+ const pkgFile = resolve(ROOT_DIR, "package.json");
48
+ if (!existsSync(pkgFile)) return "unknown";
49
+ try {
50
+ const pkg = JSON.parse(readFileSync(pkgFile, "utf8"));
51
+ return String(pkg.version || "unknown");
52
+ } catch {
53
+ return "unknown";
54
+ }
55
+ }
56
+
57
+ function isNpxRun() {
58
+ return ROOT_DIR.includes("/.npm/_npx/");
59
+ }
60
+
61
+ function showUpdateGuide(current, latest, beta) {
62
+ console.log("SilicaClaw update check");
63
+ console.log(`current: ${current}`);
64
+ console.log(`latest : ${latest || "-"}`);
65
+ console.log(`beta : ${beta || "-"}`);
66
+ console.log("");
67
+
68
+ const upToDate = Boolean(beta) && current === beta;
69
+ if (upToDate) {
70
+ console.log("You are already on the latest beta.");
71
+ } else {
72
+ console.log("Update available.");
73
+ }
74
+ console.log("");
75
+ console.log("Recommended commands:");
76
+ console.log("1) npx mode (no global install)");
77
+ console.log(" npx @silicaclaw/cli@beta onboard");
78
+ console.log(" npx @silicaclaw/cli@beta connect");
79
+ console.log("");
80
+ console.log("2) global install mode");
81
+ console.log(" npm i -g @silicaclaw/cli@beta");
82
+ console.log(" silicaclaw version");
83
+ console.log("");
84
+ if (isNpxRun()) {
85
+ console.log("Detected npx runtime: use npx commands above for immediate update.");
86
+ }
87
+ }
88
+
89
+ function update() {
90
+ const current = readPackageVersion();
91
+ try {
92
+ const result = runCapture("npm", ["view", "@silicaclaw/cli", "dist-tags", "--json"]);
93
+ if ((result.status ?? 1) !== 0) {
94
+ console.error("Failed to query npm dist-tags.");
95
+ if (result.stderr) console.error(result.stderr.trim());
96
+ process.exit(result.status ?? 1);
97
+ }
98
+ const text = String(result.stdout || "").trim();
99
+ const tags = text ? JSON.parse(text) : {};
100
+ const latest = tags.latest ? String(tags.latest) : "";
101
+ const beta = tags.beta ? String(tags.beta) : "";
102
+ showUpdateGuide(current, latest, beta);
103
+ process.exit(0);
104
+ } catch (error) {
105
+ console.error(`Update check failed: ${error.message}`);
106
+ console.log("Try manually:");
107
+ console.log("npm view @silicaclaw/cli dist-tags --json");
108
+ process.exit(1);
109
+ }
110
+ }
111
+
32
112
  function help() {
33
113
  console.log(`
34
114
  SilicaClaw CLI
35
115
 
36
116
  Usage:
37
117
  silicaclaw onboard
118
+ silicaclaw connect
119
+ silicaclaw update
120
+ silicaclaw gateway <start|stop|restart|status|logs>
38
121
  silicaclaw local-console
39
122
  silicaclaw explorer
40
123
  silicaclaw signaling
@@ -44,6 +127,9 @@ Usage:
44
127
 
45
128
  Commands:
46
129
  onboard Interactive step-by-step onboarding (recommended)
130
+ connect Cross-network connect wizard (global-preview first)
131
+ update Check latest npm version and show upgrade commands
132
+ gateway Manage background services (start/stop/restart/status/logs)
47
133
  local-console Start local console (http://localhost:4310)
48
134
  explorer Start public explorer (http://localhost:4311)
49
135
  signaling Start WebRTC signaling preview server
@@ -59,6 +145,23 @@ switch (cmd) {
59
145
  case "onboard":
60
146
  run("bash", [resolve(ROOT_DIR, "scripts", "quickstart.sh")]);
61
147
  break;
148
+ case "connect":
149
+ run("bash", [resolve(ROOT_DIR, "scripts", "quickstart.sh")], {
150
+ env: {
151
+ ...process.env,
152
+ QUICKSTART_DEFAULT_MODE: "3",
153
+ QUICKSTART_CONNECT_MODE: "1",
154
+ },
155
+ });
156
+ break;
157
+ case "update":
158
+ update();
159
+ break;
160
+ case "gateway":
161
+ run("node", [resolve(ROOT_DIR, "scripts", "silicaclaw-gateway.mjs"), ...process.argv.slice(3)], {
162
+ cwd: process.cwd(),
163
+ });
164
+ break;
62
165
  case "local-console":
63
166
  case "console":
64
167
  run("npm", ["run", "local-console"]);