@silicaclaw/cli 1.0.0-beta.8 → 2026.3.18-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
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, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
5
+ import { homedir } from "node:os";
5
6
  import { dirname, resolve } from "node:path";
6
7
  import { fileURLToPath } from "node:url";
7
8
 
@@ -9,6 +10,42 @@ const __filename = fileURLToPath(import.meta.url);
9
10
  const __dirname = dirname(__filename);
10
11
  const ROOT_DIR = resolve(__dirname, "..");
11
12
 
13
+ const COLOR = {
14
+ reset: "\x1b[0m",
15
+ bold: "\x1b[1m",
16
+ dim: "\x1b[2m",
17
+ orange: "\x1b[38;5;208m",
18
+ green: "\x1b[32m",
19
+ yellow: "\x1b[33m",
20
+ };
21
+
22
+ function useColor() {
23
+ return Boolean(process.stdout.isTTY && !process.env.NO_COLOR);
24
+ }
25
+
26
+ function paint(text, ...styles) {
27
+ if (!useColor() || styles.length === 0) return text;
28
+ return `${styles.join("")}${text}${COLOR.reset}`;
29
+ }
30
+
31
+ function displayVersion(raw) {
32
+ const text = String(raw || "unknown").trim() || "unknown";
33
+ return text.startsWith("v") ? text : `v${text}`;
34
+ }
35
+
36
+ function headline() {
37
+ console.log(`${paint("SilicaClaw", COLOR.bold, COLOR.orange)} ${paint(displayVersion(readVersion()), COLOR.dim)}`);
38
+ console.log(paint("Public identity and discovery for OpenClaw agents.", COLOR.dim));
39
+ }
40
+
41
+ function kv(label, value) {
42
+ console.log(`${paint(label.padEnd(14), COLOR.dim)} ${value}`);
43
+ }
44
+
45
+ function section(title) {
46
+ console.log(paint(title, COLOR.bold));
47
+ }
48
+
12
49
  function run(cmd, args, extra = {}) {
13
50
  const result = spawnSync(cmd, args, {
14
51
  cwd: ROOT_DIR,
@@ -17,7 +54,10 @@ function run(cmd, args, extra = {}) {
17
54
  ...extra,
18
55
  });
19
56
  if (result.error) {
20
- console.error(`[silicaclaw] failed to run ${cmd}: ${result.error.message}`);
57
+ headline();
58
+ console.log("");
59
+ console.error(`${paint("Command failed", COLOR.bold, COLOR.yellow)} ${cmd}`);
60
+ console.error(result.error.message);
21
61
  process.exit(1);
22
62
  }
23
63
  process.exit(result.status ?? 0);
@@ -37,10 +77,30 @@ function runCapture(cmd, args, extra = {}) {
37
77
  return result;
38
78
  }
39
79
 
80
+ function runInherit(cmd, args, extra = {}) {
81
+ const result = spawnSync(cmd, args, {
82
+ cwd: ROOT_DIR,
83
+ stdio: "inherit",
84
+ env: process.env,
85
+ ...extra,
86
+ });
87
+ if (result.error) {
88
+ throw result.error;
89
+ }
90
+ return result;
91
+ }
92
+
93
+ function compactOutput(text, limit = 18) {
94
+ const lines = String(text || "")
95
+ .split(/\r?\n/)
96
+ .map((line) => line.trimEnd())
97
+ .filter(Boolean);
98
+ if (lines.length <= limit) return lines.join("\n");
99
+ return lines.slice(-limit).join("\n");
100
+ }
101
+
40
102
  function readVersion() {
41
- const versionFile = resolve(ROOT_DIR, "VERSION");
42
- if (!existsSync(versionFile)) return "unknown";
43
- return readFileSync(versionFile, "utf8").trim() || "unknown";
103
+ return readPackageVersion();
44
104
  }
45
105
 
46
106
  function readPackageVersion() {
@@ -54,36 +114,292 @@ function readPackageVersion() {
54
114
  }
55
115
  }
56
116
 
117
+ function preferredShellRcFile() {
118
+ const shell = String(process.env.SHELL || "");
119
+ if (shell.endsWith("/zsh")) return resolve(homedir(), ".zshrc");
120
+ if (shell.endsWith("/bash")) return resolve(homedir(), ".bashrc");
121
+ if (process.env.ZSH_VERSION) return resolve(homedir(), ".zshrc");
122
+ return resolve(homedir(), ".bashrc");
123
+ }
124
+
125
+ function userShimDir() {
126
+ return resolve(homedir(), ".silicaclaw", "bin");
127
+ }
128
+
129
+ function userShimPath() {
130
+ return resolve(userShimDir(), "silicaclaw");
131
+ }
132
+
133
+ function userEnvFile() {
134
+ return resolve(homedir(), ".silicaclaw", "env.sh");
135
+ }
136
+
137
+ function userNpmCacheDir() {
138
+ return resolve(homedir(), ".silicaclaw", "npm-cache");
139
+ }
140
+
141
+ function ensureLineInFile(filePath, block) {
142
+ try {
143
+ const current = existsSync(filePath) ? readFileSync(filePath, "utf8") : "";
144
+ if (current.includes(block.trim())) {
145
+ return { changed: false, error: null };
146
+ }
147
+ const next = `${current.replace(/\s*$/, "")}\n\n${block}\n`;
148
+ mkdirSync(dirname(filePath), { recursive: true });
149
+ writeFileSync(filePath, next, "utf8");
150
+ return { changed: true, error: null };
151
+ } catch (error) {
152
+ return { changed: false, error: error instanceof Error ? error.message : String(error) };
153
+ }
154
+ }
155
+
156
+ function describeFileOwner(filePath) {
157
+ try {
158
+ if (!existsSync(filePath)) return null;
159
+ const stat = statSync(filePath);
160
+ return String(stat.uid);
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
165
+
166
+ function currentUid() {
167
+ try {
168
+ return typeof process.getuid === "function" ? String(process.getuid()) : null;
169
+ } catch {
170
+ return null;
171
+ }
172
+ }
173
+
174
+ function shellInitTargets() {
175
+ const home = homedir();
176
+ const shell = String(process.env.SHELL || "");
177
+ const targets = [];
178
+ const add = (filePath) => {
179
+ if (!targets.includes(filePath)) {
180
+ targets.push(filePath);
181
+ }
182
+ };
183
+
184
+ if (shell.endsWith("/zsh") || process.env.ZSH_VERSION || existsSync(resolve(home, ".zshrc"))) {
185
+ add(resolve(home, ".zshrc"));
186
+ }
187
+
188
+ // Bash login shells on macOS often read .bash_profile instead of .bashrc.
189
+ if (
190
+ shell.endsWith("/bash") ||
191
+ process.env.BASH_VERSION ||
192
+ existsSync(resolve(home, ".bashrc")) ||
193
+ existsSync(resolve(home, ".bash_profile"))
194
+ ) {
195
+ add(resolve(home, ".bashrc"));
196
+ add(resolve(home, ".bash_profile"));
197
+ }
198
+
199
+ if (targets.length === 0) {
200
+ add(preferredShellRcFile());
201
+ }
202
+ return targets;
203
+ }
204
+
205
+ function installPersistentCommand() {
206
+ const binDir = userShimDir();
207
+ const shimPath = userShimPath();
208
+ const envFile = userEnvFile();
209
+ const npmCacheDir = userNpmCacheDir();
210
+ const envBlock = [
211
+ "#!/usr/bin/env bash",
212
+ 'export PATH="$HOME/.silicaclaw/bin:$PATH"',
213
+ 'export npm_config_cache="$HOME/.silicaclaw/npm-cache"',
214
+ "",
215
+ ].join("\n");
216
+ const rcBlock = [
217
+ "# >>> silicaclaw >>>",
218
+ '[ -f "$HOME/.silicaclaw/env.sh" ] && . "$HOME/.silicaclaw/env.sh"',
219
+ "# <<< silicaclaw <<<",
220
+ ].join("\n");
221
+
222
+ mkdirSync(binDir, { recursive: true });
223
+ mkdirSync(npmCacheDir, { recursive: true });
224
+ writeFileSync(envFile, envBlock, { encoding: "utf8", mode: 0o755 });
225
+ writeFileSync(
226
+ shimPath,
227
+ [
228
+ "#!/usr/bin/env bash",
229
+ "set -euo pipefail",
230
+ 'export npm_config_cache="${npm_config_cache:-$HOME/.silicaclaw/npm-cache}"',
231
+ 'exec npx -y @silicaclaw/cli@beta "$@"',
232
+ "",
233
+ ].join("\n"),
234
+ { encoding: "utf8", mode: 0o755 }
235
+ );
236
+ const rcFiles = shellInitTargets();
237
+ const updatedFiles = [];
238
+ const configuredFiles = [];
239
+ const failedFiles = [];
240
+ for (const filePath of rcFiles) {
241
+ const result = ensureLineInFile(filePath, rcBlock);
242
+ if (result.changed) {
243
+ updatedFiles.push(filePath);
244
+ configuredFiles.push(filePath);
245
+ } else if (!result.error) {
246
+ configuredFiles.push(filePath);
247
+ }
248
+ if (result.error) {
249
+ failedFiles.push({ filePath, error: result.error });
250
+ }
251
+ }
252
+
253
+ headline();
254
+ console.log("");
255
+ console.log(`${paint("Installed", COLOR.bold)} \`silicaclaw\` command`);
256
+ kv("Command", "silicaclaw");
257
+ console.log("");
258
+ kv("Activate", `source "${envFile}"`);
259
+ if (configuredFiles.length > 0) {
260
+ kv("Startup", "configured");
261
+ } else {
262
+ kv("Startup", "manual setup required");
263
+ }
264
+ if (failedFiles.length > 0) {
265
+ console.log("");
266
+ console.log(paint("Shell files skipped", COLOR.bold, COLOR.yellow));
267
+ const uid = currentUid();
268
+ for (const item of failedFiles) {
269
+ const owner = describeFileOwner(item.filePath);
270
+ const reason =
271
+ owner && uid && owner !== uid
272
+ ? `${item.error} (owned by another user)`
273
+ : item.error;
274
+ kv(item.filePath, reason);
275
+ }
276
+ console.log("");
277
+ kv("Manual", '[ -f "$HOME/.silicaclaw/env.sh" ] && . "$HOME/.silicaclaw/env.sh"');
278
+ }
279
+ }
280
+
57
281
  function isNpxRun() {
58
282
  return ROOT_DIR.includes("/.npm/_npx/");
59
283
  }
60
284
 
285
+ function canWriteGlobalPrefix() {
286
+ try {
287
+ const prefixResult = runCapture("npm", ["prefix", "-g"]);
288
+ if ((prefixResult.status ?? 1) !== 0) return false;
289
+ const prefix = String(prefixResult.stdout || "").trim();
290
+ if (!prefix) return false;
291
+ const targetDir = resolve(prefix, "lib", "node_modules");
292
+ accessSync(targetDir, constants.W_OK);
293
+ return true;
294
+ } catch {
295
+ return false;
296
+ }
297
+ }
298
+
61
299
  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 || "-"}`);
300
+ headline();
66
301
  console.log("");
67
-
68
302
  const upToDate = Boolean(beta) && current === beta;
69
303
  if (upToDate) {
70
- console.log("You are already on the latest beta.");
304
+ kv("Status", `up to date (${current})`);
71
305
  } else {
72
- console.log("Update available.");
306
+ kv("Status", `beta update available (${beta || "-"})`);
73
307
  }
74
308
  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.");
309
+ kv("Start", "silicaclaw start");
310
+ kv("Status", "silicaclaw status");
311
+ }
312
+
313
+ function getGatewayStatus() {
314
+ try {
315
+ const result = runCapture("node", [resolve(ROOT_DIR, "scripts", "silicaclaw-gateway.mjs"), "status", "--json"], {
316
+ cwd: process.cwd(),
317
+ });
318
+ if ((result.status ?? 1) !== 0) return null;
319
+ const text = String(result.stdout || "").trim();
320
+ return text ? JSON.parse(text) : null;
321
+ } catch {
322
+ return null;
323
+ }
324
+ }
325
+
326
+ function isManagedAppDir(appDir) {
327
+ if (!appDir || !existsSync(resolve(appDir, "package.json"))) return false;
328
+ try {
329
+ const pkg = JSON.parse(readFileSync(resolve(appDir, "package.json"), "utf8"));
330
+ const name = String(pkg?.name || "");
331
+ return name === "@silicaclaw/cli" || name === "silicaclaw";
332
+ } catch {
333
+ return false;
334
+ }
335
+ }
336
+
337
+ function syncCurrentPackageToAppDir(appDir) {
338
+ if (!appDir || resolve(appDir) === ROOT_DIR) return false;
339
+ if (!isManagedAppDir(appDir)) return false;
340
+
341
+ const entries = [
342
+ "apps/local-console",
343
+ "apps/public-explorer",
344
+ "packages/core",
345
+ "packages/network",
346
+ "packages/storage",
347
+ "scripts",
348
+ "README.md",
349
+ "INSTALL.md",
350
+ "CHANGELOG.md",
351
+ "ARCHITECTURE.md",
352
+ "ROADMAP.md",
353
+ "SOCIAL_MD_SPEC.md",
354
+ "DEMO_GUIDE.md",
355
+ "RELEASE_NOTES_v1.0.md",
356
+ "social.md.example",
357
+ "openclaw.social.md.example",
358
+ "VERSION",
359
+ "package.json",
360
+ "package-lock.json",
361
+ ];
362
+
363
+ for (const rel of entries) {
364
+ const src = resolve(ROOT_DIR, rel);
365
+ if (!existsSync(src)) continue;
366
+ const dst = resolve(appDir, rel);
367
+ cpSync(src, dst, { recursive: true, force: true });
368
+ }
369
+ return true;
370
+ }
371
+
372
+ function restartGatewayIfRunning() {
373
+ const status = getGatewayStatus();
374
+ const appDir = status?.app_dir ? String(status.app_dir) : "";
375
+ syncCurrentPackageToAppDir(appDir);
376
+
377
+ const localRunning = Boolean(status?.local_console?.running);
378
+ const signalingRunning = Boolean(status?.signaling?.running);
379
+ if (!localRunning && !signalingRunning) {
380
+ return;
86
381
  }
382
+
383
+ const mode = String(status?.mode || "local");
384
+ const args = [resolve(ROOT_DIR, "scripts", "silicaclaw-gateway.mjs"), "restart", `--mode=${mode}`];
385
+ if (mode === "global-preview" && status?.signaling?.url) {
386
+ args.push(`--signaling-url=${status.signaling.url}`);
387
+ }
388
+ if (mode === "global-preview" && status?.signaling?.room) {
389
+ args.push(`--room=${status.signaling.room}`);
390
+ }
391
+
392
+ console.log("");
393
+ console.log(paint("Refreshing services", COLOR.bold));
394
+ runInherit("node", args, { cwd: process.cwd() });
395
+ }
396
+
397
+ function tryGlobalUpgrade(beta) {
398
+ const writableGlobal = canWriteGlobalPrefix();
399
+ if (!writableGlobal) return false;
400
+ kv("Upgrade", `installing @silicaclaw/cli@${beta} globally`);
401
+ const result = runInherit("npm", ["i", "-g", `@silicaclaw/cli@${beta}`]);
402
+ return (result.status ?? 1) === 0;
87
403
  }
88
404
 
89
405
  function update() {
@@ -91,8 +407,12 @@ function update() {
91
407
  try {
92
408
  const result = runCapture("npm", ["view", "@silicaclaw/cli", "dist-tags", "--json"]);
93
409
  if ((result.status ?? 1) !== 0) {
94
- console.error("Failed to query npm dist-tags.");
410
+ headline();
411
+ console.log("");
412
+ console.error(paint("Update check failed", COLOR.bold, COLOR.yellow));
95
413
  if (result.stderr) console.error(result.stderr.trim());
414
+ console.log("");
415
+ kv("Try", "npm view @silicaclaw/cli dist-tags --json");
96
416
  process.exit(result.status ?? 1);
97
417
  }
98
418
  const text = String(result.stdout || "").trim();
@@ -100,43 +420,73 @@ function update() {
100
420
  const latest = tags.latest ? String(tags.latest) : "";
101
421
  const beta = tags.beta ? String(tags.beta) : "";
102
422
  showUpdateGuide(current, latest, beta);
423
+ const hasNewBeta = Boolean(beta) && beta !== current;
424
+ const npxRuntime = isNpxRun();
425
+
426
+ if (hasNewBeta) {
427
+ if (npxRuntime) {
428
+ kv("Update", `next run will use ${beta}`);
429
+ } else if (tryGlobalUpgrade(beta)) {
430
+ kv("Update", `installed ${beta}`);
431
+ } else {
432
+ kv("Update", `install ${beta} manually if needed`);
433
+ }
434
+ }
435
+
436
+ restartGatewayIfRunning();
103
437
  process.exit(0);
104
438
  } 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");
439
+ headline();
440
+ console.log("");
441
+ console.error(paint("Update check failed", COLOR.bold, COLOR.yellow));
442
+ console.error(error.message);
443
+ console.log("");
444
+ kv("Try", "npm view @silicaclaw/cli dist-tags --json");
108
445
  process.exit(1);
109
446
  }
110
447
  }
111
448
 
449
+ function doctor() {
450
+ const steps = [
451
+ { label: "Check", cmd: "npm", args: ["run", "-ws", "check"] },
452
+ { label: "Build", cmd: "npm", args: ["run", "-ws", "build"] },
453
+ { label: "Functional", cmd: "node", args: ["scripts/functional-check.mjs"] },
454
+ ];
455
+
456
+ headline();
457
+ console.log("");
458
+
459
+ for (const step of steps) {
460
+ const result = runCapture(step.cmd, step.args, { cwd: ROOT_DIR });
461
+ if ((result.status ?? 1) === 0) {
462
+ kv(step.label, paint("ok", COLOR.green));
463
+ continue;
464
+ }
465
+
466
+ kv(step.label, paint("failed", COLOR.yellow));
467
+ const detail = compactOutput(`${result.stdout || ""}\n${result.stderr || ""}`);
468
+ if (detail) {
469
+ console.log("");
470
+ console.log(detail);
471
+ }
472
+ process.exit(result.status ?? 1);
473
+ }
474
+ }
475
+
112
476
  function help() {
113
- console.log(`
114
- SilicaClaw CLI
115
-
116
- Usage:
117
- silicaclaw onboard
118
- silicaclaw connect
119
- silicaclaw update
120
- silicaclaw gateway <start|stop|restart|status|logs>
121
- silicaclaw local-console
122
- silicaclaw explorer
123
- silicaclaw signaling
124
- silicaclaw doctor
125
- silicaclaw version
126
- silicaclaw help
127
-
128
- Commands:
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)
133
- local-console Start local console (http://localhost:4310)
134
- explorer Start public explorer (http://localhost:4311)
135
- signaling Start WebRTC signaling preview server
136
- doctor Run project checks (npm run health)
137
- version Print SilicaClaw version
138
- help Show this help
139
- `.trim());
477
+ headline();
478
+ console.log("");
479
+ section("Commands");
480
+ kv("Install", "npx -y @silicaclaw/cli@beta install");
481
+ kv("Start", "silicaclaw start");
482
+ kv("Status", "silicaclaw status");
483
+ kv("Stop", "silicaclaw stop");
484
+ kv("Update", "silicaclaw update");
485
+ kv("Onboard", "silicaclaw onboard");
486
+ kv("Connect", "silicaclaw connect");
487
+ kv("Logs", "silicaclaw logs local-console");
488
+ kv("Doctor", "silicaclaw doctor");
489
+ kv("Help", "silicaclaw help");
140
490
  }
141
491
 
142
492
  const cmd = String(process.argv[2] || "help").trim().toLowerCase();
@@ -157,11 +507,23 @@ switch (cmd) {
157
507
  case "update":
158
508
  update();
159
509
  break;
510
+ case "install":
511
+ installPersistentCommand();
512
+ break;
160
513
  case "gateway":
161
514
  run("node", [resolve(ROOT_DIR, "scripts", "silicaclaw-gateway.mjs"), ...process.argv.slice(3)], {
162
515
  cwd: process.cwd(),
163
516
  });
164
517
  break;
518
+ case "start":
519
+ case "stop":
520
+ case "restart":
521
+ case "status":
522
+ case "logs":
523
+ run("node", [resolve(ROOT_DIR, "scripts", "silicaclaw-gateway.mjs"), cmd, ...process.argv.slice(3)], {
524
+ cwd: process.cwd(),
525
+ });
526
+ break;
165
527
  case "local-console":
166
528
  case "console":
167
529
  run("npm", ["run", "local-console"]);
@@ -175,12 +537,12 @@ switch (cmd) {
175
537
  run("npm", ["run", "webrtc-signaling"]);
176
538
  break;
177
539
  case "doctor":
178
- run("npm", ["run", "health"]);
540
+ doctor();
179
541
  break;
180
542
  case "version":
181
543
  case "-v":
182
544
  case "--version":
183
- console.log(readVersion());
545
+ headline();
184
546
  break;
185
547
  case "help":
186
548
  case "-h":