@silicaclaw/cli 1.0.0-beta.7 → 1.0.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/INSTALL.md CHANGED
@@ -28,6 +28,21 @@ Cross-network quick wizard (defaults to global-preview):
28
28
  npx @silicaclaw/cli@beta connect
29
29
  ```
30
30
 
31
+ Check/update CLI version:
32
+
33
+ ```bash
34
+ npx @silicaclaw/cli@beta update
35
+ ```
36
+
37
+ Gateway background service commands:
38
+
39
+ ```bash
40
+ npx @silicaclaw/cli@beta gateway start --mode=local
41
+ npx @silicaclaw/cli@beta gateway status
42
+ npx @silicaclaw/cli@beta gateway restart --mode=lan
43
+ npx @silicaclaw/cli@beta gateway stop
44
+ ```
45
+
31
46
  For most home users, just press Enter on defaults and use `local` mode first.
32
47
 
33
48
  Optional global install (advanced users only):
@@ -36,6 +51,10 @@ Optional global install (advanced users only):
36
51
  npm i -g @silicaclaw/cli
37
52
  silicaclaw onboard
38
53
  silicaclaw connect
54
+ silicaclaw update
55
+ silicaclaw gateway start --mode=local
56
+ silicaclaw gateway status
57
+ silicaclaw gateway stop
39
58
  ```
40
59
 
41
60
  ## 3. Run
package/README.md CHANGED
@@ -34,6 +34,21 @@ Cross-network preview quick wizard:
34
34
  npx @silicaclaw/cli@beta connect
35
35
  ```
36
36
 
37
+ Check and update CLI version:
38
+
39
+ ```bash
40
+ npx @silicaclaw/cli@beta update
41
+ ```
42
+
43
+ Background gateway service:
44
+
45
+ ```bash
46
+ npx @silicaclaw/cli@beta gateway start --mode=local
47
+ npx @silicaclaw/cli@beta gateway status
48
+ npx @silicaclaw/cli@beta gateway restart --mode=lan
49
+ npx @silicaclaw/cli@beta gateway stop
50
+ ```
51
+
37
52
  Or manual:
38
53
 
39
54
  ```bash
@@ -71,6 +86,10 @@ Optional global install:
71
86
  npm i -g @silicaclaw/cli
72
87
  silicaclaw onboard
73
88
  silicaclaw connect
89
+ silicaclaw update
90
+ silicaclaw gateway start --mode=local
91
+ silicaclaw gateway status
92
+ silicaclaw gateway stop
74
93
  ```
75
94
 
76
95
  ## Quick Start (OpenClaw-style)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silicaclaw/cli",
3
- "version": "1.0.0-beta.7",
3
+ "version": "1.0.0-beta.9",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -53,6 +53,7 @@
53
53
  "dev": "npm run --workspace @silicaclaw/local-console dev",
54
54
  "onboard": "node scripts/silicaclaw-cli.mjs onboard",
55
55
  "quickstart": "bash scripts/quickstart.sh",
56
+ "gateway": "node scripts/silicaclaw-gateway.mjs",
56
57
  "local-console": "npm run --workspace @silicaclaw/local-console dev",
57
58
  "public-explorer": "npm run --workspace @silicaclaw/public-explorer dev",
58
59
  "logo": "bash scripts/install-logo.sh",
@@ -242,24 +242,47 @@ esac
242
242
  echo "已选择: $NETWORK_MODE ($NETWORK_ADAPTER)"
243
243
 
244
244
  title "启动 local-console"
245
- if [ "$NETWORK_MODE" = "global-preview" ]; then
246
- if [ "$AUTO_START_SIGNALING" -eq 1 ]; then
247
- mkdir -p "$WORK_DIR/.silicaclaw"
248
- SIGNALING_LOG="$WORK_DIR/.silicaclaw/signaling.log"
249
- SIGNALING_PID_FILE="$WORK_DIR/.silicaclaw/signaling.pid"
250
- run_cmd "cd \"$WORK_DIR\" && PORT=${SIGNALING_PORT_VALUE:-4510} nohup npm run webrtc-signaling > \"$SIGNALING_LOG\" 2>&1 & echo \$! > \"$SIGNALING_PID_FILE\""
251
- echo "已后台启动 signaling server,日志: $SIGNALING_LOG"
252
- echo "停止 signaling: kill \$(cat \"$SIGNALING_PID_FILE\")"
245
+ echo "1) gateway(推荐) 后台服务模式,可用 start/stop/restart/status 管理"
246
+ echo "2) dev watch 前台开发模式(tsx watch)"
247
+ read -r -p "请选择启动方式 [1/2] (默认 1): " START_MODE_PICK || true
248
+ START_MODE_PICK="${START_MODE_PICK:-1}"
249
+
250
+ if [ "$START_MODE_PICK" = "2" ]; then
251
+ if [ "$NETWORK_MODE" = "global-preview" ]; then
252
+ if [ "$AUTO_START_SIGNALING" -eq 1 ]; then
253
+ mkdir -p "$WORK_DIR/.silicaclaw"
254
+ SIGNALING_LOG="$WORK_DIR/.silicaclaw/signaling.log"
255
+ SIGNALING_PID_FILE="$WORK_DIR/.silicaclaw/signaling.pid"
256
+ run_cmd "cd \"$WORK_DIR\" && PORT=${SIGNALING_PORT_VALUE:-4510} nohup npm run webrtc-signaling > \"$SIGNALING_LOG\" 2>&1 & echo \$! > \"$SIGNALING_PID_FILE\""
257
+ echo "已后台启动 signaling server,日志: $SIGNALING_LOG"
258
+ echo "停止 signaling: kill \$(cat \"$SIGNALING_PID_FILE\")"
259
+ fi
260
+ echo "将使用以下参数启动(dev watch):"
261
+ echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
262
+ echo "WEBRTC_SIGNALING_URL=$WEBRTC_SIGNALING_URL_VALUE"
263
+ echo "WEBRTC_ROOM=$WEBRTC_ROOM_VALUE"
264
+ pause_continue
265
+ 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"
266
+ else
267
+ echo "将使用以下参数启动(dev watch):"
268
+ echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
269
+ pause_continue
270
+ run_cmd "cd \"$WORK_DIR\" && NETWORK_ADAPTER=$NETWORK_ADAPTER npm run local-console"
253
271
  fi
254
- echo "将使用以下参数启动:"
255
- echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
256
- echo "WEBRTC_SIGNALING_URL=$WEBRTC_SIGNALING_URL_VALUE"
257
- echo "WEBRTC_ROOM=$WEBRTC_ROOM_VALUE"
258
- pause_continue
259
- 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"
260
272
  else
261
- echo "将使用以下参数启动:"
262
- echo "NETWORK_ADAPTER=$NETWORK_ADAPTER"
273
+ GATEWAY_CMD="cd \"$WORK_DIR\" && npm run gateway -- start --mode=$NETWORK_MODE"
274
+ if [ "$NETWORK_MODE" = "global-preview" ]; then
275
+ GATEWAY_CMD="$GATEWAY_CMD --signaling-url=$WEBRTC_SIGNALING_URL_VALUE --room=$WEBRTC_ROOM_VALUE"
276
+ fi
277
+ echo "将使用 gateway 后台启动:"
278
+ echo "$GATEWAY_CMD"
263
279
  pause_continue
264
- run_cmd "cd \"$WORK_DIR\" && NETWORK_ADAPTER=$NETWORK_ADAPTER npm run local-console"
280
+ run_cmd "$GATEWAY_CMD"
281
+ echo ""
282
+ echo "已启动完成。常用命令:"
283
+ echo "cd \"$WORK_DIR\" && npm run gateway -- status"
284
+ echo "cd \"$WORK_DIR\" && npm run gateway -- logs local-console"
285
+ echo "cd \"$WORK_DIR\" && npm run gateway -- stop"
286
+ echo ""
287
+ echo "打开: http://localhost:4310"
265
288
  fi
@@ -23,12 +23,92 @@ 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
@@ -36,6 +116,8 @@ SilicaClaw CLI
36
116
  Usage:
37
117
  silicaclaw onboard
38
118
  silicaclaw connect
119
+ silicaclaw update
120
+ silicaclaw gateway <start|stop|restart|status|logs>
39
121
  silicaclaw local-console
40
122
  silicaclaw explorer
41
123
  silicaclaw signaling
@@ -46,6 +128,8 @@ Usage:
46
128
  Commands:
47
129
  onboard Interactive step-by-step onboarding (recommended)
48
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)
49
133
  local-console Start local console (http://localhost:4310)
50
134
  explorer Start public explorer (http://localhost:4311)
51
135
  signaling Start WebRTC signaling preview server
@@ -70,6 +154,14 @@ switch (cmd) {
70
154
  },
71
155
  });
72
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;
73
165
  case "local-console":
74
166
  case "console":
75
167
  run("npm", ["run", "local-console"]);
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "node:child_process";
4
+ import { existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { dirname, join, 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
+ const argv = process.argv.slice(2);
14
+ const cmd = String(argv[0] || "help").toLowerCase();
15
+
16
+ function parseFlag(name, fallback = "") {
17
+ const prefix = `--${name}=`;
18
+ for (const item of argv) {
19
+ if (item.startsWith(prefix)) return item.slice(prefix.length);
20
+ }
21
+ return fallback;
22
+ }
23
+
24
+ function hasFlag(name) {
25
+ return argv.includes(`--${name}`);
26
+ }
27
+
28
+ function detectAppDir() {
29
+ const envDir = process.env.SILICACLAW_APP_DIR;
30
+ if (envDir && existsSync(join(envDir, "package.json"))) {
31
+ return resolve(envDir);
32
+ }
33
+
34
+ const cwd = process.cwd();
35
+ if (existsSync(join(cwd, "package.json"))) {
36
+ return resolve(cwd);
37
+ }
38
+
39
+ const homeCandidate = join(homedir(), "silicaclaw");
40
+ if (existsSync(join(homeCandidate, "package.json"))) {
41
+ return resolve(homeCandidate);
42
+ }
43
+
44
+ return ROOT_DIR;
45
+ }
46
+
47
+ const APP_DIR = detectAppDir();
48
+ const STATE_DIR = join(APP_DIR, ".silicaclaw", "gateway");
49
+ const CONSOLE_PID_FILE = join(STATE_DIR, "local-console.pid");
50
+ const CONSOLE_LOG_FILE = join(STATE_DIR, "local-console.log");
51
+ const SIGNALING_PID_FILE = join(STATE_DIR, "signaling.pid");
52
+ const SIGNALING_LOG_FILE = join(STATE_DIR, "signaling.log");
53
+ const STATE_FILE = join(STATE_DIR, "state.json");
54
+
55
+ function ensureStateDir() {
56
+ mkdirSync(STATE_DIR, { recursive: true });
57
+ }
58
+
59
+ function readPid(file) {
60
+ if (!existsSync(file)) return null;
61
+ const text = String(readFileSync(file, "utf8")).trim();
62
+ const pid = Number(text);
63
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
64
+ }
65
+
66
+ function isRunning(pid) {
67
+ if (!pid) return false;
68
+ try {
69
+ process.kill(pid, 0);
70
+ return true;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ function removeFileIfExists(path) {
77
+ if (existsSync(path)) rmSync(path, { force: true });
78
+ }
79
+
80
+ async function stopPid(pid, name) {
81
+ if (!pid || !isRunning(pid)) return;
82
+ try {
83
+ process.kill(pid, "SIGTERM");
84
+ } catch {
85
+ return;
86
+ }
87
+ const start = Date.now();
88
+ while (Date.now() - start < 5000) {
89
+ if (!isRunning(pid)) return;
90
+ await new Promise((r) => setTimeout(r, 200));
91
+ }
92
+ if (isRunning(pid)) {
93
+ try {
94
+ process.kill(pid, "SIGKILL");
95
+ } catch {
96
+ // ignore
97
+ }
98
+ }
99
+ if (isRunning(pid)) {
100
+ console.error(`failed to stop ${name} (pid=${pid})`);
101
+ }
102
+ }
103
+
104
+ function parseMode(raw) {
105
+ const mode = String(raw || "local").trim().toLowerCase();
106
+ if (mode === "lan" || mode === "global-preview" || mode === "local") return mode;
107
+ return "local";
108
+ }
109
+
110
+ function adapterForMode(mode) {
111
+ if (mode === "lan") return "real-preview";
112
+ if (mode === "global-preview") return "webrtc-preview";
113
+ return "local-event-bus";
114
+ }
115
+
116
+ function parseUrlHostPort(url) {
117
+ try {
118
+ const u = new URL(url);
119
+ return {
120
+ host: u.hostname || "",
121
+ port: Number(u.port || 4510),
122
+ };
123
+ } catch {
124
+ return { host: "", port: 4510 };
125
+ }
126
+ }
127
+
128
+ function spawnBackground(command, args, env, logFile, pidFile) {
129
+ const outFd = openSync(logFile, "a");
130
+ const child = spawn(command, args, {
131
+ cwd: APP_DIR,
132
+ env: { ...process.env, ...env },
133
+ detached: true,
134
+ stdio: ["ignore", outFd, outFd],
135
+ });
136
+ child.unref();
137
+ writeFileSync(pidFile, String(child.pid));
138
+ return child.pid;
139
+ }
140
+
141
+ function writeState(state) {
142
+ ensureStateDir();
143
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
144
+ }
145
+
146
+ function readState() {
147
+ if (!existsSync(STATE_FILE)) return null;
148
+ try {
149
+ return JSON.parse(readFileSync(STATE_FILE, "utf8"));
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+
155
+ function printHelp() {
156
+ console.log(`
157
+ SilicaClaw Gateway
158
+
159
+ Usage:
160
+ silicaclaw gateway start [--mode=local|lan|global-preview] [--signaling-url=http://host:4510] [--room=silicaclaw-demo]
161
+ silicaclaw gateway stop
162
+ silicaclaw gateway restart [--mode=...]
163
+ silicaclaw gateway status
164
+ silicaclaw gateway logs [local-console|signaling]
165
+
166
+ Notes:
167
+ - Default app dir: current directory; fallback: ~/silicaclaw
168
+ - State dir: .silicaclaw/gateway
169
+ - global-preview + localhost signaling URL will auto-start signaling server
170
+ `.trim());
171
+ }
172
+
173
+ function showStatus() {
174
+ const localPid = readPid(CONSOLE_PID_FILE);
175
+ const sigPid = readPid(SIGNALING_PID_FILE);
176
+ const state = readState();
177
+ const payload = {
178
+ app_dir: APP_DIR,
179
+ mode: state?.mode || "unknown",
180
+ adapter: state?.adapter || "unknown",
181
+ local_console: {
182
+ pid: localPid,
183
+ running: isRunning(localPid),
184
+ log_file: CONSOLE_LOG_FILE,
185
+ },
186
+ signaling: {
187
+ pid: sigPid,
188
+ running: isRunning(sigPid),
189
+ log_file: SIGNALING_LOG_FILE,
190
+ url: state?.signaling_url || null,
191
+ room: state?.room || null,
192
+ },
193
+ updated_at: state?.updated_at || null,
194
+ };
195
+ console.log(JSON.stringify(payload, null, 2));
196
+ }
197
+
198
+ function tailFile(file, lines = 80) {
199
+ if (!existsSync(file)) {
200
+ console.log(`log file not found: ${file}`);
201
+ return;
202
+ }
203
+ const text = String(readFileSync(file, "utf8"));
204
+ const out = text.split(/\r?\n/).slice(-lines).join("\n");
205
+ console.log(out);
206
+ }
207
+
208
+ async function stopAll() {
209
+ const localPid = readPid(CONSOLE_PID_FILE);
210
+ const sigPid = readPid(SIGNALING_PID_FILE);
211
+ await stopPid(localPid, "local-console");
212
+ await stopPid(sigPid, "signaling");
213
+ removeFileIfExists(CONSOLE_PID_FILE);
214
+ removeFileIfExists(SIGNALING_PID_FILE);
215
+ writeState({
216
+ ...(readState() || {}),
217
+ updated_at: Date.now(),
218
+ });
219
+ console.log("gateway stopped");
220
+ }
221
+
222
+ function startAll() {
223
+ ensureStateDir();
224
+
225
+ const mode = parseMode(parseFlag("mode", process.env.NETWORK_MODE || "local"));
226
+ const adapter = adapterForMode(mode);
227
+ const signalingUrl = parseFlag("signaling-url", process.env.WEBRTC_SIGNALING_URL || "http://localhost:4510");
228
+ const room = parseFlag("room", process.env.WEBRTC_ROOM || "silicaclaw-demo");
229
+ const shouldDisableSignaling = hasFlag("no-signaling");
230
+
231
+ const currentLocalPid = readPid(CONSOLE_PID_FILE);
232
+ const currentSigPid = readPid(SIGNALING_PID_FILE);
233
+ if (isRunning(currentLocalPid)) {
234
+ console.log(`local-console already running (pid=${currentLocalPid})`);
235
+ } else {
236
+ removeFileIfExists(CONSOLE_PID_FILE);
237
+ const env = {
238
+ NETWORK_ADAPTER: adapter,
239
+ NETWORK_MODE: mode,
240
+ WEBRTC_SIGNALING_URL: signalingUrl,
241
+ WEBRTC_ROOM: room,
242
+ };
243
+ const pid = spawnBackground("npm", ["run", "local-console"], env, CONSOLE_LOG_FILE, CONSOLE_PID_FILE);
244
+ console.log(`local-console started (pid=${pid})`);
245
+ }
246
+
247
+ const { host, port } = parseUrlHostPort(signalingUrl);
248
+ const shouldAutoStartSignaling =
249
+ mode === "global-preview" &&
250
+ !shouldDisableSignaling &&
251
+ (host === "localhost" || host === "127.0.0.1");
252
+
253
+ if (shouldAutoStartSignaling) {
254
+ if (isRunning(currentSigPid)) {
255
+ console.log(`signaling already running (pid=${currentSigPid})`);
256
+ } else {
257
+ removeFileIfExists(SIGNALING_PID_FILE);
258
+ const pid = spawnBackground(
259
+ "npm",
260
+ ["run", "webrtc-signaling"],
261
+ { PORT: String(port) },
262
+ SIGNALING_LOG_FILE,
263
+ SIGNALING_PID_FILE,
264
+ );
265
+ console.log(`signaling started (pid=${pid}, port=${port})`);
266
+ }
267
+ }
268
+
269
+ writeState({
270
+ app_dir: APP_DIR,
271
+ mode,
272
+ adapter,
273
+ signaling_url: signalingUrl,
274
+ room,
275
+ updated_at: Date.now(),
276
+ });
277
+ }
278
+
279
+ async function main() {
280
+ if (cmd === "help" || cmd === "-h" || cmd === "--help") {
281
+ printHelp();
282
+ return;
283
+ }
284
+ if (cmd === "status") {
285
+ showStatus();
286
+ return;
287
+ }
288
+ if (cmd === "start") {
289
+ startAll();
290
+ showStatus();
291
+ return;
292
+ }
293
+ if (cmd === "stop") {
294
+ await stopAll();
295
+ showStatus();
296
+ return;
297
+ }
298
+ if (cmd === "restart") {
299
+ await stopAll();
300
+ startAll();
301
+ showStatus();
302
+ return;
303
+ }
304
+ if (cmd === "logs") {
305
+ const target = String(argv[1] || "local-console");
306
+ if (target === "signaling") {
307
+ tailFile(SIGNALING_LOG_FILE);
308
+ return;
309
+ }
310
+ tailFile(CONSOLE_LOG_FILE);
311
+ return;
312
+ }
313
+ printHelp();
314
+ process.exitCode = 1;
315
+ }
316
+
317
+ main().catch((error) => {
318
+ console.error(error?.message || String(error));
319
+ process.exit(1);
320
+ });
321
+