nextclaw 0.13.5 → 0.13.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  // src/cli/index.ts
4
4
  import { Command } from "commander";
5
- import { APP_NAME as APP_NAME5, APP_TAGLINE } from "@nextclaw/core";
5
+ import { APP_NAME as APP_NAME6, APP_TAGLINE } from "@nextclaw/core";
6
+ import { registerRemoteCommands } from "@nextclaw/remote";
6
7
 
7
8
  // src/cli/runtime.ts
8
9
  import {
9
- loadConfig as loadConfig13,
10
- saveConfig as saveConfig9,
11
- getConfigPath as getConfigPath6,
10
+ loadConfig as loadConfig14,
11
+ saveConfig as saveConfig10,
12
+ getConfigPath as getConfigPath7,
12
13
  getDataDir as getDataDir9,
13
14
  ConfigSchema as ConfigSchema2,
14
15
  getWorkspacePath as getWorkspacePath10,
@@ -17,17 +18,18 @@ import {
17
18
  AgentLoop,
18
19
  ProviderManager as ProviderManager2,
19
20
  resolveConfigSecrets as resolveConfigSecrets3,
20
- APP_NAME as APP_NAME4,
21
+ APP_NAME as APP_NAME5,
21
22
  DEFAULT_WORKSPACE_DIR,
22
23
  DEFAULT_WORKSPACE_PATH
23
24
  } from "@nextclaw/core";
25
+ import { RemoteRuntimeActions } from "@nextclaw/remote";
24
26
  import {
25
27
  getPluginChannelBindings as getPluginChannelBindings4,
26
28
  resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2,
27
29
  setPluginRuntimeBridge as setPluginRuntimeBridge2
28
30
  } from "@nextclaw/openclaw-compat";
29
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
30
- import { join as join9, resolve as resolve12 } from "path";
31
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
32
+ import { join as join8, resolve as resolve12 } from "path";
31
33
  import { createInterface as createInterface3 } from "readline";
32
34
  import { fileURLToPath as fileURLToPath5 } from "url";
33
35
  import { spawn as spawn3 } from "child_process";
@@ -74,9 +76,9 @@ var RestartCoordinator = class {
74
76
  message: "Restart already scheduled; skipping duplicate request."
75
77
  };
76
78
  }
77
- const delay2 = typeof request.delayMs === "number" && Number.isFinite(request.delayMs) ? Math.max(0, Math.floor(request.delayMs)) : 100;
79
+ const delay = typeof request.delayMs === "number" && Number.isFinite(request.delayMs) ? Math.max(0, Math.floor(request.delayMs)) : 100;
78
80
  this.exitScheduled = true;
79
- this.deps.scheduleProcessExit(delay2, reason);
81
+ this.deps.scheduleProcessExit(delay, reason);
80
82
  return {
81
83
  status: "exit-scheduled",
82
84
  message: `Restart scheduled (${reason}).`
@@ -822,6 +824,15 @@ function writeServiceState(state) {
822
824
  mkdirSync3(resolve4(path2, ".."), { recursive: true });
823
825
  writeFileSync3(path2, JSON.stringify(state, null, 2));
824
826
  }
827
+ function updateServiceState(updater) {
828
+ const current = readServiceState();
829
+ if (!current) {
830
+ return null;
831
+ }
832
+ const next = updater(current);
833
+ writeServiceState(next);
834
+ return next;
835
+ }
825
836
  function clearServiceState() {
826
837
  const path2 = resolveServiceStatePath();
827
838
  if (existsSync4(path2)) {
@@ -3025,285 +3036,315 @@ var PlatformAuthCommands = class {
3025
3036
  };
3026
3037
 
3027
3038
  // src/cli/commands/remote.ts
3039
+ import { getConfigPath as getConfigPath4, loadConfig as loadConfig9, saveConfig as saveConfig7 } from "@nextclaw/core";
3040
+ import "@nextclaw/remote";
3041
+ import { hostname } from "os";
3042
+
3043
+ // src/cli/commands/remote-runtime-support.ts
3028
3044
  import { getConfigPath as getConfigPath3, getDataDir as getDataDir4, loadConfig as loadConfig8 } from "@nextclaw/core";
3029
- import { ensureUiBridgeSecret } from "@nextclaw/server";
3030
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3031
- import { dirname as dirname2, join as join4 } from "path";
3032
- import { hostname, platform as readPlatform } from "os";
3033
- function encodeBase64(bytes) {
3034
- return Buffer.from(bytes).toString("base64");
3045
+ import {
3046
+ RemoteConnector,
3047
+ RemotePlatformClient,
3048
+ RemoteStatusStore,
3049
+ buildConfiguredRemoteState,
3050
+ resolveRemoteStatusSnapshot
3051
+ } from "@nextclaw/remote";
3052
+ function hasRunningNextclawManagedService() {
3053
+ const state = readServiceState();
3054
+ return Boolean(state && isProcessRunning(state.pid));
3055
+ }
3056
+ function createNextclawRemotePlatformClient() {
3057
+ return new RemotePlatformClient({
3058
+ loadConfig: () => loadConfig8(getConfigPath3()),
3059
+ getDataDir: getDataDir4,
3060
+ getPackageVersion,
3061
+ resolvePlatformBase: (rawApiBase) => resolvePlatformApiBase({
3062
+ explicitApiBase: rawApiBase,
3063
+ requireConfigured: true
3064
+ }).platformBase,
3065
+ readManagedServiceState: () => {
3066
+ const state = readServiceState();
3067
+ if (!state) {
3068
+ return null;
3069
+ }
3070
+ return {
3071
+ pid: state.pid,
3072
+ uiPort: state.uiPort
3073
+ };
3074
+ },
3075
+ isProcessRunning
3076
+ });
3035
3077
  }
3036
- function decodeBase64(base64) {
3037
- if (!base64) {
3038
- return new Uint8Array();
3039
- }
3040
- return new Uint8Array(Buffer.from(base64, "base64"));
3078
+ function createNextclawRemoteConnector(params = {}) {
3079
+ return new RemoteConnector({
3080
+ platformClient: createNextclawRemotePlatformClient(),
3081
+ logger: params.logger
3082
+ });
3041
3083
  }
3042
- function delay(ms) {
3043
- return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
3084
+ function createNextclawRemoteStatusStore(mode) {
3085
+ return new RemoteStatusStore(mode, {
3086
+ writeRemoteState: (next) => {
3087
+ updateServiceState((state) => ({
3088
+ ...state,
3089
+ remote: next
3090
+ }));
3091
+ }
3092
+ });
3044
3093
  }
3045
- function ensureDir(path2) {
3046
- mkdirSync4(path2, { recursive: true });
3094
+ function buildNextclawConfiguredRemoteState(config2) {
3095
+ return buildConfiguredRemoteState(config2);
3047
3096
  }
3048
- function readJsonFile2(path2) {
3049
- if (!existsSync6(path2)) {
3050
- return null;
3051
- }
3052
- try {
3053
- return JSON.parse(readFileSync6(path2, "utf-8"));
3054
- } catch {
3055
- return null;
3056
- }
3097
+ function readCurrentNextclawRemoteRuntimeState() {
3098
+ return readServiceState()?.remote ?? null;
3057
3099
  }
3058
- function writeJsonFile(path2, value) {
3059
- ensureDir(dirname2(path2));
3060
- writeFileSync4(path2, `${JSON.stringify(value, null, 2)}
3061
- `, "utf-8");
3100
+ function resolveNextclawRemoteStatusSnapshot(config2) {
3101
+ return resolveRemoteStatusSnapshot({
3102
+ config: config2,
3103
+ currentRemoteState: readCurrentNextclawRemoteRuntimeState()
3104
+ });
3062
3105
  }
3063
- var RemoteCommands = class {
3064
- remoteDir = join4(getDataDir4(), "remote");
3065
- devicePath = join4(this.remoteDir, "device.json");
3066
- ensureDeviceInstallId() {
3067
- const existing = readJsonFile2(this.devicePath);
3068
- if (existing?.deviceInstallId?.trim()) {
3069
- return existing.deviceInstallId.trim();
3070
- }
3071
- const deviceInstallId = crypto.randomUUID();
3072
- ensureDir(this.remoteDir);
3073
- writeJsonFile(this.devicePath, { deviceInstallId });
3074
- return deviceInstallId;
3075
- }
3076
- resolvePlatformAccess(opts) {
3077
- const config2 = loadConfig8(getConfigPath3());
3078
- const providers = config2.providers;
3079
- const nextclawProvider = providers.nextclaw;
3080
- const token = typeof nextclawProvider?.apiKey === "string" ? nextclawProvider.apiKey.trim() : "";
3081
- if (!token) {
3082
- throw new Error('NextClaw platform token is missing. Run "nextclaw login" first.');
3083
- }
3084
- const configuredApiBase = typeof nextclawProvider?.apiBase === "string" ? nextclawProvider.apiBase.trim() : "";
3085
- const rawApiBase = typeof opts.apiBase === "string" && opts.apiBase.trim().length > 0 ? opts.apiBase.trim() : configuredApiBase;
3086
- if (!rawApiBase) {
3087
- throw new Error("Platform API base is missing. Pass --api-base or run nextclaw login.");
3088
- }
3089
- const { platformBase } = resolvePlatformApiBase({
3090
- explicitApiBase: rawApiBase,
3091
- requireConfigured: true
3092
- });
3093
- return { platformBase, token, config: config2 };
3106
+
3107
+ // src/cli/commands/remote.ts
3108
+ function normalizeOptionalString3(value) {
3109
+ if (typeof value !== "string") {
3110
+ return void 0;
3094
3111
  }
3095
- resolveLocalOrigin(config2, opts) {
3096
- if (typeof opts.localOrigin === "string" && opts.localOrigin.trim().length > 0) {
3097
- return opts.localOrigin.trim().replace(/\/$/, "");
3098
- }
3099
- const state = readServiceState();
3100
- if (state && isProcessRunning(state.pid) && Number.isFinite(state.uiPort)) {
3101
- return `http://127.0.0.1:${state.uiPort}`;
3102
- }
3103
- const configuredPort = typeof config2.ui?.port === "number" && Number.isFinite(config2.ui.port) ? config2.ui.port : 18791;
3104
- return `http://127.0.0.1:${configuredPort}`;
3112
+ const trimmed = value.trim();
3113
+ return trimmed.length > 0 ? trimmed : void 0;
3114
+ }
3115
+ function resolveConfiguredLocalOrigin(config2) {
3116
+ const state = readServiceState();
3117
+ if (state && isProcessRunning(state.pid) && Number.isFinite(state.uiPort)) {
3118
+ return `http://127.0.0.1:${state.uiPort}`;
3105
3119
  }
3106
- async ensureLocalUiHealthy(localOrigin) {
3120
+ const port = typeof config2.ui.port === "number" && Number.isFinite(config2.ui.port) ? config2.ui.port : 18791;
3121
+ return `http://127.0.0.1:${port}`;
3122
+ }
3123
+ async function probeLocalUi(localOrigin) {
3124
+ try {
3107
3125
  const response = await fetch(`${localOrigin}/api/health`);
3108
3126
  if (!response.ok) {
3109
- throw new Error(`Local UI is not healthy at ${localOrigin}. Start NextClaw first.`);
3127
+ return { ok: false, detail: `health returned ${response.status}` };
3110
3128
  }
3129
+ return { ok: true, detail: "health endpoint returned ok" };
3130
+ } catch (error) {
3131
+ return {
3132
+ ok: false,
3133
+ detail: error instanceof Error ? error.message : String(error)
3134
+ };
3111
3135
  }
3112
- async registerDevice(params) {
3113
- const response = await fetch(`${params.platformBase}/platform/remote/devices/register`, {
3114
- method: "POST",
3115
- headers: {
3116
- "content-type": "application/json",
3117
- authorization: `Bearer ${params.token}`
3118
- },
3119
- body: JSON.stringify({
3120
- deviceInstallId: params.deviceInstallId,
3121
- displayName: params.displayName,
3122
- platform: readPlatform(),
3123
- appVersion: getPackageVersion(),
3124
- localOrigin: params.localOrigin
3125
- })
3126
- });
3127
- const payload = await response.json();
3128
- if (!response.ok || !payload.ok || !payload.data?.device) {
3129
- throw new Error(payload.error?.message ?? `Failed to register remote device (${response.status}).`);
3130
- }
3131
- return payload.data.device;
3136
+ }
3137
+ var RemoteCommands = class {
3138
+ enableConfig(opts = {}) {
3139
+ const config2 = loadConfig9(getConfigPath4());
3140
+ const next = {
3141
+ ...config2,
3142
+ remote: {
3143
+ ...config2.remote,
3144
+ enabled: true,
3145
+ ...normalizeOptionalString3(opts.apiBase) ? { platformApiBase: normalizeOptionalString3(opts.apiBase) ?? "" } : {},
3146
+ ...normalizeOptionalString3(opts.name) ? { deviceName: normalizeOptionalString3(opts.name) ?? "" } : {}
3147
+ }
3148
+ };
3149
+ saveConfig7(next);
3150
+ return {
3151
+ changed: config2.remote.enabled !== next.remote.enabled || config2.remote.platformApiBase !== next.remote.platformApiBase || config2.remote.deviceName !== next.remote.deviceName,
3152
+ config: next
3153
+ };
3132
3154
  }
3133
- async requestBridgeCookie(localOrigin) {
3134
- const response = await fetch(`${localOrigin}/api/auth/bridge`, {
3135
- method: "POST",
3136
- headers: {
3137
- "x-nextclaw-ui-bridge-secret": ensureUiBridgeSecret()
3155
+ disableConfig() {
3156
+ const config2 = loadConfig9(getConfigPath4());
3157
+ const next = {
3158
+ ...config2,
3159
+ remote: {
3160
+ ...config2.remote,
3161
+ enabled: false
3138
3162
  }
3163
+ };
3164
+ saveConfig7(next);
3165
+ return {
3166
+ changed: config2.remote.enabled !== next.remote.enabled,
3167
+ config: next
3168
+ };
3169
+ }
3170
+ async connect(opts = {}) {
3171
+ const connector = createNextclawRemoteConnector();
3172
+ await connector.run({
3173
+ ...opts,
3174
+ mode: "foreground"
3139
3175
  });
3140
- const payload = await response.json();
3141
- if (!response.ok || !payload.ok) {
3142
- throw new Error(payload.error?.message ?? `Failed to request local auth bridge (${response.status}).`);
3143
- }
3144
- return typeof payload.data?.cookie === "string" && payload.data.cookie.trim().length > 0 ? payload.data.cookie.trim() : null;
3145
- }
3146
- async handleRelayRequest(params) {
3147
- const bridgeCookie = await this.requestBridgeCookie(params.localOrigin);
3148
- const url = new URL(params.frame.path, params.localOrigin);
3149
- const headers = new Headers();
3150
- for (const [key, value] of params.frame.headers) {
3151
- const lower = key.toLowerCase();
3152
- if ([
3153
- "host",
3154
- "connection",
3155
- "content-length",
3156
- "cookie",
3157
- "x-forwarded-for",
3158
- "x-forwarded-proto",
3159
- "cf-connecting-ip"
3160
- ].includes(lower)) {
3161
- continue;
3162
- }
3163
- headers.set(key, value);
3176
+ }
3177
+ async status(opts = {}) {
3178
+ const config2 = loadConfig9(getConfigPath4());
3179
+ const snapshot = resolveNextclawRemoteStatusSnapshot(config2);
3180
+ if (opts.json) {
3181
+ console.log(JSON.stringify(snapshot, null, 2));
3182
+ return;
3164
3183
  }
3165
- if (bridgeCookie) {
3166
- headers.set("cookie", bridgeCookie);
3184
+ const runtime2 = snapshot.runtime;
3185
+ console.log("NextClaw Remote Status");
3186
+ console.log(`Enabled: ${snapshot.configuredEnabled ? "yes" : "no"}`);
3187
+ console.log(`Mode: ${runtime2?.mode ?? "service"}`);
3188
+ console.log(`State: ${runtime2?.state ?? "disabled"}`);
3189
+ console.log(`Device: ${runtime2?.deviceName ?? normalizeOptionalString3(config2.remote.deviceName) ?? hostname()}`);
3190
+ console.log(
3191
+ `Platform: ${runtime2?.platformBase ?? normalizeOptionalString3(config2.remote.platformApiBase) ?? normalizeOptionalString3(config2.providers.nextclaw?.apiBase) ?? "not set"}`
3192
+ );
3193
+ console.log(`Local origin: ${runtime2?.localOrigin ?? resolveConfiguredLocalOrigin(config2)}`);
3194
+ if (runtime2?.deviceId) {
3195
+ console.log(`Device ID: ${runtime2.deviceId}`);
3167
3196
  }
3168
- const bodyBytes = decodeBase64(params.frame.bodyBase64);
3169
- const response = await fetch(url, {
3170
- method: params.frame.method,
3171
- headers,
3172
- body: params.frame.method === "GET" || params.frame.method === "HEAD" ? void 0 : bodyBytes
3173
- });
3174
- const responseHeaders = Array.from(response.headers.entries()).filter(([key]) => {
3175
- const lower = key.toLowerCase();
3176
- return !["content-length", "connection", "transfer-encoding", "set-cookie"].includes(lower);
3177
- });
3178
- const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
3179
- if (response.body && contentType.startsWith("text/event-stream")) {
3180
- params.socket.send(JSON.stringify({
3181
- type: "response.start",
3182
- requestId: params.frame.requestId,
3183
- status: response.status,
3184
- headers: responseHeaders
3185
- }));
3186
- const reader = response.body.getReader();
3187
- try {
3188
- while (true) {
3189
- const { value, done } = await reader.read();
3190
- if (done) {
3191
- break;
3192
- }
3193
- if (value && value.length > 0) {
3194
- params.socket.send(JSON.stringify({
3195
- type: "response.chunk",
3196
- requestId: params.frame.requestId,
3197
- bodyBase64: encodeBase64(value)
3198
- }));
3199
- }
3200
- }
3201
- } finally {
3202
- reader.releaseLock();
3203
- }
3204
- params.socket.send(JSON.stringify({
3205
- type: "response.end",
3206
- requestId: params.frame.requestId
3207
- }));
3208
- return;
3197
+ if (runtime2?.lastConnectedAt) {
3198
+ console.log(`Last connected: ${runtime2.lastConnectedAt}`);
3199
+ }
3200
+ if (runtime2?.lastError) {
3201
+ console.log(`Last error: ${runtime2.lastError}`);
3209
3202
  }
3210
- const responseBody = response.body ? new Uint8Array(await response.arrayBuffer()) : new Uint8Array();
3211
- params.socket.send(JSON.stringify({
3212
- type: "response",
3213
- requestId: params.frame.requestId,
3214
- status: response.status,
3215
- headers: responseHeaders,
3216
- bodyBase64: encodeBase64(responseBody)
3217
- }));
3218
- }
3219
- async connectOnce(params) {
3220
- await new Promise((resolve13, reject) => {
3221
- const socket = new WebSocket(params.wsUrl);
3222
- const pingTimer = setInterval(() => {
3223
- if (socket.readyState === WebSocket.OPEN) {
3224
- socket.send(JSON.stringify({ type: "ping", at: (/* @__PURE__ */ new Date()).toISOString() }));
3225
- }
3226
- }, 15e3);
3227
- socket.addEventListener("open", () => {
3228
- console.log(`\u2713 Remote connector connected: ${params.wsUrl}`);
3229
- });
3230
- socket.addEventListener("message", (event) => {
3231
- void (async () => {
3232
- let frame = null;
3233
- try {
3234
- frame = JSON.parse(String(event.data ?? ""));
3235
- } catch {
3236
- return;
3237
- }
3238
- if (!frame || frame.type !== "request") {
3239
- return;
3240
- }
3241
- try {
3242
- await this.handleRelayRequest({ frame, localOrigin: params.localOrigin, socket });
3243
- } catch (error) {
3244
- socket.send(JSON.stringify({
3245
- type: "response.error",
3246
- requestId: frame.requestId,
3247
- message: error instanceof Error ? error.message : String(error)
3248
- }));
3249
- }
3250
- })();
3251
- });
3252
- socket.addEventListener("close", () => {
3253
- clearInterval(pingTimer);
3254
- resolve13();
3255
- });
3256
- socket.addEventListener("error", () => {
3257
- clearInterval(pingTimer);
3258
- reject(new Error("Remote connector websocket failed."));
3259
- });
3260
- });
3261
3203
  }
3262
- async connect(opts = {}) {
3263
- const { platformBase, token, config: config2 } = this.resolvePlatformAccess(opts);
3264
- const localOrigin = this.resolveLocalOrigin(config2, opts);
3265
- await this.ensureLocalUiHealthy(localOrigin);
3266
- const deviceInstallId = this.ensureDeviceInstallId();
3267
- const displayName = typeof opts.name === "string" && opts.name.trim().length > 0 ? opts.name.trim() : hostname();
3268
- const device = await this.registerDevice({
3269
- platformBase,
3270
- token,
3271
- deviceInstallId,
3272
- displayName,
3273
- localOrigin
3274
- });
3275
- console.log(`\u2713 Remote device registered: ${device.displayName} (${device.id})`);
3276
- console.log(`\u2713 Local origin: ${localOrigin}`);
3277
- console.log(`\u2713 Platform: ${platformBase}`);
3278
- const wsUrl = `${platformBase.replace(/^http/i, "ws")}/platform/remote/connect?deviceId=${encodeURIComponent(device.id)}&token=${encodeURIComponent(token)}`;
3279
- do {
3280
- try {
3281
- await this.connectOnce({ wsUrl, localOrigin });
3282
- } catch (error) {
3283
- console.error(`Remote connector error: ${error instanceof Error ? error.message : String(error)}`);
3284
- }
3285
- if (opts.once) {
3286
- break;
3204
+ async doctor(opts = {}) {
3205
+ const config2 = loadConfig9(getConfigPath4());
3206
+ const snapshot = resolveNextclawRemoteStatusSnapshot(config2);
3207
+ const localOrigin = resolveConfiguredLocalOrigin(config2);
3208
+ const localUi = await probeLocalUi(localOrigin);
3209
+ const token = normalizeOptionalString3(config2.providers.nextclaw?.apiKey);
3210
+ const platformApiBase = normalizeOptionalString3(config2.remote.platformApiBase) ?? normalizeOptionalString3(config2.providers.nextclaw?.apiBase);
3211
+ const checks = [
3212
+ {
3213
+ name: "remote-enabled",
3214
+ ok: snapshot.configuredEnabled,
3215
+ detail: snapshot.configuredEnabled ? "enabled in config" : "disabled in config"
3216
+ },
3217
+ {
3218
+ name: "platform-token",
3219
+ ok: Boolean(token),
3220
+ detail: token ? "token configured" : 'run "nextclaw login" first'
3221
+ },
3222
+ {
3223
+ name: "platform-api-base",
3224
+ ok: Boolean(platformApiBase),
3225
+ detail: platformApiBase ?? "set remote.platformApiBase or login with --api-base"
3226
+ },
3227
+ {
3228
+ name: "local-ui",
3229
+ ok: localUi.ok,
3230
+ detail: `${localOrigin} (${localUi.detail})`
3231
+ },
3232
+ {
3233
+ name: "service-runtime",
3234
+ ok: snapshot.runtime?.state === "connected",
3235
+ detail: snapshot.runtime ? snapshot.runtime.state : "no managed remote runtime detected"
3287
3236
  }
3288
- console.log("Remote connector disconnected. Reconnecting in 3s...");
3289
- await delay(3e3);
3290
- } while (!opts.once);
3237
+ ];
3238
+ if (opts.json) {
3239
+ console.log(JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), checks, snapshot }, null, 2));
3240
+ return;
3241
+ }
3242
+ console.log("NextClaw Remote Doctor");
3243
+ for (const check of checks) {
3244
+ console.log(`${check.ok ? "\u2713" : "\u2717"} ${check.name}: ${check.detail}`);
3245
+ }
3291
3246
  }
3292
3247
  };
3293
3248
 
3294
3249
  // src/cli/commands/diagnostics.ts
3295
3250
  import { createServer as createNetServer } from "net";
3296
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
3251
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
3297
3252
  import { resolve as resolve8 } from "path";
3298
3253
  import {
3299
- APP_NAME,
3300
- getConfigPath as getConfigPath4,
3254
+ APP_NAME as APP_NAME2,
3255
+ getConfigPath as getConfigPath5,
3301
3256
  getDataDir as getDataDir5,
3302
3257
  getWorkspacePath as getWorkspacePath4,
3303
3258
  hasSecretRef,
3304
- loadConfig as loadConfig9
3259
+ loadConfig as loadConfig10
3305
3260
  } from "@nextclaw/core";
3306
3261
  import { listBuiltinProviders } from "@nextclaw/runtime";
3262
+
3263
+ // src/cli/commands/diagnostics-render.ts
3264
+ import { APP_NAME } from "@nextclaw/core";
3265
+ function printStatusReport(params) {
3266
+ const { logo, report, verbose } = params;
3267
+ console.log(`${logo} ${APP_NAME} Status`);
3268
+ console.log(`Level: ${report.level}`);
3269
+ console.log(`Generated: ${report.generatedAt}`);
3270
+ console.log("");
3271
+ printProcessSection(report);
3272
+ printEndpointSection(report);
3273
+ printProviderSection(report);
3274
+ printTextList("Fix actions", report.fixActions);
3275
+ printTextList("Issues", report.issues);
3276
+ printTextList("Recommendations", report.recommendations);
3277
+ if (verbose && report.logTail.length > 0) {
3278
+ console.log("");
3279
+ console.log("Recent logs:");
3280
+ for (const line of report.logTail) {
3281
+ console.log(line);
3282
+ }
3283
+ }
3284
+ }
3285
+ function printProcessSection(report) {
3286
+ const processLabel = report.process.running ? `running (PID ${report.process.pid})` : report.process.staleState ? "stale-state" : "stopped";
3287
+ console.log(`Process: ${processLabel}`);
3288
+ console.log(`State file: ${report.serviceStatePath} ${report.serviceStateExists ? "\u2713" : "\u2717"}`);
3289
+ if (report.process.startedAt) {
3290
+ console.log(`Started: ${report.process.startedAt}`);
3291
+ }
3292
+ console.log(`Managed health: ${report.health.managed.state} (${report.health.managed.detail})`);
3293
+ if (!report.process.running) {
3294
+ console.log(`Configured health: ${report.health.configured.state} (${report.health.configured.detail})`);
3295
+ }
3296
+ }
3297
+ function printEndpointSection(report) {
3298
+ console.log(`UI: ${report.endpoints.uiUrl ?? report.endpoints.configuredUiUrl}`);
3299
+ console.log(`API: ${report.endpoints.apiUrl ?? report.endpoints.configuredApiUrl}`);
3300
+ console.log(`Remote: ${report.remote.configuredEnabled ? "enabled" : "disabled"}${report.remote.runtime ? ` (${report.remote.runtime.state})` : ""}`);
3301
+ if (report.remote.runtime?.deviceName) {
3302
+ console.log(`Remote device: ${report.remote.runtime.deviceName}`);
3303
+ }
3304
+ if (report.remote.runtime?.platformBase) {
3305
+ console.log(`Remote platform: ${report.remote.runtime.platformBase}`);
3306
+ }
3307
+ if (report.remote.runtime?.lastError) {
3308
+ console.log(`Remote error: ${report.remote.runtime.lastError}`);
3309
+ }
3310
+ console.log(`Config: ${report.configPath} ${report.configExists ? "\u2713" : "\u2717"}`);
3311
+ console.log(`Workspace: ${report.workspacePath} ${report.workspaceExists ? "\u2713" : "\u2717"}`);
3312
+ console.log(`Model: ${report.model}`);
3313
+ }
3314
+ function printProviderSection(report) {
3315
+ for (const provider of report.providers) {
3316
+ console.log(`${provider.name}: ${provider.configured ? "\u2713" : "not set"}${provider.detail ? ` (${provider.detail})` : ""}`);
3317
+ }
3318
+ }
3319
+ function printDoctorReport(params) {
3320
+ console.log(`${params.logo} ${APP_NAME} Doctor`);
3321
+ console.log(`Generated: ${params.generatedAt}`);
3322
+ console.log("");
3323
+ for (const check of params.checks) {
3324
+ const icon = check.status === "pass" ? "\u2713" : check.status === "warn" ? "!" : "\u2717";
3325
+ console.log(`${icon} ${check.name}: ${check.detail}`);
3326
+ }
3327
+ printTextList("Recommendations", params.recommendations);
3328
+ if (params.verbose && params.logTail.length > 0) {
3329
+ console.log("");
3330
+ console.log("Recent logs:");
3331
+ for (const line of params.logTail) {
3332
+ console.log(line);
3333
+ }
3334
+ }
3335
+ }
3336
+ function printTextList(title, items) {
3337
+ if (items.length === 0) {
3338
+ return;
3339
+ }
3340
+ console.log("");
3341
+ console.log(`${title}:`);
3342
+ for (const item of items) {
3343
+ console.log(`- ${item}`);
3344
+ }
3345
+ }
3346
+
3347
+ // src/cli/commands/diagnostics.ts
3307
3348
  var DiagnosticsCommands = class {
3308
3349
  constructor(deps) {
3309
3350
  this.deps = deps;
@@ -3318,56 +3359,7 @@ var DiagnosticsCommands = class {
3318
3359
  process.exitCode = 0;
3319
3360
  return;
3320
3361
  }
3321
- console.log(`${this.deps.logo} ${APP_NAME} Status`);
3322
- console.log(`Level: ${report.level}`);
3323
- console.log(`Generated: ${report.generatedAt}`);
3324
- console.log("");
3325
- const processLabel = report.process.running ? `running (PID ${report.process.pid})` : report.process.staleState ? "stale-state" : "stopped";
3326
- console.log(`Process: ${processLabel}`);
3327
- console.log(`State file: ${report.serviceStatePath} ${report.serviceStateExists ? "\u2713" : "\u2717"}`);
3328
- if (report.process.startedAt) {
3329
- console.log(`Started: ${report.process.startedAt}`);
3330
- }
3331
- console.log(`Managed health: ${report.health.managed.state} (${report.health.managed.detail})`);
3332
- if (!report.process.running) {
3333
- console.log(`Configured health: ${report.health.configured.state} (${report.health.configured.detail})`);
3334
- }
3335
- console.log(`UI: ${report.endpoints.uiUrl ?? report.endpoints.configuredUiUrl}`);
3336
- console.log(`API: ${report.endpoints.apiUrl ?? report.endpoints.configuredApiUrl}`);
3337
- console.log(`Config: ${report.configPath} ${report.configExists ? "\u2713" : "\u2717"}`);
3338
- console.log(`Workspace: ${report.workspacePath} ${report.workspaceExists ? "\u2713" : "\u2717"}`);
3339
- console.log(`Model: ${report.model}`);
3340
- for (const provider of report.providers) {
3341
- console.log(`${provider.name}: ${provider.configured ? "\u2713" : "not set"}${provider.detail ? ` (${provider.detail})` : ""}`);
3342
- }
3343
- if (report.fixActions.length > 0) {
3344
- console.log("");
3345
- console.log("Fix actions:");
3346
- for (const action of report.fixActions) {
3347
- console.log(`- ${action}`);
3348
- }
3349
- }
3350
- if (report.issues.length > 0) {
3351
- console.log("");
3352
- console.log("Issues:");
3353
- for (const issue of report.issues) {
3354
- console.log(`- ${issue}`);
3355
- }
3356
- }
3357
- if (report.recommendations.length > 0) {
3358
- console.log("");
3359
- console.log("Recommendations:");
3360
- for (const recommendation of report.recommendations) {
3361
- console.log(`- ${recommendation}`);
3362
- }
3363
- }
3364
- if (opts.verbose && report.logTail.length > 0) {
3365
- console.log("");
3366
- console.log("Recent logs:");
3367
- for (const line of report.logTail) {
3368
- console.log(line);
3369
- }
3370
- }
3362
+ printStatusReport({ logo: this.deps.logo, report, verbose: Boolean(opts.verbose) });
3371
3363
  process.exitCode = 0;
3372
3364
  }
3373
3365
  async doctor(opts = {}) {
@@ -3438,32 +3430,19 @@ var DiagnosticsCommands = class {
3438
3430
  process.exitCode = exitCode;
3439
3431
  return;
3440
3432
  }
3441
- console.log(`${this.deps.logo} ${APP_NAME} Doctor`);
3442
- console.log(`Generated: ${report.generatedAt}`);
3443
- console.log("");
3444
- for (const check of checks) {
3445
- const icon = check.status === "pass" ? "\u2713" : check.status === "warn" ? "!" : "\u2717";
3446
- console.log(`${icon} ${check.name}: ${check.detail}`);
3447
- }
3448
- if (report.recommendations.length > 0) {
3449
- console.log("");
3450
- console.log("Recommendations:");
3451
- for (const recommendation of report.recommendations) {
3452
- console.log(`- ${recommendation}`);
3453
- }
3454
- }
3455
- if (opts.verbose && report.logTail.length > 0) {
3456
- console.log("");
3457
- console.log("Recent logs:");
3458
- for (const line of report.logTail) {
3459
- console.log(line);
3460
- }
3461
- }
3433
+ printDoctorReport({
3434
+ logo: this.deps.logo,
3435
+ generatedAt: report.generatedAt,
3436
+ checks,
3437
+ recommendations: report.recommendations,
3438
+ verbose: Boolean(opts.verbose),
3439
+ logTail: report.logTail
3440
+ });
3462
3441
  process.exitCode = exitCode;
3463
3442
  }
3464
3443
  async collectRuntimeStatus(params) {
3465
- const configPath = getConfigPath4();
3466
- const config2 = loadConfig9();
3444
+ const configPath = getConfigPath5();
3445
+ const config2 = loadConfig10();
3467
3446
  const workspacePath = getWorkspacePath4(config2.agents.defaults.workspace);
3468
3447
  const serviceStatePath = resolve8(getDataDir5(), "run", "service.json");
3469
3448
  const fixActions = [];
@@ -3483,72 +3462,36 @@ var DiagnosticsCommands = class {
3483
3462
  const managedApiUrl = serviceState?.apiUrl ?? null;
3484
3463
  const managedHealth = running && managedApiUrl ? await this.probeApiHealth(`${managedApiUrl}/health`) : { state: "unreachable", detail: "service not running" };
3485
3464
  const configuredHealth = await this.probeApiHealth(`${configuredApiUrl}/health`, 900);
3465
+ const remote = resolveNextclawRemoteStatusSnapshot(config2);
3486
3466
  const orphanSuspected = !running && configuredHealth.state === "ok";
3487
- const providers = listBuiltinProviders().map((spec) => {
3488
- const provider = config2.providers[spec.name];
3489
- const apiKeyRefSet = hasSecretRef(config2, `providers.${spec.name}.apiKey`);
3490
- if (!provider) {
3491
- return { name: spec.displayName ?? spec.name, configured: false, detail: "missing config" };
3492
- }
3493
- if (spec.isLocal) {
3494
- return {
3495
- name: spec.displayName ?? spec.name,
3496
- configured: Boolean(provider.apiBase),
3497
- detail: provider.apiBase ? provider.apiBase : "apiBase not set"
3498
- };
3499
- }
3500
- return {
3501
- name: spec.displayName ?? spec.name,
3502
- configured: Boolean(provider.apiKey) || apiKeyRefSet,
3503
- detail: provider.apiKey ? "apiKey set" : apiKeyRefSet ? "apiKey ref set" : "apiKey not set"
3504
- };
3505
- });
3467
+ const providers = this.listProviderStatuses(config2);
3506
3468
  const issues = [];
3507
3469
  const recommendations = [];
3508
- if (!existsSync7(configPath)) {
3509
- issues.push("Config file is missing.");
3510
- recommendations.push(`Run ${APP_NAME} init to create config files.`);
3511
- }
3512
- if (!existsSync7(workspacePath)) {
3513
- issues.push("Workspace directory does not exist.");
3514
- recommendations.push(`Run ${APP_NAME} init to create workspace templates.`);
3515
- }
3516
- if (staleState) {
3517
- issues.push("Service state is stale (state exists but process is not running).");
3518
- recommendations.push(`Run ${APP_NAME} status --fix to clean stale state.`);
3519
- }
3520
- if (running && managedHealth.state !== "ok") {
3521
- issues.push(`Managed service health check failed: ${managedHealth.detail}`);
3522
- recommendations.push(`Check logs at ${serviceState?.logPath ?? resolveServiceLogPath()}.`);
3523
- }
3524
- if (running && serviceState?.startupState === "degraded" && managedHealth.state !== "ok") {
3525
- const startupHint = serviceState.startupLastProbeError ? ` (${serviceState.startupLastProbeError})` : "";
3526
- issues.push(`Service is in degraded startup state${startupHint}.`);
3527
- recommendations.push(`Wait and re-check ${APP_NAME} status; if it does not recover, inspect logs and restart.`);
3528
- }
3529
- if (!running) {
3530
- recommendations.push(`Run ${APP_NAME} start to launch the service.`);
3531
- }
3532
- if (orphanSuspected) {
3533
- issues.push("A service appears healthy on configured API endpoint, but state is missing/stale.");
3534
- recommendations.push("Another process may be occupying the UI port; stop it or use --ui-port with a free port.");
3535
- }
3536
- if (!providers.some((provider) => provider.configured)) {
3537
- recommendations.push("Configure at least one provider API key in UI or config before expecting agent replies.");
3538
- }
3470
+ this.collectRuntimeIssues({
3471
+ configPath,
3472
+ workspacePath,
3473
+ staleState,
3474
+ running,
3475
+ managedHealth,
3476
+ serviceState,
3477
+ orphanSuspected,
3478
+ providers,
3479
+ issues,
3480
+ recommendations
3481
+ });
3539
3482
  const logTail = params.verbose ? this.readLogTail(serviceState?.logPath ?? resolveServiceLogPath(), 25) : [];
3540
3483
  const level = running ? managedHealth.state === "ok" ? issues.length > 0 ? "degraded" : "healthy" : "degraded" : "stopped";
3541
3484
  const exitCode = 0;
3542
3485
  return {
3543
3486
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3544
3487
  configPath,
3545
- configExists: existsSync7(configPath),
3488
+ configExists: existsSync6(configPath),
3546
3489
  workspacePath,
3547
- workspaceExists: existsSync7(workspacePath),
3490
+ workspaceExists: existsSync6(workspacePath),
3548
3491
  model: config2.agents.defaults.model,
3549
3492
  providers,
3550
3493
  serviceStatePath,
3551
- serviceStateExists: existsSync7(serviceStatePath),
3494
+ serviceStateExists: existsSync6(serviceStatePath),
3552
3495
  fixActions,
3553
3496
  process: {
3554
3497
  managedByState,
@@ -3571,6 +3514,7 @@ var DiagnosticsCommands = class {
3571
3514
  issues,
3572
3515
  recommendations,
3573
3516
  logTail,
3517
+ remote,
3574
3518
  level,
3575
3519
  exitCode
3576
3520
  };
@@ -3597,12 +3541,66 @@ var DiagnosticsCommands = class {
3597
3541
  clearTimeout(timer);
3598
3542
  }
3599
3543
  }
3544
+ listProviderStatuses(config2) {
3545
+ return listBuiltinProviders().map((spec) => {
3546
+ const provider = config2.providers[spec.name];
3547
+ const apiKeyRefSet = hasSecretRef(config2, `providers.${spec.name}.apiKey`);
3548
+ if (!provider) {
3549
+ return { name: spec.displayName ?? spec.name, configured: false, detail: "missing config" };
3550
+ }
3551
+ if (spec.isLocal) {
3552
+ return {
3553
+ name: spec.displayName ?? spec.name,
3554
+ configured: Boolean(provider.apiBase),
3555
+ detail: provider.apiBase ? provider.apiBase : "apiBase not set"
3556
+ };
3557
+ }
3558
+ return {
3559
+ name: spec.displayName ?? spec.name,
3560
+ configured: Boolean(provider.apiKey) || apiKeyRefSet,
3561
+ detail: provider.apiKey ? "apiKey set" : apiKeyRefSet ? "apiKey ref set" : "apiKey not set"
3562
+ };
3563
+ });
3564
+ }
3565
+ collectRuntimeIssues(params) {
3566
+ if (!existsSync6(params.configPath)) {
3567
+ params.issues.push("Config file is missing.");
3568
+ params.recommendations.push(`Run ${APP_NAME2} init to create config files.`);
3569
+ }
3570
+ if (!existsSync6(params.workspacePath)) {
3571
+ params.issues.push("Workspace directory does not exist.");
3572
+ params.recommendations.push(`Run ${APP_NAME2} init to create workspace templates.`);
3573
+ }
3574
+ if (params.staleState) {
3575
+ params.issues.push("Service state is stale (state exists but process is not running).");
3576
+ params.recommendations.push(`Run ${APP_NAME2} status --fix to clean stale state.`);
3577
+ }
3578
+ if (params.running && params.managedHealth.state !== "ok") {
3579
+ params.issues.push(`Managed service health check failed: ${params.managedHealth.detail}`);
3580
+ params.recommendations.push(`Check logs at ${params.serviceState?.logPath ?? resolveServiceLogPath()}.`);
3581
+ }
3582
+ if (params.running && params.serviceState?.startupState === "degraded" && params.managedHealth.state !== "ok") {
3583
+ const startupHint = params.serviceState.startupLastProbeError ? ` (${params.serviceState.startupLastProbeError})` : "";
3584
+ params.issues.push(`Service is in degraded startup state${startupHint}.`);
3585
+ params.recommendations.push(`Wait and re-check ${APP_NAME2} status; if it does not recover, inspect logs and restart.`);
3586
+ }
3587
+ if (!params.running) {
3588
+ params.recommendations.push(`Run ${APP_NAME2} start to launch the service.`);
3589
+ }
3590
+ if (params.orphanSuspected) {
3591
+ params.issues.push("A service appears healthy on configured API endpoint, but state is missing/stale.");
3592
+ params.recommendations.push("Another process may be occupying the UI port; stop it or use --ui-port with a free port.");
3593
+ }
3594
+ if (!params.providers.some((provider) => provider.configured)) {
3595
+ params.recommendations.push("Configure at least one provider API key in UI or config before expecting agent replies.");
3596
+ }
3597
+ }
3600
3598
  readLogTail(path2, maxLines = 25) {
3601
- if (!existsSync7(path2)) {
3599
+ if (!existsSync6(path2)) {
3602
3600
  return [];
3603
3601
  }
3604
3602
  try {
3605
- const lines = readFileSync7(path2, "utf-8").split(/\r?\n/).filter(Boolean);
3603
+ const lines = readFileSync6(path2, "utf-8").split(/\r?\n/).filter(Boolean);
3606
3604
  if (lines.length <= maxLines) {
3607
3605
  return lines;
3608
3606
  }
@@ -3642,8 +3640,8 @@ import {
3642
3640
  stopPluginChannelGateways as stopPluginChannelGateways2
3643
3641
  } from "@nextclaw/openclaw-compat";
3644
3642
  import { startUiServer } from "@nextclaw/server";
3645
- import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as existsSync11, mkdirSync as mkdirSync6, openSync } from "fs";
3646
- import { dirname as dirname3, join as join7, resolve as resolve10 } from "path";
3643
+ import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as existsSync10, mkdirSync as mkdirSync5, openSync } from "fs";
3644
+ import { dirname as dirname2, join as join6, resolve as resolve10 } from "path";
3647
3645
  import { spawn as spawn2 } from "child_process";
3648
3646
  import { request as httpRequest } from "http";
3649
3647
  import { request as httpsRequest } from "https";
@@ -3652,7 +3650,7 @@ import chokidar from "chokidar";
3652
3650
 
3653
3651
  // src/cli/gateway/controller.ts
3654
3652
  import { createHash } from "crypto";
3655
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
3653
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
3656
3654
  import {
3657
3655
  buildConfigSchema,
3658
3656
  ConfigSchema,
@@ -3660,12 +3658,12 @@ import {
3660
3658
  redactConfigObject
3661
3659
  } from "@nextclaw/core";
3662
3660
  var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
3663
- var readConfigSnapshot = (getConfigPath7) => {
3664
- const path2 = getConfigPath7();
3661
+ var readConfigSnapshot = (getConfigPath8) => {
3662
+ const path2 = getConfigPath8();
3665
3663
  let raw = "";
3666
3664
  let parsed = {};
3667
- if (existsSync8(path2)) {
3668
- raw = readFileSync8(path2, "utf-8");
3665
+ if (existsSync7(path2)) {
3666
+ raw = readFileSync7(path2, "utf-8");
3669
3667
  try {
3670
3668
  parsed = JSON.parse(raw);
3671
3669
  } catch {
@@ -3772,11 +3770,11 @@ var GatewayControllerImpl = class {
3772
3770
  await this.deps.requestRestart(options);
3773
3771
  return;
3774
3772
  }
3775
- const delay2 = typeof options?.delayMs === "number" && Number.isFinite(options.delayMs) ? Math.max(0, options.delayMs) : 100;
3773
+ const delay = typeof options?.delayMs === "number" && Number.isFinite(options.delayMs) ? Math.max(0, options.delayMs) : 100;
3776
3774
  console.log(`Gateway restart requested via tool${options?.reason ? ` (${options.reason})` : ""}.`);
3777
3775
  setTimeout(() => {
3778
3776
  process.exit(0);
3779
- }, delay2);
3777
+ }, delay);
3780
3778
  }
3781
3779
  status() {
3782
3780
  return {
@@ -4122,9 +4120,9 @@ var MissingProvider = class extends LLMProvider {
4122
4120
  };
4123
4121
 
4124
4122
  // src/cli/commands/service-marketplace-installer.ts
4125
- import { getWorkspacePath as getWorkspacePath5, loadConfig as loadConfig11 } from "@nextclaw/core";
4126
- import { existsSync as existsSync9, rmSync as rmSync4 } from "fs";
4127
- import { join as join5 } from "path";
4123
+ import { getWorkspacePath as getWorkspacePath5, loadConfig as loadConfig12 } from "@nextclaw/core";
4124
+ import { existsSync as existsSync8, rmSync as rmSync4 } from "fs";
4125
+ import { join as join4 } from "path";
4128
4126
 
4129
4127
  // src/cli/commands/service-marketplace-helpers.ts
4130
4128
  var containsAbsoluteFsPath = (line) => {
@@ -4172,7 +4170,7 @@ var buildMarketplaceSkillInstallArgs = (params) => {
4172
4170
  };
4173
4171
 
4174
4172
  // src/cli/commands/service-mcp-marketplace-ops.ts
4175
- import { loadConfig as loadConfig10, saveConfig as saveConfig7 } from "@nextclaw/core";
4173
+ import { loadConfig as loadConfig11, saveConfig as saveConfig8 } from "@nextclaw/core";
4176
4174
  import { McpDoctorFacade as McpDoctorFacade2, McpMutationService as McpMutationService2 } from "@nextclaw/mcp";
4177
4175
  var ServiceMcpMarketplaceOps = class {
4178
4176
  constructor(options) {
@@ -4240,13 +4238,13 @@ var ServiceMcpMarketplaceOps = class {
4240
4238
  }
4241
4239
  createMutationService() {
4242
4240
  return new McpMutationService2({
4243
- getConfig: () => loadConfig10(),
4244
- saveConfig: (config2) => saveConfig7(config2)
4241
+ getConfig: () => loadConfig11(),
4242
+ saveConfig: (config2) => saveConfig8(config2)
4245
4243
  });
4246
4244
  }
4247
4245
  createDoctorFacade() {
4248
4246
  return new McpDoctorFacade2({
4249
- getConfig: () => loadConfig10()
4247
+ getConfig: () => loadConfig11()
4250
4248
  });
4251
4249
  }
4252
4250
  };
@@ -4287,7 +4285,7 @@ var ServiceMarketplaceInstaller = class {
4287
4285
  if (params.kind && params.kind !== "marketplace") {
4288
4286
  throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
4289
4287
  }
4290
- const workspace = getWorkspacePath5(loadConfig11().agents.defaults.workspace);
4288
+ const workspace = getWorkspacePath5(loadConfig12().agents.defaults.workspace);
4291
4289
  const args = buildMarketplaceSkillInstallArgs({
4292
4290
  slug: params.slug,
4293
4291
  workspace,
@@ -4326,9 +4324,9 @@ var ServiceMarketplaceInstaller = class {
4326
4324
  return { message: result.message };
4327
4325
  }
4328
4326
  async uninstallSkill(slug) {
4329
- const workspace = getWorkspacePath5(loadConfig11().agents.defaults.workspace);
4330
- const targetDir = join5(workspace, "skills", slug);
4331
- if (!existsSync9(targetDir)) {
4327
+ const workspace = getWorkspacePath5(loadConfig12().agents.defaults.workspace);
4328
+ const targetDir = join4(workspace, "skills", slug);
4329
+ if (!existsSync8(targetDir)) {
4332
4330
  throw new Error(`Skill not installed in workspace: ${slug}`);
4333
4331
  }
4334
4332
  rmSync4(targetDir, { recursive: true, force: true });
@@ -4453,6 +4451,35 @@ async function reloadServicePlugins(params) {
4453
4451
  };
4454
4452
  }
4455
4453
 
4454
+ // src/cli/commands/service-startup-support.ts
4455
+ var pluginGatewayLogger = {
4456
+ info: (message) => console.log(`[plugins] ${message}`),
4457
+ warn: (message) => console.warn(`[plugins] ${message}`),
4458
+ error: (message) => console.error(`[plugins] ${message}`),
4459
+ debug: (message) => console.debug(`[plugins] ${message}`)
4460
+ };
4461
+ function logPluginGatewayDiagnostics(diagnostics) {
4462
+ for (const diag of diagnostics) {
4463
+ const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
4464
+ const text = `${prefix}${diag.message}`;
4465
+ if (diag.level === "error") {
4466
+ console.error(`[plugins] ${text}`);
4467
+ } else {
4468
+ console.warn(`[plugins] ${text}`);
4469
+ }
4470
+ }
4471
+ }
4472
+ async function startGatewaySupportServices(params) {
4473
+ if (params.cronJobs > 0) {
4474
+ console.log(`\u2713 Cron: ${params.cronJobs} scheduled jobs`);
4475
+ }
4476
+ console.log("\u2713 Heartbeat: every 30m");
4477
+ params.remoteModule?.start();
4478
+ params.watchConfigFile();
4479
+ await params.startCron();
4480
+ await params.startHeartbeat();
4481
+ }
4482
+
4456
4483
  // src/cli/commands/agent-runtime-pool.ts
4457
4484
  import {
4458
4485
  NativeAgentEngine,
@@ -5432,7 +5459,7 @@ function resolveRequestedToolNames(metadata) {
5432
5459
  )
5433
5460
  );
5434
5461
  }
5435
- function normalizeOptionalString3(value) {
5462
+ function normalizeOptionalString4(value) {
5436
5463
  return normalizeString(value) ?? void 0;
5437
5464
  }
5438
5465
  function readMetadataModel(metadata) {
@@ -5552,7 +5579,7 @@ var NextclawNcpContextBuilder = class {
5552
5579
  if (inboundModel) {
5553
5580
  session.metadata.preferred_model = inboundModel;
5554
5581
  }
5555
- const effectiveModel = normalizeOptionalString3(session.metadata.preferred_model) ?? profile.model;
5582
+ const effectiveModel = normalizeOptionalString4(session.metadata.preferred_model) ?? profile.model;
5556
5583
  const clearThinking = requestMetadata.clear_thinking === true || requestMetadata.reset_thinking === true;
5557
5584
  if (clearThinking) {
5558
5585
  delete session.metadata.preferred_thinking;
@@ -5569,8 +5596,8 @@ var NextclawNcpContextBuilder = class {
5569
5596
  model: effectiveModel,
5570
5597
  sessionThinkingLevel: parseThinkingLevel(session.metadata.preferred_thinking) ?? null
5571
5598
  });
5572
- const channel = normalizeOptionalString3(requestMetadata.channel) ?? normalizeOptionalString3(session.metadata.last_channel) ?? "ui";
5573
- const chatId = normalizeOptionalString3(requestMetadata.chatId) ?? normalizeOptionalString3(requestMetadata.chat_id) ?? normalizeOptionalString3(session.metadata.last_to) ?? "web-ui";
5599
+ const channel = normalizeOptionalString4(requestMetadata.channel) ?? normalizeOptionalString4(session.metadata.last_channel) ?? "ui";
5600
+ const chatId = normalizeOptionalString4(requestMetadata.chatId) ?? normalizeOptionalString4(requestMetadata.chat_id) ?? normalizeOptionalString4(session.metadata.last_to) ?? "web-ui";
5574
5601
  session.metadata.last_channel = channel;
5575
5602
  session.metadata.last_to = chatId;
5576
5603
  const requestedSkillNames = resolveRequestedSkillNames(requestMetadata);
@@ -6288,15 +6315,68 @@ async function createUiNcpAgent(params) {
6288
6315
  };
6289
6316
  }
6290
6317
 
6318
+ // src/cli/commands/service-remote-runtime.ts
6319
+ import { RemoteServiceModule } from "@nextclaw/remote";
6320
+ function createManagedRemoteModule(params) {
6321
+ if (!params.config.ui.enabled) {
6322
+ return null;
6323
+ }
6324
+ return new RemoteServiceModule({
6325
+ config: params.config,
6326
+ localOrigin: params.localOrigin,
6327
+ statusStore: createNextclawRemoteStatusStore("service"),
6328
+ createConnector: (logger) => createNextclawRemoteConnector({ logger }),
6329
+ logger: {
6330
+ info: (message) => console.log(`[remote] ${message}`),
6331
+ warn: (message) => console.warn(`[remote] ${message}`),
6332
+ error: (message) => console.error(`[remote] ${message}`)
6333
+ }
6334
+ });
6335
+ }
6336
+ function writeInitialManagedServiceState(params) {
6337
+ writeServiceState({
6338
+ pid: params.snapshot.pid,
6339
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6340
+ uiUrl: params.snapshot.uiUrl,
6341
+ apiUrl: params.snapshot.apiUrl,
6342
+ uiHost: params.snapshot.uiHost,
6343
+ uiPort: params.snapshot.uiPort,
6344
+ logPath: params.snapshot.logPath,
6345
+ startupLastProbeError: null,
6346
+ startupTimeoutMs: params.readinessTimeoutMs,
6347
+ startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
6348
+ ...params.config.remote.enabled ? { remote: buildNextclawConfiguredRemoteState(params.config) } : {}
6349
+ });
6350
+ }
6351
+ function writeReadyManagedServiceState(params) {
6352
+ const currentState = readServiceState();
6353
+ const state = {
6354
+ pid: params.snapshot.pid,
6355
+ startedAt: currentState?.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
6356
+ uiUrl: params.snapshot.uiUrl,
6357
+ apiUrl: params.snapshot.apiUrl,
6358
+ uiHost: params.snapshot.uiHost,
6359
+ uiPort: params.snapshot.uiPort,
6360
+ logPath: params.snapshot.logPath,
6361
+ startupState: params.readiness.ready ? "ready" : "degraded",
6362
+ startupLastProbeError: params.readiness.lastProbeError,
6363
+ startupTimeoutMs: params.readinessTimeoutMs,
6364
+ startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
6365
+ ...currentState?.remote ? { remote: currentState.remote } : {}
6366
+ };
6367
+ writeServiceState(state);
6368
+ return state;
6369
+ }
6370
+
6291
6371
  // src/cli/commands/ui-chat-run-coordinator.ts
6292
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
6293
- import { join as join6 } from "path";
6372
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
6373
+ import { join as join5 } from "path";
6294
6374
  import {
6295
6375
  getDataDir as getDataDir6,
6296
6376
  parseAgentScopedSessionKey as parseAgentScopedSessionKey2,
6297
6377
  safeFilename
6298
6378
  } from "@nextclaw/core";
6299
- var RUNS_DIR = join6(getDataDir6(), "runs");
6379
+ var RUNS_DIR = join5(getDataDir6(), "runs");
6300
6380
  var NON_TERMINAL_STATES = /* @__PURE__ */ new Set(["queued", "running"]);
6301
6381
  var DEFAULT_SESSION_TYPE = "native";
6302
6382
  var SESSION_TYPE_METADATA_KEY = "session_type";
@@ -6355,7 +6435,7 @@ function hasToolSessionEvent(run) {
6355
6435
  var UiChatRunCoordinator = class {
6356
6436
  constructor(options) {
6357
6437
  this.options = options;
6358
- mkdirSync5(RUNS_DIR, { recursive: true });
6438
+ mkdirSync4(RUNS_DIR, { recursive: true });
6359
6439
  this.loadPersistedRuns();
6360
6440
  }
6361
6441
  runs = /* @__PURE__ */ new Map();
@@ -6822,7 +6902,7 @@ var UiChatRunCoordinator = class {
6822
6902
  };
6823
6903
  }
6824
6904
  getRunPath(runId) {
6825
- return join6(RUNS_DIR, `${safeFilename(runId)}.json`);
6905
+ return join5(RUNS_DIR, `${safeFilename(runId)}.json`);
6826
6906
  }
6827
6907
  persistRun(run) {
6828
6908
  const persisted = {
@@ -6840,20 +6920,20 @@ var UiChatRunCoordinator = class {
6840
6920
  ...typeof run.reply === "string" ? { reply: run.reply } : {},
6841
6921
  events: run.events
6842
6922
  };
6843
- writeFileSync5(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
6923
+ writeFileSync4(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
6844
6924
  `);
6845
6925
  }
6846
6926
  loadPersistedRuns() {
6847
- if (!existsSync10(RUNS_DIR)) {
6927
+ if (!existsSync9(RUNS_DIR)) {
6848
6928
  return;
6849
6929
  }
6850
6930
  for (const entry of readdirSync2(RUNS_DIR, { withFileTypes: true })) {
6851
6931
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
6852
6932
  continue;
6853
6933
  }
6854
- const path2 = join6(RUNS_DIR, entry.name);
6934
+ const path2 = join5(RUNS_DIR, entry.name);
6855
6935
  try {
6856
- const parsed = JSON.parse(readFileSync9(path2, "utf-8"));
6936
+ const parsed = JSON.parse(readFileSync8(path2, "utf-8"));
6857
6937
  const runId = readOptionalString(parsed.runId);
6858
6938
  const sessionKey = readOptionalString(parsed.sessionKey);
6859
6939
  if (!runId || !sessionKey) {
@@ -6908,22 +6988,22 @@ var UiChatRunCoordinator = class {
6908
6988
 
6909
6989
  // src/cli/commands/service.ts
6910
6990
  var {
6911
- APP_NAME: APP_NAME2,
6991
+ APP_NAME: APP_NAME3,
6912
6992
  ChannelManager: ChannelManager2,
6913
6993
  CronService: CronService2,
6914
6994
  getApiBase,
6915
- getConfigPath: getConfigPath5,
6995
+ getConfigPath: getConfigPath6,
6916
6996
  getDataDir: getDataDir7,
6917
6997
  getProvider,
6918
6998
  getProviderName,
6919
6999
  getWorkspacePath: getWorkspacePath9,
6920
7000
  HeartbeatService,
6921
7001
  LiteLLMProvider,
6922
- loadConfig: loadConfig12,
7002
+ loadConfig: loadConfig13,
6923
7003
  MessageBus,
6924
7004
  ProviderManager,
6925
7005
  resolveConfigSecrets: resolveConfigSecrets2,
6926
- saveConfig: saveConfig8,
7006
+ saveConfig: saveConfig9,
6927
7007
  SessionManager,
6928
7008
  parseAgentScopedSessionKey: parseAgentScopedSessionKey3
6929
7009
  } = NextclawCore;
@@ -6943,8 +7023,8 @@ var ServiceCommands = class {
6943
7023
  async startGateway(options = {}) {
6944
7024
  this.applyLiveConfigReload = null;
6945
7025
  this.liveUiNcpAgent = null;
6946
- const runtimeConfigPath = getConfigPath5();
6947
- const config2 = resolveConfigSecrets2(loadConfig12(), { configPath: runtimeConfigPath });
7026
+ const runtimeConfigPath = getConfigPath6();
7027
+ const config2 = resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath });
6948
7028
  const workspace = getWorkspacePath9(config2.agents.defaults.workspace);
6949
7029
  let pluginRegistry = loadPluginRegistry(config2, workspace);
6950
7030
  let extensionRegistry = toExtensionRegistry(pluginRegistry);
@@ -6957,27 +7037,11 @@ var ServiceCommands = class {
6957
7037
  });
6958
7038
  const sessionManager = new SessionManager(workspace);
6959
7039
  let pluginGatewayHandles = [];
6960
- const pluginGatewayLogger = {
6961
- info: (message) => console.log(`[plugins] ${message}`),
6962
- warn: (message) => console.warn(`[plugins] ${message}`),
6963
- error: (message) => console.error(`[plugins] ${message}`),
6964
- debug: (message) => console.debug(`[plugins] ${message}`)
6965
- };
6966
- const logPluginGatewayDiagnostics = (diagnostics) => {
6967
- for (const diag of diagnostics) {
6968
- const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
6969
- const text = `${prefix}${diag.message}`;
6970
- if (diag.level === "error") {
6971
- console.error(`[plugins] ${text}`);
6972
- } else {
6973
- console.warn(`[plugins] ${text}`);
6974
- }
6975
- }
6976
- };
6977
- const cronStorePath = join7(getDataDir7(), "cron", "jobs.json");
7040
+ const cronStorePath = join6(getDataDir7(), "cron", "jobs.json");
6978
7041
  const cron2 = new CronService2(cronStorePath);
6979
7042
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
6980
7043
  const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
7044
+ const localOrigin = resolveUiApiBase(uiConfig.host, uiConfig.port);
6981
7045
  if (!provider) {
6982
7046
  console.warn("Warning: No API key configured. The gateway is running, but agent replies are disabled until provider config is set.");
6983
7047
  }
@@ -6989,7 +7053,7 @@ var ServiceCommands = class {
6989
7053
  sessionManager,
6990
7054
  providerManager,
6991
7055
  makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }) ?? this.makeMissingProvider(nextConfig),
6992
- loadConfig: () => resolveConfigSecrets2(loadConfig12(), { configPath: runtimeConfigPath }),
7056
+ loadConfig: () => resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }),
6993
7057
  getExtensionChannels: () => extensionRegistry.channels,
6994
7058
  onRestartRequired: (paths) => {
6995
7059
  void this.deps.requestRestart({
@@ -7000,14 +7064,14 @@ var ServiceCommands = class {
7000
7064
  }
7001
7065
  });
7002
7066
  this.applyLiveConfigReload = async () => {
7003
- await reloader.applyReloadPlan(resolveConfigSecrets2(loadConfig12(), { configPath: runtimeConfigPath }));
7067
+ await reloader.applyReloadPlan(resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }));
7004
7068
  };
7005
7069
  const gatewayController = new GatewayControllerImpl({
7006
7070
  reloader,
7007
7071
  cron: cron2,
7008
7072
  sessionManager,
7009
- getConfigPath: getConfigPath5,
7010
- saveConfig: saveConfig8,
7073
+ getConfigPath: getConfigPath6,
7074
+ saveConfig: saveConfig9,
7011
7075
  requestRestart: async (options2) => {
7012
7076
  await this.deps.requestRestart({
7013
7077
  reason: options2?.reason ?? "gateway tool restart",
@@ -7033,7 +7097,7 @@ var ServiceCommands = class {
7033
7097
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
7034
7098
  registry: pluginRegistry,
7035
7099
  channel,
7036
- cfg: resolveConfigSecrets2(loadConfig12(), { configPath: runtimeConfigPath }),
7100
+ cfg: resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }),
7037
7101
  accountId
7038
7102
  })
7039
7103
  });
@@ -7066,14 +7130,14 @@ var ServiceCommands = class {
7066
7130
  });
7067
7131
  let pluginChannelBindings = getPluginChannelBindings3(pluginRegistry);
7068
7132
  setPluginRuntimeBridge({
7069
- loadConfig: () => toPluginConfigView(resolveConfigSecrets2(loadConfig12(), { configPath: runtimeConfigPath }), pluginChannelBindings),
7133
+ loadConfig: () => toPluginConfigView(resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }), pluginChannelBindings),
7070
7134
  writeConfigFile: async (nextConfigView) => {
7071
7135
  if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
7072
7136
  throw new Error("plugin runtime writeConfigFile expects an object config");
7073
7137
  }
7074
- const current = loadConfig12();
7138
+ const current = loadConfig13();
7075
7139
  const next = mergePluginConfigView(current, nextConfigView, pluginChannelBindings);
7076
- saveConfig8(next);
7140
+ saveConfig9(next);
7077
7141
  },
7078
7142
  dispatchReplyWithBufferedBlockDispatcher: async ({ ctx, dispatcherOptions }) => {
7079
7143
  const bodyForAgent = typeof ctx.BodyForAgent === "string" ? ctx.BodyForAgent : "";
@@ -7147,23 +7211,23 @@ var ServiceCommands = class {
7147
7211
  providerManager,
7148
7212
  bus,
7149
7213
  gatewayController,
7150
- () => resolveConfigSecrets2(loadConfig12(), { configPath: runtimeConfigPath }),
7214
+ () => resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }),
7151
7215
  () => extensionRegistry,
7152
7216
  ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
7153
7217
  registry: pluginRegistry,
7154
7218
  channel,
7155
- cfg: resolveConfigSecrets2(loadConfig12(), { configPath: runtimeConfigPath }),
7219
+ cfg: resolveConfigSecrets2(loadConfig13(), { configPath: runtimeConfigPath }),
7156
7220
  accountId
7157
7221
  })
7158
7222
  );
7159
- const cronStatus = cron2.status();
7160
- if (cronStatus.jobs > 0) {
7161
- console.log(`\u2713 Cron: ${cronStatus.jobs} scheduled jobs`);
7162
- }
7163
- console.log("\u2713 Heartbeat: every 30m");
7164
- this.watchConfigFile(reloader);
7165
- await cron2.start();
7166
- await heartbeat.start();
7223
+ const remoteModule = createManagedRemoteModule({ config: config2, localOrigin });
7224
+ await startGatewaySupportServices({
7225
+ cronJobs: cron2.status().jobs,
7226
+ remoteModule,
7227
+ watchConfigFile: () => this.watchConfigFile(reloader),
7228
+ startCron: () => cron2.start(),
7229
+ startHeartbeat: () => heartbeat.start()
7230
+ });
7167
7231
  try {
7168
7232
  const startedPluginGateways = await startPluginChannelGateways2({
7169
7233
  registry: pluginRegistry,
@@ -7177,6 +7241,7 @@ var ServiceCommands = class {
7177
7241
  } finally {
7178
7242
  this.applyLiveConfigReload = null;
7179
7243
  this.liveUiNcpAgent = null;
7244
+ await remoteModule?.stop();
7180
7245
  await stopPluginChannelGateways2(pluginGatewayHandles);
7181
7246
  setPluginRuntimeBridge(null);
7182
7247
  }
@@ -7189,7 +7254,7 @@ var ServiceCommands = class {
7189
7254
  return trimmed || void 0;
7190
7255
  }
7191
7256
  watchConfigFile(reloader) {
7192
- const configPath = resolve10(getConfigPath5());
7257
+ const configPath = resolve10(getConfigPath6());
7193
7258
  const watcher = chokidar.watch(configPath, {
7194
7259
  ignoreInitial: true,
7195
7260
  awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
@@ -7310,7 +7375,7 @@ var ServiceCommands = class {
7310
7375
  });
7311
7376
  }
7312
7377
  async runForeground(options) {
7313
- const config2 = loadConfig12();
7378
+ const config2 = loadConfig13();
7314
7379
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
7315
7380
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
7316
7381
  if (options.open) {
@@ -7323,14 +7388,14 @@ var ServiceCommands = class {
7323
7388
  });
7324
7389
  }
7325
7390
  async startService(options) {
7326
- const config2 = loadConfig12();
7391
+ const config2 = loadConfig13();
7327
7392
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
7328
7393
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
7329
7394
  const apiUrl = `${uiUrl}/api`;
7330
7395
  const staticDir = resolveUiStaticDir();
7331
7396
  const existing = readServiceState();
7332
7397
  if (existing && isProcessRunning(existing.pid)) {
7333
- console.log(`\u2713 ${APP_NAME2} is already running (PID ${existing.pid})`);
7398
+ console.log(`\u2713 ${APP_NAME3} is already running (PID ${existing.pid})`);
7334
7399
  console.log(`UI: ${existing.uiUrl}`);
7335
7400
  console.log(`API: ${existing.apiUrl}`);
7336
7401
  const parsedUi = (() => {
@@ -7378,13 +7443,13 @@ var ServiceCommands = class {
7378
7443
  healthUrl
7379
7444
  });
7380
7445
  if (!portPreflight.ok) {
7381
- console.error(`Error: Cannot start ${APP_NAME2} because UI port ${uiConfig.port} is already occupied.`);
7446
+ console.error(`Error: Cannot start ${APP_NAME3} because UI port ${uiConfig.port} is already occupied.`);
7382
7447
  console.error(portPreflight.message);
7383
7448
  return;
7384
7449
  }
7385
7450
  const logPath = resolveServiceLogPath();
7386
7451
  const logDir = resolve10(logPath, "..");
7387
- mkdirSync6(logDir, { recursive: true });
7452
+ mkdirSync5(logDir, { recursive: true });
7388
7453
  const logFd = openSync(logPath, "a");
7389
7454
  const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
7390
7455
  const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
@@ -7393,10 +7458,8 @@ var ServiceCommands = class {
7393
7458
  logPath,
7394
7459
  `start requested: ui=${uiConfig.host}:${uiConfig.port}, readinessTimeoutMs=${readinessTimeoutMs}`
7395
7460
  );
7396
- console.log(`Starting ${APP_NAME2} background service (readiness timeout ${Math.ceil(readinessTimeoutMs / 1e3)}s)...`);
7397
- const serveArgs = buildServeArgs({
7398
- uiPort: uiConfig.port
7399
- });
7461
+ console.log(`Starting ${APP_NAME3} background service (readiness timeout ${Math.ceil(readinessTimeoutMs / 1e3)}s)...`);
7462
+ const serveArgs = buildServeArgs({ uiPort: uiConfig.port });
7400
7463
  this.appendStartupStage(logPath, `spawning background process: ${process.execPath} ${[...process.execArgv, ...serveArgs].join(" ")}`);
7401
7464
  const child = spawn2(process.execPath, [...process.execArgv, ...serveArgs], {
7402
7465
  env: process.env,
@@ -7417,6 +7480,11 @@ var ServiceCommands = class {
7417
7480
  });
7418
7481
  return;
7419
7482
  }
7483
+ writeInitialManagedServiceState({
7484
+ config: config2,
7485
+ readinessTimeoutMs,
7486
+ snapshot: { pid: child.pid, uiUrl, apiUrl, uiHost: uiConfig.host, uiPort: uiConfig.port, logPath }
7487
+ });
7420
7488
  this.appendStartupStage(logPath, `health probe started: ${healthUrl} (phase=quick, timeoutMs=${quickPhaseTimeoutMs})`);
7421
7489
  let readiness = await this.waitForBackgroundServiceReady({
7422
7490
  pid: child.pid,
@@ -7458,28 +7526,19 @@ var ServiceCommands = class {
7458
7526
  );
7459
7527
  }
7460
7528
  child.unref();
7461
- const state = {
7462
- pid: child.pid,
7463
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7464
- uiUrl,
7465
- apiUrl,
7466
- uiHost: uiConfig.host,
7467
- uiPort: uiConfig.port,
7468
- logPath,
7469
- startupState: readiness.ready ? "ready" : "degraded",
7470
- startupLastProbeError: readiness.lastProbeError,
7471
- startupTimeoutMs: readinessTimeoutMs,
7472
- startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
7473
- };
7474
- writeServiceState(state);
7529
+ const state = writeReadyManagedServiceState({
7530
+ readinessTimeoutMs,
7531
+ readiness,
7532
+ snapshot: { pid: child.pid, uiUrl, apiUrl, uiHost: uiConfig.host, uiPort: uiConfig.port, logPath }
7533
+ });
7475
7534
  if (!readiness.ready) {
7476
7535
  const hint = readiness.lastProbeError ? ` Last probe error: ${readiness.lastProbeError}` : "";
7477
7536
  console.warn(
7478
- `Warning: ${APP_NAME2} is running (PID ${state.pid}) but not healthy yet after ${Math.ceil(readinessTimeoutMs / 1e3)}s. Marked as degraded.${hint}`
7537
+ `Warning: ${APP_NAME3} is running (PID ${state.pid}) but not healthy yet after ${Math.ceil(readinessTimeoutMs / 1e3)}s. Marked as degraded.${hint}`
7479
7538
  );
7480
- console.warn(`Tip: Run "${APP_NAME2} status --json" and check logs: ${logPath}`);
7539
+ console.warn(`Tip: Run "${APP_NAME3} status --json" and check logs: ${logPath}`);
7481
7540
  } else {
7482
- console.log(`\u2713 ${APP_NAME2} started in background (PID ${state.pid})`);
7541
+ console.log(`\u2713 ${APP_NAME3} started in background (PID ${state.pid})`);
7483
7542
  }
7484
7543
  console.log(`UI: ${uiUrl}`);
7485
7544
  console.log(`API: ${apiUrl}`);
@@ -7501,7 +7560,7 @@ var ServiceCommands = class {
7501
7560
  clearServiceState();
7502
7561
  return;
7503
7562
  }
7504
- console.log(`Stopping ${APP_NAME2} (PID ${state.pid})...`);
7563
+ console.log(`Stopping ${APP_NAME3} (PID ${state.pid})...`);
7505
7564
  try {
7506
7565
  process.kill(state.pid, "SIGTERM");
7507
7566
  } catch (error) {
@@ -7519,7 +7578,7 @@ var ServiceCommands = class {
7519
7578
  await waitForExit(state.pid, 2e3);
7520
7579
  }
7521
7580
  clearServiceState();
7522
- console.log(`\u2713 ${APP_NAME2} stopped`);
7581
+ console.log(`\u2713 ${APP_NAME3} stopped`);
7523
7582
  }
7524
7583
  async waitForBackgroundServiceReady(params) {
7525
7584
  const startedAt = Date.now();
@@ -7737,7 +7796,7 @@ var ServiceCommands = class {
7737
7796
  return null;
7738
7797
  }
7739
7798
  console.error("Error: No API key configured.");
7740
- console.error(`Set one in ${getConfigPath5()} under providers section`);
7799
+ console.error(`Set one in ${getConfigPath6()} under providers section`);
7741
7800
  process.exit(1);
7742
7801
  }
7743
7802
  return new LiteLLMProvider({
@@ -7774,8 +7833,8 @@ var ServiceCommands = class {
7774
7833
  }
7775
7834
  printServiceControlHints() {
7776
7835
  console.log("Service controls:");
7777
- console.log(` - Check status: ${APP_NAME2} status`);
7778
- console.log(` - If you need to stop the service, run: ${APP_NAME2} stop`);
7836
+ console.log(` - Check status: ${APP_NAME3} status`);
7837
+ console.log(` - If you need to stop the service, run: ${APP_NAME3} stop`);
7779
7838
  }
7780
7839
  async startUiIfEnabled(uiConfig, uiStaticDir, cronService, runtimePool, sessionManager, providerManager, bus, gatewayController, getConfig, getExtensionRegistry, resolveMessageToolHints) {
7781
7840
  if (!uiConfig.enabled) {
@@ -7864,7 +7923,7 @@ var ServiceCommands = class {
7864
7923
  const uiServer = startUiServer({
7865
7924
  host: uiConfig.host,
7866
7925
  port: uiConfig.port,
7867
- configPath: getConfigPath5(),
7926
+ configPath: getConfigPath6(),
7868
7927
  productVersion: getPackageVersion(),
7869
7928
  staticDir: uiStaticDir ?? void 0,
7870
7929
  cronService,
@@ -7952,10 +8011,10 @@ var ServiceCommands = class {
7952
8011
  }
7953
8012
  }
7954
8013
  installBuiltinMarketplaceSkill(slug, force) {
7955
- const workspace = getWorkspacePath9(loadConfig12().agents.defaults.workspace);
7956
- const destination = join7(workspace, "skills", slug);
7957
- const destinationSkillFile = join7(destination, "SKILL.md");
7958
- if (existsSync11(destinationSkillFile) && !force) {
8014
+ const workspace = getWorkspacePath9(loadConfig13().agents.defaults.workspace);
8015
+ const destination = join6(workspace, "skills", slug);
8016
+ const destinationSkillFile = join6(destination, "SKILL.md");
8017
+ if (existsSync10(destinationSkillFile) && !force) {
7959
8018
  return {
7960
8019
  message: `${slug} is already installed`
7961
8020
  };
@@ -7963,15 +8022,15 @@ var ServiceCommands = class {
7963
8022
  const loader = createSkillsLoader(workspace);
7964
8023
  const builtin = (loader?.listSkills(false) ?? []).find((skill) => skill.name === slug && skill.source === "builtin");
7965
8024
  if (!builtin) {
7966
- if (existsSync11(destinationSkillFile)) {
8025
+ if (existsSync10(destinationSkillFile)) {
7967
8026
  return {
7968
8027
  message: `${slug} is already installed`
7969
8028
  };
7970
8029
  }
7971
8030
  return null;
7972
8031
  }
7973
- mkdirSync6(join7(workspace, "skills"), { recursive: true });
7974
- cpSync2(dirname3(builtin.path), destination, { recursive: true, force: true });
8032
+ mkdirSync5(join6(workspace, "skills"), { recursive: true });
8033
+ cpSync2(dirname2(builtin.path), destination, { recursive: true, force: true });
7975
8034
  return {
7976
8035
  message: `Installed skill: ${slug}`
7977
8036
  };
@@ -8030,11 +8089,11 @@ ${stderr}`.trim();
8030
8089
  };
8031
8090
 
8032
8091
  // src/cli/workspace.ts
8033
- import { cpSync as cpSync3, existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
8092
+ import { cpSync as cpSync3, existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
8034
8093
  import { createRequire as createRequire2 } from "module";
8035
- import { dirname as dirname4, join as join8, resolve as resolve11 } from "path";
8094
+ import { dirname as dirname3, join as join7, resolve as resolve11 } from "path";
8036
8095
  import { fileURLToPath as fileURLToPath4 } from "url";
8037
- import { APP_NAME as APP_NAME3, getDataDir as getDataDir8 } from "@nextclaw/core";
8096
+ import { APP_NAME as APP_NAME4, getDataDir as getDataDir8 } from "@nextclaw/core";
8038
8097
  import { spawnSync as spawnSync3 } from "child_process";
8039
8098
  var WorkspaceManager = class {
8040
8099
  constructor(logo) {
@@ -8062,30 +8121,30 @@ var WorkspaceManager = class {
8062
8121
  { source: "memory/MEMORY.md", target: "memory/MEMORY.md" }
8063
8122
  ];
8064
8123
  for (const entry of templateFiles) {
8065
- const filePath = join8(workspace, entry.target);
8066
- if (!force && existsSync12(filePath)) {
8124
+ const filePath = join7(workspace, entry.target);
8125
+ if (!force && existsSync11(filePath)) {
8067
8126
  continue;
8068
8127
  }
8069
- const templatePath = join8(templateDir, entry.source);
8070
- if (!existsSync12(templatePath)) {
8128
+ const templatePath = join7(templateDir, entry.source);
8129
+ if (!existsSync11(templatePath)) {
8071
8130
  console.warn(`Warning: Template file missing: ${templatePath}`);
8072
8131
  continue;
8073
8132
  }
8074
- const raw = readFileSync10(templatePath, "utf-8");
8075
- const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
8076
- mkdirSync7(dirname4(filePath), { recursive: true });
8077
- writeFileSync6(filePath, content);
8133
+ const raw = readFileSync9(templatePath, "utf-8");
8134
+ const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME4);
8135
+ mkdirSync6(dirname3(filePath), { recursive: true });
8136
+ writeFileSync5(filePath, content);
8078
8137
  created.push(entry.target);
8079
8138
  }
8080
- const memoryDir = join8(workspace, "memory");
8081
- if (!existsSync12(memoryDir)) {
8082
- mkdirSync7(memoryDir, { recursive: true });
8083
- created.push(join8("memory", ""));
8139
+ const memoryDir = join7(workspace, "memory");
8140
+ if (!existsSync11(memoryDir)) {
8141
+ mkdirSync6(memoryDir, { recursive: true });
8142
+ created.push(join7("memory", ""));
8084
8143
  }
8085
- const skillsDir = join8(workspace, "skills");
8086
- if (!existsSync12(skillsDir)) {
8087
- mkdirSync7(skillsDir, { recursive: true });
8088
- created.push(join8("skills", ""));
8144
+ const skillsDir = join7(workspace, "skills");
8145
+ if (!existsSync11(skillsDir)) {
8146
+ mkdirSync6(skillsDir, { recursive: true });
8147
+ created.push(join7("skills", ""));
8089
8148
  }
8090
8149
  const seeded = this.seedBuiltinSkills(skillsDir, { force });
8091
8150
  if (seeded > 0) {
@@ -8104,12 +8163,12 @@ var WorkspaceManager = class {
8104
8163
  if (!entry.isDirectory()) {
8105
8164
  continue;
8106
8165
  }
8107
- const src = join8(sourceDir, entry.name);
8108
- if (!existsSync12(join8(src, "SKILL.md"))) {
8166
+ const src = join7(sourceDir, entry.name);
8167
+ if (!existsSync11(join7(src, "SKILL.md"))) {
8109
8168
  continue;
8110
8169
  }
8111
- const dest = join8(targetDir, entry.name);
8112
- if (!force && existsSync12(dest)) {
8170
+ const dest = join7(targetDir, entry.name);
8171
+ if (!force && existsSync11(dest)) {
8113
8172
  continue;
8114
8173
  }
8115
8174
  try {
@@ -8126,13 +8185,13 @@ var WorkspaceManager = class {
8126
8185
  try {
8127
8186
  const require3 = createRequire2(import.meta.url);
8128
8187
  const entry = require3.resolve("@nextclaw/core");
8129
- const pkgRoot = resolve11(dirname4(entry), "..");
8130
- const distSkills = join8(pkgRoot, "dist", "skills");
8131
- if (existsSync12(distSkills)) {
8188
+ const pkgRoot = resolve11(dirname3(entry), "..");
8189
+ const distSkills = join7(pkgRoot, "dist", "skills");
8190
+ if (existsSync11(distSkills)) {
8132
8191
  return distSkills;
8133
8192
  }
8134
- const srcSkills = join8(pkgRoot, "src", "agent", "skills");
8135
- if (existsSync12(srcSkills)) {
8193
+ const srcSkills = join7(pkgRoot, "src", "agent", "skills");
8194
+ if (existsSync11(srcSkills)) {
8136
8195
  return srcSkills;
8137
8196
  }
8138
8197
  return null;
@@ -8147,17 +8206,17 @@ var WorkspaceManager = class {
8147
8206
  }
8148
8207
  const cliDir = resolve11(fileURLToPath4(new URL(".", import.meta.url)));
8149
8208
  const pkgRoot = resolve11(cliDir, "..", "..");
8150
- const candidates = [join8(pkgRoot, "templates")];
8209
+ const candidates = [join7(pkgRoot, "templates")];
8151
8210
  for (const candidate of candidates) {
8152
- if (existsSync12(candidate)) {
8211
+ if (existsSync11(candidate)) {
8153
8212
  return candidate;
8154
8213
  }
8155
8214
  }
8156
8215
  return null;
8157
8216
  }
8158
8217
  getBridgeDir() {
8159
- const userBridge = join8(getDataDir8(), "bridge");
8160
- if (existsSync12(join8(userBridge, "dist", "index.js"))) {
8218
+ const userBridge = join7(getDataDir8(), "bridge");
8219
+ if (existsSync11(join7(userBridge, "dist", "index.js"))) {
8161
8220
  return userBridge;
8162
8221
  }
8163
8222
  if (!which("npm")) {
@@ -8166,21 +8225,21 @@ var WorkspaceManager = class {
8166
8225
  }
8167
8226
  const cliDir = resolve11(fileURLToPath4(new URL(".", import.meta.url)));
8168
8227
  const pkgRoot = resolve11(cliDir, "..", "..");
8169
- const pkgBridge = join8(pkgRoot, "bridge");
8170
- const srcBridge = join8(pkgRoot, "..", "..", "bridge");
8228
+ const pkgBridge = join7(pkgRoot, "bridge");
8229
+ const srcBridge = join7(pkgRoot, "..", "..", "bridge");
8171
8230
  let source = null;
8172
- if (existsSync12(join8(pkgBridge, "package.json"))) {
8231
+ if (existsSync11(join7(pkgBridge, "package.json"))) {
8173
8232
  source = pkgBridge;
8174
- } else if (existsSync12(join8(srcBridge, "package.json"))) {
8233
+ } else if (existsSync11(join7(srcBridge, "package.json"))) {
8175
8234
  source = srcBridge;
8176
8235
  }
8177
8236
  if (!source) {
8178
- console.error(`Bridge source not found. Try reinstalling ${APP_NAME3}.`);
8237
+ console.error(`Bridge source not found. Try reinstalling ${APP_NAME4}.`);
8179
8238
  process.exit(1);
8180
8239
  }
8181
8240
  console.log(`${this.logo} Setting up bridge...`);
8182
- mkdirSync7(resolve11(userBridge, ".."), { recursive: true });
8183
- if (existsSync12(userBridge)) {
8241
+ mkdirSync6(resolve11(userBridge, ".."), { recursive: true });
8242
+ if (existsSync11(userBridge)) {
8184
8243
  rmSync5(userBridge, { recursive: true, force: true });
8185
8244
  }
8186
8245
  cpSync3(source, userBridge, {
@@ -8233,6 +8292,7 @@ var CliRuntime = class {
8233
8292
  cronCommands;
8234
8293
  platformAuthCommands;
8235
8294
  remoteCommands;
8295
+ remote;
8236
8296
  diagnosticsCommands;
8237
8297
  constructor(options = {}) {
8238
8298
  this.logo = options.logo ?? LOGO;
@@ -8256,6 +8316,13 @@ var CliRuntime = class {
8256
8316
  this.cronCommands = new CronCommands();
8257
8317
  this.platformAuthCommands = new PlatformAuthCommands();
8258
8318
  this.remoteCommands = new RemoteCommands();
8319
+ this.remote = new RemoteRuntimeActions({
8320
+ appName: APP_NAME5,
8321
+ initAuto: (source) => this.init({ source, auto: true }),
8322
+ remoteCommands: this.remoteCommands,
8323
+ restartBackgroundService: (reason) => this.restartBackgroundService(reason),
8324
+ hasRunningManagedService: hasRunningNextclawManagedService
8325
+ });
8259
8326
  this.diagnosticsCommands = new DiagnosticsCommands({ logo: this.logo });
8260
8327
  this.restartCoordinator = new RestartCoordinator({
8261
8328
  readServiceState,
@@ -8286,7 +8353,7 @@ var CliRuntime = class {
8286
8353
  const uiHost = FORCED_PUBLIC_UI_HOST;
8287
8354
  const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 18791;
8288
8355
  console.log(
8289
- `Applying changes (${reason}): restarting ${APP_NAME4} background service...`
8356
+ `Applying changes (${reason}): restarting ${APP_NAME5} background service...`
8290
8357
  );
8291
8358
  await this.serviceCommands.stopService();
8292
8359
  await this.serviceCommands.startService({
@@ -8441,7 +8508,7 @@ var CliRuntime = class {
8441
8508
  }
8442
8509
  async onboard() {
8443
8510
  console.warn(
8444
- `Warning: ${APP_NAME4} onboard is deprecated. Use "${APP_NAME4} init" instead.`
8511
+ `Warning: ${APP_NAME5} onboard is deprecated. Use "${APP_NAME5} init" instead.`
8445
8512
  );
8446
8513
  await this.init({ source: "onboard" });
8447
8514
  }
@@ -8449,18 +8516,18 @@ var CliRuntime = class {
8449
8516
  const source = options.source ?? "init";
8450
8517
  const prefix = options.auto ? "Auto init" : "Init";
8451
8518
  const force = Boolean(options.force);
8452
- const configPath = getConfigPath6();
8519
+ const configPath = getConfigPath7();
8453
8520
  let createdConfig = false;
8454
- if (!existsSync13(configPath)) {
8521
+ if (!existsSync12(configPath)) {
8455
8522
  const config3 = ConfigSchema2.parse({});
8456
- saveConfig9(config3);
8523
+ saveConfig10(config3);
8457
8524
  createdConfig = true;
8458
8525
  }
8459
- const config2 = loadConfig13();
8526
+ const config2 = loadConfig14();
8460
8527
  const workspaceSetting = config2.agents.defaults.workspace;
8461
- const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join9(getDataDir9(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
8462
- const workspaceExisted = existsSync13(workspacePath);
8463
- mkdirSync8(workspacePath, { recursive: true });
8528
+ const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join8(getDataDir9(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
8529
+ const workspaceExisted = existsSync12(workspacePath);
8530
+ mkdirSync7(workspacePath, { recursive: true });
8464
8531
  const templateResult = this.workspaceManager.createWorkspaceTemplates(
8465
8532
  workspacePath,
8466
8533
  { force }
@@ -8479,13 +8546,13 @@ var CliRuntime = class {
8479
8546
  }
8480
8547
  if (!options.auto) {
8481
8548
  console.log(`
8482
- ${this.logo} ${APP_NAME4} is ready! (${source})`);
8549
+ ${this.logo} ${APP_NAME5} is ready! (${source})`);
8483
8550
  console.log("\nNext steps:");
8484
8551
  console.log(` 1. Add your API key to ${configPath}`);
8485
- console.log(` 2. Chat: ${APP_NAME4} agent -m "Hello!"`);
8552
+ console.log(` 2. Chat: ${APP_NAME5} agent -m "Hello!"`);
8486
8553
  } else {
8487
8554
  console.log(
8488
- `Tip: Run "${APP_NAME4} init${force ? " --force" : ""}" to re-run initialization if needed.`
8555
+ `Tip: Run "${APP_NAME5} init${force ? " --force" : ""}" to re-run initialization if needed.`
8489
8556
  );
8490
8557
  }
8491
8558
  }
@@ -8493,9 +8560,6 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8493
8560
  await this.init({ source: "login", auto: true });
8494
8561
  await this.platformAuthCommands.login(opts);
8495
8562
  }
8496
- async remoteConnect(opts = {}) {
8497
- await this.remoteCommands.connect(opts);
8498
- }
8499
8563
  async gateway(opts) {
8500
8564
  const uiOverrides = {
8501
8565
  host: FORCED_PUBLIC_UI_HOST
@@ -8546,7 +8610,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8546
8610
  await this.writeRestartSentinelFromExecContext("cli.restart");
8547
8611
  const state = readServiceState();
8548
8612
  if (state && isProcessRunning(state.pid)) {
8549
- console.log(`Restarting ${APP_NAME4}...`);
8613
+ console.log(`Restarting ${APP_NAME5}...`);
8550
8614
  await this.serviceCommands.stopService();
8551
8615
  } else if (state) {
8552
8616
  clearServiceState();
@@ -8585,8 +8649,8 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8585
8649
  await this.serviceCommands.stopService();
8586
8650
  }
8587
8651
  async agent(opts) {
8588
- const configPath = getConfigPath6();
8589
- const config2 = resolveConfigSecrets3(loadConfig13(), { configPath });
8652
+ const configPath = getConfigPath7();
8653
+ const config2 = resolveConfigSecrets3(loadConfig14(), { configPath });
8590
8654
  const workspace = getWorkspacePath10(config2.agents.defaults.workspace);
8591
8655
  const pluginRegistry = loadPluginRegistry(config2, workspace);
8592
8656
  const extensionRegistry = toExtensionRegistry(pluginRegistry);
@@ -8594,7 +8658,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8594
8658
  const pluginChannelBindings = getPluginChannelBindings4(pluginRegistry);
8595
8659
  setPluginRuntimeBridge2({
8596
8660
  loadConfig: () => toPluginConfigView(
8597
- resolveConfigSecrets3(loadConfig13(), { configPath }),
8661
+ resolveConfigSecrets3(loadConfig14(), { configPath }),
8598
8662
  pluginChannelBindings
8599
8663
  ),
8600
8664
  writeConfigFile: async (nextConfigView) => {
@@ -8603,13 +8667,13 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8603
8667
  "plugin runtime writeConfigFile expects an object config"
8604
8668
  );
8605
8669
  }
8606
- const current = loadConfig13();
8670
+ const current = loadConfig14();
8607
8671
  const next = mergePluginConfigView(
8608
8672
  current,
8609
8673
  nextConfigView,
8610
8674
  pluginChannelBindings
8611
8675
  );
8612
- saveConfig9(next);
8676
+ saveConfig10(next);
8613
8677
  }
8614
8678
  });
8615
8679
  try {
@@ -8635,7 +8699,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8635
8699
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints2({
8636
8700
  registry: pluginRegistry,
8637
8701
  channel,
8638
- cfg: resolveConfigSecrets3(loadConfig13(), { configPath }),
8702
+ cfg: resolveConfigSecrets3(loadConfig14(), { configPath }),
8639
8703
  accountId
8640
8704
  })
8641
8705
  });
@@ -8654,10 +8718,10 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8654
8718
  `${this.logo} Interactive mode (type exit or Ctrl+C to quit)
8655
8719
  `
8656
8720
  );
8657
- const historyFile = join9(getDataDir9(), "history", "cli_history");
8721
+ const historyFile = join8(getDataDir9(), "history", "cli_history");
8658
8722
  const historyDir = resolve12(historyFile, "..");
8659
- mkdirSync8(historyDir, { recursive: true });
8660
- const history = existsSync13(historyFile) ? readFileSync11(historyFile, "utf-8").split("\n").filter(Boolean) : [];
8723
+ mkdirSync7(historyDir, { recursive: true });
8724
+ const history = existsSync12(historyFile) ? readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean) : [];
8661
8725
  const rl = createInterface3({
8662
8726
  input: process.stdin,
8663
8727
  output: process.stdout
@@ -8666,7 +8730,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8666
8730
  const merged = history.concat(
8667
8731
  rl.history ?? []
8668
8732
  );
8669
- writeFileSync7(historyFile, merged.join("\n"));
8733
+ writeFileSync6(historyFile, merged.join("\n"));
8670
8734
  process.exit(0);
8671
8735
  });
8672
8736
  let running = true;
@@ -8736,7 +8800,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8736
8800
  }
8737
8801
  const state = readServiceState();
8738
8802
  if (state && isProcessRunning(state.pid)) {
8739
- console.log(`Tip: restart ${APP_NAME4} to apply the update.`);
8803
+ console.log(`Tip: restart ${APP_NAME5} to apply the update.`);
8740
8804
  }
8741
8805
  }
8742
8806
  pluginsList(opts = {}) {
@@ -8830,7 +8894,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8830
8894
  await this.diagnosticsCommands.doctor(opts);
8831
8895
  }
8832
8896
  async skillsInstall(options) {
8833
- const config2 = loadConfig13();
8897
+ const config2 = loadConfig14();
8834
8898
  const workdir = resolveSkillsInstallWorkdir({
8835
8899
  explicitWorkdir: options.workdir,
8836
8900
  configuredWorkspace: config2.agents.defaults.workspace
@@ -8894,20 +8958,19 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8894
8958
  // src/cli/index.ts
8895
8959
  var program = new Command();
8896
8960
  var runtime = new CliRuntime({ logo: LOGO });
8897
- program.name(APP_NAME5).description(`${LOGO} ${APP_NAME5} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
8898
- program.command("onboard").description(`Initialize ${APP_NAME5} configuration and workspace`).action(async () => runtime.onboard());
8899
- program.command("init").description(`Initialize ${APP_NAME5} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
8961
+ program.name(APP_NAME6).description(`${LOGO} ${APP_NAME6} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
8962
+ program.command("onboard").description(`Initialize ${APP_NAME6} configuration and workspace`).action(async () => runtime.onboard());
8963
+ program.command("init").description(`Initialize ${APP_NAME6} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
8900
8964
  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));
8901
- var remote = program.command("remote").description("Manage remote access");
8902
- remote.command("connect").description("Register this machine as a remote device 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) => runtime.remoteConnect(opts));
8903
- program.command("gateway").description(`Start the ${APP_NAME5} 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));
8904
- program.command("ui").description(`Start the ${APP_NAME5} UI with gateway`).option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
8905
- program.command("start").description(`Start the ${APP_NAME5} 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));
8906
- program.command("restart").description(`Restart the ${APP_NAME5} 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));
8907
- program.command("serve").description(`Run the ${APP_NAME5} gateway + UI in the foreground`).option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).action(async (opts) => runtime.serve(opts));
8908
- program.command("stop").description(`Stop the ${APP_NAME5} background service`).action(async () => runtime.stop());
8965
+ registerRemoteCommands(program, runtime.remote);
8966
+ program.command("gateway").description(`Start the ${APP_NAME6} 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));
8967
+ program.command("ui").description(`Start the ${APP_NAME6} UI with gateway`).option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
8968
+ program.command("start").description(`Start the ${APP_NAME6} 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));
8969
+ program.command("restart").description(`Restart the ${APP_NAME6} 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));
8970
+ program.command("serve").description(`Run the ${APP_NAME6} gateway + UI in the foreground`).option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).action(async (opts) => runtime.serve(opts));
8971
+ program.command("stop").description(`Stop the ${APP_NAME6} background service`).action(async () => runtime.stop());
8909
8972
  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));
8910
- program.command("update").description(`Update ${APP_NAME5}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
8973
+ program.command("update").description(`Update ${APP_NAME6}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
8911
8974
  var skills = program.command("skills").description("Manage skills");
8912
8975
  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 }));
8913
8976
  var withRepeatableTag = (value, previous = []) => [...previous, value];
@@ -8952,6 +9015,6 @@ cron.command("add").requiredOption("-n, --name <name>", "Job name").requiredOpti
8952
9015
  cron.command("remove <jobId>").action((jobId) => runtime.cronRemove(jobId));
8953
9016
  cron.command("enable <jobId>").option("--disable", "Disable instead of enable").action((jobId, opts) => runtime.cronEnable(jobId, opts));
8954
9017
  cron.command("run <jobId>").option("-f, --force", "Run even if disabled").action(async (jobId, opts) => runtime.cronRun(jobId, opts));
8955
- program.command("status").description(`Show ${APP_NAME5} 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));
8956
- program.command("doctor").description(`Run ${APP_NAME5} 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));
9018
+ program.command("status").description(`Show ${APP_NAME6} 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));
9019
+ program.command("doctor").description(`Run ${APP_NAME6} 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));
8957
9020
  program.parseAsync(process.argv);