nextclaw 0.5.2 → 0.5.4
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 +311 -17
- package/package.json +3 -3
- package/templates/AGENTS.md +1 -0
- package/templates/USAGE.md +616 -0
package/dist/cli/index.js
CHANGED
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
writeFileSync as writeFileSync2
|
|
64
64
|
} from "fs";
|
|
65
65
|
import { dirname, join as join3, resolve as resolve4 } from "path";
|
|
66
|
+
import { createServer as createNetServer } from "net";
|
|
66
67
|
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
67
68
|
import { createInterface } from "readline";
|
|
68
69
|
import { createRequire } from "module";
|
|
@@ -1925,26 +1926,321 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1925
1926
|
const ok = await service.runJob(jobId, Boolean(opts.force));
|
|
1926
1927
|
console.log(ok ? "\u2713 Job executed" : `Failed to run job ${jobId}`);
|
|
1927
1928
|
}
|
|
1928
|
-
status() {
|
|
1929
|
+
async status(opts = {}) {
|
|
1930
|
+
const report = await this.collectRuntimeStatus({
|
|
1931
|
+
verbose: Boolean(opts.verbose),
|
|
1932
|
+
fix: Boolean(opts.fix)
|
|
1933
|
+
});
|
|
1934
|
+
if (opts.json) {
|
|
1935
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1936
|
+
process.exitCode = report.exitCode;
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1939
|
+
console.log(`${this.logo} ${APP_NAME} Status`);
|
|
1940
|
+
console.log(`Level: ${report.level}`);
|
|
1941
|
+
console.log(`Generated: ${report.generatedAt}`);
|
|
1942
|
+
console.log("");
|
|
1943
|
+
const processLabel = report.process.running ? `running (PID ${report.process.pid})` : report.process.staleState ? "stale-state" : "stopped";
|
|
1944
|
+
console.log(`Process: ${processLabel}`);
|
|
1945
|
+
console.log(`State file: ${report.serviceStatePath} ${report.serviceStateExists ? "\u2713" : "\u2717"}`);
|
|
1946
|
+
if (report.process.startedAt) {
|
|
1947
|
+
console.log(`Started: ${report.process.startedAt}`);
|
|
1948
|
+
}
|
|
1949
|
+
console.log(`Managed health: ${report.health.managed.state} (${report.health.managed.detail})`);
|
|
1950
|
+
if (!report.process.running) {
|
|
1951
|
+
console.log(`Configured health: ${report.health.configured.state} (${report.health.configured.detail})`);
|
|
1952
|
+
}
|
|
1953
|
+
console.log(`UI: ${report.endpoints.uiUrl ?? report.endpoints.configuredUiUrl}`);
|
|
1954
|
+
console.log(`API: ${report.endpoints.apiUrl ?? report.endpoints.configuredApiUrl}`);
|
|
1955
|
+
console.log(`Config: ${report.configPath} ${report.configExists ? "\u2713" : "\u2717"}`);
|
|
1956
|
+
console.log(`Workspace: ${report.workspacePath} ${report.workspaceExists ? "\u2713" : "\u2717"}`);
|
|
1957
|
+
console.log(`Model: ${report.model}`);
|
|
1958
|
+
for (const provider of report.providers) {
|
|
1959
|
+
console.log(`${provider.name}: ${provider.configured ? "\u2713" : "not set"}${provider.detail ? ` (${provider.detail})` : ""}`);
|
|
1960
|
+
}
|
|
1961
|
+
if (report.fixActions.length > 0) {
|
|
1962
|
+
console.log("");
|
|
1963
|
+
console.log("Fix actions:");
|
|
1964
|
+
for (const action of report.fixActions) {
|
|
1965
|
+
console.log(`- ${action}`);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
if (report.issues.length > 0) {
|
|
1969
|
+
console.log("");
|
|
1970
|
+
console.log("Issues:");
|
|
1971
|
+
for (const issue of report.issues) {
|
|
1972
|
+
console.log(`- ${issue}`);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
if (report.recommendations.length > 0) {
|
|
1976
|
+
console.log("");
|
|
1977
|
+
console.log("Recommendations:");
|
|
1978
|
+
for (const recommendation of report.recommendations) {
|
|
1979
|
+
console.log(`- ${recommendation}`);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
if (opts.verbose && report.logTail.length > 0) {
|
|
1983
|
+
console.log("");
|
|
1984
|
+
console.log("Recent logs:");
|
|
1985
|
+
for (const line of report.logTail) {
|
|
1986
|
+
console.log(line);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
process.exitCode = report.exitCode;
|
|
1990
|
+
}
|
|
1991
|
+
async doctor(opts = {}) {
|
|
1992
|
+
const report = await this.collectRuntimeStatus({
|
|
1993
|
+
verbose: Boolean(opts.verbose),
|
|
1994
|
+
fix: Boolean(opts.fix)
|
|
1995
|
+
});
|
|
1996
|
+
const checkPort = await this.checkPortAvailability({
|
|
1997
|
+
host: report.process.running ? report.endpoints.uiUrl ? new URL(report.endpoints.uiUrl).hostname : "127.0.0.1" : "127.0.0.1",
|
|
1998
|
+
port: (() => {
|
|
1999
|
+
try {
|
|
2000
|
+
const base = report.process.running && report.endpoints.uiUrl ? report.endpoints.uiUrl : report.endpoints.configuredUiUrl;
|
|
2001
|
+
return Number(new URL(base).port || 80);
|
|
2002
|
+
} catch {
|
|
2003
|
+
return 18791;
|
|
2004
|
+
}
|
|
2005
|
+
})()
|
|
2006
|
+
});
|
|
2007
|
+
const providerConfigured = report.providers.some((provider) => provider.configured);
|
|
2008
|
+
const checks = [
|
|
2009
|
+
{
|
|
2010
|
+
name: "config-file",
|
|
2011
|
+
status: report.configExists ? "pass" : "fail",
|
|
2012
|
+
detail: report.configPath
|
|
2013
|
+
},
|
|
2014
|
+
{
|
|
2015
|
+
name: "workspace-dir",
|
|
2016
|
+
status: report.workspaceExists ? "pass" : "warn",
|
|
2017
|
+
detail: report.workspacePath
|
|
2018
|
+
},
|
|
2019
|
+
{
|
|
2020
|
+
name: "service-state",
|
|
2021
|
+
status: report.process.staleState ? "fail" : report.process.running ? "pass" : "warn",
|
|
2022
|
+
detail: report.process.running ? `PID ${report.process.pid}` : report.process.staleState ? "state exists but process is not running" : "service not running"
|
|
2023
|
+
},
|
|
2024
|
+
{
|
|
2025
|
+
name: "service-health",
|
|
2026
|
+
status: report.process.running ? report.health.managed.state === "ok" ? "pass" : "fail" : report.health.configured.state === "ok" ? "warn" : "warn",
|
|
2027
|
+
detail: report.process.running ? `${report.health.managed.state}: ${report.health.managed.detail}` : `${report.health.configured.state}: ${report.health.configured.detail}`
|
|
2028
|
+
},
|
|
2029
|
+
{
|
|
2030
|
+
name: "ui-port-availability",
|
|
2031
|
+
status: report.process.running ? "pass" : checkPort.available ? "pass" : "fail",
|
|
2032
|
+
detail: report.process.running ? "managed by running service" : checkPort.available ? "available" : checkPort.detail
|
|
2033
|
+
},
|
|
2034
|
+
{
|
|
2035
|
+
name: "provider-config",
|
|
2036
|
+
status: providerConfigured ? "pass" : "warn",
|
|
2037
|
+
detail: providerConfigured ? "at least one provider configured" : "no provider api key configured"
|
|
2038
|
+
}
|
|
2039
|
+
];
|
|
2040
|
+
const failed = checks.filter((check) => check.status === "fail");
|
|
2041
|
+
const warned = checks.filter((check) => check.status === "warn");
|
|
2042
|
+
const exitCode = failed.length > 0 ? 1 : warned.length > 0 ? 1 : 0;
|
|
2043
|
+
if (opts.json) {
|
|
2044
|
+
console.log(
|
|
2045
|
+
JSON.stringify(
|
|
2046
|
+
{
|
|
2047
|
+
generatedAt: report.generatedAt,
|
|
2048
|
+
checks,
|
|
2049
|
+
status: report,
|
|
2050
|
+
exitCode
|
|
2051
|
+
},
|
|
2052
|
+
null,
|
|
2053
|
+
2
|
|
2054
|
+
)
|
|
2055
|
+
);
|
|
2056
|
+
process.exitCode = exitCode;
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
console.log(`${this.logo} ${APP_NAME} Doctor`);
|
|
2060
|
+
console.log(`Generated: ${report.generatedAt}`);
|
|
2061
|
+
console.log("");
|
|
2062
|
+
for (const check of checks) {
|
|
2063
|
+
const icon = check.status === "pass" ? "\u2713" : check.status === "warn" ? "!" : "\u2717";
|
|
2064
|
+
console.log(`${icon} ${check.name}: ${check.detail}`);
|
|
2065
|
+
}
|
|
2066
|
+
if (report.recommendations.length > 0) {
|
|
2067
|
+
console.log("");
|
|
2068
|
+
console.log("Recommendations:");
|
|
2069
|
+
for (const recommendation of report.recommendations) {
|
|
2070
|
+
console.log(`- ${recommendation}`);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
if (opts.verbose && report.logTail.length > 0) {
|
|
2074
|
+
console.log("");
|
|
2075
|
+
console.log("Recent logs:");
|
|
2076
|
+
for (const line of report.logTail) {
|
|
2077
|
+
console.log(line);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
process.exitCode = exitCode;
|
|
2081
|
+
}
|
|
2082
|
+
async collectRuntimeStatus(params) {
|
|
1929
2083
|
const configPath = getConfigPath();
|
|
1930
2084
|
const config2 = loadConfig();
|
|
1931
|
-
const
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
2085
|
+
const workspacePath = getWorkspacePath(config2.agents.defaults.workspace);
|
|
2086
|
+
const serviceStatePath = resolve4(getDataDir2(), "run", "service.json");
|
|
2087
|
+
const fixActions = [];
|
|
2088
|
+
let serviceState = readServiceState();
|
|
2089
|
+
if (params.fix && serviceState && !isProcessRunning(serviceState.pid)) {
|
|
2090
|
+
clearServiceState();
|
|
2091
|
+
fixActions.push("Cleared stale service state file.");
|
|
2092
|
+
serviceState = readServiceState();
|
|
2093
|
+
}
|
|
2094
|
+
const managedByState = Boolean(serviceState);
|
|
2095
|
+
const running = Boolean(serviceState && isProcessRunning(serviceState.pid));
|
|
2096
|
+
const staleState = Boolean(serviceState && !running);
|
|
2097
|
+
const configuredUi = resolveUiConfig(config2, { enabled: true, host: config2.ui.host, port: config2.ui.port });
|
|
2098
|
+
const configuredUiUrl = resolveUiApiBase(configuredUi.host, configuredUi.port);
|
|
2099
|
+
const configuredApiUrl = `${configuredUiUrl}/api`;
|
|
2100
|
+
const managedUiUrl = serviceState?.uiUrl ?? null;
|
|
2101
|
+
const managedApiUrl = serviceState?.apiUrl ?? null;
|
|
2102
|
+
const managedHealth = running && managedApiUrl ? await this.probeApiHealth(`${managedApiUrl}/health`) : { state: "unreachable", detail: "service not running" };
|
|
2103
|
+
const configuredHealth = await this.probeApiHealth(`${configuredApiUrl}/health`, 900);
|
|
2104
|
+
const orphanSuspected = !running && configuredHealth.state === "ok";
|
|
2105
|
+
const providers = PROVIDERS.map((spec) => {
|
|
1938
2106
|
const provider = config2.providers[spec.name];
|
|
1939
2107
|
if (!provider) {
|
|
1940
|
-
|
|
2108
|
+
return { name: spec.displayName ?? spec.name, configured: false, detail: "missing config" };
|
|
1941
2109
|
}
|
|
1942
2110
|
if (spec.isLocal) {
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2111
|
+
return {
|
|
2112
|
+
name: spec.displayName ?? spec.name,
|
|
2113
|
+
configured: Boolean(provider.apiBase),
|
|
2114
|
+
detail: provider.apiBase ? provider.apiBase : "apiBase not set"
|
|
2115
|
+
};
|
|
1946
2116
|
}
|
|
2117
|
+
return {
|
|
2118
|
+
name: spec.displayName ?? spec.name,
|
|
2119
|
+
configured: Boolean(provider.apiKey),
|
|
2120
|
+
detail: provider.apiKey ? "apiKey set" : "apiKey not set"
|
|
2121
|
+
};
|
|
2122
|
+
});
|
|
2123
|
+
const issues = [];
|
|
2124
|
+
const recommendations = [];
|
|
2125
|
+
if (!existsSync4(configPath)) {
|
|
2126
|
+
issues.push("Config file is missing.");
|
|
2127
|
+
recommendations.push(`Run ${APP_NAME} init to create config files.`);
|
|
1947
2128
|
}
|
|
2129
|
+
if (!existsSync4(workspacePath)) {
|
|
2130
|
+
issues.push("Workspace directory does not exist.");
|
|
2131
|
+
recommendations.push(`Run ${APP_NAME} init to create workspace templates.`);
|
|
2132
|
+
}
|
|
2133
|
+
if (staleState) {
|
|
2134
|
+
issues.push("Service state is stale (state exists but process is not running).");
|
|
2135
|
+
recommendations.push(`Run ${APP_NAME} status --fix to clean stale state.`);
|
|
2136
|
+
}
|
|
2137
|
+
if (running && managedHealth.state !== "ok") {
|
|
2138
|
+
issues.push(`Managed service health check failed: ${managedHealth.detail}`);
|
|
2139
|
+
recommendations.push(`Check logs at ${serviceState?.logPath ?? resolveServiceLogPath()}.`);
|
|
2140
|
+
}
|
|
2141
|
+
if (!running) {
|
|
2142
|
+
recommendations.push(`Run ${APP_NAME} start to launch the service.`);
|
|
2143
|
+
}
|
|
2144
|
+
if (orphanSuspected) {
|
|
2145
|
+
issues.push("A service appears healthy on configured API endpoint, but state is missing/stale.");
|
|
2146
|
+
recommendations.push("Another process may be occupying the UI port; stop it or use --ui-port with a free port.");
|
|
2147
|
+
}
|
|
2148
|
+
if (!providers.some((provider) => provider.configured)) {
|
|
2149
|
+
recommendations.push("Configure at least one provider API key in UI or config before expecting agent replies.");
|
|
2150
|
+
}
|
|
2151
|
+
const logTail = params.verbose ? this.readLogTail(serviceState?.logPath ?? resolveServiceLogPath(), 25) : [];
|
|
2152
|
+
const level = running ? managedHealth.state === "ok" ? issues.length > 0 ? "degraded" : "healthy" : "degraded" : "stopped";
|
|
2153
|
+
const exitCode = level === "healthy" ? 0 : level === "degraded" ? 1 : 2;
|
|
2154
|
+
return {
|
|
2155
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2156
|
+
configPath,
|
|
2157
|
+
configExists: existsSync4(configPath),
|
|
2158
|
+
workspacePath,
|
|
2159
|
+
workspaceExists: existsSync4(workspacePath),
|
|
2160
|
+
model: config2.agents.defaults.model,
|
|
2161
|
+
providers,
|
|
2162
|
+
serviceStatePath,
|
|
2163
|
+
serviceStateExists: existsSync4(serviceStatePath),
|
|
2164
|
+
fixActions,
|
|
2165
|
+
process: {
|
|
2166
|
+
managedByState,
|
|
2167
|
+
pid: serviceState?.pid ?? null,
|
|
2168
|
+
running,
|
|
2169
|
+
staleState,
|
|
2170
|
+
orphanSuspected,
|
|
2171
|
+
startedAt: serviceState?.startedAt ?? null
|
|
2172
|
+
},
|
|
2173
|
+
endpoints: {
|
|
2174
|
+
uiUrl: managedUiUrl,
|
|
2175
|
+
apiUrl: managedApiUrl,
|
|
2176
|
+
configuredUiUrl,
|
|
2177
|
+
configuredApiUrl
|
|
2178
|
+
},
|
|
2179
|
+
health: {
|
|
2180
|
+
managed: managedHealth,
|
|
2181
|
+
configured: configuredHealth
|
|
2182
|
+
},
|
|
2183
|
+
issues,
|
|
2184
|
+
recommendations,
|
|
2185
|
+
logTail,
|
|
2186
|
+
level,
|
|
2187
|
+
exitCode
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
async probeApiHealth(url, timeoutMs = 1500) {
|
|
2191
|
+
const controller = new AbortController();
|
|
2192
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2193
|
+
try {
|
|
2194
|
+
const response = await fetch(url, {
|
|
2195
|
+
method: "GET",
|
|
2196
|
+
signal: controller.signal
|
|
2197
|
+
});
|
|
2198
|
+
if (!response.ok) {
|
|
2199
|
+
return { state: "invalid-response", detail: `HTTP ${response.status}` };
|
|
2200
|
+
}
|
|
2201
|
+
const payload = await response.json();
|
|
2202
|
+
if (payload?.ok === true && payload?.data?.status === "ok") {
|
|
2203
|
+
return { state: "ok", detail: "health endpoint returned ok", payload };
|
|
2204
|
+
}
|
|
2205
|
+
return { state: "invalid-response", detail: "unexpected health payload", payload };
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
return { state: "unreachable", detail: String(error) };
|
|
2208
|
+
} finally {
|
|
2209
|
+
clearTimeout(timer);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
readLogTail(path, maxLines = 25) {
|
|
2213
|
+
if (!existsSync4(path)) {
|
|
2214
|
+
return [];
|
|
2215
|
+
}
|
|
2216
|
+
try {
|
|
2217
|
+
const lines = readFileSync3(path, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
2218
|
+
if (lines.length <= maxLines) {
|
|
2219
|
+
return lines;
|
|
2220
|
+
}
|
|
2221
|
+
return lines.slice(lines.length - maxLines);
|
|
2222
|
+
} catch {
|
|
2223
|
+
return [];
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
async checkPortAvailability(params) {
|
|
2227
|
+
return await new Promise((resolve5) => {
|
|
2228
|
+
const server = createNetServer();
|
|
2229
|
+
server.once("error", (error) => {
|
|
2230
|
+
resolve5({
|
|
2231
|
+
available: false,
|
|
2232
|
+
detail: `bind failed on ${params.host}:${params.port} (${String(error)})`
|
|
2233
|
+
});
|
|
2234
|
+
});
|
|
2235
|
+
server.listen(params.port, params.host, () => {
|
|
2236
|
+
server.close(() => {
|
|
2237
|
+
resolve5({
|
|
2238
|
+
available: true,
|
|
2239
|
+
detail: `bind ok on ${params.host}:${params.port}`
|
|
2240
|
+
});
|
|
2241
|
+
});
|
|
2242
|
+
});
|
|
2243
|
+
});
|
|
1948
2244
|
}
|
|
1949
2245
|
loadPluginRegistry(config2, workspaceDir) {
|
|
1950
2246
|
return loadOpenClawPlugins({
|
|
@@ -2490,6 +2786,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2490
2786
|
{ source: "USER.md", target: "USER.md" },
|
|
2491
2787
|
{ source: "IDENTITY.md", target: "IDENTITY.md" },
|
|
2492
2788
|
{ source: "TOOLS.md", target: "TOOLS.md" },
|
|
2789
|
+
{ source: "USAGE.md", target: "USAGE.md" },
|
|
2493
2790
|
{ source: "BOOT.md", target: "BOOT.md" },
|
|
2494
2791
|
{ source: "BOOTSTRAP.md", target: "BOOTSTRAP.md" },
|
|
2495
2792
|
{ source: "HEARTBEAT.md", target: "HEARTBEAT.md" },
|
|
@@ -2534,10 +2831,6 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2534
2831
|
return 0;
|
|
2535
2832
|
}
|
|
2536
2833
|
const force = Boolean(options.force);
|
|
2537
|
-
const existing = readdirSync(targetDir, { withFileTypes: true }).filter((entry) => !entry.name.startsWith("."));
|
|
2538
|
-
if (!force && existing.length > 0) {
|
|
2539
|
-
return 0;
|
|
2540
|
-
}
|
|
2541
2834
|
let seeded = 0;
|
|
2542
2835
|
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
2543
2836
|
if (!entry.isDirectory()) {
|
|
@@ -2685,5 +2978,6 @@ cron.command("add").requiredOption("-n, --name <name>", "Job name").requiredOpti
|
|
|
2685
2978
|
cron.command("remove <jobId>").action((jobId) => runtime.cronRemove(jobId));
|
|
2686
2979
|
cron.command("enable <jobId>").option("--disable", "Disable instead of enable").action((jobId, opts) => runtime.cronEnable(jobId, opts));
|
|
2687
2980
|
cron.command("run <jobId>").option("-f, --force", "Run even if disabled").action(async (jobId, opts) => runtime.cronRun(jobId, opts));
|
|
2688
|
-
program.command("status").description(`Show ${APP_NAME2} status`).action(() => runtime.status());
|
|
2981
|
+
program.command("status").description(`Show ${APP_NAME2} status`).option("--json", "Output JSON", false).option("--verbose", "Show extra diagnostics", false).option("--fix", "Fix stale service state when safe", false).action(async (opts) => runtime.status(opts));
|
|
2982
|
+
program.command("doctor").description(`Run ${APP_NAME2} diagnostics`).option("--json", "Output JSON", false).option("--verbose", "Show extra diagnostics", false).option("--fix", "Fix stale service state when safe", false).action(async (opts) => runtime.doctor(opts));
|
|
2689
2983
|
program.parseAsync(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextclaw",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
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.5.
|
|
41
|
+
"@nextclaw/core": "^0.5.2",
|
|
42
42
|
"@nextclaw/server": "^0.3.7",
|
|
43
43
|
"@nextclaw/openclaw-compat": "^0.1.3"
|
|
44
44
|
},
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"scripts": {
|
|
58
58
|
"dev": "tsx watch --tsconfig tsconfig.json src/cli/index.ts",
|
|
59
59
|
"dev:build": "tsx src/cli/index.ts",
|
|
60
|
-
"build": "tsup src/index.ts src/cli/index.ts --format esm --dts --out-dir dist && node scripts/copy-ui-dist.mjs",
|
|
60
|
+
"build": "node scripts/sync-usage-template.mjs && tsup src/index.ts src/cli/index.ts --format esm --dts --out-dir dist && node scripts/copy-ui-dist.mjs",
|
|
61
61
|
"start": "node dist/cli.js",
|
|
62
62
|
"lint": "eslint .",
|
|
63
63
|
"tsc": "tsc -p tsconfig.json",
|
package/templates/AGENTS.md
CHANGED
|
@@ -14,6 +14,7 @@ Before doing anything else:
|
|
|
14
14
|
2. Read `USER.md` (who you are helping)
|
|
15
15
|
3. Read `memory/YYYY-MM-DD.md` for today and yesterday
|
|
16
16
|
4. If in the main session with your human, also read `MEMORY.md`
|
|
17
|
+
5. For NextClaw self-management tasks (service/plugins/channels/config/cron), read `USAGE.md` first
|
|
17
18
|
|
|
18
19
|
## Memory
|
|
19
20
|
|
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
<!-- Generated by packages/nextclaw/scripts/sync-usage-template.mjs -->
|
|
2
|
+
<!-- Do not edit this file directly; edit docs/USAGE.md instead. -->
|
|
3
|
+
# NextClaw User Guide
|
|
4
|
+
|
|
5
|
+
This guide covers installation, configuration, channels, tools, automation, and troubleshooting for NextClaw.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## AI Self-Management Contract
|
|
10
|
+
|
|
11
|
+
When NextClaw AI needs to operate the product itself (status/doctor/plugins/channels/config/cron), follow these rules:
|
|
12
|
+
|
|
13
|
+
1. **Read this guide first** (`USAGE.md`) before executing management commands.
|
|
14
|
+
2. **Prefer machine-readable output** (`--json`) whenever available.
|
|
15
|
+
3. **Close the loop after changes** with `nextclaw status --json` (and `nextclaw doctor --json` when needed).
|
|
16
|
+
4. **Be explicit about restart semantics** (hot-apply, auto-restart, or manual restart required).
|
|
17
|
+
5. **Never invent commands**; use documented commands or `nextclaw --help` / `nextclaw <subcommand> --help`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Table of contents
|
|
22
|
+
|
|
23
|
+
- [AI Self-Management Contract](#ai-self-management-contract)
|
|
24
|
+
- [Quick Start](#quick-start)
|
|
25
|
+
- [Configuration](#configuration)
|
|
26
|
+
- [Workspace](#workspace)
|
|
27
|
+
- [Commands](#commands)
|
|
28
|
+
- [Plugins (OpenClaw compatibility)](#plugins-openclaw-compatibility)
|
|
29
|
+
- [Channels](#channels)
|
|
30
|
+
- [Tools](#tools)
|
|
31
|
+
- [Cron & Heartbeat](#cron--heartbeat)
|
|
32
|
+
- [Troubleshooting](#troubleshooting)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
1. Install:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm i -g nextclaw
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
2. Start the service (gateway + config UI in the background):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
nextclaw start
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
3. Open **http://127.0.0.1:18791** in your browser. Set a provider (e.g. OpenRouter) and model in the UI.
|
|
51
|
+
|
|
52
|
+
4. Optionally run `nextclaw init` to create a workspace with agent templates, or chat from the CLI:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
nextclaw agent -m "Hello!"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
5. Stop the service when done:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
nextclaw stop
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
- **Config file:** `~/.nextclaw/config.json`
|
|
69
|
+
- **Data directory:** Override with `NEXTCLAW_HOME=/path/to/dir` (config path becomes `$NEXTCLAW_HOME/config.json`).
|
|
70
|
+
|
|
71
|
+
### Minimal config
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"providers": {
|
|
76
|
+
"openrouter": { "apiKey": "sk-or-v1-xxx" }
|
|
77
|
+
},
|
|
78
|
+
"agents": {
|
|
79
|
+
"defaults": { "model": "minimax/MiniMax-M2.5" }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Provider examples
|
|
85
|
+
|
|
86
|
+
**OpenRouter (recommended)**
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"providers": { "openrouter": { "apiKey": "sk-or-v1-xxx" } },
|
|
91
|
+
"agents": { "defaults": { "model": "minimax/MiniMax-M2.5" } }
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**MiniMax (Mainland China)**
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"providers": {
|
|
100
|
+
"minimax": {
|
|
101
|
+
"apiKey": "sk-api-xxx",
|
|
102
|
+
"apiBase": "https://api.minimaxi.com/v1"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"agents": { "defaults": { "model": "minimax/MiniMax-M2.5" } }
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Local vLLM (or any OpenAI-compatible server)**
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"providers": {
|
|
114
|
+
"vllm": {
|
|
115
|
+
"apiKey": "dummy",
|
|
116
|
+
"apiBase": "http://localhost:8000/v1"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"agents": { "defaults": { "model": "meta-llama/Llama-3.1-8B-Instruct" } }
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Supported providers include OpenRouter, OpenAI, Anthropic, MiniMax, Moonshot, Gemini, DeepSeek, DashScope, Zhipu, Groq, vLLM, and AiHubMix. You can configure them in the UI or by editing `config.json`.
|
|
124
|
+
|
|
125
|
+
### Runtime config apply behavior (no restart)
|
|
126
|
+
|
|
127
|
+
When the gateway is already running, config changes from the UI or `nextclaw config set` are hot-applied for these paths:
|
|
128
|
+
|
|
129
|
+
- `providers.*`
|
|
130
|
+
- `channels.*`
|
|
131
|
+
- `agents.defaults.model`
|
|
132
|
+
- `agents.defaults.maxToolIterations`
|
|
133
|
+
- `agents.defaults.maxTokens`
|
|
134
|
+
- `agents.defaults.temperature`
|
|
135
|
+
- `agents.context.*`
|
|
136
|
+
- `tools.*`
|
|
137
|
+
|
|
138
|
+
Restart is still required for:
|
|
139
|
+
|
|
140
|
+
- `plugins.*`
|
|
141
|
+
- UI bind port (`--port` / `--ui-port`)
|
|
142
|
+
|
|
143
|
+
To confirm hot reload succeeded, check gateway console logs or `${NEXTCLAW_HOME:-~/.nextclaw}/logs/service.log` for messages like `Config reload: ... applied.`
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Workspace
|
|
148
|
+
|
|
149
|
+
- **Default path:** `~/.nextclaw/workspace`
|
|
150
|
+
- Override in config:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"agents": { "defaults": { "workspace": "~/my-nextclaw" } }
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Initialize the workspace (creates template files if missing):
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
nextclaw init
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Use `nextclaw init --force` to overwrite existing template files.
|
|
165
|
+
|
|
166
|
+
Created under the workspace:
|
|
167
|
+
|
|
168
|
+
| File / folder | Purpose |
|
|
169
|
+
|-----------------|----------------------------------|
|
|
170
|
+
| `AGENTS.md` | System instructions for the agent |
|
|
171
|
+
| `SOUL.md` | Personality and values |
|
|
172
|
+
| `USER.md` | User profile hints |
|
|
173
|
+
| `IDENTITY.md` | Identity context |
|
|
174
|
+
| `TOOLS.md` | Tool usage guidelines |
|
|
175
|
+
| `USAGE.md` | CLI operation guide for users and AI |
|
|
176
|
+
| `BOOT.md` / `BOOTSTRAP.md` | Boot context |
|
|
177
|
+
| `HEARTBEAT.md` | Tasks checked periodically |
|
|
178
|
+
| `memory/MEMORY.md` | Long-term notes |
|
|
179
|
+
| `skills/` | Custom skills |
|
|
180
|
+
|
|
181
|
+
**Heartbeat:** When the gateway is running, `HEARTBEAT.md` in the workspace is checked every 30 minutes. If it contains actionable tasks, the agent will process them.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Commands
|
|
186
|
+
|
|
187
|
+
| Command | Description |
|
|
188
|
+
|---------|-------------|
|
|
189
|
+
| `nextclaw start` | Start gateway + UI in the background |
|
|
190
|
+
| `nextclaw restart` | Restart the background service with optional start flags |
|
|
191
|
+
| `nextclaw stop` | Stop the background service |
|
|
192
|
+
| `nextclaw ui` | Start UI and gateway in the foreground |
|
|
193
|
+
| `nextclaw gateway` | Start gateway only (for channels) |
|
|
194
|
+
| `nextclaw serve` | Run gateway + UI in the foreground (no background) |
|
|
195
|
+
| `nextclaw agent -m "message"` | Send a one-off message to the agent |
|
|
196
|
+
| `nextclaw agent` | Interactive chat in the terminal |
|
|
197
|
+
| `nextclaw status` | Show runtime process/health/config status (`--json`, `--verbose`, `--fix`) |
|
|
198
|
+
| `nextclaw init` | Initialize workspace and template files |
|
|
199
|
+
| `nextclaw init --force` | Re-run init and overwrite templates |
|
|
200
|
+
| `nextclaw update` | Self-update the CLI |
|
|
201
|
+
| `nextclaw channels status` | Show enabled channels and status |
|
|
202
|
+
| `nextclaw doctor` | Run runtime diagnostics (`--json`, `--verbose`, `--fix`) |
|
|
203
|
+
| `nextclaw channels login` | Open QR login for supported channels |
|
|
204
|
+
| `nextclaw channels add --channel <id> [--code/--token/...]` | Run plugin channel setup (OpenClaw-compatible) and write config |
|
|
205
|
+
| `nextclaw cron list` | List scheduled jobs |
|
|
206
|
+
| `nextclaw cron add ...` | Add a cron job (see [Cron](#cron--heartbeat)) |
|
|
207
|
+
| `nextclaw cron remove <jobId>` | Remove a job |
|
|
208
|
+
| `nextclaw cron enable <jobId>` | Enable a job (use `--disable` to disable) |
|
|
209
|
+
| `nextclaw cron run <jobId>` | Run a job once (optionally with `--force` if disabled) |
|
|
210
|
+
| `nextclaw skills install <slug>` | Install a skill from ClawHub |
|
|
211
|
+
| `nextclaw clawhub install <slug>` | Same as `skills install` |
|
|
212
|
+
| `nextclaw plugins list` | List discovered OpenClaw-compatible plugins |
|
|
213
|
+
| `nextclaw plugins info <id>` | Show details of a plugin |
|
|
214
|
+
| `nextclaw config get <path>` | Get config value by path (use `--json` for structured output) |
|
|
215
|
+
| `nextclaw config set <path> <value>` | Set config value by path (use `--json` to parse value as JSON) |
|
|
216
|
+
| `nextclaw config unset <path>` | Remove config value by path |
|
|
217
|
+
| `nextclaw plugins install <path-or-spec>` | Install from local path, archive, or npm package |
|
|
218
|
+
| `nextclaw plugins enable <id>` | Enable a plugin in config |
|
|
219
|
+
| `nextclaw plugins disable <id>` | Disable a plugin in config |
|
|
220
|
+
| `nextclaw plugins uninstall <id>` | Remove plugin config/install record (supports `--dry-run`, `--force`, `--keep-files`) |
|
|
221
|
+
| `nextclaw plugins doctor` | Diagnose plugin load conflicts/errors |
|
|
222
|
+
|
|
223
|
+
Gateway options (when running `nextclaw gateway` or `nextclaw start`):
|
|
224
|
+
|
|
225
|
+
- `--ui` — enable the UI server with the gateway
|
|
226
|
+
- `--ui-port <port>` — UI port (default 18791 for start)
|
|
227
|
+
- `--ui-open` — open the browser when the UI starts
|
|
228
|
+
|
|
229
|
+
If service is already running, new UI port flags do not hot-apply; use `nextclaw restart ...` to apply them.
|
|
230
|
+
|
|
231
|
+
Status/diagnostics tips:
|
|
232
|
+
|
|
233
|
+
- `nextclaw status` shows runtime truth (process + health + config summary).
|
|
234
|
+
- `nextclaw status --json` outputs machine-readable status and sets exit code (`0` healthy, `1` degraded, `2` stopped).
|
|
235
|
+
- `nextclaw status --fix` safely clears stale service state if PID is dead.
|
|
236
|
+
- `nextclaw doctor` runs additional checks (state coherence, health, port availability, provider readiness).
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Plugins (OpenClaw compatibility)
|
|
241
|
+
|
|
242
|
+
NextClaw supports OpenClaw-compatible plugins while keeping compatibility logic isolated in `@nextclaw/openclaw-compat`.
|
|
243
|
+
For architecture boundaries and capability matrix, see [OpenClaw plugin compatibility guide](./openclaw-plugin-compat.md).
|
|
244
|
+
|
|
245
|
+
Typical flow:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# 1) Inspect discovered plugins
|
|
249
|
+
nextclaw plugins list
|
|
250
|
+
|
|
251
|
+
# 2) Install (path/archive/npm)
|
|
252
|
+
nextclaw plugins install ./my-plugin
|
|
253
|
+
nextclaw plugins install ./my-plugin.tgz
|
|
254
|
+
nextclaw plugins install @scope/openclaw-plugin
|
|
255
|
+
|
|
256
|
+
# 3) Inspect and toggle
|
|
257
|
+
nextclaw plugins info my-plugin
|
|
258
|
+
nextclaw config get plugins.entries.my-plugin.config --json
|
|
259
|
+
nextclaw plugins disable my-plugin
|
|
260
|
+
nextclaw plugins enable my-plugin
|
|
261
|
+
|
|
262
|
+
# 4) Uninstall
|
|
263
|
+
nextclaw plugins uninstall my-plugin --dry-run
|
|
264
|
+
nextclaw plugins uninstall my-plugin --force
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Notes:
|
|
268
|
+
|
|
269
|
+
- Plugin config is merged under `plugins.entries.<id>.config`.
|
|
270
|
+
- `plugins uninstall --keep-config` is accepted as a backward-compatible alias of `--keep-files`.
|
|
271
|
+
- If a plugin tool/channel/provider conflicts with a built-in capability, NextClaw rejects the conflicting registration and reports diagnostics.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Self-update
|
|
276
|
+
|
|
277
|
+
Use the built-in updater:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
nextclaw update
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Behavior:
|
|
284
|
+
|
|
285
|
+
- If `NEXTCLAW_UPDATE_COMMAND` is set, the CLI executes it (useful for custom update flows).
|
|
286
|
+
- Otherwise it falls back to `npm i -g nextclaw`.
|
|
287
|
+
- If the background service is running, restart it after the update to apply changes.
|
|
288
|
+
|
|
289
|
+
If the gateway is running, you can also ask the agent to update; the agent will call the gateway update tool only when you explicitly request it, and a restart will be scheduled afterward.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Channels
|
|
294
|
+
|
|
295
|
+
All message channels use a common **allowFrom** rule:
|
|
296
|
+
|
|
297
|
+
- **Empty `allowFrom`** (`[]`): allow all senders.
|
|
298
|
+
- **Non-empty `allowFrom`**: only messages from the listed user IDs are accepted.
|
|
299
|
+
|
|
300
|
+
Configure channels in the UI at http://127.0.0.1:18791 or in `~/.nextclaw/config.json` under `channels`.
|
|
301
|
+
|
|
302
|
+
### Discord
|
|
303
|
+
|
|
304
|
+
1. Create a bot in the [Discord Developer Portal](https://discord.com/developers/applications) and get the bot token.
|
|
305
|
+
2. Enable **MESSAGE CONTENT INTENT** for the bot.
|
|
306
|
+
3. Invite the bot to your server with permissions to read and send messages.
|
|
307
|
+
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"channels": {
|
|
311
|
+
"discord": {
|
|
312
|
+
"enabled": true,
|
|
313
|
+
"token": "YOUR_BOT_TOKEN",
|
|
314
|
+
"allowFrom": []
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Telegram
|
|
321
|
+
|
|
322
|
+
1. Create a bot via [@BotFather](https://t.me/BotFather) and get the token.
|
|
323
|
+
2. Get your user ID (e.g. from [@userinfobot](https://t.me/userinfobot)).
|
|
324
|
+
3. Add your user ID to `allowFrom` to restrict who can use the bot.
|
|
325
|
+
|
|
326
|
+
```json
|
|
327
|
+
{
|
|
328
|
+
"channels": {
|
|
329
|
+
"telegram": {
|
|
330
|
+
"enabled": true,
|
|
331
|
+
"token": "YOUR_BOT_TOKEN",
|
|
332
|
+
"allowFrom": ["YOUR_USER_ID"]
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Optional: set `"proxy": "http://localhost:7890"` (or your proxy URL) for network access.
|
|
339
|
+
|
|
340
|
+
### Slack
|
|
341
|
+
|
|
342
|
+
Socket mode is the typical setup. You need a **Bot Token** and an **App-Level Token** (with `connections:write`).
|
|
343
|
+
|
|
344
|
+
```json
|
|
345
|
+
{
|
|
346
|
+
"channels": {
|
|
347
|
+
"slack": {
|
|
348
|
+
"enabled": true,
|
|
349
|
+
"mode": "socket",
|
|
350
|
+
"botToken": "xoxb-...",
|
|
351
|
+
"appToken": "xapp-...",
|
|
352
|
+
"dm": { "enabled": true, "allowFrom": [] }
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
- `dm.enabled`: allow DMs to the bot.
|
|
359
|
+
- `dm.allowFrom`: restrict DMs to these user IDs; empty means allow all.
|
|
360
|
+
|
|
361
|
+
### Feishu (Lark)
|
|
362
|
+
|
|
363
|
+
Create an app in the [Feishu open platform](https://open.feishu.com/), obtain App ID, App Secret, and (if using encryption) Encrypt Key and Verification Token.
|
|
364
|
+
|
|
365
|
+
```json
|
|
366
|
+
{
|
|
367
|
+
"channels": {
|
|
368
|
+
"feishu": {
|
|
369
|
+
"enabled": true,
|
|
370
|
+
"appId": "YOUR_APP_ID",
|
|
371
|
+
"appSecret": "YOUR_APP_SECRET",
|
|
372
|
+
"encryptKey": "",
|
|
373
|
+
"verificationToken": "",
|
|
374
|
+
"allowFrom": []
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### DingTalk
|
|
381
|
+
|
|
382
|
+
Create an app in the [DingTalk open platform](https://open.dingtalk.com/) and get Client ID and Client Secret.
|
|
383
|
+
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"channels": {
|
|
387
|
+
"dingtalk": {
|
|
388
|
+
"enabled": true,
|
|
389
|
+
"clientId": "YOUR_CLIENT_ID",
|
|
390
|
+
"clientSecret": "YOUR_CLIENT_SECRET",
|
|
391
|
+
"allowFrom": []
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### WhatsApp
|
|
398
|
+
|
|
399
|
+
WhatsApp typically requires a bridge (e.g. a companion service). Configure the bridge URL and optional allowlist:
|
|
400
|
+
|
|
401
|
+
```json
|
|
402
|
+
{
|
|
403
|
+
"channels": {
|
|
404
|
+
"whatsapp": {
|
|
405
|
+
"enabled": true,
|
|
406
|
+
"bridgeUrl": "ws://localhost:3001",
|
|
407
|
+
"allowFrom": []
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Use `nextclaw channels login` when the bridge supports QR-based linking.
|
|
414
|
+
|
|
415
|
+
### Email
|
|
416
|
+
|
|
417
|
+
Configure IMAP (inbox) and SMTP (sending). The agent can read and reply to emails.
|
|
418
|
+
|
|
419
|
+
```json
|
|
420
|
+
{
|
|
421
|
+
"channels": {
|
|
422
|
+
"email": {
|
|
423
|
+
"enabled": true,
|
|
424
|
+
"consentGranted": true,
|
|
425
|
+
"imapHost": "imap.example.com",
|
|
426
|
+
"imapPort": 993,
|
|
427
|
+
"imapUsername": "you@example.com",
|
|
428
|
+
"imapPassword": "YOUR_PASSWORD",
|
|
429
|
+
"imapMailbox": "INBOX",
|
|
430
|
+
"imapUseSsl": true,
|
|
431
|
+
"smtpHost": "smtp.example.com",
|
|
432
|
+
"smtpPort": 587,
|
|
433
|
+
"smtpUsername": "you@example.com",
|
|
434
|
+
"smtpPassword": "YOUR_PASSWORD",
|
|
435
|
+
"smtpUseTls": true,
|
|
436
|
+
"fromAddress": "you@example.com",
|
|
437
|
+
"autoReplyEnabled": true,
|
|
438
|
+
"pollIntervalSeconds": 30,
|
|
439
|
+
"allowFrom": []
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Set `consentGranted` to `true` after you understand that the agent will read and send mail. Use `allowFrom` to restrict to certain sender addresses if desired.
|
|
446
|
+
|
|
447
|
+
### QQ
|
|
448
|
+
|
|
449
|
+
Use the QQ open platform app credentials.
|
|
450
|
+
|
|
451
|
+
```json
|
|
452
|
+
{
|
|
453
|
+
"channels": {
|
|
454
|
+
"qq": {
|
|
455
|
+
"enabled": true,
|
|
456
|
+
"appId": "YOUR_APP_ID",
|
|
457
|
+
"secret": "YOUR_SECRET",
|
|
458
|
+
"markdownSupport": false,
|
|
459
|
+
"allowFrom": []
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Mochat
|
|
466
|
+
|
|
467
|
+
Mochat uses a claw token and optional socket URL. Configure base URL, socket, and (optionally) sessions/panels and group rules.
|
|
468
|
+
|
|
469
|
+
```json
|
|
470
|
+
{
|
|
471
|
+
"channels": {
|
|
472
|
+
"mochat": {
|
|
473
|
+
"enabled": true,
|
|
474
|
+
"baseUrl": "https://mochat.io",
|
|
475
|
+
"socketUrl": "",
|
|
476
|
+
"clawToken": "YOUR_CLAW_TOKEN",
|
|
477
|
+
"agentUserId": "",
|
|
478
|
+
"sessions": [],
|
|
479
|
+
"panels": [],
|
|
480
|
+
"allowFrom": []
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
After changing channel config, NextClaw hot-reloads channel runtime automatically when the gateway is running.
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## Tools
|
|
491
|
+
|
|
492
|
+
### Web search (Brave)
|
|
493
|
+
|
|
494
|
+
Add a Brave Search API key to enable web search for the agent:
|
|
495
|
+
|
|
496
|
+
```json
|
|
497
|
+
{
|
|
498
|
+
"tools": {
|
|
499
|
+
"web": {
|
|
500
|
+
"search": { "apiKey": "YOUR_BRAVE_KEY", "maxResults": 5 }
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Command execution (exec)
|
|
507
|
+
|
|
508
|
+
Allow the agent to run shell commands:
|
|
509
|
+
|
|
510
|
+
```json
|
|
511
|
+
{
|
|
512
|
+
"tools": {
|
|
513
|
+
"exec": { "timeout": 60 }
|
|
514
|
+
},
|
|
515
|
+
"restrictToWorkspace": false
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
- `timeout`: max seconds per command.
|
|
520
|
+
- `restrictToWorkspace`: if `true`, commands are restricted to the agent workspace directory; if `false`, the agent can run commands in other paths (use with care).
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Cron & Heartbeat
|
|
525
|
+
|
|
526
|
+
### Cron
|
|
527
|
+
|
|
528
|
+
Schedule one-off or recurring tasks. The agent receives the message at the scheduled time.
|
|
529
|
+
|
|
530
|
+
List jobs:
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
nextclaw cron list
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Add a one-time job (run at a specific time, ISO format):
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
nextclaw cron add -n "reminder" -m "Stand up and stretch" --at "2026-02-15T09:00:00"
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
Add a recurring job (cron expression):
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
nextclaw cron add -n "daily-summary" -m "Summarize yesterday" -c "0 9 * * *"
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Add a job that runs every N seconds:
|
|
549
|
+
|
|
550
|
+
```bash
|
|
551
|
+
nextclaw cron add -n "ping" -m "Ping" -e 3600
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Optional: deliver the agent’s reply to a channel:
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
nextclaw cron add -n "daily" -m "Daily briefing" -c "0 9 * * *" --deliver --to <recipient> --channel <channel>
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Remove, enable, or disable a job:
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
nextclaw cron remove <jobId>
|
|
564
|
+
nextclaw cron enable <jobId>
|
|
565
|
+
nextclaw cron enable <jobId> --disable
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
Run a job once (e.g. for testing):
|
|
569
|
+
|
|
570
|
+
```bash
|
|
571
|
+
nextclaw cron run <jobId>
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Heartbeat
|
|
575
|
+
|
|
576
|
+
When the gateway is running, it checks the workspace file `HEARTBEAT.md` periodically (e.g. every 30 minutes). If the file contains actionable tasks, the agent processes them. Edit `HEARTBEAT.md` in your workspace to add or change tasks.
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## UI (optional)
|
|
581
|
+
|
|
582
|
+
You can tune the UI server in config:
|
|
583
|
+
|
|
584
|
+
```json
|
|
585
|
+
{
|
|
586
|
+
"ui": {
|
|
587
|
+
"enabled": true,
|
|
588
|
+
"host": "0.0.0.0",
|
|
589
|
+
"port": 18791,
|
|
590
|
+
"open": false
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
- `enabled`: whether the UI server is started with the gateway (e.g. when using `nextclaw start`).
|
|
596
|
+
- `host` / `port`: bind address and port; `ui.host` is read-only in practice (CLI start paths always enforce `0.0.0.0`).
|
|
597
|
+
- `open`: open the default browser when the UI starts.
|
|
598
|
+
|
|
599
|
+
Default URL when using `nextclaw start`: **http://127.0.0.1:18791**.
|
|
600
|
+
|
|
601
|
+
NextClaw binds UI to `0.0.0.0` by default and attempts to detect/print a public IP-based URL at startup.
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## Troubleshooting
|
|
606
|
+
|
|
607
|
+
| Issue | What to check |
|
|
608
|
+
|-------|----------------|
|
|
609
|
+
| **401 / invalid API key** | Verify the provider `apiKey` and `apiBase` in config or UI. Ensure no extra spaces or wrong key. |
|
|
610
|
+
| **Unknown model** | Confirm the model ID is supported by your provider (e.g. OpenRouter model list). |
|
|
611
|
+
| **No replies on a channel** | Ensure the channel is `enabled`, `allowFrom` includes your user ID if set, and the gateway is running (`nextclaw start` or `nextclaw gateway`). Run `nextclaw channels status` to see channel status. |
|
|
612
|
+
| **Port already in use** | Change `ui.port` in config or use `--ui-port` when starting. Default UI port is 18791, gateway 18790. |
|
|
613
|
+
| **Config not loading** | Ensure `NEXTCLAW_HOME` (if set) points to the directory that contains `config.json`. Run `nextclaw status` to see which config file is used. |
|
|
614
|
+
| **Agent not responding in CLI** | Run `nextclaw init` if you have not yet; ensure a provider and model are set and the provider key is valid. |
|
|
615
|
+
|
|
616
|
+
---
|