nextclaw 0.4.13 → 0.4.15

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/dist/cli/index.js CHANGED
@@ -81,7 +81,7 @@ import {
81
81
  import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "fs";
82
82
  import { join, resolve } from "path";
83
83
  import { spawn } from "child_process";
84
- import { createServer } from "net";
84
+ import { createServer, isIP } from "net";
85
85
  import { fileURLToPath } from "url";
86
86
  import { getDataDir, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
87
87
  function resolveUiConfig(config2, overrides) {
@@ -92,6 +92,41 @@ function resolveUiApiBase(host, port) {
92
92
  const normalizedHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
93
93
  return `http://${normalizedHost}:${port}`;
94
94
  }
95
+ function isLoopbackHost(host) {
96
+ const normalized = host.trim().toLowerCase();
97
+ return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1";
98
+ }
99
+ var PUBLIC_IP_CHECK_URLS = ["https://api.ipify.org", "https://ifconfig.me/ip"];
100
+ async function fetchPublicIpFrom(url, timeoutMs) {
101
+ const controller = new AbortController();
102
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
103
+ try {
104
+ const response = await fetch(url, {
105
+ signal: controller.signal,
106
+ headers: {
107
+ Accept: "text/plain"
108
+ }
109
+ });
110
+ if (!response.ok) {
111
+ return null;
112
+ }
113
+ const text = (await response.text()).trim();
114
+ return isIP(text) ? text : null;
115
+ } catch {
116
+ return null;
117
+ } finally {
118
+ clearTimeout(timer);
119
+ }
120
+ }
121
+ async function resolvePublicIp(timeoutMs = 1500) {
122
+ for (const endpoint of PUBLIC_IP_CHECK_URLS) {
123
+ const candidate = await fetchPublicIpFrom(endpoint, timeoutMs);
124
+ if (candidate) {
125
+ return candidate;
126
+ }
127
+ }
128
+ return null;
129
+ }
95
130
  function isDevRuntime() {
96
131
  return import.meta.url.includes("/src/cli/") || process.env.NEXTCLAW_DEV === "1";
97
132
  }
@@ -989,6 +1024,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
989
1024
  if (opts.uiOpen) {
990
1025
  uiOverrides.open = true;
991
1026
  }
1027
+ if (opts.public) {
1028
+ uiOverrides.enabled = true;
1029
+ if (!opts.uiHost) {
1030
+ uiOverrides.host = "0.0.0.0";
1031
+ }
1032
+ }
992
1033
  await this.startGateway({ uiOverrides });
993
1034
  }
994
1035
  async ui(opts) {
@@ -1002,6 +1043,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1002
1043
  if (opts.port) {
1003
1044
  uiOverrides.port = Number(opts.port);
1004
1045
  }
1046
+ if (opts.public && !opts.host) {
1047
+ uiOverrides.host = "0.0.0.0";
1048
+ }
1005
1049
  await this.startGateway({ uiOverrides, allowMissingProvider: true });
1006
1050
  }
1007
1051
  async start(opts) {
@@ -1016,6 +1060,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1016
1060
  if (opts.uiPort) {
1017
1061
  uiOverrides.port = Number(opts.uiPort);
1018
1062
  }
1063
+ if (opts.public && !opts.uiHost) {
1064
+ uiOverrides.host = "0.0.0.0";
1065
+ }
1019
1066
  const devMode = isDevRuntime();
1020
1067
  if (devMode) {
1021
1068
  const requestedUiPort = Number.isFinite(Number(opts.uiPort)) ? Number(opts.uiPort) : 18792;
@@ -1048,6 +1095,19 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1048
1095
  open: Boolean(opts.open)
1049
1096
  });
1050
1097
  }
1098
+ async restart(opts) {
1099
+ const state = readServiceState();
1100
+ if (state && isProcessRunning(state.pid)) {
1101
+ console.log(`Restarting ${APP_NAME}...`);
1102
+ await this.stopService();
1103
+ } else if (state) {
1104
+ clearServiceState();
1105
+ console.log("Service state was stale and has been cleaned up.");
1106
+ } else {
1107
+ console.log("No running service found. Starting a new service.");
1108
+ }
1109
+ await this.start(opts);
1110
+ }
1051
1111
  async serve(opts) {
1052
1112
  const uiOverrides = {
1053
1113
  enabled: true,
@@ -1059,6 +1119,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1059
1119
  if (opts.uiPort) {
1060
1120
  uiOverrides.port = Number(opts.uiPort);
1061
1121
  }
1122
+ if (opts.public && !opts.uiHost) {
1123
+ uiOverrides.host = "0.0.0.0";
1124
+ }
1062
1125
  const devMode = isDevRuntime();
1063
1126
  if (devMode && uiOverrides.port === void 0) {
1064
1127
  uiOverrides.port = 18792;
@@ -2091,6 +2154,20 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
2091
2154
  setPluginRuntimeBridge(null);
2092
2155
  }
2093
2156
  }
2157
+ async printPublicUiUrls(host, port) {
2158
+ if (isLoopbackHost(host)) {
2159
+ console.log('Public URL: disabled (UI host is loopback). Use "--public" or "--ui-host 0.0.0.0" to expose it.');
2160
+ return;
2161
+ }
2162
+ const publicIp = await resolvePublicIp();
2163
+ if (!publicIp) {
2164
+ console.log("Public URL: UI is exposed, but automatic public IP detection failed.");
2165
+ return;
2166
+ }
2167
+ const publicBase = `http://${publicIp}:${port}`;
2168
+ console.log(`Public UI (if firewall/NAT allows): ${publicBase}`);
2169
+ console.log(`Public API (if firewall/NAT allows): ${publicBase}/api`);
2170
+ }
2094
2171
  startUiIfEnabled(uiConfig, uiStaticDir) {
2095
2172
  if (!uiConfig.enabled) {
2096
2173
  return;
@@ -2106,6 +2183,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
2106
2183
  if (uiStaticDir) {
2107
2184
  console.log(`\u2713 UI frontend: ${uiUrl}`);
2108
2185
  }
2186
+ void this.printPublicUiUrls(uiServer.host, uiServer.port);
2109
2187
  if (uiConfig.open) {
2110
2188
  openBrowser(uiUrl);
2111
2189
  }
@@ -2196,6 +2274,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
2196
2274
  console.log(`\u2713 ${APP_NAME} started in background (PID ${state.pid})`);
2197
2275
  console.log(`UI: ${uiUrl}`);
2198
2276
  console.log(`API: ${apiUrl}`);
2277
+ await this.printPublicUiUrls(uiConfig.host, uiConfig.port);
2199
2278
  console.log(`Logs: ${logPath}`);
2200
2279
  console.log(`Stop: ${APP_NAME} stop`);
2201
2280
  if (options.open) {
@@ -2467,10 +2546,11 @@ var runtime = new CliRuntime({ logo: LOGO });
2467
2546
  program.name(APP_NAME2).description(`${LOGO} ${APP_NAME2} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
2468
2547
  program.command("onboard").description(`Initialize ${APP_NAME2} configuration and workspace`).action(async () => runtime.onboard());
2469
2548
  program.command("init").description(`Initialize ${APP_NAME2} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
2470
- program.command("gateway").description(`Start the ${APP_NAME2} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).action(async (opts) => runtime.gateway(opts));
2471
- program.command("ui").description(`Start the ${APP_NAME2} UI with gateway`).option("--host <host>", "UI host").option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
2472
- program.command("start").description(`Start the ${APP_NAME2} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => runtime.start(opts));
2473
- program.command("serve").description(`Run the ${APP_NAME2} gateway + UI in the foreground`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => runtime.serve(opts));
2549
+ program.command("gateway").description(`Start the ${APP_NAME2} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.gateway(opts));
2550
+ program.command("ui").description(`Start the ${APP_NAME2} UI with gateway`).option("--host <host>", "UI host").option("--port <port>", "UI port").option("--no-open", "Disable opening browser").option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.ui(opts));
2551
+ program.command("start").description(`Start the ${APP_NAME2} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.start(opts));
2552
+ program.command("restart").description(`Restart the ${APP_NAME2} background service`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after restart", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.restart(opts));
2553
+ program.command("serve").description(`Run the ${APP_NAME2} gateway + UI in the foreground`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.serve(opts));
2474
2554
  program.command("stop").description(`Stop the ${APP_NAME2} background service`).action(async () => runtime.stop());
2475
2555
  program.command("agent").description("Interact with the agent directly").option("-m, --message <message>", "Message to send to the agent").option("-s, --session <session>", "Session ID", "cli:default").option("--no-markdown", "Disable Markdown rendering").action(async (opts) => runtime.agent(opts));
2476
2556
  program.command("update").description(`Update ${APP_NAME2}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.4.13",
3
+ "version": "0.4.15",
4
4
  "description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
5
5
  "private": false,
6
6
  "type": "module",