nextclaw 0.13.2 → 0.13.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 +1095 -446
- package/package.json +8 -8
- package/templates/USAGE.md +15 -5
package/dist/cli/index.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { APP_NAME as
|
|
5
|
+
import { APP_NAME as APP_NAME7, APP_TAGLINE } from "@nextclaw/core";
|
|
6
6
|
|
|
7
7
|
// src/cli/runtime.ts
|
|
8
8
|
import {
|
|
9
|
-
loadConfig as
|
|
10
|
-
saveConfig as
|
|
11
|
-
getConfigPath as
|
|
9
|
+
loadConfig as loadConfig15,
|
|
10
|
+
saveConfig as saveConfig10,
|
|
11
|
+
getConfigPath as getConfigPath7,
|
|
12
12
|
getDataDir as getDataDir9,
|
|
13
13
|
ConfigSchema as ConfigSchema2,
|
|
14
14
|
getWorkspacePath as getWorkspacePath10,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
AgentLoop,
|
|
18
18
|
ProviderManager as ProviderManager2,
|
|
19
19
|
resolveConfigSecrets as resolveConfigSecrets3,
|
|
20
|
-
APP_NAME as
|
|
20
|
+
APP_NAME as APP_NAME6,
|
|
21
21
|
DEFAULT_WORKSPACE_DIR,
|
|
22
22
|
DEFAULT_WORKSPACE_PATH
|
|
23
23
|
} from "@nextclaw/core";
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
30
30
|
import { join as join9, resolve as resolve12 } from "path";
|
|
31
31
|
import { createInterface as createInterface3 } from "readline";
|
|
32
|
-
import { fileURLToPath as
|
|
32
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
33
33
|
import { spawn as spawn3 } from "child_process";
|
|
34
34
|
|
|
35
35
|
// src/cli/restart-coordinator.ts
|
|
@@ -822,6 +822,15 @@ function writeServiceState(state) {
|
|
|
822
822
|
mkdirSync3(resolve4(path2, ".."), { recursive: true });
|
|
823
823
|
writeFileSync3(path2, JSON.stringify(state, null, 2));
|
|
824
824
|
}
|
|
825
|
+
function updateServiceState(updater) {
|
|
826
|
+
const current = readServiceState();
|
|
827
|
+
if (!current) {
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
const next = updater(current);
|
|
831
|
+
writeServiceState(next);
|
|
832
|
+
return next;
|
|
833
|
+
}
|
|
825
834
|
function clearServiceState() {
|
|
826
835
|
const path2 = resolveServiceStatePath();
|
|
827
836
|
if (existsSync4(path2)) {
|
|
@@ -1101,6 +1110,7 @@ function appendPluginCapabilityLines(lines, plugin) {
|
|
|
1101
1110
|
// src/cli/commands/dev-first-party-plugin-load-paths.ts
|
|
1102
1111
|
import fs from "fs";
|
|
1103
1112
|
import path from "path";
|
|
1113
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1104
1114
|
var readJsonFile = (filePath) => {
|
|
1105
1115
|
try {
|
|
1106
1116
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
@@ -1117,6 +1127,14 @@ var readString = (value) => {
|
|
|
1117
1127
|
const trimmed = value.trim();
|
|
1118
1128
|
return trimmed || void 0;
|
|
1119
1129
|
};
|
|
1130
|
+
var resolveDevFirstPartyPluginDir = (explicitDir, moduleDir = path.dirname(fileURLToPath2(import.meta.url))) => {
|
|
1131
|
+
const configured = explicitDir?.trim();
|
|
1132
|
+
if (configured) {
|
|
1133
|
+
return configured;
|
|
1134
|
+
}
|
|
1135
|
+
const inferred = path.resolve(moduleDir, "../../../../extensions");
|
|
1136
|
+
return fs.existsSync(inferred) ? inferred : void 0;
|
|
1137
|
+
};
|
|
1120
1138
|
var hasOpenClawExtensions = (pkg) => {
|
|
1121
1139
|
const openclaw = pkg.openclaw;
|
|
1122
1140
|
if (!openclaw || typeof openclaw !== "object" || Array.isArray(openclaw)) {
|
|
@@ -1168,7 +1186,7 @@ var readWorkspacePluginPackages = (workspaceExtensionsDir) => {
|
|
|
1168
1186
|
return packages;
|
|
1169
1187
|
};
|
|
1170
1188
|
var resolveDevFirstPartyPluginLoadPaths = (config2, workspaceExtensionsDir) => {
|
|
1171
|
-
const rootDir = workspaceExtensionsDir
|
|
1189
|
+
const rootDir = resolveDevFirstPartyPluginDir(workspaceExtensionsDir);
|
|
1172
1190
|
if (!rootDir) {
|
|
1173
1191
|
return [];
|
|
1174
1192
|
}
|
|
@@ -1193,7 +1211,7 @@ var resolveDevFirstPartyPluginLoadPaths = (config2, workspaceExtensionsDir) => {
|
|
|
1193
1211
|
return loadPaths;
|
|
1194
1212
|
};
|
|
1195
1213
|
var resolveDevFirstPartyPluginInstallRoots = (config2, workspaceExtensionsDir) => {
|
|
1196
|
-
const rootDir = workspaceExtensionsDir
|
|
1214
|
+
const rootDir = resolveDevFirstPartyPluginDir(workspaceExtensionsDir);
|
|
1197
1215
|
if (!rootDir) {
|
|
1198
1216
|
return [];
|
|
1199
1217
|
}
|
|
@@ -1480,7 +1498,7 @@ function toExtensionRegistry(pluginRegistry) {
|
|
|
1480
1498
|
|
|
1481
1499
|
// src/cli/commands/plugins.ts
|
|
1482
1500
|
function loadPluginRegistry(config2, workspaceDir) {
|
|
1483
|
-
const workspaceExtensionsDir = process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR;
|
|
1501
|
+
const workspaceExtensionsDir = resolveDevFirstPartyPluginDir(process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR);
|
|
1484
1502
|
const configWithDevPluginPaths = applyDevFirstPartyPluginLoadPaths(
|
|
1485
1503
|
config2,
|
|
1486
1504
|
workspaceExtensionsDir
|
|
@@ -3016,11 +3034,11 @@ var PlatformAuthCommands = class {
|
|
|
3016
3034
|
};
|
|
3017
3035
|
|
|
3018
3036
|
// src/cli/commands/remote.ts
|
|
3019
|
-
import { getConfigPath as
|
|
3037
|
+
import { getConfigPath as getConfigPath4, loadConfig as loadConfig10, saveConfig as saveConfig7 } from "@nextclaw/core";
|
|
3038
|
+
import { hostname as hostname3 } from "os";
|
|
3039
|
+
|
|
3040
|
+
// src/cli/remote/remote-relay-bridge.ts
|
|
3020
3041
|
import { ensureUiBridgeSecret } from "@nextclaw/server";
|
|
3021
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
3022
|
-
import { dirname as dirname2, join as join4 } from "path";
|
|
3023
|
-
import { hostname, platform as readPlatform } from "os";
|
|
3024
3042
|
function encodeBase64(bytes) {
|
|
3025
3043
|
return Buffer.from(bytes).toString("base64");
|
|
3026
3044
|
}
|
|
@@ -3030,9 +3048,122 @@ function decodeBase64(base64) {
|
|
|
3030
3048
|
}
|
|
3031
3049
|
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
3032
3050
|
}
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3051
|
+
var RemoteRelayBridge = class {
|
|
3052
|
+
constructor(localOrigin) {
|
|
3053
|
+
this.localOrigin = localOrigin;
|
|
3054
|
+
}
|
|
3055
|
+
async ensureLocalUiHealthy() {
|
|
3056
|
+
const response = await fetch(`${this.localOrigin}/api/health`);
|
|
3057
|
+
if (!response.ok) {
|
|
3058
|
+
throw new Error(`Local UI is not healthy at ${this.localOrigin}. Start NextClaw first.`);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
async forward(frame, socket) {
|
|
3062
|
+
const bridgeCookie = await this.requestBridgeCookie();
|
|
3063
|
+
const url = new URL(frame.path, this.localOrigin);
|
|
3064
|
+
const headers = this.createForwardHeaders(frame.headers, bridgeCookie);
|
|
3065
|
+
const response = await fetch(url, {
|
|
3066
|
+
method: frame.method,
|
|
3067
|
+
headers,
|
|
3068
|
+
body: frame.method === "GET" || frame.method === "HEAD" ? void 0 : decodeBase64(frame.bodyBase64)
|
|
3069
|
+
});
|
|
3070
|
+
const responseHeaders = Array.from(response.headers.entries()).filter(([key]) => {
|
|
3071
|
+
const lower = key.toLowerCase();
|
|
3072
|
+
return !["content-length", "connection", "transfer-encoding", "set-cookie"].includes(lower);
|
|
3073
|
+
});
|
|
3074
|
+
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
3075
|
+
if (response.body && contentType.startsWith("text/event-stream")) {
|
|
3076
|
+
await this.sendStreamingResponse({ frame, response, responseHeaders, socket });
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
const responseBody = response.body ? new Uint8Array(await response.arrayBuffer()) : new Uint8Array();
|
|
3080
|
+
socket.send(JSON.stringify({
|
|
3081
|
+
type: "response",
|
|
3082
|
+
requestId: frame.requestId,
|
|
3083
|
+
status: response.status,
|
|
3084
|
+
headers: responseHeaders,
|
|
3085
|
+
bodyBase64: encodeBase64(responseBody)
|
|
3086
|
+
}));
|
|
3087
|
+
}
|
|
3088
|
+
createForwardHeaders(headersList, bridgeCookie) {
|
|
3089
|
+
const headers = new Headers();
|
|
3090
|
+
for (const [key, value] of headersList) {
|
|
3091
|
+
const lower = key.toLowerCase();
|
|
3092
|
+
if ([
|
|
3093
|
+
"host",
|
|
3094
|
+
"connection",
|
|
3095
|
+
"content-length",
|
|
3096
|
+
"cookie",
|
|
3097
|
+
"x-forwarded-for",
|
|
3098
|
+
"x-forwarded-proto",
|
|
3099
|
+
"cf-connecting-ip"
|
|
3100
|
+
].includes(lower)) {
|
|
3101
|
+
continue;
|
|
3102
|
+
}
|
|
3103
|
+
headers.set(key, value);
|
|
3104
|
+
}
|
|
3105
|
+
if (bridgeCookie) {
|
|
3106
|
+
headers.set("cookie", bridgeCookie);
|
|
3107
|
+
}
|
|
3108
|
+
return headers;
|
|
3109
|
+
}
|
|
3110
|
+
async requestBridgeCookie() {
|
|
3111
|
+
const response = await fetch(`${this.localOrigin}/api/auth/bridge`, {
|
|
3112
|
+
method: "POST",
|
|
3113
|
+
headers: {
|
|
3114
|
+
"x-nextclaw-ui-bridge-secret": ensureUiBridgeSecret()
|
|
3115
|
+
}
|
|
3116
|
+
});
|
|
3117
|
+
const payload = await response.json();
|
|
3118
|
+
if (!response.ok || !payload.ok) {
|
|
3119
|
+
throw new Error(payload.error?.message ?? `Failed to request local auth bridge (${response.status}).`);
|
|
3120
|
+
}
|
|
3121
|
+
return typeof payload.data?.cookie === "string" && payload.data.cookie.trim().length > 0 ? payload.data.cookie.trim() : null;
|
|
3122
|
+
}
|
|
3123
|
+
async sendStreamingResponse(params) {
|
|
3124
|
+
params.socket.send(JSON.stringify({
|
|
3125
|
+
type: "response.start",
|
|
3126
|
+
requestId: params.frame.requestId,
|
|
3127
|
+
status: params.response.status,
|
|
3128
|
+
headers: params.responseHeaders
|
|
3129
|
+
}));
|
|
3130
|
+
const reader = params.response.body?.getReader();
|
|
3131
|
+
if (!reader) {
|
|
3132
|
+
params.socket.send(JSON.stringify({
|
|
3133
|
+
type: "response.end",
|
|
3134
|
+
requestId: params.frame.requestId
|
|
3135
|
+
}));
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
try {
|
|
3139
|
+
while (true) {
|
|
3140
|
+
const { value, done } = await reader.read();
|
|
3141
|
+
if (done) {
|
|
3142
|
+
break;
|
|
3143
|
+
}
|
|
3144
|
+
if (value && value.length > 0) {
|
|
3145
|
+
params.socket.send(JSON.stringify({
|
|
3146
|
+
type: "response.chunk",
|
|
3147
|
+
requestId: params.frame.requestId,
|
|
3148
|
+
bodyBase64: encodeBase64(value)
|
|
3149
|
+
}));
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
} finally {
|
|
3153
|
+
reader.releaseLock();
|
|
3154
|
+
}
|
|
3155
|
+
params.socket.send(JSON.stringify({
|
|
3156
|
+
type: "response.end",
|
|
3157
|
+
requestId: params.frame.requestId
|
|
3158
|
+
}));
|
|
3159
|
+
}
|
|
3160
|
+
};
|
|
3161
|
+
|
|
3162
|
+
// src/cli/remote/remote-platform-client.ts
|
|
3163
|
+
import { getConfigPath as getConfigPath3, getDataDir as getDataDir4, loadConfig as loadConfig8 } from "@nextclaw/core";
|
|
3164
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
3165
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
3166
|
+
import { hostname, platform as readPlatform } from "os";
|
|
3036
3167
|
function ensureDir(path2) {
|
|
3037
3168
|
mkdirSync4(path2, { recursive: true });
|
|
3038
3169
|
}
|
|
@@ -3051,9 +3182,82 @@ function writeJsonFile(path2, value) {
|
|
|
3051
3182
|
writeFileSync4(path2, `${JSON.stringify(value, null, 2)}
|
|
3052
3183
|
`, "utf-8");
|
|
3053
3184
|
}
|
|
3054
|
-
|
|
3185
|
+
function maskToken(value) {
|
|
3186
|
+
if (value.length <= 12) {
|
|
3187
|
+
return "<redacted>";
|
|
3188
|
+
}
|
|
3189
|
+
return `${value.slice(0, 6)}...${value.slice(-4)}`;
|
|
3190
|
+
}
|
|
3191
|
+
function normalizeOptionalString3(value) {
|
|
3192
|
+
if (typeof value !== "string") {
|
|
3193
|
+
return void 0;
|
|
3194
|
+
}
|
|
3195
|
+
const trimmed = value.trim();
|
|
3196
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
3197
|
+
}
|
|
3198
|
+
function delay(ms, signal) {
|
|
3199
|
+
return new Promise((resolveDelay, rejectDelay) => {
|
|
3200
|
+
const timer = setTimeout(() => {
|
|
3201
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3202
|
+
resolveDelay();
|
|
3203
|
+
}, ms);
|
|
3204
|
+
const onAbort = () => {
|
|
3205
|
+
clearTimeout(timer);
|
|
3206
|
+
rejectDelay(new Error("Remote connector aborted."));
|
|
3207
|
+
};
|
|
3208
|
+
if (signal) {
|
|
3209
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3210
|
+
}
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
function redactWsUrl(url) {
|
|
3214
|
+
try {
|
|
3215
|
+
const parsed = new URL(url);
|
|
3216
|
+
const token = parsed.searchParams.get("token");
|
|
3217
|
+
if (token) {
|
|
3218
|
+
parsed.searchParams.set("token", maskToken(token));
|
|
3219
|
+
}
|
|
3220
|
+
return parsed.toString();
|
|
3221
|
+
} catch {
|
|
3222
|
+
return url;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
var RemotePlatformClient = class {
|
|
3055
3226
|
remoteDir = join4(getDataDir4(), "remote");
|
|
3056
3227
|
devicePath = join4(this.remoteDir, "device.json");
|
|
3228
|
+
resolveRunContext(opts) {
|
|
3229
|
+
const { platformBase, token, config: config2 } = this.resolvePlatformAccess(opts);
|
|
3230
|
+
return {
|
|
3231
|
+
config: config2,
|
|
3232
|
+
platformBase,
|
|
3233
|
+
token,
|
|
3234
|
+
localOrigin: this.resolveLocalOrigin(config2, opts),
|
|
3235
|
+
displayName: this.resolveDisplayName(config2, opts),
|
|
3236
|
+
deviceInstallId: this.ensureDeviceInstallId(),
|
|
3237
|
+
autoReconnect: opts.once ? false : opts.autoReconnect ?? config2.remote.autoReconnect
|
|
3238
|
+
};
|
|
3239
|
+
}
|
|
3240
|
+
async registerDevice(params) {
|
|
3241
|
+
const response = await fetch(`${params.platformBase}/platform/remote/devices/register`, {
|
|
3242
|
+
method: "POST",
|
|
3243
|
+
headers: {
|
|
3244
|
+
"content-type": "application/json",
|
|
3245
|
+
authorization: `Bearer ${params.token}`
|
|
3246
|
+
},
|
|
3247
|
+
body: JSON.stringify({
|
|
3248
|
+
deviceInstallId: params.deviceInstallId,
|
|
3249
|
+
displayName: params.displayName,
|
|
3250
|
+
platform: readPlatform(),
|
|
3251
|
+
appVersion: getPackageVersion(),
|
|
3252
|
+
localOrigin: params.localOrigin
|
|
3253
|
+
})
|
|
3254
|
+
});
|
|
3255
|
+
const payload = await response.json();
|
|
3256
|
+
if (!response.ok || !payload.ok || !payload.data?.device) {
|
|
3257
|
+
throw new Error(payload.error?.message ?? `Failed to register remote device (${response.status}).`);
|
|
3258
|
+
}
|
|
3259
|
+
return payload.data.device;
|
|
3260
|
+
}
|
|
3057
3261
|
ensureDeviceInstallId() {
|
|
3058
3262
|
const existing = readJsonFile2(this.devicePath);
|
|
3059
3263
|
if (existing?.deviceInstallId?.trim()) {
|
|
@@ -3072,10 +3276,10 @@ var RemoteCommands = class {
|
|
|
3072
3276
|
if (!token) {
|
|
3073
3277
|
throw new Error('NextClaw platform token is missing. Run "nextclaw login" first.');
|
|
3074
3278
|
}
|
|
3075
|
-
const configuredApiBase = typeof nextclawProvider?.apiBase === "string" ? nextclawProvider.apiBase.trim() : "";
|
|
3076
|
-
const rawApiBase =
|
|
3279
|
+
const configuredApiBase = normalizeOptionalString3(config2.remote.platformApiBase) ?? (typeof nextclawProvider?.apiBase === "string" ? nextclawProvider.apiBase.trim() : "");
|
|
3280
|
+
const rawApiBase = normalizeOptionalString3(opts.apiBase) ?? configuredApiBase;
|
|
3077
3281
|
if (!rawApiBase) {
|
|
3078
|
-
throw new Error("Platform API base is missing. Pass --api-base
|
|
3282
|
+
throw new Error("Platform API base is missing. Pass --api-base, run nextclaw login, or set remote.platformApiBase.");
|
|
3079
3283
|
}
|
|
3080
3284
|
const { platformBase } = resolvePlatformApiBase({
|
|
3081
3285
|
explicitApiBase: rawApiBase,
|
|
@@ -3084,8 +3288,9 @@ var RemoteCommands = class {
|
|
|
3084
3288
|
return { platformBase, token, config: config2 };
|
|
3085
3289
|
}
|
|
3086
3290
|
resolveLocalOrigin(config2, opts) {
|
|
3087
|
-
|
|
3088
|
-
|
|
3291
|
+
const explicitOrigin = normalizeOptionalString3(opts.localOrigin);
|
|
3292
|
+
if (explicitOrigin) {
|
|
3293
|
+
return explicitOrigin.replace(/\/$/, "");
|
|
3089
3294
|
}
|
|
3090
3295
|
const state = readServiceState();
|
|
3091
3296
|
if (state && isProcessRunning(state.pid) && Number.isFinite(state.uiPort)) {
|
|
@@ -3094,191 +3299,413 @@ var RemoteCommands = class {
|
|
|
3094
3299
|
const configuredPort = typeof config2.ui?.port === "number" && Number.isFinite(config2.ui.port) ? config2.ui.port : 18791;
|
|
3095
3300
|
return `http://127.0.0.1:${configuredPort}`;
|
|
3096
3301
|
}
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
if (!response.ok) {
|
|
3100
|
-
throw new Error(`Local UI is not healthy at ${localOrigin}. Start NextClaw first.`);
|
|
3101
|
-
}
|
|
3302
|
+
resolveDisplayName(config2, opts) {
|
|
3303
|
+
return normalizeOptionalString3(opts.name) ?? normalizeOptionalString3(config2.remote.deviceName) ?? hostname();
|
|
3102
3304
|
}
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
},
|
|
3110
|
-
body: JSON.stringify({
|
|
3111
|
-
deviceInstallId: params.deviceInstallId,
|
|
3112
|
-
displayName: params.displayName,
|
|
3113
|
-
platform: readPlatform(),
|
|
3114
|
-
appVersion: getPackageVersion(),
|
|
3115
|
-
localOrigin: params.localOrigin
|
|
3116
|
-
})
|
|
3117
|
-
});
|
|
3118
|
-
const payload = await response.json();
|
|
3119
|
-
if (!response.ok || !payload.ok || !payload.data?.device) {
|
|
3120
|
-
throw new Error(payload.error?.message ?? `Failed to register remote device (${response.status}).`);
|
|
3121
|
-
}
|
|
3122
|
-
return payload.data.device;
|
|
3305
|
+
};
|
|
3306
|
+
|
|
3307
|
+
// src/cli/remote/remote-connector.ts
|
|
3308
|
+
var RemoteConnector = class {
|
|
3309
|
+
constructor(logger = console) {
|
|
3310
|
+
this.logger = logger;
|
|
3123
3311
|
}
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3312
|
+
platformClient = new RemotePlatformClient();
|
|
3313
|
+
async connectOnce(params) {
|
|
3314
|
+
return await new Promise((resolve13, reject) => {
|
|
3315
|
+
const socket = new WebSocket(params.wsUrl);
|
|
3316
|
+
let settled = false;
|
|
3317
|
+
let aborted = false;
|
|
3318
|
+
const pingTimer = setInterval(() => {
|
|
3319
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
3320
|
+
socket.send(JSON.stringify({ type: "ping", at: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
3321
|
+
}
|
|
3322
|
+
}, 15e3);
|
|
3323
|
+
const cleanup = () => {
|
|
3324
|
+
clearInterval(pingTimer);
|
|
3325
|
+
params.signal?.removeEventListener("abort", onAbort);
|
|
3326
|
+
};
|
|
3327
|
+
const finishResolve = (value) => {
|
|
3328
|
+
if (settled) {
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3331
|
+
settled = true;
|
|
3332
|
+
cleanup();
|
|
3333
|
+
resolve13(value);
|
|
3334
|
+
};
|
|
3335
|
+
const finishReject = (error) => {
|
|
3336
|
+
if (settled) {
|
|
3337
|
+
return;
|
|
3338
|
+
}
|
|
3339
|
+
settled = true;
|
|
3340
|
+
cleanup();
|
|
3341
|
+
reject(error);
|
|
3342
|
+
};
|
|
3343
|
+
const onAbort = () => {
|
|
3344
|
+
aborted = true;
|
|
3345
|
+
try {
|
|
3346
|
+
socket.close(1e3, "Remote connector aborted");
|
|
3347
|
+
} catch {
|
|
3348
|
+
finishResolve("aborted");
|
|
3349
|
+
}
|
|
3350
|
+
};
|
|
3351
|
+
if (params.signal) {
|
|
3352
|
+
if (params.signal.aborted) {
|
|
3353
|
+
onAbort();
|
|
3354
|
+
} else {
|
|
3355
|
+
params.signal.addEventListener("abort", onAbort, { once: true });
|
|
3356
|
+
}
|
|
3129
3357
|
}
|
|
3358
|
+
socket.addEventListener("open", () => {
|
|
3359
|
+
params.statusStore?.write({
|
|
3360
|
+
enabled: true,
|
|
3361
|
+
state: "connected",
|
|
3362
|
+
deviceId: params.deviceId,
|
|
3363
|
+
deviceName: params.displayName,
|
|
3364
|
+
platformBase: params.platformBase,
|
|
3365
|
+
localOrigin: params.localOrigin,
|
|
3366
|
+
lastConnectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3367
|
+
lastError: null
|
|
3368
|
+
});
|
|
3369
|
+
this.logger.info(`\u2713 Remote connector connected: ${redactWsUrl(params.wsUrl)}`);
|
|
3370
|
+
});
|
|
3371
|
+
socket.addEventListener("message", (event) => {
|
|
3372
|
+
this.handleSocketMessage({ data: event.data, relayBridge: params.relayBridge, socket });
|
|
3373
|
+
});
|
|
3374
|
+
socket.addEventListener("close", () => {
|
|
3375
|
+
finishResolve(aborted ? "aborted" : "closed");
|
|
3376
|
+
});
|
|
3377
|
+
socket.addEventListener("error", () => {
|
|
3378
|
+
if (aborted) {
|
|
3379
|
+
finishResolve("aborted");
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
finishReject(new Error("Remote connector websocket failed."));
|
|
3383
|
+
});
|
|
3130
3384
|
});
|
|
3131
|
-
const payload = await response.json();
|
|
3132
|
-
if (!response.ok || !payload.ok) {
|
|
3133
|
-
throw new Error(payload.error?.message ?? `Failed to request local auth bridge (${response.status}).`);
|
|
3134
|
-
}
|
|
3135
|
-
return typeof payload.data?.cookie === "string" && payload.data.cookie.trim().length > 0 ? payload.data.cookie.trim() : null;
|
|
3136
3385
|
}
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
const lower = key.toLowerCase();
|
|
3143
|
-
if ([
|
|
3144
|
-
"host",
|
|
3145
|
-
"connection",
|
|
3146
|
-
"content-length",
|
|
3147
|
-
"cookie",
|
|
3148
|
-
"x-forwarded-for",
|
|
3149
|
-
"x-forwarded-proto",
|
|
3150
|
-
"cf-connecting-ip"
|
|
3151
|
-
].includes(lower)) {
|
|
3152
|
-
continue;
|
|
3386
|
+
handleSocketMessage(params) {
|
|
3387
|
+
void (async () => {
|
|
3388
|
+
const frame = this.parseRelayFrame(params.data);
|
|
3389
|
+
if (!frame) {
|
|
3390
|
+
return;
|
|
3153
3391
|
}
|
|
3154
|
-
|
|
3392
|
+
try {
|
|
3393
|
+
await params.relayBridge.forward(frame, params.socket);
|
|
3394
|
+
} catch (error) {
|
|
3395
|
+
params.socket.send(JSON.stringify({
|
|
3396
|
+
type: "response.error",
|
|
3397
|
+
requestId: frame.requestId,
|
|
3398
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3399
|
+
}));
|
|
3400
|
+
}
|
|
3401
|
+
})();
|
|
3402
|
+
}
|
|
3403
|
+
parseRelayFrame(data) {
|
|
3404
|
+
try {
|
|
3405
|
+
const frame = JSON.parse(String(data ?? ""));
|
|
3406
|
+
return frame.type === "request" ? frame : null;
|
|
3407
|
+
} catch {
|
|
3408
|
+
return null;
|
|
3155
3409
|
}
|
|
3156
|
-
|
|
3157
|
-
|
|
3410
|
+
}
|
|
3411
|
+
async ensureDevice(params) {
|
|
3412
|
+
if (params.device) {
|
|
3413
|
+
return params.device;
|
|
3158
3414
|
}
|
|
3159
|
-
const
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3415
|
+
const device = await this.platformClient.registerDevice({
|
|
3416
|
+
platformBase: params.context.platformBase,
|
|
3417
|
+
token: params.context.token,
|
|
3418
|
+
deviceInstallId: params.context.deviceInstallId,
|
|
3419
|
+
displayName: params.context.displayName,
|
|
3420
|
+
localOrigin: params.context.localOrigin
|
|
3164
3421
|
});
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3422
|
+
this.logger.info(`\u2713 Remote device registered: ${device.displayName} (${device.id})`);
|
|
3423
|
+
this.logger.info(`\u2713 Local origin: ${params.context.localOrigin}`);
|
|
3424
|
+
this.logger.info(`\u2713 Platform: ${params.context.platformBase}`);
|
|
3425
|
+
return device;
|
|
3426
|
+
}
|
|
3427
|
+
writeRemoteState(statusStore, next) {
|
|
3428
|
+
statusStore?.write(next);
|
|
3429
|
+
}
|
|
3430
|
+
async runCycle(params) {
|
|
3431
|
+
try {
|
|
3432
|
+
this.writeRemoteState(params.opts.statusStore, {
|
|
3433
|
+
enabled: true,
|
|
3434
|
+
state: "connecting",
|
|
3435
|
+
deviceId: params.device?.id,
|
|
3436
|
+
deviceName: params.context.displayName,
|
|
3437
|
+
platformBase: params.context.platformBase,
|
|
3438
|
+
localOrigin: params.context.localOrigin,
|
|
3439
|
+
lastError: null
|
|
3440
|
+
});
|
|
3441
|
+
const device = await this.ensureDevice({ device: params.device, context: params.context });
|
|
3442
|
+
const wsUrl = `${params.context.platformBase.replace(/^http/i, "ws")}/platform/remote/connect?deviceId=${encodeURIComponent(device.id)}&token=${encodeURIComponent(params.context.token)}`;
|
|
3443
|
+
const outcome = await this.connectOnce({
|
|
3444
|
+
wsUrl,
|
|
3445
|
+
relayBridge: params.relayBridge,
|
|
3446
|
+
signal: params.opts.signal,
|
|
3447
|
+
statusStore: params.opts.statusStore,
|
|
3448
|
+
displayName: params.context.displayName,
|
|
3449
|
+
deviceId: device.id,
|
|
3450
|
+
platformBase: params.context.platformBase,
|
|
3451
|
+
localOrigin: params.context.localOrigin
|
|
3452
|
+
});
|
|
3453
|
+
if (outcome !== "aborted") {
|
|
3454
|
+
this.writeRemoteState(params.opts.statusStore, {
|
|
3455
|
+
enabled: true,
|
|
3456
|
+
state: "disconnected",
|
|
3457
|
+
deviceId: device.id,
|
|
3458
|
+
deviceName: params.context.displayName,
|
|
3459
|
+
platformBase: params.context.platformBase,
|
|
3460
|
+
localOrigin: params.context.localOrigin,
|
|
3461
|
+
lastError: null
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3464
|
+
return { device, aborted: outcome === "aborted" };
|
|
3465
|
+
} catch (error) {
|
|
3466
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3467
|
+
this.writeRemoteState(params.opts.statusStore, {
|
|
3468
|
+
enabled: true,
|
|
3469
|
+
state: "error",
|
|
3470
|
+
deviceId: params.device?.id,
|
|
3471
|
+
deviceName: params.context.displayName,
|
|
3472
|
+
platformBase: params.context.platformBase,
|
|
3473
|
+
localOrigin: params.context.localOrigin,
|
|
3474
|
+
lastError: message
|
|
3475
|
+
});
|
|
3476
|
+
this.logger.error(`Remote connector error: ${message}`);
|
|
3477
|
+
return { device: params.device, aborted: false };
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
async run(opts = {}) {
|
|
3481
|
+
const context = this.platformClient.resolveRunContext(opts);
|
|
3482
|
+
const relayBridge = new RemoteRelayBridge(context.localOrigin);
|
|
3483
|
+
await relayBridge.ensureLocalUiHealthy();
|
|
3484
|
+
let device = null;
|
|
3485
|
+
while (!opts.signal?.aborted) {
|
|
3486
|
+
const cycle = await this.runCycle({ device, context, relayBridge, opts });
|
|
3487
|
+
device = cycle.device;
|
|
3488
|
+
if (cycle.aborted || !context.autoReconnect || opts.signal?.aborted) {
|
|
3489
|
+
break;
|
|
3490
|
+
}
|
|
3491
|
+
this.logger.warn("Remote connector disconnected. Reconnecting in 3s...");
|
|
3178
3492
|
try {
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
break;
|
|
3183
|
-
}
|
|
3184
|
-
if (value && value.length > 0) {
|
|
3185
|
-
params.socket.send(JSON.stringify({
|
|
3186
|
-
type: "response.chunk",
|
|
3187
|
-
requestId: params.frame.requestId,
|
|
3188
|
-
bodyBase64: encodeBase64(value)
|
|
3189
|
-
}));
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
} finally {
|
|
3193
|
-
reader.releaseLock();
|
|
3493
|
+
await delay(3e3, opts.signal);
|
|
3494
|
+
} catch {
|
|
3495
|
+
break;
|
|
3194
3496
|
}
|
|
3195
|
-
params.socket.send(JSON.stringify({
|
|
3196
|
-
type: "response.end",
|
|
3197
|
-
requestId: params.frame.requestId
|
|
3198
|
-
}));
|
|
3199
|
-
return;
|
|
3200
3497
|
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3498
|
+
this.writeRemoteState(opts.statusStore, {
|
|
3499
|
+
enabled: opts.mode === "service" ? true : Boolean(context.config.remote.enabled),
|
|
3500
|
+
state: opts.signal?.aborted ? "disconnected" : "disabled",
|
|
3501
|
+
deviceId: device?.id,
|
|
3502
|
+
deviceName: context.displayName,
|
|
3503
|
+
platformBase: context.platformBase,
|
|
3504
|
+
localOrigin: context.localOrigin,
|
|
3505
|
+
lastError: null
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
};
|
|
3509
|
+
|
|
3510
|
+
// src/cli/remote/remote-status-store.ts
|
|
3511
|
+
import { hostname as hostname2 } from "os";
|
|
3512
|
+
import { loadConfig as loadConfig9 } from "@nextclaw/core";
|
|
3513
|
+
function normalizeOptionalString4(value) {
|
|
3514
|
+
if (typeof value !== "string") {
|
|
3515
|
+
return void 0;
|
|
3516
|
+
}
|
|
3517
|
+
const trimmed = value.trim();
|
|
3518
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
3519
|
+
}
|
|
3520
|
+
function buildConfiguredRemoteState(config2 = loadConfig9()) {
|
|
3521
|
+
const remote = config2.remote;
|
|
3522
|
+
return {
|
|
3523
|
+
enabled: Boolean(remote.enabled),
|
|
3524
|
+
mode: "service",
|
|
3525
|
+
state: remote.enabled ? "disconnected" : "disabled",
|
|
3526
|
+
...normalizeOptionalString4(remote.deviceName) ? { deviceName: normalizeOptionalString4(remote.deviceName) } : {},
|
|
3527
|
+
...normalizeOptionalString4(remote.platformApiBase) ? { platformBase: normalizeOptionalString4(remote.platformApiBase) } : {},
|
|
3528
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
function resolveRemoteStatusSnapshot(config2 = loadConfig9()) {
|
|
3532
|
+
const serviceState = readServiceState();
|
|
3533
|
+
if (serviceState?.remote) {
|
|
3534
|
+
return {
|
|
3535
|
+
configuredEnabled: Boolean(config2.remote.enabled),
|
|
3536
|
+
runtime: serviceState.remote
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3539
|
+
if (config2.remote.enabled) {
|
|
3540
|
+
return {
|
|
3541
|
+
configuredEnabled: true,
|
|
3542
|
+
runtime: {
|
|
3543
|
+
...buildConfiguredRemoteState(config2),
|
|
3544
|
+
deviceName: normalizeOptionalString4(config2.remote.deviceName) ?? hostname2()
|
|
3545
|
+
}
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
return {
|
|
3549
|
+
configuredEnabled: false,
|
|
3550
|
+
runtime: null
|
|
3551
|
+
};
|
|
3552
|
+
}
|
|
3553
|
+
var RemoteStatusStore = class {
|
|
3554
|
+
constructor(mode) {
|
|
3555
|
+
this.mode = mode;
|
|
3556
|
+
}
|
|
3557
|
+
write(next) {
|
|
3558
|
+
updateServiceState((state) => ({
|
|
3559
|
+
...state,
|
|
3560
|
+
remote: {
|
|
3561
|
+
...state.remote,
|
|
3562
|
+
...next,
|
|
3563
|
+
mode: this.mode,
|
|
3564
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3565
|
+
}
|
|
3208
3566
|
}));
|
|
3209
3567
|
}
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3568
|
+
};
|
|
3569
|
+
|
|
3570
|
+
// src/cli/commands/remote.ts
|
|
3571
|
+
function normalizeOptionalString5(value) {
|
|
3572
|
+
if (typeof value !== "string") {
|
|
3573
|
+
return void 0;
|
|
3574
|
+
}
|
|
3575
|
+
const trimmed = value.trim();
|
|
3576
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
3577
|
+
}
|
|
3578
|
+
function resolveConfiguredLocalOrigin(config2) {
|
|
3579
|
+
const state = readServiceState();
|
|
3580
|
+
if (state && isProcessRunning(state.pid) && Number.isFinite(state.uiPort)) {
|
|
3581
|
+
return `http://127.0.0.1:${state.uiPort}`;
|
|
3582
|
+
}
|
|
3583
|
+
const port = typeof config2.ui.port === "number" && Number.isFinite(config2.ui.port) ? config2.ui.port : 18791;
|
|
3584
|
+
return `http://127.0.0.1:${port}`;
|
|
3585
|
+
}
|
|
3586
|
+
async function probeLocalUi(localOrigin) {
|
|
3587
|
+
try {
|
|
3588
|
+
const response = await fetch(`${localOrigin}/api/health`);
|
|
3589
|
+
if (!response.ok) {
|
|
3590
|
+
return { ok: false, detail: `health returned ${response.status}` };
|
|
3591
|
+
}
|
|
3592
|
+
return { ok: true, detail: "health endpoint returned ok" };
|
|
3593
|
+
} catch (error) {
|
|
3594
|
+
return {
|
|
3595
|
+
ok: false,
|
|
3596
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
3597
|
+
};
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
var RemoteCommands = class {
|
|
3601
|
+
enableConfig(opts = {}) {
|
|
3602
|
+
const config2 = loadConfig10(getConfigPath4());
|
|
3603
|
+
const next = {
|
|
3604
|
+
...config2,
|
|
3605
|
+
remote: {
|
|
3606
|
+
...config2.remote,
|
|
3607
|
+
enabled: true,
|
|
3608
|
+
...normalizeOptionalString5(opts.apiBase) ? { platformApiBase: normalizeOptionalString5(opts.apiBase) ?? "" } : {},
|
|
3609
|
+
...normalizeOptionalString5(opts.name) ? { deviceName: normalizeOptionalString5(opts.name) ?? "" } : {}
|
|
3610
|
+
}
|
|
3611
|
+
};
|
|
3612
|
+
saveConfig7(next);
|
|
3613
|
+
return {
|
|
3614
|
+
changed: config2.remote.enabled !== next.remote.enabled || config2.remote.platformApiBase !== next.remote.platformApiBase || config2.remote.deviceName !== next.remote.deviceName,
|
|
3615
|
+
config: next
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
disableConfig() {
|
|
3619
|
+
const config2 = loadConfig10(getConfigPath4());
|
|
3620
|
+
const next = {
|
|
3621
|
+
...config2,
|
|
3622
|
+
remote: {
|
|
3623
|
+
...config2.remote,
|
|
3624
|
+
enabled: false
|
|
3625
|
+
}
|
|
3626
|
+
};
|
|
3627
|
+
saveConfig7(next);
|
|
3628
|
+
return {
|
|
3629
|
+
changed: config2.remote.enabled !== next.remote.enabled,
|
|
3630
|
+
config: next
|
|
3631
|
+
};
|
|
3632
|
+
}
|
|
3633
|
+
async connect(opts = {}) {
|
|
3634
|
+
const connector = new RemoteConnector();
|
|
3635
|
+
await connector.run({
|
|
3636
|
+
...opts,
|
|
3637
|
+
mode: "foreground"
|
|
3251
3638
|
});
|
|
3252
3639
|
}
|
|
3253
|
-
async
|
|
3254
|
-
const
|
|
3255
|
-
const
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
});
|
|
3266
|
-
console.log(
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3640
|
+
async status(opts = {}) {
|
|
3641
|
+
const config2 = loadConfig10(getConfigPath4());
|
|
3642
|
+
const snapshot = resolveRemoteStatusSnapshot(config2);
|
|
3643
|
+
if (opts.json) {
|
|
3644
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3647
|
+
const runtime2 = snapshot.runtime;
|
|
3648
|
+
console.log("NextClaw Remote Status");
|
|
3649
|
+
console.log(`Enabled: ${snapshot.configuredEnabled ? "yes" : "no"}`);
|
|
3650
|
+
console.log(`Mode: ${runtime2?.mode ?? "service"}`);
|
|
3651
|
+
console.log(`State: ${runtime2?.state ?? "disabled"}`);
|
|
3652
|
+
console.log(`Device: ${runtime2?.deviceName ?? normalizeOptionalString5(config2.remote.deviceName) ?? hostname3()}`);
|
|
3653
|
+
console.log(
|
|
3654
|
+
`Platform: ${runtime2?.platformBase ?? normalizeOptionalString5(config2.remote.platformApiBase) ?? normalizeOptionalString5(config2.providers.nextclaw?.apiBase) ?? "not set"}`
|
|
3655
|
+
);
|
|
3656
|
+
console.log(`Local origin: ${runtime2?.localOrigin ?? resolveConfiguredLocalOrigin(config2)}`);
|
|
3657
|
+
if (runtime2?.deviceId) {
|
|
3658
|
+
console.log(`Device ID: ${runtime2.deviceId}`);
|
|
3659
|
+
}
|
|
3660
|
+
if (runtime2?.lastConnectedAt) {
|
|
3661
|
+
console.log(`Last connected: ${runtime2.lastConnectedAt}`);
|
|
3662
|
+
}
|
|
3663
|
+
if (runtime2?.lastError) {
|
|
3664
|
+
console.log(`Last error: ${runtime2.lastError}`);
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
async doctor(opts = {}) {
|
|
3668
|
+
const config2 = loadConfig10(getConfigPath4());
|
|
3669
|
+
const snapshot = resolveRemoteStatusSnapshot(config2);
|
|
3670
|
+
const localOrigin = resolveConfiguredLocalOrigin(config2);
|
|
3671
|
+
const localUi = await probeLocalUi(localOrigin);
|
|
3672
|
+
const token = normalizeOptionalString5(config2.providers.nextclaw?.apiKey);
|
|
3673
|
+
const platformApiBase = normalizeOptionalString5(config2.remote.platformApiBase) ?? normalizeOptionalString5(config2.providers.nextclaw?.apiBase);
|
|
3674
|
+
const checks = [
|
|
3675
|
+
{
|
|
3676
|
+
name: "remote-enabled",
|
|
3677
|
+
ok: snapshot.configuredEnabled,
|
|
3678
|
+
detail: snapshot.configuredEnabled ? "enabled in config" : "disabled in config"
|
|
3679
|
+
},
|
|
3680
|
+
{
|
|
3681
|
+
name: "platform-token",
|
|
3682
|
+
ok: Boolean(token),
|
|
3683
|
+
detail: token ? "token configured" : 'run "nextclaw login" first'
|
|
3684
|
+
},
|
|
3685
|
+
{
|
|
3686
|
+
name: "platform-api-base",
|
|
3687
|
+
ok: Boolean(platformApiBase),
|
|
3688
|
+
detail: platformApiBase ?? "set remote.platformApiBase or login with --api-base"
|
|
3689
|
+
},
|
|
3690
|
+
{
|
|
3691
|
+
name: "local-ui",
|
|
3692
|
+
ok: localUi.ok,
|
|
3693
|
+
detail: `${localOrigin} (${localUi.detail})`
|
|
3694
|
+
},
|
|
3695
|
+
{
|
|
3696
|
+
name: "service-runtime",
|
|
3697
|
+
ok: snapshot.runtime?.state === "connected",
|
|
3698
|
+
detail: snapshot.runtime ? snapshot.runtime.state : "no managed remote runtime detected"
|
|
3278
3699
|
}
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3700
|
+
];
|
|
3701
|
+
if (opts.json) {
|
|
3702
|
+
console.log(JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), checks, snapshot }, null, 2));
|
|
3703
|
+
return;
|
|
3704
|
+
}
|
|
3705
|
+
console.log("NextClaw Remote Doctor");
|
|
3706
|
+
for (const check of checks) {
|
|
3707
|
+
console.log(`${check.ok ? "\u2713" : "\u2717"} ${check.name}: ${check.detail}`);
|
|
3708
|
+
}
|
|
3282
3709
|
}
|
|
3283
3710
|
};
|
|
3284
3711
|
|
|
@@ -3287,14 +3714,100 @@ import { createServer as createNetServer } from "net";
|
|
|
3287
3714
|
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
3288
3715
|
import { resolve as resolve8 } from "path";
|
|
3289
3716
|
import {
|
|
3290
|
-
APP_NAME,
|
|
3291
|
-
getConfigPath as
|
|
3717
|
+
APP_NAME as APP_NAME2,
|
|
3718
|
+
getConfigPath as getConfigPath5,
|
|
3292
3719
|
getDataDir as getDataDir5,
|
|
3293
3720
|
getWorkspacePath as getWorkspacePath4,
|
|
3294
3721
|
hasSecretRef,
|
|
3295
|
-
loadConfig as
|
|
3722
|
+
loadConfig as loadConfig11
|
|
3296
3723
|
} from "@nextclaw/core";
|
|
3297
3724
|
import { listBuiltinProviders } from "@nextclaw/runtime";
|
|
3725
|
+
|
|
3726
|
+
// src/cli/commands/diagnostics-render.ts
|
|
3727
|
+
import { APP_NAME } from "@nextclaw/core";
|
|
3728
|
+
function printStatusReport(params) {
|
|
3729
|
+
const { logo, report, verbose } = params;
|
|
3730
|
+
console.log(`${logo} ${APP_NAME} Status`);
|
|
3731
|
+
console.log(`Level: ${report.level}`);
|
|
3732
|
+
console.log(`Generated: ${report.generatedAt}`);
|
|
3733
|
+
console.log("");
|
|
3734
|
+
printProcessSection(report);
|
|
3735
|
+
printEndpointSection(report);
|
|
3736
|
+
printProviderSection(report);
|
|
3737
|
+
printTextList("Fix actions", report.fixActions);
|
|
3738
|
+
printTextList("Issues", report.issues);
|
|
3739
|
+
printTextList("Recommendations", report.recommendations);
|
|
3740
|
+
if (verbose && report.logTail.length > 0) {
|
|
3741
|
+
console.log("");
|
|
3742
|
+
console.log("Recent logs:");
|
|
3743
|
+
for (const line of report.logTail) {
|
|
3744
|
+
console.log(line);
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
function printProcessSection(report) {
|
|
3749
|
+
const processLabel = report.process.running ? `running (PID ${report.process.pid})` : report.process.staleState ? "stale-state" : "stopped";
|
|
3750
|
+
console.log(`Process: ${processLabel}`);
|
|
3751
|
+
console.log(`State file: ${report.serviceStatePath} ${report.serviceStateExists ? "\u2713" : "\u2717"}`);
|
|
3752
|
+
if (report.process.startedAt) {
|
|
3753
|
+
console.log(`Started: ${report.process.startedAt}`);
|
|
3754
|
+
}
|
|
3755
|
+
console.log(`Managed health: ${report.health.managed.state} (${report.health.managed.detail})`);
|
|
3756
|
+
if (!report.process.running) {
|
|
3757
|
+
console.log(`Configured health: ${report.health.configured.state} (${report.health.configured.detail})`);
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
function printEndpointSection(report) {
|
|
3761
|
+
console.log(`UI: ${report.endpoints.uiUrl ?? report.endpoints.configuredUiUrl}`);
|
|
3762
|
+
console.log(`API: ${report.endpoints.apiUrl ?? report.endpoints.configuredApiUrl}`);
|
|
3763
|
+
console.log(`Remote: ${report.remote.configuredEnabled ? "enabled" : "disabled"}${report.remote.runtime ? ` (${report.remote.runtime.state})` : ""}`);
|
|
3764
|
+
if (report.remote.runtime?.deviceName) {
|
|
3765
|
+
console.log(`Remote device: ${report.remote.runtime.deviceName}`);
|
|
3766
|
+
}
|
|
3767
|
+
if (report.remote.runtime?.platformBase) {
|
|
3768
|
+
console.log(`Remote platform: ${report.remote.runtime.platformBase}`);
|
|
3769
|
+
}
|
|
3770
|
+
if (report.remote.runtime?.lastError) {
|
|
3771
|
+
console.log(`Remote error: ${report.remote.runtime.lastError}`);
|
|
3772
|
+
}
|
|
3773
|
+
console.log(`Config: ${report.configPath} ${report.configExists ? "\u2713" : "\u2717"}`);
|
|
3774
|
+
console.log(`Workspace: ${report.workspacePath} ${report.workspaceExists ? "\u2713" : "\u2717"}`);
|
|
3775
|
+
console.log(`Model: ${report.model}`);
|
|
3776
|
+
}
|
|
3777
|
+
function printProviderSection(report) {
|
|
3778
|
+
for (const provider of report.providers) {
|
|
3779
|
+
console.log(`${provider.name}: ${provider.configured ? "\u2713" : "not set"}${provider.detail ? ` (${provider.detail})` : ""}`);
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
function printDoctorReport(params) {
|
|
3783
|
+
console.log(`${params.logo} ${APP_NAME} Doctor`);
|
|
3784
|
+
console.log(`Generated: ${params.generatedAt}`);
|
|
3785
|
+
console.log("");
|
|
3786
|
+
for (const check of params.checks) {
|
|
3787
|
+
const icon = check.status === "pass" ? "\u2713" : check.status === "warn" ? "!" : "\u2717";
|
|
3788
|
+
console.log(`${icon} ${check.name}: ${check.detail}`);
|
|
3789
|
+
}
|
|
3790
|
+
printTextList("Recommendations", params.recommendations);
|
|
3791
|
+
if (params.verbose && params.logTail.length > 0) {
|
|
3792
|
+
console.log("");
|
|
3793
|
+
console.log("Recent logs:");
|
|
3794
|
+
for (const line of params.logTail) {
|
|
3795
|
+
console.log(line);
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
function printTextList(title, items) {
|
|
3800
|
+
if (items.length === 0) {
|
|
3801
|
+
return;
|
|
3802
|
+
}
|
|
3803
|
+
console.log("");
|
|
3804
|
+
console.log(`${title}:`);
|
|
3805
|
+
for (const item of items) {
|
|
3806
|
+
console.log(`- ${item}`);
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
// src/cli/commands/diagnostics.ts
|
|
3298
3811
|
var DiagnosticsCommands = class {
|
|
3299
3812
|
constructor(deps) {
|
|
3300
3813
|
this.deps = deps;
|
|
@@ -3309,56 +3822,7 @@ var DiagnosticsCommands = class {
|
|
|
3309
3822
|
process.exitCode = 0;
|
|
3310
3823
|
return;
|
|
3311
3824
|
}
|
|
3312
|
-
|
|
3313
|
-
console.log(`Level: ${report.level}`);
|
|
3314
|
-
console.log(`Generated: ${report.generatedAt}`);
|
|
3315
|
-
console.log("");
|
|
3316
|
-
const processLabel = report.process.running ? `running (PID ${report.process.pid})` : report.process.staleState ? "stale-state" : "stopped";
|
|
3317
|
-
console.log(`Process: ${processLabel}`);
|
|
3318
|
-
console.log(`State file: ${report.serviceStatePath} ${report.serviceStateExists ? "\u2713" : "\u2717"}`);
|
|
3319
|
-
if (report.process.startedAt) {
|
|
3320
|
-
console.log(`Started: ${report.process.startedAt}`);
|
|
3321
|
-
}
|
|
3322
|
-
console.log(`Managed health: ${report.health.managed.state} (${report.health.managed.detail})`);
|
|
3323
|
-
if (!report.process.running) {
|
|
3324
|
-
console.log(`Configured health: ${report.health.configured.state} (${report.health.configured.detail})`);
|
|
3325
|
-
}
|
|
3326
|
-
console.log(`UI: ${report.endpoints.uiUrl ?? report.endpoints.configuredUiUrl}`);
|
|
3327
|
-
console.log(`API: ${report.endpoints.apiUrl ?? report.endpoints.configuredApiUrl}`);
|
|
3328
|
-
console.log(`Config: ${report.configPath} ${report.configExists ? "\u2713" : "\u2717"}`);
|
|
3329
|
-
console.log(`Workspace: ${report.workspacePath} ${report.workspaceExists ? "\u2713" : "\u2717"}`);
|
|
3330
|
-
console.log(`Model: ${report.model}`);
|
|
3331
|
-
for (const provider of report.providers) {
|
|
3332
|
-
console.log(`${provider.name}: ${provider.configured ? "\u2713" : "not set"}${provider.detail ? ` (${provider.detail})` : ""}`);
|
|
3333
|
-
}
|
|
3334
|
-
if (report.fixActions.length > 0) {
|
|
3335
|
-
console.log("");
|
|
3336
|
-
console.log("Fix actions:");
|
|
3337
|
-
for (const action of report.fixActions) {
|
|
3338
|
-
console.log(`- ${action}`);
|
|
3339
|
-
}
|
|
3340
|
-
}
|
|
3341
|
-
if (report.issues.length > 0) {
|
|
3342
|
-
console.log("");
|
|
3343
|
-
console.log("Issues:");
|
|
3344
|
-
for (const issue of report.issues) {
|
|
3345
|
-
console.log(`- ${issue}`);
|
|
3346
|
-
}
|
|
3347
|
-
}
|
|
3348
|
-
if (report.recommendations.length > 0) {
|
|
3349
|
-
console.log("");
|
|
3350
|
-
console.log("Recommendations:");
|
|
3351
|
-
for (const recommendation of report.recommendations) {
|
|
3352
|
-
console.log(`- ${recommendation}`);
|
|
3353
|
-
}
|
|
3354
|
-
}
|
|
3355
|
-
if (opts.verbose && report.logTail.length > 0) {
|
|
3356
|
-
console.log("");
|
|
3357
|
-
console.log("Recent logs:");
|
|
3358
|
-
for (const line of report.logTail) {
|
|
3359
|
-
console.log(line);
|
|
3360
|
-
}
|
|
3361
|
-
}
|
|
3825
|
+
printStatusReport({ logo: this.deps.logo, report, verbose: Boolean(opts.verbose) });
|
|
3362
3826
|
process.exitCode = 0;
|
|
3363
3827
|
}
|
|
3364
3828
|
async doctor(opts = {}) {
|
|
@@ -3429,32 +3893,19 @@ var DiagnosticsCommands = class {
|
|
|
3429
3893
|
process.exitCode = exitCode;
|
|
3430
3894
|
return;
|
|
3431
3895
|
}
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
console.log("");
|
|
3441
|
-
console.log("Recommendations:");
|
|
3442
|
-
for (const recommendation of report.recommendations) {
|
|
3443
|
-
console.log(`- ${recommendation}`);
|
|
3444
|
-
}
|
|
3445
|
-
}
|
|
3446
|
-
if (opts.verbose && report.logTail.length > 0) {
|
|
3447
|
-
console.log("");
|
|
3448
|
-
console.log("Recent logs:");
|
|
3449
|
-
for (const line of report.logTail) {
|
|
3450
|
-
console.log(line);
|
|
3451
|
-
}
|
|
3452
|
-
}
|
|
3896
|
+
printDoctorReport({
|
|
3897
|
+
logo: this.deps.logo,
|
|
3898
|
+
generatedAt: report.generatedAt,
|
|
3899
|
+
checks,
|
|
3900
|
+
recommendations: report.recommendations,
|
|
3901
|
+
verbose: Boolean(opts.verbose),
|
|
3902
|
+
logTail: report.logTail
|
|
3903
|
+
});
|
|
3453
3904
|
process.exitCode = exitCode;
|
|
3454
3905
|
}
|
|
3455
3906
|
async collectRuntimeStatus(params) {
|
|
3456
|
-
const configPath =
|
|
3457
|
-
const config2 =
|
|
3907
|
+
const configPath = getConfigPath5();
|
|
3908
|
+
const config2 = loadConfig11();
|
|
3458
3909
|
const workspacePath = getWorkspacePath4(config2.agents.defaults.workspace);
|
|
3459
3910
|
const serviceStatePath = resolve8(getDataDir5(), "run", "service.json");
|
|
3460
3911
|
const fixActions = [];
|
|
@@ -3474,59 +3925,23 @@ var DiagnosticsCommands = class {
|
|
|
3474
3925
|
const managedApiUrl = serviceState?.apiUrl ?? null;
|
|
3475
3926
|
const managedHealth = running && managedApiUrl ? await this.probeApiHealth(`${managedApiUrl}/health`) : { state: "unreachable", detail: "service not running" };
|
|
3476
3927
|
const configuredHealth = await this.probeApiHealth(`${configuredApiUrl}/health`, 900);
|
|
3928
|
+
const remote = resolveRemoteStatusSnapshot(config2);
|
|
3477
3929
|
const orphanSuspected = !running && configuredHealth.state === "ok";
|
|
3478
|
-
const providers =
|
|
3479
|
-
const provider = config2.providers[spec.name];
|
|
3480
|
-
const apiKeyRefSet = hasSecretRef(config2, `providers.${spec.name}.apiKey`);
|
|
3481
|
-
if (!provider) {
|
|
3482
|
-
return { name: spec.displayName ?? spec.name, configured: false, detail: "missing config" };
|
|
3483
|
-
}
|
|
3484
|
-
if (spec.isLocal) {
|
|
3485
|
-
return {
|
|
3486
|
-
name: spec.displayName ?? spec.name,
|
|
3487
|
-
configured: Boolean(provider.apiBase),
|
|
3488
|
-
detail: provider.apiBase ? provider.apiBase : "apiBase not set"
|
|
3489
|
-
};
|
|
3490
|
-
}
|
|
3491
|
-
return {
|
|
3492
|
-
name: spec.displayName ?? spec.name,
|
|
3493
|
-
configured: Boolean(provider.apiKey) || apiKeyRefSet,
|
|
3494
|
-
detail: provider.apiKey ? "apiKey set" : apiKeyRefSet ? "apiKey ref set" : "apiKey not set"
|
|
3495
|
-
};
|
|
3496
|
-
});
|
|
3930
|
+
const providers = this.listProviderStatuses(config2);
|
|
3497
3931
|
const issues = [];
|
|
3498
3932
|
const recommendations = [];
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
issues
|
|
3509
|
-
recommendations
|
|
3510
|
-
}
|
|
3511
|
-
if (running && managedHealth.state !== "ok") {
|
|
3512
|
-
issues.push(`Managed service health check failed: ${managedHealth.detail}`);
|
|
3513
|
-
recommendations.push(`Check logs at ${serviceState?.logPath ?? resolveServiceLogPath()}.`);
|
|
3514
|
-
}
|
|
3515
|
-
if (running && serviceState?.startupState === "degraded" && managedHealth.state !== "ok") {
|
|
3516
|
-
const startupHint = serviceState.startupLastProbeError ? ` (${serviceState.startupLastProbeError})` : "";
|
|
3517
|
-
issues.push(`Service is in degraded startup state${startupHint}.`);
|
|
3518
|
-
recommendations.push(`Wait and re-check ${APP_NAME} status; if it does not recover, inspect logs and restart.`);
|
|
3519
|
-
}
|
|
3520
|
-
if (!running) {
|
|
3521
|
-
recommendations.push(`Run ${APP_NAME} start to launch the service.`);
|
|
3522
|
-
}
|
|
3523
|
-
if (orphanSuspected) {
|
|
3524
|
-
issues.push("A service appears healthy on configured API endpoint, but state is missing/stale.");
|
|
3525
|
-
recommendations.push("Another process may be occupying the UI port; stop it or use --ui-port with a free port.");
|
|
3526
|
-
}
|
|
3527
|
-
if (!providers.some((provider) => provider.configured)) {
|
|
3528
|
-
recommendations.push("Configure at least one provider API key in UI or config before expecting agent replies.");
|
|
3529
|
-
}
|
|
3933
|
+
this.collectRuntimeIssues({
|
|
3934
|
+
configPath,
|
|
3935
|
+
workspacePath,
|
|
3936
|
+
staleState,
|
|
3937
|
+
running,
|
|
3938
|
+
managedHealth,
|
|
3939
|
+
serviceState,
|
|
3940
|
+
orphanSuspected,
|
|
3941
|
+
providers,
|
|
3942
|
+
issues,
|
|
3943
|
+
recommendations
|
|
3944
|
+
});
|
|
3530
3945
|
const logTail = params.verbose ? this.readLogTail(serviceState?.logPath ?? resolveServiceLogPath(), 25) : [];
|
|
3531
3946
|
const level = running ? managedHealth.state === "ok" ? issues.length > 0 ? "degraded" : "healthy" : "degraded" : "stopped";
|
|
3532
3947
|
const exitCode = 0;
|
|
@@ -3562,6 +3977,7 @@ var DiagnosticsCommands = class {
|
|
|
3562
3977
|
issues,
|
|
3563
3978
|
recommendations,
|
|
3564
3979
|
logTail,
|
|
3980
|
+
remote,
|
|
3565
3981
|
level,
|
|
3566
3982
|
exitCode
|
|
3567
3983
|
};
|
|
@@ -3588,6 +4004,60 @@ var DiagnosticsCommands = class {
|
|
|
3588
4004
|
clearTimeout(timer);
|
|
3589
4005
|
}
|
|
3590
4006
|
}
|
|
4007
|
+
listProviderStatuses(config2) {
|
|
4008
|
+
return listBuiltinProviders().map((spec) => {
|
|
4009
|
+
const provider = config2.providers[spec.name];
|
|
4010
|
+
const apiKeyRefSet = hasSecretRef(config2, `providers.${spec.name}.apiKey`);
|
|
4011
|
+
if (!provider) {
|
|
4012
|
+
return { name: spec.displayName ?? spec.name, configured: false, detail: "missing config" };
|
|
4013
|
+
}
|
|
4014
|
+
if (spec.isLocal) {
|
|
4015
|
+
return {
|
|
4016
|
+
name: spec.displayName ?? spec.name,
|
|
4017
|
+
configured: Boolean(provider.apiBase),
|
|
4018
|
+
detail: provider.apiBase ? provider.apiBase : "apiBase not set"
|
|
4019
|
+
};
|
|
4020
|
+
}
|
|
4021
|
+
return {
|
|
4022
|
+
name: spec.displayName ?? spec.name,
|
|
4023
|
+
configured: Boolean(provider.apiKey) || apiKeyRefSet,
|
|
4024
|
+
detail: provider.apiKey ? "apiKey set" : apiKeyRefSet ? "apiKey ref set" : "apiKey not set"
|
|
4025
|
+
};
|
|
4026
|
+
});
|
|
4027
|
+
}
|
|
4028
|
+
collectRuntimeIssues(params) {
|
|
4029
|
+
if (!existsSync7(params.configPath)) {
|
|
4030
|
+
params.issues.push("Config file is missing.");
|
|
4031
|
+
params.recommendations.push(`Run ${APP_NAME2} init to create config files.`);
|
|
4032
|
+
}
|
|
4033
|
+
if (!existsSync7(params.workspacePath)) {
|
|
4034
|
+
params.issues.push("Workspace directory does not exist.");
|
|
4035
|
+
params.recommendations.push(`Run ${APP_NAME2} init to create workspace templates.`);
|
|
4036
|
+
}
|
|
4037
|
+
if (params.staleState) {
|
|
4038
|
+
params.issues.push("Service state is stale (state exists but process is not running).");
|
|
4039
|
+
params.recommendations.push(`Run ${APP_NAME2} status --fix to clean stale state.`);
|
|
4040
|
+
}
|
|
4041
|
+
if (params.running && params.managedHealth.state !== "ok") {
|
|
4042
|
+
params.issues.push(`Managed service health check failed: ${params.managedHealth.detail}`);
|
|
4043
|
+
params.recommendations.push(`Check logs at ${params.serviceState?.logPath ?? resolveServiceLogPath()}.`);
|
|
4044
|
+
}
|
|
4045
|
+
if (params.running && params.serviceState?.startupState === "degraded" && params.managedHealth.state !== "ok") {
|
|
4046
|
+
const startupHint = params.serviceState.startupLastProbeError ? ` (${params.serviceState.startupLastProbeError})` : "";
|
|
4047
|
+
params.issues.push(`Service is in degraded startup state${startupHint}.`);
|
|
4048
|
+
params.recommendations.push(`Wait and re-check ${APP_NAME2} status; if it does not recover, inspect logs and restart.`);
|
|
4049
|
+
}
|
|
4050
|
+
if (!params.running) {
|
|
4051
|
+
params.recommendations.push(`Run ${APP_NAME2} start to launch the service.`);
|
|
4052
|
+
}
|
|
4053
|
+
if (params.orphanSuspected) {
|
|
4054
|
+
params.issues.push("A service appears healthy on configured API endpoint, but state is missing/stale.");
|
|
4055
|
+
params.recommendations.push("Another process may be occupying the UI port; stop it or use --ui-port with a free port.");
|
|
4056
|
+
}
|
|
4057
|
+
if (!params.providers.some((provider) => provider.configured)) {
|
|
4058
|
+
params.recommendations.push("Configure at least one provider API key in UI or config before expecting agent replies.");
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
3591
4061
|
readLogTail(path2, maxLines = 25) {
|
|
3592
4062
|
if (!existsSync7(path2)) {
|
|
3593
4063
|
return [];
|
|
@@ -3651,8 +4121,8 @@ import {
|
|
|
3651
4121
|
redactConfigObject
|
|
3652
4122
|
} from "@nextclaw/core";
|
|
3653
4123
|
var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
|
|
3654
|
-
var readConfigSnapshot = (
|
|
3655
|
-
const path2 =
|
|
4124
|
+
var readConfigSnapshot = (getConfigPath8) => {
|
|
4125
|
+
const path2 = getConfigPath8();
|
|
3656
4126
|
let raw = "";
|
|
3657
4127
|
let parsed = {};
|
|
3658
4128
|
if (existsSync8(path2)) {
|
|
@@ -4113,7 +4583,7 @@ var MissingProvider = class extends LLMProvider {
|
|
|
4113
4583
|
};
|
|
4114
4584
|
|
|
4115
4585
|
// src/cli/commands/service-marketplace-installer.ts
|
|
4116
|
-
import { getWorkspacePath as getWorkspacePath5, loadConfig as
|
|
4586
|
+
import { getWorkspacePath as getWorkspacePath5, loadConfig as loadConfig13 } from "@nextclaw/core";
|
|
4117
4587
|
import { existsSync as existsSync9, rmSync as rmSync4 } from "fs";
|
|
4118
4588
|
import { join as join5 } from "path";
|
|
4119
4589
|
|
|
@@ -4163,7 +4633,7 @@ var buildMarketplaceSkillInstallArgs = (params) => {
|
|
|
4163
4633
|
};
|
|
4164
4634
|
|
|
4165
4635
|
// src/cli/commands/service-mcp-marketplace-ops.ts
|
|
4166
|
-
import { loadConfig as
|
|
4636
|
+
import { loadConfig as loadConfig12, saveConfig as saveConfig8 } from "@nextclaw/core";
|
|
4167
4637
|
import { McpDoctorFacade as McpDoctorFacade2, McpMutationService as McpMutationService2 } from "@nextclaw/mcp";
|
|
4168
4638
|
var ServiceMcpMarketplaceOps = class {
|
|
4169
4639
|
constructor(options) {
|
|
@@ -4231,13 +4701,13 @@ var ServiceMcpMarketplaceOps = class {
|
|
|
4231
4701
|
}
|
|
4232
4702
|
createMutationService() {
|
|
4233
4703
|
return new McpMutationService2({
|
|
4234
|
-
getConfig: () =>
|
|
4235
|
-
saveConfig: (config2) =>
|
|
4704
|
+
getConfig: () => loadConfig12(),
|
|
4705
|
+
saveConfig: (config2) => saveConfig8(config2)
|
|
4236
4706
|
});
|
|
4237
4707
|
}
|
|
4238
4708
|
createDoctorFacade() {
|
|
4239
4709
|
return new McpDoctorFacade2({
|
|
4240
|
-
getConfig: () =>
|
|
4710
|
+
getConfig: () => loadConfig12()
|
|
4241
4711
|
});
|
|
4242
4712
|
}
|
|
4243
4713
|
};
|
|
@@ -4278,7 +4748,7 @@ var ServiceMarketplaceInstaller = class {
|
|
|
4278
4748
|
if (params.kind && params.kind !== "marketplace") {
|
|
4279
4749
|
throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
|
|
4280
4750
|
}
|
|
4281
|
-
const workspace = getWorkspacePath5(
|
|
4751
|
+
const workspace = getWorkspacePath5(loadConfig13().agents.defaults.workspace);
|
|
4282
4752
|
const args = buildMarketplaceSkillInstallArgs({
|
|
4283
4753
|
slug: params.slug,
|
|
4284
4754
|
workspace,
|
|
@@ -4317,7 +4787,7 @@ var ServiceMarketplaceInstaller = class {
|
|
|
4317
4787
|
return { message: result.message };
|
|
4318
4788
|
}
|
|
4319
4789
|
async uninstallSkill(slug) {
|
|
4320
|
-
const workspace = getWorkspacePath5(
|
|
4790
|
+
const workspace = getWorkspacePath5(loadConfig13().agents.defaults.workspace);
|
|
4321
4791
|
const targetDir = join5(workspace, "skills", slug);
|
|
4322
4792
|
if (!existsSync9(targetDir)) {
|
|
4323
4793
|
throw new Error(`Skill not installed in workspace: ${slug}`);
|
|
@@ -4444,6 +4914,35 @@ async function reloadServicePlugins(params) {
|
|
|
4444
4914
|
};
|
|
4445
4915
|
}
|
|
4446
4916
|
|
|
4917
|
+
// src/cli/commands/service-startup-support.ts
|
|
4918
|
+
var pluginGatewayLogger = {
|
|
4919
|
+
info: (message) => console.log(`[plugins] ${message}`),
|
|
4920
|
+
warn: (message) => console.warn(`[plugins] ${message}`),
|
|
4921
|
+
error: (message) => console.error(`[plugins] ${message}`),
|
|
4922
|
+
debug: (message) => console.debug(`[plugins] ${message}`)
|
|
4923
|
+
};
|
|
4924
|
+
function logPluginGatewayDiagnostics(diagnostics) {
|
|
4925
|
+
for (const diag of diagnostics) {
|
|
4926
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
4927
|
+
const text = `${prefix}${diag.message}`;
|
|
4928
|
+
if (diag.level === "error") {
|
|
4929
|
+
console.error(`[plugins] ${text}`);
|
|
4930
|
+
} else {
|
|
4931
|
+
console.warn(`[plugins] ${text}`);
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
async function startGatewaySupportServices(params) {
|
|
4936
|
+
if (params.cronJobs > 0) {
|
|
4937
|
+
console.log(`\u2713 Cron: ${params.cronJobs} scheduled jobs`);
|
|
4938
|
+
}
|
|
4939
|
+
console.log("\u2713 Heartbeat: every 30m");
|
|
4940
|
+
params.remoteModule?.start();
|
|
4941
|
+
params.watchConfigFile();
|
|
4942
|
+
await params.startCron();
|
|
4943
|
+
await params.startHeartbeat();
|
|
4944
|
+
}
|
|
4945
|
+
|
|
4447
4946
|
// src/cli/commands/agent-runtime-pool.ts
|
|
4448
4947
|
import {
|
|
4449
4948
|
NativeAgentEngine,
|
|
@@ -4941,14 +5440,14 @@ function formatUserFacingError(error, maxChars = 320) {
|
|
|
4941
5440
|
// src/cli/commands/cli-subcommand-launch.ts
|
|
4942
5441
|
import { createRequire } from "module";
|
|
4943
5442
|
import { extname, resolve as resolve9 } from "path";
|
|
4944
|
-
import { fileURLToPath as
|
|
5443
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4945
5444
|
var require2 = createRequire(import.meta.url);
|
|
4946
5445
|
var resolveCliSubcommandEntry = (params) => {
|
|
4947
5446
|
const argvEntry = params.argvEntry?.trim();
|
|
4948
5447
|
if (argvEntry) {
|
|
4949
5448
|
return resolve9(argvEntry);
|
|
4950
5449
|
}
|
|
4951
|
-
return
|
|
5450
|
+
return fileURLToPath3(new URL("../index.js", params.importMetaUrl));
|
|
4952
5451
|
};
|
|
4953
5452
|
|
|
4954
5453
|
// src/cli/commands/ncp/create-ui-ncp-agent.ts
|
|
@@ -5423,7 +5922,7 @@ function resolveRequestedToolNames(metadata) {
|
|
|
5423
5922
|
)
|
|
5424
5923
|
);
|
|
5425
5924
|
}
|
|
5426
|
-
function
|
|
5925
|
+
function normalizeOptionalString6(value) {
|
|
5427
5926
|
return normalizeString(value) ?? void 0;
|
|
5428
5927
|
}
|
|
5429
5928
|
function readMetadataModel(metadata) {
|
|
@@ -5543,7 +6042,7 @@ var NextclawNcpContextBuilder = class {
|
|
|
5543
6042
|
if (inboundModel) {
|
|
5544
6043
|
session.metadata.preferred_model = inboundModel;
|
|
5545
6044
|
}
|
|
5546
|
-
const effectiveModel =
|
|
6045
|
+
const effectiveModel = normalizeOptionalString6(session.metadata.preferred_model) ?? profile.model;
|
|
5547
6046
|
const clearThinking = requestMetadata.clear_thinking === true || requestMetadata.reset_thinking === true;
|
|
5548
6047
|
if (clearThinking) {
|
|
5549
6048
|
delete session.metadata.preferred_thinking;
|
|
@@ -5560,8 +6059,8 @@ var NextclawNcpContextBuilder = class {
|
|
|
5560
6059
|
model: effectiveModel,
|
|
5561
6060
|
sessionThinkingLevel: parseThinkingLevel(session.metadata.preferred_thinking) ?? null
|
|
5562
6061
|
});
|
|
5563
|
-
const channel =
|
|
5564
|
-
const chatId =
|
|
6062
|
+
const channel = normalizeOptionalString6(requestMetadata.channel) ?? normalizeOptionalString6(session.metadata.last_channel) ?? "ui";
|
|
6063
|
+
const chatId = normalizeOptionalString6(requestMetadata.chatId) ?? normalizeOptionalString6(requestMetadata.chat_id) ?? normalizeOptionalString6(session.metadata.last_to) ?? "web-ui";
|
|
5565
6064
|
session.metadata.last_channel = channel;
|
|
5566
6065
|
session.metadata.last_to = chatId;
|
|
5567
6066
|
const requestedSkillNames = resolveRequestedSkillNames(requestMetadata);
|
|
@@ -6279,6 +6778,119 @@ async function createUiNcpAgent(params) {
|
|
|
6279
6778
|
};
|
|
6280
6779
|
}
|
|
6281
6780
|
|
|
6781
|
+
// src/cli/remote/remote-service-module.ts
|
|
6782
|
+
var RemoteServiceModule = class {
|
|
6783
|
+
constructor(deps) {
|
|
6784
|
+
this.deps = deps;
|
|
6785
|
+
}
|
|
6786
|
+
abortController = null;
|
|
6787
|
+
runTask = null;
|
|
6788
|
+
statusStore = new RemoteStatusStore("service");
|
|
6789
|
+
start() {
|
|
6790
|
+
if (!this.deps.config.remote.enabled) {
|
|
6791
|
+
this.statusStore.write({
|
|
6792
|
+
enabled: false,
|
|
6793
|
+
state: "disabled",
|
|
6794
|
+
deviceName: void 0,
|
|
6795
|
+
deviceId: void 0,
|
|
6796
|
+
platformBase: void 0,
|
|
6797
|
+
localOrigin: this.deps.localOrigin,
|
|
6798
|
+
lastError: null,
|
|
6799
|
+
lastConnectedAt: null
|
|
6800
|
+
});
|
|
6801
|
+
return null;
|
|
6802
|
+
}
|
|
6803
|
+
const logger = this.deps.logger ?? {
|
|
6804
|
+
info: (message) => console.log(`[remote] ${message}`),
|
|
6805
|
+
warn: (message) => console.warn(`[remote] ${message}`),
|
|
6806
|
+
error: (message) => console.error(`[remote] ${message}`)
|
|
6807
|
+
};
|
|
6808
|
+
this.abortController = new AbortController();
|
|
6809
|
+
const connector = new RemoteConnector(logger);
|
|
6810
|
+
this.runTask = connector.run({
|
|
6811
|
+
mode: "service",
|
|
6812
|
+
signal: this.abortController.signal,
|
|
6813
|
+
autoReconnect: this.deps.config.remote.autoReconnect,
|
|
6814
|
+
localOrigin: this.deps.localOrigin,
|
|
6815
|
+
statusStore: this.statusStore
|
|
6816
|
+
});
|
|
6817
|
+
void this.runTask.catch((error) => {
|
|
6818
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6819
|
+
this.statusStore.write({
|
|
6820
|
+
enabled: true,
|
|
6821
|
+
state: "error",
|
|
6822
|
+
deviceName: this.deps.config.remote.deviceName || void 0,
|
|
6823
|
+
deviceId: void 0,
|
|
6824
|
+
platformBase: this.deps.config.remote.platformApiBase || void 0,
|
|
6825
|
+
localOrigin: this.deps.localOrigin,
|
|
6826
|
+
lastError: message
|
|
6827
|
+
});
|
|
6828
|
+
logger.error(message);
|
|
6829
|
+
});
|
|
6830
|
+
return this.runTask;
|
|
6831
|
+
}
|
|
6832
|
+
async stop() {
|
|
6833
|
+
this.abortController?.abort();
|
|
6834
|
+
try {
|
|
6835
|
+
await this.runTask;
|
|
6836
|
+
} catch {
|
|
6837
|
+
} finally {
|
|
6838
|
+
this.abortController = null;
|
|
6839
|
+
this.runTask = null;
|
|
6840
|
+
}
|
|
6841
|
+
}
|
|
6842
|
+
};
|
|
6843
|
+
|
|
6844
|
+
// src/cli/commands/service-remote-runtime.ts
|
|
6845
|
+
function createManagedRemoteModule(params) {
|
|
6846
|
+
if (!params.config.ui.enabled) {
|
|
6847
|
+
return null;
|
|
6848
|
+
}
|
|
6849
|
+
return new RemoteServiceModule({
|
|
6850
|
+
config: params.config,
|
|
6851
|
+
localOrigin: params.localOrigin,
|
|
6852
|
+
logger: {
|
|
6853
|
+
info: (message) => console.log(`[remote] ${message}`),
|
|
6854
|
+
warn: (message) => console.warn(`[remote] ${message}`),
|
|
6855
|
+
error: (message) => console.error(`[remote] ${message}`)
|
|
6856
|
+
}
|
|
6857
|
+
});
|
|
6858
|
+
}
|
|
6859
|
+
function writeInitialManagedServiceState(params) {
|
|
6860
|
+
writeServiceState({
|
|
6861
|
+
pid: params.snapshot.pid,
|
|
6862
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6863
|
+
uiUrl: params.snapshot.uiUrl,
|
|
6864
|
+
apiUrl: params.snapshot.apiUrl,
|
|
6865
|
+
uiHost: params.snapshot.uiHost,
|
|
6866
|
+
uiPort: params.snapshot.uiPort,
|
|
6867
|
+
logPath: params.snapshot.logPath,
|
|
6868
|
+
startupLastProbeError: null,
|
|
6869
|
+
startupTimeoutMs: params.readinessTimeoutMs,
|
|
6870
|
+
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6871
|
+
...params.config.remote.enabled ? { remote: buildConfiguredRemoteState(params.config) } : {}
|
|
6872
|
+
});
|
|
6873
|
+
}
|
|
6874
|
+
function writeReadyManagedServiceState(params) {
|
|
6875
|
+
const currentState = readServiceState();
|
|
6876
|
+
const state = {
|
|
6877
|
+
pid: params.snapshot.pid,
|
|
6878
|
+
startedAt: currentState?.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6879
|
+
uiUrl: params.snapshot.uiUrl,
|
|
6880
|
+
apiUrl: params.snapshot.apiUrl,
|
|
6881
|
+
uiHost: params.snapshot.uiHost,
|
|
6882
|
+
uiPort: params.snapshot.uiPort,
|
|
6883
|
+
logPath: params.snapshot.logPath,
|
|
6884
|
+
startupState: params.readiness.ready ? "ready" : "degraded",
|
|
6885
|
+
startupLastProbeError: params.readiness.lastProbeError,
|
|
6886
|
+
startupTimeoutMs: params.readinessTimeoutMs,
|
|
6887
|
+
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6888
|
+
...currentState?.remote ? { remote: currentState.remote } : {}
|
|
6889
|
+
};
|
|
6890
|
+
writeServiceState(state);
|
|
6891
|
+
return state;
|
|
6892
|
+
}
|
|
6893
|
+
|
|
6282
6894
|
// src/cli/commands/ui-chat-run-coordinator.ts
|
|
6283
6895
|
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
6284
6896
|
import { join as join6 } from "path";
|
|
@@ -6899,22 +7511,22 @@ var UiChatRunCoordinator = class {
|
|
|
6899
7511
|
|
|
6900
7512
|
// src/cli/commands/service.ts
|
|
6901
7513
|
var {
|
|
6902
|
-
APP_NAME:
|
|
7514
|
+
APP_NAME: APP_NAME3,
|
|
6903
7515
|
ChannelManager: ChannelManager2,
|
|
6904
7516
|
CronService: CronService2,
|
|
6905
7517
|
getApiBase,
|
|
6906
|
-
getConfigPath:
|
|
7518
|
+
getConfigPath: getConfigPath6,
|
|
6907
7519
|
getDataDir: getDataDir7,
|
|
6908
7520
|
getProvider,
|
|
6909
7521
|
getProviderName,
|
|
6910
7522
|
getWorkspacePath: getWorkspacePath9,
|
|
6911
7523
|
HeartbeatService,
|
|
6912
7524
|
LiteLLMProvider,
|
|
6913
|
-
loadConfig:
|
|
7525
|
+
loadConfig: loadConfig14,
|
|
6914
7526
|
MessageBus,
|
|
6915
7527
|
ProviderManager,
|
|
6916
7528
|
resolveConfigSecrets: resolveConfigSecrets2,
|
|
6917
|
-
saveConfig:
|
|
7529
|
+
saveConfig: saveConfig9,
|
|
6918
7530
|
SessionManager,
|
|
6919
7531
|
parseAgentScopedSessionKey: parseAgentScopedSessionKey3
|
|
6920
7532
|
} = NextclawCore;
|
|
@@ -6934,8 +7546,8 @@ var ServiceCommands = class {
|
|
|
6934
7546
|
async startGateway(options = {}) {
|
|
6935
7547
|
this.applyLiveConfigReload = null;
|
|
6936
7548
|
this.liveUiNcpAgent = null;
|
|
6937
|
-
const runtimeConfigPath =
|
|
6938
|
-
const config2 = resolveConfigSecrets2(
|
|
7549
|
+
const runtimeConfigPath = getConfigPath6();
|
|
7550
|
+
const config2 = resolveConfigSecrets2(loadConfig14(), { configPath: runtimeConfigPath });
|
|
6939
7551
|
const workspace = getWorkspacePath9(config2.agents.defaults.workspace);
|
|
6940
7552
|
let pluginRegistry = loadPluginRegistry(config2, workspace);
|
|
6941
7553
|
let extensionRegistry = toExtensionRegistry(pluginRegistry);
|
|
@@ -6948,27 +7560,11 @@ var ServiceCommands = class {
|
|
|
6948
7560
|
});
|
|
6949
7561
|
const sessionManager = new SessionManager(workspace);
|
|
6950
7562
|
let pluginGatewayHandles = [];
|
|
6951
|
-
const pluginGatewayLogger = {
|
|
6952
|
-
info: (message) => console.log(`[plugins] ${message}`),
|
|
6953
|
-
warn: (message) => console.warn(`[plugins] ${message}`),
|
|
6954
|
-
error: (message) => console.error(`[plugins] ${message}`),
|
|
6955
|
-
debug: (message) => console.debug(`[plugins] ${message}`)
|
|
6956
|
-
};
|
|
6957
|
-
const logPluginGatewayDiagnostics = (diagnostics) => {
|
|
6958
|
-
for (const diag of diagnostics) {
|
|
6959
|
-
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
6960
|
-
const text = `${prefix}${diag.message}`;
|
|
6961
|
-
if (diag.level === "error") {
|
|
6962
|
-
console.error(`[plugins] ${text}`);
|
|
6963
|
-
} else {
|
|
6964
|
-
console.warn(`[plugins] ${text}`);
|
|
6965
|
-
}
|
|
6966
|
-
}
|
|
6967
|
-
};
|
|
6968
7563
|
const cronStorePath = join7(getDataDir7(), "cron", "jobs.json");
|
|
6969
7564
|
const cron2 = new CronService2(cronStorePath);
|
|
6970
7565
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
6971
7566
|
const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
|
|
7567
|
+
const localOrigin = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
6972
7568
|
if (!provider) {
|
|
6973
7569
|
console.warn("Warning: No API key configured. The gateway is running, but agent replies are disabled until provider config is set.");
|
|
6974
7570
|
}
|
|
@@ -6980,7 +7576,7 @@ var ServiceCommands = class {
|
|
|
6980
7576
|
sessionManager,
|
|
6981
7577
|
providerManager,
|
|
6982
7578
|
makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }) ?? this.makeMissingProvider(nextConfig),
|
|
6983
|
-
loadConfig: () => resolveConfigSecrets2(
|
|
7579
|
+
loadConfig: () => resolveConfigSecrets2(loadConfig14(), { configPath: runtimeConfigPath }),
|
|
6984
7580
|
getExtensionChannels: () => extensionRegistry.channels,
|
|
6985
7581
|
onRestartRequired: (paths) => {
|
|
6986
7582
|
void this.deps.requestRestart({
|
|
@@ -6991,14 +7587,14 @@ var ServiceCommands = class {
|
|
|
6991
7587
|
}
|
|
6992
7588
|
});
|
|
6993
7589
|
this.applyLiveConfigReload = async () => {
|
|
6994
|
-
await reloader.applyReloadPlan(resolveConfigSecrets2(
|
|
7590
|
+
await reloader.applyReloadPlan(resolveConfigSecrets2(loadConfig14(), { configPath: runtimeConfigPath }));
|
|
6995
7591
|
};
|
|
6996
7592
|
const gatewayController = new GatewayControllerImpl({
|
|
6997
7593
|
reloader,
|
|
6998
7594
|
cron: cron2,
|
|
6999
7595
|
sessionManager,
|
|
7000
|
-
getConfigPath:
|
|
7001
|
-
saveConfig:
|
|
7596
|
+
getConfigPath: getConfigPath6,
|
|
7597
|
+
saveConfig: saveConfig9,
|
|
7002
7598
|
requestRestart: async (options2) => {
|
|
7003
7599
|
await this.deps.requestRestart({
|
|
7004
7600
|
reason: options2?.reason ?? "gateway tool restart",
|
|
@@ -7024,7 +7620,7 @@ var ServiceCommands = class {
|
|
|
7024
7620
|
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
7025
7621
|
registry: pluginRegistry,
|
|
7026
7622
|
channel,
|
|
7027
|
-
cfg: resolveConfigSecrets2(
|
|
7623
|
+
cfg: resolveConfigSecrets2(loadConfig14(), { configPath: runtimeConfigPath }),
|
|
7028
7624
|
accountId
|
|
7029
7625
|
})
|
|
7030
7626
|
});
|
|
@@ -7057,14 +7653,14 @@ var ServiceCommands = class {
|
|
|
7057
7653
|
});
|
|
7058
7654
|
let pluginChannelBindings = getPluginChannelBindings3(pluginRegistry);
|
|
7059
7655
|
setPluginRuntimeBridge({
|
|
7060
|
-
loadConfig: () => toPluginConfigView(resolveConfigSecrets2(
|
|
7656
|
+
loadConfig: () => toPluginConfigView(resolveConfigSecrets2(loadConfig14(), { configPath: runtimeConfigPath }), pluginChannelBindings),
|
|
7061
7657
|
writeConfigFile: async (nextConfigView) => {
|
|
7062
7658
|
if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
|
|
7063
7659
|
throw new Error("plugin runtime writeConfigFile expects an object config");
|
|
7064
7660
|
}
|
|
7065
|
-
const current =
|
|
7661
|
+
const current = loadConfig14();
|
|
7066
7662
|
const next = mergePluginConfigView(current, nextConfigView, pluginChannelBindings);
|
|
7067
|
-
|
|
7663
|
+
saveConfig9(next);
|
|
7068
7664
|
},
|
|
7069
7665
|
dispatchReplyWithBufferedBlockDispatcher: async ({ ctx, dispatcherOptions }) => {
|
|
7070
7666
|
const bodyForAgent = typeof ctx.BodyForAgent === "string" ? ctx.BodyForAgent : "";
|
|
@@ -7138,23 +7734,23 @@ var ServiceCommands = class {
|
|
|
7138
7734
|
providerManager,
|
|
7139
7735
|
bus,
|
|
7140
7736
|
gatewayController,
|
|
7141
|
-
() => resolveConfigSecrets2(
|
|
7737
|
+
() => resolveConfigSecrets2(loadConfig14(), { configPath: runtimeConfigPath }),
|
|
7142
7738
|
() => extensionRegistry,
|
|
7143
7739
|
({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
7144
7740
|
registry: pluginRegistry,
|
|
7145
7741
|
channel,
|
|
7146
|
-
cfg: resolveConfigSecrets2(
|
|
7742
|
+
cfg: resolveConfigSecrets2(loadConfig14(), { configPath: runtimeConfigPath }),
|
|
7147
7743
|
accountId
|
|
7148
7744
|
})
|
|
7149
7745
|
);
|
|
7150
|
-
const
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7746
|
+
const remoteModule = createManagedRemoteModule({ config: config2, localOrigin });
|
|
7747
|
+
await startGatewaySupportServices({
|
|
7748
|
+
cronJobs: cron2.status().jobs,
|
|
7749
|
+
remoteModule,
|
|
7750
|
+
watchConfigFile: () => this.watchConfigFile(reloader),
|
|
7751
|
+
startCron: () => cron2.start(),
|
|
7752
|
+
startHeartbeat: () => heartbeat.start()
|
|
7753
|
+
});
|
|
7158
7754
|
try {
|
|
7159
7755
|
const startedPluginGateways = await startPluginChannelGateways2({
|
|
7160
7756
|
registry: pluginRegistry,
|
|
@@ -7168,6 +7764,7 @@ var ServiceCommands = class {
|
|
|
7168
7764
|
} finally {
|
|
7169
7765
|
this.applyLiveConfigReload = null;
|
|
7170
7766
|
this.liveUiNcpAgent = null;
|
|
7767
|
+
await remoteModule?.stop();
|
|
7171
7768
|
await stopPluginChannelGateways2(pluginGatewayHandles);
|
|
7172
7769
|
setPluginRuntimeBridge(null);
|
|
7173
7770
|
}
|
|
@@ -7180,7 +7777,7 @@ var ServiceCommands = class {
|
|
|
7180
7777
|
return trimmed || void 0;
|
|
7181
7778
|
}
|
|
7182
7779
|
watchConfigFile(reloader) {
|
|
7183
|
-
const configPath = resolve10(
|
|
7780
|
+
const configPath = resolve10(getConfigPath6());
|
|
7184
7781
|
const watcher = chokidar.watch(configPath, {
|
|
7185
7782
|
ignoreInitial: true,
|
|
7186
7783
|
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
|
|
@@ -7301,7 +7898,7 @@ var ServiceCommands = class {
|
|
|
7301
7898
|
});
|
|
7302
7899
|
}
|
|
7303
7900
|
async runForeground(options) {
|
|
7304
|
-
const config2 =
|
|
7901
|
+
const config2 = loadConfig14();
|
|
7305
7902
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
7306
7903
|
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
7307
7904
|
if (options.open) {
|
|
@@ -7314,14 +7911,14 @@ var ServiceCommands = class {
|
|
|
7314
7911
|
});
|
|
7315
7912
|
}
|
|
7316
7913
|
async startService(options) {
|
|
7317
|
-
const config2 =
|
|
7914
|
+
const config2 = loadConfig14();
|
|
7318
7915
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
7319
7916
|
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
7320
7917
|
const apiUrl = `${uiUrl}/api`;
|
|
7321
7918
|
const staticDir = resolveUiStaticDir();
|
|
7322
7919
|
const existing = readServiceState();
|
|
7323
7920
|
if (existing && isProcessRunning(existing.pid)) {
|
|
7324
|
-
console.log(`\u2713 ${
|
|
7921
|
+
console.log(`\u2713 ${APP_NAME3} is already running (PID ${existing.pid})`);
|
|
7325
7922
|
console.log(`UI: ${existing.uiUrl}`);
|
|
7326
7923
|
console.log(`API: ${existing.apiUrl}`);
|
|
7327
7924
|
const parsedUi = (() => {
|
|
@@ -7369,7 +7966,7 @@ var ServiceCommands = class {
|
|
|
7369
7966
|
healthUrl
|
|
7370
7967
|
});
|
|
7371
7968
|
if (!portPreflight.ok) {
|
|
7372
|
-
console.error(`Error: Cannot start ${
|
|
7969
|
+
console.error(`Error: Cannot start ${APP_NAME3} because UI port ${uiConfig.port} is already occupied.`);
|
|
7373
7970
|
console.error(portPreflight.message);
|
|
7374
7971
|
return;
|
|
7375
7972
|
}
|
|
@@ -7384,10 +7981,8 @@ var ServiceCommands = class {
|
|
|
7384
7981
|
logPath,
|
|
7385
7982
|
`start requested: ui=${uiConfig.host}:${uiConfig.port}, readinessTimeoutMs=${readinessTimeoutMs}`
|
|
7386
7983
|
);
|
|
7387
|
-
console.log(`Starting ${
|
|
7388
|
-
const serveArgs = buildServeArgs({
|
|
7389
|
-
uiPort: uiConfig.port
|
|
7390
|
-
});
|
|
7984
|
+
console.log(`Starting ${APP_NAME3} background service (readiness timeout ${Math.ceil(readinessTimeoutMs / 1e3)}s)...`);
|
|
7985
|
+
const serveArgs = buildServeArgs({ uiPort: uiConfig.port });
|
|
7391
7986
|
this.appendStartupStage(logPath, `spawning background process: ${process.execPath} ${[...process.execArgv, ...serveArgs].join(" ")}`);
|
|
7392
7987
|
const child = spawn2(process.execPath, [...process.execArgv, ...serveArgs], {
|
|
7393
7988
|
env: process.env,
|
|
@@ -7408,6 +8003,11 @@ var ServiceCommands = class {
|
|
|
7408
8003
|
});
|
|
7409
8004
|
return;
|
|
7410
8005
|
}
|
|
8006
|
+
writeInitialManagedServiceState({
|
|
8007
|
+
config: config2,
|
|
8008
|
+
readinessTimeoutMs,
|
|
8009
|
+
snapshot: { pid: child.pid, uiUrl, apiUrl, uiHost: uiConfig.host, uiPort: uiConfig.port, logPath }
|
|
8010
|
+
});
|
|
7411
8011
|
this.appendStartupStage(logPath, `health probe started: ${healthUrl} (phase=quick, timeoutMs=${quickPhaseTimeoutMs})`);
|
|
7412
8012
|
let readiness = await this.waitForBackgroundServiceReady({
|
|
7413
8013
|
pid: child.pid,
|
|
@@ -7449,28 +8049,19 @@ var ServiceCommands = class {
|
|
|
7449
8049
|
);
|
|
7450
8050
|
}
|
|
7451
8051
|
child.unref();
|
|
7452
|
-
const state = {
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
uiUrl,
|
|
7456
|
-
|
|
7457
|
-
uiHost: uiConfig.host,
|
|
7458
|
-
uiPort: uiConfig.port,
|
|
7459
|
-
logPath,
|
|
7460
|
-
startupState: readiness.ready ? "ready" : "degraded",
|
|
7461
|
-
startupLastProbeError: readiness.lastProbeError,
|
|
7462
|
-
startupTimeoutMs: readinessTimeoutMs,
|
|
7463
|
-
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7464
|
-
};
|
|
7465
|
-
writeServiceState(state);
|
|
8052
|
+
const state = writeReadyManagedServiceState({
|
|
8053
|
+
readinessTimeoutMs,
|
|
8054
|
+
readiness,
|
|
8055
|
+
snapshot: { pid: child.pid, uiUrl, apiUrl, uiHost: uiConfig.host, uiPort: uiConfig.port, logPath }
|
|
8056
|
+
});
|
|
7466
8057
|
if (!readiness.ready) {
|
|
7467
8058
|
const hint = readiness.lastProbeError ? ` Last probe error: ${readiness.lastProbeError}` : "";
|
|
7468
8059
|
console.warn(
|
|
7469
|
-
`Warning: ${
|
|
8060
|
+
`Warning: ${APP_NAME3} is running (PID ${state.pid}) but not healthy yet after ${Math.ceil(readinessTimeoutMs / 1e3)}s. Marked as degraded.${hint}`
|
|
7470
8061
|
);
|
|
7471
|
-
console.warn(`Tip: Run "${
|
|
8062
|
+
console.warn(`Tip: Run "${APP_NAME3} status --json" and check logs: ${logPath}`);
|
|
7472
8063
|
} else {
|
|
7473
|
-
console.log(`\u2713 ${
|
|
8064
|
+
console.log(`\u2713 ${APP_NAME3} started in background (PID ${state.pid})`);
|
|
7474
8065
|
}
|
|
7475
8066
|
console.log(`UI: ${uiUrl}`);
|
|
7476
8067
|
console.log(`API: ${apiUrl}`);
|
|
@@ -7492,7 +8083,7 @@ var ServiceCommands = class {
|
|
|
7492
8083
|
clearServiceState();
|
|
7493
8084
|
return;
|
|
7494
8085
|
}
|
|
7495
|
-
console.log(`Stopping ${
|
|
8086
|
+
console.log(`Stopping ${APP_NAME3} (PID ${state.pid})...`);
|
|
7496
8087
|
try {
|
|
7497
8088
|
process.kill(state.pid, "SIGTERM");
|
|
7498
8089
|
} catch (error) {
|
|
@@ -7510,7 +8101,7 @@ var ServiceCommands = class {
|
|
|
7510
8101
|
await waitForExit(state.pid, 2e3);
|
|
7511
8102
|
}
|
|
7512
8103
|
clearServiceState();
|
|
7513
|
-
console.log(`\u2713 ${
|
|
8104
|
+
console.log(`\u2713 ${APP_NAME3} stopped`);
|
|
7514
8105
|
}
|
|
7515
8106
|
async waitForBackgroundServiceReady(params) {
|
|
7516
8107
|
const startedAt = Date.now();
|
|
@@ -7728,7 +8319,7 @@ var ServiceCommands = class {
|
|
|
7728
8319
|
return null;
|
|
7729
8320
|
}
|
|
7730
8321
|
console.error("Error: No API key configured.");
|
|
7731
|
-
console.error(`Set one in ${
|
|
8322
|
+
console.error(`Set one in ${getConfigPath6()} under providers section`);
|
|
7732
8323
|
process.exit(1);
|
|
7733
8324
|
}
|
|
7734
8325
|
return new LiteLLMProvider({
|
|
@@ -7765,8 +8356,8 @@ var ServiceCommands = class {
|
|
|
7765
8356
|
}
|
|
7766
8357
|
printServiceControlHints() {
|
|
7767
8358
|
console.log("Service controls:");
|
|
7768
|
-
console.log(` - Check status: ${
|
|
7769
|
-
console.log(` - If you need to stop the service, run: ${
|
|
8359
|
+
console.log(` - Check status: ${APP_NAME3} status`);
|
|
8360
|
+
console.log(` - If you need to stop the service, run: ${APP_NAME3} stop`);
|
|
7770
8361
|
}
|
|
7771
8362
|
async startUiIfEnabled(uiConfig, uiStaticDir, cronService, runtimePool, sessionManager, providerManager, bus, gatewayController, getConfig, getExtensionRegistry, resolveMessageToolHints) {
|
|
7772
8363
|
if (!uiConfig.enabled) {
|
|
@@ -7855,7 +8446,7 @@ var ServiceCommands = class {
|
|
|
7855
8446
|
const uiServer = startUiServer({
|
|
7856
8447
|
host: uiConfig.host,
|
|
7857
8448
|
port: uiConfig.port,
|
|
7858
|
-
configPath:
|
|
8449
|
+
configPath: getConfigPath6(),
|
|
7859
8450
|
productVersion: getPackageVersion(),
|
|
7860
8451
|
staticDir: uiStaticDir ?? void 0,
|
|
7861
8452
|
cronService,
|
|
@@ -7943,7 +8534,7 @@ var ServiceCommands = class {
|
|
|
7943
8534
|
}
|
|
7944
8535
|
}
|
|
7945
8536
|
installBuiltinMarketplaceSkill(slug, force) {
|
|
7946
|
-
const workspace = getWorkspacePath9(
|
|
8537
|
+
const workspace = getWorkspacePath9(loadConfig14().agents.defaults.workspace);
|
|
7947
8538
|
const destination = join7(workspace, "skills", slug);
|
|
7948
8539
|
const destinationSkillFile = join7(destination, "SKILL.md");
|
|
7949
8540
|
if (existsSync11(destinationSkillFile) && !force) {
|
|
@@ -8020,12 +8611,58 @@ ${stderr}`.trim();
|
|
|
8020
8611
|
}
|
|
8021
8612
|
};
|
|
8022
8613
|
|
|
8614
|
+
// src/cli/remote/remote-runtime-actions.ts
|
|
8615
|
+
import { APP_NAME as APP_NAME4 } from "@nextclaw/core";
|
|
8616
|
+
var RemoteRuntimeActions = class {
|
|
8617
|
+
constructor(deps) {
|
|
8618
|
+
this.deps = deps;
|
|
8619
|
+
}
|
|
8620
|
+
async connect(opts = {}) {
|
|
8621
|
+
await this.deps.remoteCommands.connect(opts);
|
|
8622
|
+
}
|
|
8623
|
+
async enable(opts = {}) {
|
|
8624
|
+
await this.deps.initAuto("remote enable");
|
|
8625
|
+
const result = this.deps.remoteCommands.enableConfig(opts);
|
|
8626
|
+
console.log("\u2713 Remote access enabled");
|
|
8627
|
+
if (result.config.remote.deviceName.trim()) {
|
|
8628
|
+
console.log(`Device: ${result.config.remote.deviceName.trim()}`);
|
|
8629
|
+
}
|
|
8630
|
+
if (result.config.remote.platformApiBase.trim()) {
|
|
8631
|
+
console.log(`Platform: ${result.config.remote.platformApiBase.trim()}`);
|
|
8632
|
+
}
|
|
8633
|
+
if (this.hasRunningManagedService()) {
|
|
8634
|
+
await this.deps.restartBackgroundService("remote configuration updated");
|
|
8635
|
+
console.log("\u2713 Applied remote settings to running background service");
|
|
8636
|
+
return;
|
|
8637
|
+
}
|
|
8638
|
+
console.log(`Tip: Run "${APP_NAME4} start" to bring the managed remote connector online.`);
|
|
8639
|
+
}
|
|
8640
|
+
async disable() {
|
|
8641
|
+
const result = this.deps.remoteCommands.disableConfig();
|
|
8642
|
+
console.log(result.changed ? "\u2713 Remote access disabled" : "Remote access was already disabled");
|
|
8643
|
+
if (this.hasRunningManagedService()) {
|
|
8644
|
+
await this.deps.restartBackgroundService("remote access disabled");
|
|
8645
|
+
console.log("\u2713 Running background service restarted without remote access");
|
|
8646
|
+
}
|
|
8647
|
+
}
|
|
8648
|
+
async status(opts = {}) {
|
|
8649
|
+
await this.deps.remoteCommands.status(opts);
|
|
8650
|
+
}
|
|
8651
|
+
async doctor(opts = {}) {
|
|
8652
|
+
await this.deps.remoteCommands.doctor(opts);
|
|
8653
|
+
}
|
|
8654
|
+
hasRunningManagedService() {
|
|
8655
|
+
const state = readServiceState();
|
|
8656
|
+
return Boolean(state && isProcessRunning(state.pid));
|
|
8657
|
+
}
|
|
8658
|
+
};
|
|
8659
|
+
|
|
8023
8660
|
// src/cli/workspace.ts
|
|
8024
8661
|
import { cpSync as cpSync3, existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
8025
8662
|
import { createRequire as createRequire2 } from "module";
|
|
8026
8663
|
import { dirname as dirname4, join as join8, resolve as resolve11 } from "path";
|
|
8027
|
-
import { fileURLToPath as
|
|
8028
|
-
import { APP_NAME as
|
|
8664
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8665
|
+
import { APP_NAME as APP_NAME5, getDataDir as getDataDir8 } from "@nextclaw/core";
|
|
8029
8666
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
8030
8667
|
var WorkspaceManager = class {
|
|
8031
8668
|
constructor(logo) {
|
|
@@ -8063,7 +8700,7 @@ var WorkspaceManager = class {
|
|
|
8063
8700
|
continue;
|
|
8064
8701
|
}
|
|
8065
8702
|
const raw = readFileSync10(templatePath, "utf-8");
|
|
8066
|
-
const content = raw.replace(/\$\{APP_NAME\}/g,
|
|
8703
|
+
const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME5);
|
|
8067
8704
|
mkdirSync7(dirname4(filePath), { recursive: true });
|
|
8068
8705
|
writeFileSync6(filePath, content);
|
|
8069
8706
|
created.push(entry.target);
|
|
@@ -8136,7 +8773,7 @@ var WorkspaceManager = class {
|
|
|
8136
8773
|
if (override) {
|
|
8137
8774
|
return override;
|
|
8138
8775
|
}
|
|
8139
|
-
const cliDir = resolve11(
|
|
8776
|
+
const cliDir = resolve11(fileURLToPath4(new URL(".", import.meta.url)));
|
|
8140
8777
|
const pkgRoot = resolve11(cliDir, "..", "..");
|
|
8141
8778
|
const candidates = [join8(pkgRoot, "templates")];
|
|
8142
8779
|
for (const candidate of candidates) {
|
|
@@ -8155,7 +8792,7 @@ var WorkspaceManager = class {
|
|
|
8155
8792
|
console.error("npm not found. Please install Node.js >= 18.");
|
|
8156
8793
|
process.exit(1);
|
|
8157
8794
|
}
|
|
8158
|
-
const cliDir = resolve11(
|
|
8795
|
+
const cliDir = resolve11(fileURLToPath4(new URL(".", import.meta.url)));
|
|
8159
8796
|
const pkgRoot = resolve11(cliDir, "..", "..");
|
|
8160
8797
|
const pkgBridge = join8(pkgRoot, "bridge");
|
|
8161
8798
|
const srcBridge = join8(pkgRoot, "..", "..", "bridge");
|
|
@@ -8166,7 +8803,7 @@ var WorkspaceManager = class {
|
|
|
8166
8803
|
source = srcBridge;
|
|
8167
8804
|
}
|
|
8168
8805
|
if (!source) {
|
|
8169
|
-
console.error(`Bridge source not found. Try reinstalling ${
|
|
8806
|
+
console.error(`Bridge source not found. Try reinstalling ${APP_NAME5}.`);
|
|
8170
8807
|
process.exit(1);
|
|
8171
8808
|
}
|
|
8172
8809
|
console.log(`${this.logo} Setting up bridge...`);
|
|
@@ -8224,6 +8861,7 @@ var CliRuntime = class {
|
|
|
8224
8861
|
cronCommands;
|
|
8225
8862
|
platformAuthCommands;
|
|
8226
8863
|
remoteCommands;
|
|
8864
|
+
remote;
|
|
8227
8865
|
diagnosticsCommands;
|
|
8228
8866
|
constructor(options = {}) {
|
|
8229
8867
|
this.logo = options.logo ?? LOGO;
|
|
@@ -8247,6 +8885,11 @@ var CliRuntime = class {
|
|
|
8247
8885
|
this.cronCommands = new CronCommands();
|
|
8248
8886
|
this.platformAuthCommands = new PlatformAuthCommands();
|
|
8249
8887
|
this.remoteCommands = new RemoteCommands();
|
|
8888
|
+
this.remote = new RemoteRuntimeActions({
|
|
8889
|
+
initAuto: (source) => this.init({ source, auto: true }),
|
|
8890
|
+
remoteCommands: this.remoteCommands,
|
|
8891
|
+
restartBackgroundService: (reason) => this.restartBackgroundService(reason)
|
|
8892
|
+
});
|
|
8250
8893
|
this.diagnosticsCommands = new DiagnosticsCommands({ logo: this.logo });
|
|
8251
8894
|
this.restartCoordinator = new RestartCoordinator({
|
|
8252
8895
|
readServiceState,
|
|
@@ -8277,7 +8920,7 @@ var CliRuntime = class {
|
|
|
8277
8920
|
const uiHost = FORCED_PUBLIC_UI_HOST;
|
|
8278
8921
|
const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 18791;
|
|
8279
8922
|
console.log(
|
|
8280
|
-
`Applying changes (${reason}): restarting ${
|
|
8923
|
+
`Applying changes (${reason}): restarting ${APP_NAME6} background service...`
|
|
8281
8924
|
);
|
|
8282
8925
|
await this.serviceCommands.stopService();
|
|
8283
8926
|
await this.serviceCommands.startService({
|
|
@@ -8310,7 +8953,7 @@ var CliRuntime = class {
|
|
|
8310
8953
|
}
|
|
8311
8954
|
const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 18791;
|
|
8312
8955
|
const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
|
|
8313
|
-
const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() ||
|
|
8956
|
+
const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath5(new URL("./index.js", import.meta.url));
|
|
8314
8957
|
const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
|
|
8315
8958
|
const serviceStatePath = resolve12(getDataDir9(), "run", "service.json");
|
|
8316
8959
|
const helperScript = [
|
|
@@ -8432,7 +9075,7 @@ var CliRuntime = class {
|
|
|
8432
9075
|
}
|
|
8433
9076
|
async onboard() {
|
|
8434
9077
|
console.warn(
|
|
8435
|
-
`Warning: ${
|
|
9078
|
+
`Warning: ${APP_NAME6} onboard is deprecated. Use "${APP_NAME6} init" instead.`
|
|
8436
9079
|
);
|
|
8437
9080
|
await this.init({ source: "onboard" });
|
|
8438
9081
|
}
|
|
@@ -8440,14 +9083,14 @@ var CliRuntime = class {
|
|
|
8440
9083
|
const source = options.source ?? "init";
|
|
8441
9084
|
const prefix = options.auto ? "Auto init" : "Init";
|
|
8442
9085
|
const force = Boolean(options.force);
|
|
8443
|
-
const configPath =
|
|
9086
|
+
const configPath = getConfigPath7();
|
|
8444
9087
|
let createdConfig = false;
|
|
8445
9088
|
if (!existsSync13(configPath)) {
|
|
8446
9089
|
const config3 = ConfigSchema2.parse({});
|
|
8447
|
-
|
|
9090
|
+
saveConfig10(config3);
|
|
8448
9091
|
createdConfig = true;
|
|
8449
9092
|
}
|
|
8450
|
-
const config2 =
|
|
9093
|
+
const config2 = loadConfig15();
|
|
8451
9094
|
const workspaceSetting = config2.agents.defaults.workspace;
|
|
8452
9095
|
const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join9(getDataDir9(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
|
|
8453
9096
|
const workspaceExisted = existsSync13(workspacePath);
|
|
@@ -8470,13 +9113,13 @@ var CliRuntime = class {
|
|
|
8470
9113
|
}
|
|
8471
9114
|
if (!options.auto) {
|
|
8472
9115
|
console.log(`
|
|
8473
|
-
${this.logo} ${
|
|
9116
|
+
${this.logo} ${APP_NAME6} is ready! (${source})`);
|
|
8474
9117
|
console.log("\nNext steps:");
|
|
8475
9118
|
console.log(` 1. Add your API key to ${configPath}`);
|
|
8476
|
-
console.log(` 2. Chat: ${
|
|
9119
|
+
console.log(` 2. Chat: ${APP_NAME6} agent -m "Hello!"`);
|
|
8477
9120
|
} else {
|
|
8478
9121
|
console.log(
|
|
8479
|
-
`Tip: Run "${
|
|
9122
|
+
`Tip: Run "${APP_NAME6} init${force ? " --force" : ""}" to re-run initialization if needed.`
|
|
8480
9123
|
);
|
|
8481
9124
|
}
|
|
8482
9125
|
}
|
|
@@ -8484,9 +9127,6 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8484
9127
|
await this.init({ source: "login", auto: true });
|
|
8485
9128
|
await this.platformAuthCommands.login(opts);
|
|
8486
9129
|
}
|
|
8487
|
-
async remoteConnect(opts = {}) {
|
|
8488
|
-
await this.remoteCommands.connect(opts);
|
|
8489
|
-
}
|
|
8490
9130
|
async gateway(opts) {
|
|
8491
9131
|
const uiOverrides = {
|
|
8492
9132
|
host: FORCED_PUBLIC_UI_HOST
|
|
@@ -8537,7 +9177,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8537
9177
|
await this.writeRestartSentinelFromExecContext("cli.restart");
|
|
8538
9178
|
const state = readServiceState();
|
|
8539
9179
|
if (state && isProcessRunning(state.pid)) {
|
|
8540
|
-
console.log(`Restarting ${
|
|
9180
|
+
console.log(`Restarting ${APP_NAME6}...`);
|
|
8541
9181
|
await this.serviceCommands.stopService();
|
|
8542
9182
|
} else if (state) {
|
|
8543
9183
|
clearServiceState();
|
|
@@ -8576,8 +9216,8 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8576
9216
|
await this.serviceCommands.stopService();
|
|
8577
9217
|
}
|
|
8578
9218
|
async agent(opts) {
|
|
8579
|
-
const configPath =
|
|
8580
|
-
const config2 = resolveConfigSecrets3(
|
|
9219
|
+
const configPath = getConfigPath7();
|
|
9220
|
+
const config2 = resolveConfigSecrets3(loadConfig15(), { configPath });
|
|
8581
9221
|
const workspace = getWorkspacePath10(config2.agents.defaults.workspace);
|
|
8582
9222
|
const pluginRegistry = loadPluginRegistry(config2, workspace);
|
|
8583
9223
|
const extensionRegistry = toExtensionRegistry(pluginRegistry);
|
|
@@ -8585,7 +9225,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8585
9225
|
const pluginChannelBindings = getPluginChannelBindings4(pluginRegistry);
|
|
8586
9226
|
setPluginRuntimeBridge2({
|
|
8587
9227
|
loadConfig: () => toPluginConfigView(
|
|
8588
|
-
resolveConfigSecrets3(
|
|
9228
|
+
resolveConfigSecrets3(loadConfig15(), { configPath }),
|
|
8589
9229
|
pluginChannelBindings
|
|
8590
9230
|
),
|
|
8591
9231
|
writeConfigFile: async (nextConfigView) => {
|
|
@@ -8594,13 +9234,13 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8594
9234
|
"plugin runtime writeConfigFile expects an object config"
|
|
8595
9235
|
);
|
|
8596
9236
|
}
|
|
8597
|
-
const current =
|
|
9237
|
+
const current = loadConfig15();
|
|
8598
9238
|
const next = mergePluginConfigView(
|
|
8599
9239
|
current,
|
|
8600
9240
|
nextConfigView,
|
|
8601
9241
|
pluginChannelBindings
|
|
8602
9242
|
);
|
|
8603
|
-
|
|
9243
|
+
saveConfig10(next);
|
|
8604
9244
|
}
|
|
8605
9245
|
});
|
|
8606
9246
|
try {
|
|
@@ -8626,7 +9266,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8626
9266
|
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints2({
|
|
8627
9267
|
registry: pluginRegistry,
|
|
8628
9268
|
channel,
|
|
8629
|
-
cfg: resolveConfigSecrets3(
|
|
9269
|
+
cfg: resolveConfigSecrets3(loadConfig15(), { configPath }),
|
|
8630
9270
|
accountId
|
|
8631
9271
|
})
|
|
8632
9272
|
});
|
|
@@ -8727,7 +9367,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8727
9367
|
}
|
|
8728
9368
|
const state = readServiceState();
|
|
8729
9369
|
if (state && isProcessRunning(state.pid)) {
|
|
8730
|
-
console.log(`Tip: restart ${
|
|
9370
|
+
console.log(`Tip: restart ${APP_NAME6} to apply the update.`);
|
|
8731
9371
|
}
|
|
8732
9372
|
}
|
|
8733
9373
|
pluginsList(opts = {}) {
|
|
@@ -8821,7 +9461,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8821
9461
|
await this.diagnosticsCommands.doctor(opts);
|
|
8822
9462
|
}
|
|
8823
9463
|
async skillsInstall(options) {
|
|
8824
|
-
const config2 =
|
|
9464
|
+
const config2 = loadConfig15();
|
|
8825
9465
|
const workdir = resolveSkillsInstallWorkdir({
|
|
8826
9466
|
explicitWorkdir: options.workdir,
|
|
8827
9467
|
configuredWorkspace: config2.agents.defaults.workspace
|
|
@@ -8882,23 +9522,32 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
8882
9522
|
}
|
|
8883
9523
|
};
|
|
8884
9524
|
|
|
9525
|
+
// src/cli/remote/register-remote-commands.ts
|
|
9526
|
+
function registerRemoteCommands(program2, runtime2) {
|
|
9527
|
+
const remote = program2.command("remote").description("Manage remote access");
|
|
9528
|
+
remote.command("enable").description("Enable service-managed remote access").option("--api-base <url>", "Platform API base (supports /v1 suffix)").option("--name <name>", "Device display name").action(async (opts) => runtime2.enable(opts));
|
|
9529
|
+
remote.command("disable").description("Disable service-managed remote access").action(async () => runtime2.disable());
|
|
9530
|
+
remote.command("status").description("Show remote access status").option("--json", "Print JSON").action(async (opts) => runtime2.status(opts));
|
|
9531
|
+
remote.command("doctor").description("Run remote access diagnostics").option("--json", "Print JSON").action(async (opts) => runtime2.doctor(opts));
|
|
9532
|
+
remote.command("connect").description("Foreground debug mode: register this machine and keep the connector online").option("--api-base <url>", "Platform API base (supports /v1 suffix)").option("--local-origin <url>", "Local NextClaw UI origin (default: active service or http://127.0.0.1:18791)").option("--name <name>", "Device display name").option("--once", "Connect once without auto-reconnect", false).action(async (opts) => runtime2.connect(opts));
|
|
9533
|
+
}
|
|
9534
|
+
|
|
8885
9535
|
// src/cli/index.ts
|
|
8886
9536
|
var program = new Command();
|
|
8887
9537
|
var runtime = new CliRuntime({ logo: LOGO });
|
|
8888
|
-
program.name(
|
|
8889
|
-
program.command("onboard").description(`Initialize ${
|
|
8890
|
-
program.command("init").description(`Initialize ${
|
|
9538
|
+
program.name(APP_NAME7).description(`${LOGO} ${APP_NAME7} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
|
|
9539
|
+
program.command("onboard").description(`Initialize ${APP_NAME7} configuration and workspace`).action(async () => runtime.onboard());
|
|
9540
|
+
program.command("init").description(`Initialize ${APP_NAME7} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
|
|
8891
9541
|
program.command("login").description("Login to NextClaw platform and save token into providers.nextclaw.apiKey").option("--api-base <url>", "Platform API base (supports /v1 suffix)").option("--email <email>", "Login email").option("--password <password>", "Login password").option("--register", "Register first, then login", false).action(async (opts) => runtime.login(opts));
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
program.command("
|
|
8895
|
-
program.command("
|
|
8896
|
-
program.command("
|
|
8897
|
-
program.command("
|
|
8898
|
-
program.command("
|
|
8899
|
-
program.command("stop").description(`Stop the ${APP_NAME5} background service`).action(async () => runtime.stop());
|
|
9542
|
+
registerRemoteCommands(program, runtime.remote);
|
|
9543
|
+
program.command("gateway").description(`Start the ${APP_NAME7} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).action(async (opts) => runtime.gateway(opts));
|
|
9544
|
+
program.command("ui").description(`Start the ${APP_NAME7} UI with gateway`).option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
|
|
9545
|
+
program.command("start").description(`Start the ${APP_NAME7} gateway + UI in the background`).option("--ui-port <port>", "UI port").option("--start-timeout <ms>", "Maximum wait time for startup readiness in milliseconds").option("--open", "Open browser after start", false).action(async (opts) => runtime.start(opts));
|
|
9546
|
+
program.command("restart").description(`Restart the ${APP_NAME7} background service`).option("--ui-port <port>", "UI port").option("--start-timeout <ms>", "Maximum wait time for startup readiness in milliseconds").option("--open", "Open browser after restart", false).action(async (opts) => runtime.restart(opts));
|
|
9547
|
+
program.command("serve").description(`Run the ${APP_NAME7} gateway + UI in the foreground`).option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).action(async (opts) => runtime.serve(opts));
|
|
9548
|
+
program.command("stop").description(`Stop the ${APP_NAME7} background service`).action(async () => runtime.stop());
|
|
8900
9549
|
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("--model <model>", "Session model override for this run").option("--no-markdown", "Disable Markdown rendering").action(async (opts) => runtime.agent(opts));
|
|
8901
|
-
program.command("update").description(`Update ${
|
|
9550
|
+
program.command("update").description(`Update ${APP_NAME7}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
|
|
8902
9551
|
var skills = program.command("skills").description("Manage skills");
|
|
8903
9552
|
skills.command("install <slug>").description("Install a skill from NextClaw marketplace").option("--api-base <url>", "Marketplace API base URL").option("--workdir <dir>", "Workspace directory to install into").option("--dir <dir>", "Skills directory name (default: skills)").option("-f, --force", "Overwrite existing skill files", false).action(async (slug, opts) => runtime.skillsInstall({ slug, ...opts, apiBaseUrl: opts.apiBase }));
|
|
8904
9553
|
var withRepeatableTag = (value, previous = []) => [...previous, value];
|
|
@@ -8943,6 +9592,6 @@ cron.command("add").requiredOption("-n, --name <name>", "Job name").requiredOpti
|
|
|
8943
9592
|
cron.command("remove <jobId>").action((jobId) => runtime.cronRemove(jobId));
|
|
8944
9593
|
cron.command("enable <jobId>").option("--disable", "Disable instead of enable").action((jobId, opts) => runtime.cronEnable(jobId, opts));
|
|
8945
9594
|
cron.command("run <jobId>").option("-f, --force", "Run even if disabled").action(async (jobId, opts) => runtime.cronRun(jobId, opts));
|
|
8946
|
-
program.command("status").description(`Show ${
|
|
8947
|
-
program.command("doctor").description(`Run ${
|
|
9595
|
+
program.command("status").description(`Show ${APP_NAME7} 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));
|
|
9596
|
+
program.command("doctor").description(`Run ${APP_NAME7} 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));
|
|
8948
9597
|
program.parseAsync(process.argv);
|