nextclaw 0.4.14 → 0.4.16
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
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
MessageBus,
|
|
22
22
|
AgentLoop,
|
|
23
23
|
LiteLLMProvider,
|
|
24
|
+
LLMProvider,
|
|
24
25
|
ProviderManager,
|
|
25
26
|
ChannelManager,
|
|
26
27
|
SessionManager,
|
|
@@ -81,7 +82,7 @@ import {
|
|
|
81
82
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
82
83
|
import { join, resolve } from "path";
|
|
83
84
|
import { spawn } from "child_process";
|
|
84
|
-
import { createServer } from "net";
|
|
85
|
+
import { createServer, isIP } from "net";
|
|
85
86
|
import { fileURLToPath } from "url";
|
|
86
87
|
import { getDataDir, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
|
|
87
88
|
function resolveUiConfig(config2, overrides) {
|
|
@@ -92,6 +93,41 @@ function resolveUiApiBase(host, port) {
|
|
|
92
93
|
const normalizedHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
93
94
|
return `http://${normalizedHost}:${port}`;
|
|
94
95
|
}
|
|
96
|
+
function isLoopbackHost(host) {
|
|
97
|
+
const normalized = host.trim().toLowerCase();
|
|
98
|
+
return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1";
|
|
99
|
+
}
|
|
100
|
+
var PUBLIC_IP_CHECK_URLS = ["https://api.ipify.org", "https://ifconfig.me/ip"];
|
|
101
|
+
async function fetchPublicIpFrom(url, timeoutMs) {
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(url, {
|
|
106
|
+
signal: controller.signal,
|
|
107
|
+
headers: {
|
|
108
|
+
Accept: "text/plain"
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const text = (await response.text()).trim();
|
|
115
|
+
return isIP(text) ? text : null;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
} finally {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function resolvePublicIp(timeoutMs = 1500) {
|
|
123
|
+
for (const endpoint of PUBLIC_IP_CHECK_URLS) {
|
|
124
|
+
const candidate = await fetchPublicIpFrom(endpoint, timeoutMs);
|
|
125
|
+
if (candidate) {
|
|
126
|
+
return candidate;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
95
131
|
function isDevRuntime() {
|
|
96
132
|
return import.meta.url.includes("/src/cli/") || process.env.NEXTCLAW_DEV === "1";
|
|
97
133
|
}
|
|
@@ -812,6 +848,21 @@ function unsetAtConfigPath(root, pathSegments) {
|
|
|
812
848
|
delete record[last];
|
|
813
849
|
return true;
|
|
814
850
|
}
|
|
851
|
+
var MissingProvider = class extends LLMProvider {
|
|
852
|
+
constructor(defaultModel) {
|
|
853
|
+
super(null, null);
|
|
854
|
+
this.defaultModel = defaultModel;
|
|
855
|
+
}
|
|
856
|
+
setDefaultModel(model) {
|
|
857
|
+
this.defaultModel = model;
|
|
858
|
+
}
|
|
859
|
+
async chat() {
|
|
860
|
+
throw new Error("No API key configured yet. Configure provider credentials in UI and retry.");
|
|
861
|
+
}
|
|
862
|
+
getDefaultModel() {
|
|
863
|
+
return this.defaultModel;
|
|
864
|
+
}
|
|
865
|
+
};
|
|
815
866
|
var ConfigReloader = class {
|
|
816
867
|
constructor(options) {
|
|
817
868
|
this.options = options;
|
|
@@ -828,6 +879,9 @@ var ConfigReloader = class {
|
|
|
828
879
|
getChannels() {
|
|
829
880
|
return this.channels;
|
|
830
881
|
}
|
|
882
|
+
setApplyAgentRuntimeConfig(callback) {
|
|
883
|
+
this.options.applyAgentRuntimeConfig = callback;
|
|
884
|
+
}
|
|
831
885
|
async applyReloadPlan(nextConfig) {
|
|
832
886
|
const changedPaths = diffConfigPaths(this.currentConfig, nextConfig);
|
|
833
887
|
if (!changedPaths.length) {
|
|
@@ -837,9 +891,15 @@ var ConfigReloader = class {
|
|
|
837
891
|
const plan = buildReloadPlan(changedPaths);
|
|
838
892
|
if (plan.restartChannels) {
|
|
839
893
|
await this.reloadChannels(nextConfig);
|
|
894
|
+
console.log("Config reload: channels restarted.");
|
|
840
895
|
}
|
|
841
896
|
if (plan.reloadProviders) {
|
|
842
897
|
await this.reloadProvider(nextConfig);
|
|
898
|
+
console.log("Config reload: provider settings applied.");
|
|
899
|
+
}
|
|
900
|
+
if (plan.reloadAgent) {
|
|
901
|
+
this.options.applyAgentRuntimeConfig?.(nextConfig);
|
|
902
|
+
console.log("Config reload: agent defaults applied.");
|
|
843
903
|
}
|
|
844
904
|
if (plan.restartRequired.length > 0) {
|
|
845
905
|
this.options.onRestartRequired(plan.restartRequired);
|
|
@@ -989,6 +1049,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
989
1049
|
if (opts.uiOpen) {
|
|
990
1050
|
uiOverrides.open = true;
|
|
991
1051
|
}
|
|
1052
|
+
if (opts.public) {
|
|
1053
|
+
uiOverrides.enabled = true;
|
|
1054
|
+
if (!opts.uiHost) {
|
|
1055
|
+
uiOverrides.host = "0.0.0.0";
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
992
1058
|
await this.startGateway({ uiOverrides });
|
|
993
1059
|
}
|
|
994
1060
|
async ui(opts) {
|
|
@@ -1002,6 +1068,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1002
1068
|
if (opts.port) {
|
|
1003
1069
|
uiOverrides.port = Number(opts.port);
|
|
1004
1070
|
}
|
|
1071
|
+
if (opts.public && !opts.host) {
|
|
1072
|
+
uiOverrides.host = "0.0.0.0";
|
|
1073
|
+
}
|
|
1005
1074
|
await this.startGateway({ uiOverrides, allowMissingProvider: true });
|
|
1006
1075
|
}
|
|
1007
1076
|
async start(opts) {
|
|
@@ -1016,6 +1085,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1016
1085
|
if (opts.uiPort) {
|
|
1017
1086
|
uiOverrides.port = Number(opts.uiPort);
|
|
1018
1087
|
}
|
|
1088
|
+
if (opts.public && !opts.uiHost) {
|
|
1089
|
+
uiOverrides.host = "0.0.0.0";
|
|
1090
|
+
}
|
|
1019
1091
|
const devMode = isDevRuntime();
|
|
1020
1092
|
if (devMode) {
|
|
1021
1093
|
const requestedUiPort = Number.isFinite(Number(opts.uiPort)) ? Number(opts.uiPort) : 18792;
|
|
@@ -1072,6 +1144,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1072
1144
|
if (opts.uiPort) {
|
|
1073
1145
|
uiOverrides.port = Number(opts.uiPort);
|
|
1074
1146
|
}
|
|
1147
|
+
if (opts.public && !opts.uiHost) {
|
|
1148
|
+
uiOverrides.host = "0.0.0.0";
|
|
1149
|
+
}
|
|
1075
1150
|
const devMode = isDevRuntime();
|
|
1076
1151
|
if (devMode && uiOverrides.port === void 0) {
|
|
1077
1152
|
uiOverrides.port = 18792;
|
|
@@ -1936,7 +2011,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1936
2011
|
this.logPluginDiagnostics(pluginRegistry);
|
|
1937
2012
|
const bus = new MessageBus();
|
|
1938
2013
|
const provider = options.allowMissingProvider === true ? this.makeProvider(config2, { allowMissing: true }) : this.makeProvider(config2);
|
|
1939
|
-
const providerManager =
|
|
2014
|
+
const providerManager = new ProviderManager(provider ?? this.makeMissingProvider(config2));
|
|
1940
2015
|
const sessionManager = new SessionManager(workspace);
|
|
1941
2016
|
const cronStorePath = join3(getDataDir2(), "cron", "jobs.json");
|
|
1942
2017
|
const cron2 = new CronService(cronStorePath);
|
|
@@ -1944,11 +2019,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1944
2019
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
1945
2020
|
const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
|
|
1946
2021
|
if (!provider) {
|
|
1947
|
-
|
|
1948
|
-
console.log("Warning: No API key configured. UI server only.");
|
|
1949
|
-
await new Promise(() => {
|
|
1950
|
-
});
|
|
1951
|
-
return;
|
|
2022
|
+
console.warn("Warning: No API key configured. The gateway is running, but agent replies are disabled until provider config is set.");
|
|
1952
2023
|
}
|
|
1953
2024
|
const channels2 = new ChannelManager(config2, bus, sessionManager, extensionRegistry.channels);
|
|
1954
2025
|
const reloader = new ConfigReloader({
|
|
@@ -1957,7 +2028,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1957
2028
|
bus,
|
|
1958
2029
|
sessionManager,
|
|
1959
2030
|
providerManager,
|
|
1960
|
-
makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }),
|
|
2031
|
+
makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }) ?? this.makeMissingProvider(nextConfig),
|
|
1961
2032
|
loadConfig,
|
|
1962
2033
|
getExtensionChannels: () => extensionRegistry.channels,
|
|
1963
2034
|
onRestartRequired: (paths) => {
|
|
@@ -1973,7 +2044,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1973
2044
|
});
|
|
1974
2045
|
const agent = new AgentLoop({
|
|
1975
2046
|
bus,
|
|
1976
|
-
providerManager
|
|
2047
|
+
providerManager,
|
|
1977
2048
|
workspace,
|
|
1978
2049
|
model: config2.agents.defaults.model,
|
|
1979
2050
|
maxIterations: config2.agents.defaults.maxToolIterations,
|
|
@@ -1993,6 +2064,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1993
2064
|
accountId
|
|
1994
2065
|
})
|
|
1995
2066
|
});
|
|
2067
|
+
reloader.setApplyAgentRuntimeConfig((nextConfig) => agent.applyRuntimeConfig(nextConfig));
|
|
1996
2068
|
const pluginChannelBindings = getPluginChannelBindings(pluginRegistry);
|
|
1997
2069
|
setPluginRuntimeBridge({
|
|
1998
2070
|
loadConfig: () => this.toPluginConfigView(loadConfig(), pluginChannelBindings),
|
|
@@ -2104,6 +2176,20 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2104
2176
|
setPluginRuntimeBridge(null);
|
|
2105
2177
|
}
|
|
2106
2178
|
}
|
|
2179
|
+
async printPublicUiUrls(host, port) {
|
|
2180
|
+
if (isLoopbackHost(host)) {
|
|
2181
|
+
console.log('Public URL: disabled (UI host is loopback). Use "--public" or "--ui-host 0.0.0.0" to expose it.');
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const publicIp = await resolvePublicIp();
|
|
2185
|
+
if (!publicIp) {
|
|
2186
|
+
console.log("Public URL: UI is exposed, but automatic public IP detection failed.");
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
const publicBase = `http://${publicIp}:${port}`;
|
|
2190
|
+
console.log(`Public UI (if firewall/NAT allows): ${publicBase}`);
|
|
2191
|
+
console.log(`Public API (if firewall/NAT allows): ${publicBase}/api`);
|
|
2192
|
+
}
|
|
2107
2193
|
startUiIfEnabled(uiConfig, uiStaticDir) {
|
|
2108
2194
|
if (!uiConfig.enabled) {
|
|
2109
2195
|
return;
|
|
@@ -2119,6 +2205,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2119
2205
|
if (uiStaticDir) {
|
|
2120
2206
|
console.log(`\u2713 UI frontend: ${uiUrl}`);
|
|
2121
2207
|
}
|
|
2208
|
+
void this.printPublicUiUrls(uiServer.host, uiServer.port);
|
|
2122
2209
|
if (uiConfig.open) {
|
|
2123
2210
|
openBrowser(uiUrl);
|
|
2124
2211
|
}
|
|
@@ -2167,6 +2254,28 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2167
2254
|
console.log(`\u2713 ${APP_NAME} is already running (PID ${existing.pid})`);
|
|
2168
2255
|
console.log(`UI: ${existing.uiUrl}`);
|
|
2169
2256
|
console.log(`API: ${existing.apiUrl}`);
|
|
2257
|
+
const parsedUi = (() => {
|
|
2258
|
+
try {
|
|
2259
|
+
const parsed = new URL(existing.uiUrl);
|
|
2260
|
+
const port = Number(parsed.port || 80);
|
|
2261
|
+
return {
|
|
2262
|
+
host: existing.uiHost ?? parsed.hostname,
|
|
2263
|
+
port: Number.isFinite(port) ? port : existing.uiPort ?? 18791
|
|
2264
|
+
};
|
|
2265
|
+
} catch {
|
|
2266
|
+
return {
|
|
2267
|
+
host: existing.uiHost ?? "127.0.0.1",
|
|
2268
|
+
port: existing.uiPort ?? 18791
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
})();
|
|
2272
|
+
await this.printPublicUiUrls(parsedUi.host, parsedUi.port);
|
|
2273
|
+
if (parsedUi.host !== uiConfig.host || parsedUi.port !== uiConfig.port) {
|
|
2274
|
+
console.log(
|
|
2275
|
+
`Note: requested UI bind (${uiConfig.host}:${uiConfig.port}) differs from running service (${parsedUi.host}:${parsedUi.port}).`
|
|
2276
|
+
);
|
|
2277
|
+
console.log(`Run: ${APP_NAME} restart${uiConfig.host === "0.0.0.0" ? " --public" : ""}`);
|
|
2278
|
+
}
|
|
2170
2279
|
console.log(`Logs: ${existing.logPath}`);
|
|
2171
2280
|
console.log(`Stop: ${APP_NAME} stop`);
|
|
2172
2281
|
return;
|
|
@@ -2203,12 +2312,15 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2203
2312
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2204
2313
|
uiUrl,
|
|
2205
2314
|
apiUrl,
|
|
2315
|
+
uiHost: uiConfig.host,
|
|
2316
|
+
uiPort: uiConfig.port,
|
|
2206
2317
|
logPath
|
|
2207
2318
|
};
|
|
2208
2319
|
writeServiceState(state);
|
|
2209
2320
|
console.log(`\u2713 ${APP_NAME} started in background (PID ${state.pid})`);
|
|
2210
2321
|
console.log(`UI: ${uiUrl}`);
|
|
2211
2322
|
console.log(`API: ${apiUrl}`);
|
|
2323
|
+
await this.printPublicUiUrls(uiConfig.host, uiConfig.port);
|
|
2212
2324
|
console.log(`Logs: ${logPath}`);
|
|
2213
2325
|
console.log(`Stop: ${APP_NAME} stop`);
|
|
2214
2326
|
if (options.open) {
|
|
@@ -2258,6 +2370,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2258
2370
|
const normalized = answer.trim().toLowerCase();
|
|
2259
2371
|
return normalized === "y" || normalized === "yes";
|
|
2260
2372
|
}
|
|
2373
|
+
makeMissingProvider(config2) {
|
|
2374
|
+
return new MissingProvider(config2.agents.defaults.model);
|
|
2375
|
+
}
|
|
2261
2376
|
makeProvider(config2, options) {
|
|
2262
2377
|
const provider = getProvider(config2);
|
|
2263
2378
|
const model = config2.agents.defaults.model;
|
|
@@ -2480,11 +2595,11 @@ var runtime = new CliRuntime({ logo: LOGO });
|
|
|
2480
2595
|
program.name(APP_NAME2).description(`${LOGO} ${APP_NAME2} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
|
|
2481
2596
|
program.command("onboard").description(`Initialize ${APP_NAME2} configuration and workspace`).action(async () => runtime.onboard());
|
|
2482
2597
|
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) }));
|
|
2483
|
-
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));
|
|
2484
|
-
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));
|
|
2485
|
-
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));
|
|
2486
|
-
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).action(async (opts) => runtime.restart(opts));
|
|
2487
|
-
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));
|
|
2598
|
+
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));
|
|
2599
|
+
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));
|
|
2600
|
+
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));
|
|
2601
|
+
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));
|
|
2602
|
+
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));
|
|
2488
2603
|
program.command("stop").description(`Stop the ${APP_NAME2} background service`).action(async () => runtime.stop());
|
|
2489
2604
|
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));
|
|
2490
2605
|
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.
|
|
3
|
+
"version": "0.4.16",
|
|
4
4
|
"description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"chokidar": "^3.6.0",
|
|
40
40
|
"commander": "^12.1.0",
|
|
41
|
-
"@nextclaw/core": "^0.4.
|
|
41
|
+
"@nextclaw/core": "^0.4.14",
|
|
42
42
|
"@nextclaw/server": "^0.3.5",
|
|
43
43
|
"@nextclaw/openclaw-compat": "^0.1.2"
|
|
44
44
|
},
|