nextclaw 0.12.5 → 0.13.0

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.
Files changed (35) hide show
  1. package/dist/cli/index.js +722 -299
  2. package/package.json +6 -6
  3. package/templates/USAGE.md +9 -0
  4. package/ui-dist/assets/{ChannelsList-DhvjpZcs.js → ChannelsList-DH1Ur9XW.js} +1 -1
  5. package/ui-dist/assets/ChatPage-YTDcN7XS.js +38 -0
  6. package/ui-dist/assets/{DocBrowser-LpzGe8An.js → DocBrowser-Bi-RpLIw.js} +1 -1
  7. package/ui-dist/assets/{LogoBadge-Be4lktJN.js → LogoBadge-BCR9CU7n.js} +1 -1
  8. package/ui-dist/assets/MarketplacePage-BgCdiku7.js +49 -0
  9. package/ui-dist/assets/McpMarketplacePage-nyCbiQH6.js +40 -0
  10. package/ui-dist/assets/{ModelConfig-DuImUHIX.js → ModelConfig-Cf4AAYaB.js} +1 -1
  11. package/ui-dist/assets/{ProvidersList-Ccleg25k.js → ProvidersList-CfkfKQbw.js} +1 -1
  12. package/ui-dist/assets/{RuntimeConfig-C6iqpJR_.js → RuntimeConfig-BI-zClCl.js} +1 -1
  13. package/ui-dist/assets/{SearchConfig-Dvp1TAXu.js → SearchConfig-MBmvco1J.js} +1 -1
  14. package/ui-dist/assets/{SecretsConfig-D5Ymlvt9.js → SecretsConfig-CC2B6pVQ.js} +1 -1
  15. package/ui-dist/assets/{SessionsConfig-CIA_jA1P.js → SessionsConfig-CTxJeVQs.js} +1 -1
  16. package/ui-dist/assets/{chat-message-B60Fh9kI.js → chat-message-5OiyZViy.js} +1 -1
  17. package/ui-dist/assets/index-C2OKcVdN.css +1 -0
  18. package/ui-dist/assets/{index-CPDASUXh.js → index-ElnZdv5o.js} +1 -1
  19. package/ui-dist/assets/index-LgjZxLjc.js +8 -0
  20. package/ui-dist/assets/{label-D4fGx6Wb.js → label-CPdcDrir.js} +1 -1
  21. package/ui-dist/assets/marketplace-localization-Dk31LJJJ.js +1 -0
  22. package/ui-dist/assets/{page-layout-twy8gmBE.js → page-layout-B8V5_vM_.js} +1 -1
  23. package/ui-dist/assets/{popover-DYbYpt1j.js → popover-Bk53A74_.js} +1 -1
  24. package/ui-dist/assets/{security-config-BcIZ4rpb.js → security-config-BNjgoyo4.js} +1 -1
  25. package/ui-dist/assets/skeleton-NPxxR-L0.js +1 -0
  26. package/ui-dist/assets/{switch-DqA6r5XR.js → switch-EowdzMK2.js} +1 -1
  27. package/ui-dist/assets/{tabs-custom-C6enKKs1.js → tabs-custom-BjLv-uCT.js} +1 -1
  28. package/ui-dist/assets/{useConfirmDialog-CHBf5Of7.js → useConfirmDialog-TJcJQMfu.js} +2 -2
  29. package/ui-dist/assets/{vendor-DKBNiC31.js → vendor-425xp8cv.js} +1 -1
  30. package/ui-dist/index.html +3 -3
  31. package/ui-dist/assets/ChatPage-B8VBaMQm.js +0 -38
  32. package/ui-dist/assets/MarketplacePage-Cx9AI3_h.js +0 -49
  33. package/ui-dist/assets/index-BiPDnzv0.js +0 -8
  34. package/ui-dist/assets/index-C8GsgIUn.css +0 -1
  35. package/ui-dist/assets/skeleton-DypBy7jp.js +0 -1
package/dist/cli/index.js CHANGED
@@ -6,12 +6,12 @@ import { APP_NAME as APP_NAME5, APP_TAGLINE } from "@nextclaw/core";
6
6
 
7
7
  // src/cli/runtime.ts
8
8
  import {
9
- loadConfig as loadConfig9,
10
- saveConfig as saveConfig7,
11
- getConfigPath as getConfigPath4,
12
- getDataDir as getDataDir8,
9
+ loadConfig as loadConfig12,
10
+ saveConfig as saveConfig8,
11
+ getConfigPath as getConfigPath5,
12
+ getDataDir as getDataDir9,
13
13
  ConfigSchema as ConfigSchema2,
14
- getWorkspacePath as getWorkspacePath9,
14
+ getWorkspacePath as getWorkspacePath10,
15
15
  expandHome as expandHome2,
16
16
  MessageBus as MessageBus2,
17
17
  AgentLoop,
@@ -26,8 +26,8 @@ import {
26
26
  resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2,
27
27
  setPluginRuntimeBridge as setPluginRuntimeBridge2
28
28
  } from "@nextclaw/openclaw-compat";
29
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
30
- import { join as join7, resolve as resolve12 } from "path";
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
31
  import { createInterface as createInterface2 } from "readline";
32
32
  import { fileURLToPath as fileURLToPath4 } from "url";
33
33
  import { spawn as spawn3 } from "child_process";
@@ -74,9 +74,9 @@ var RestartCoordinator = class {
74
74
  message: "Restart already scheduled; skipping duplicate request."
75
75
  };
76
76
  }
77
- const delay = typeof request.delayMs === "number" && Number.isFinite(request.delayMs) ? Math.max(0, Math.floor(request.delayMs)) : 100;
77
+ const delay2 = typeof request.delayMs === "number" && Number.isFinite(request.delayMs) ? Math.max(0, Math.floor(request.delayMs)) : 100;
78
78
  this.exitScheduled = true;
79
- this.deps.scheduleProcessExit(delay, reason);
79
+ this.deps.scheduleProcessExit(delay2, reason);
80
80
  return {
81
81
  status: "exit-scheduled",
82
82
  message: `Restart scheduled (${reason}).`
@@ -1486,6 +1486,7 @@ function toExtensionRegistry(pluginRegistry) {
1486
1486
  kind: runtime2.kind,
1487
1487
  label: runtime2.label,
1488
1488
  createRuntime: runtime2.createRuntime,
1489
+ describeSessionType: runtime2.describeSessionType,
1489
1490
  source: runtime2.source
1490
1491
  })),
1491
1492
  diagnostics: pluginRegistry.diagnostics.map((diag) => ({
@@ -2118,7 +2119,11 @@ var ConfigCommands = class {
2118
2119
 
2119
2120
  // src/cli/commands/mcp.ts
2120
2121
  import { loadConfig as loadConfig4, saveConfig as saveConfig3 } from "@nextclaw/core";
2121
- import { McpDoctorService, McpRegistryService, normalizeMcpServerName } from "@nextclaw/mcp";
2122
+ import {
2123
+ McpDoctorFacade,
2124
+ McpMutationService,
2125
+ McpRegistryService
2126
+ } from "@nextclaw/mcp";
2122
2127
  function normalizeOptionalString(value) {
2123
2128
  if (typeof value !== "string") {
2124
2129
  return void 0;
@@ -2155,12 +2160,12 @@ function parseTimeoutMs(value) {
2155
2160
  function buildMcpServerDefinition(command, opts) {
2156
2161
  const transport = (normalizeOptionalString(opts.transport) ?? "stdio").toLowerCase();
2157
2162
  const disabled = Boolean(opts.disabled);
2158
- const allAgents = Boolean(opts.allAgents);
2159
2163
  const explicitAgents = Array.from(
2160
2164
  new Set(
2161
2165
  (opts.agent ?? []).map((agentId) => normalizeOptionalString(agentId)).filter((agentId) => Boolean(agentId))
2162
2166
  )
2163
2167
  );
2168
+ const allAgents = explicitAgents.length === 0 ? true : Boolean(opts.allAgents);
2164
2169
  if (transport === "stdio") {
2165
2170
  if (command.length === 0) {
2166
2171
  throw new Error("stdio transport requires a command after --");
@@ -2177,11 +2182,15 @@ function buildMcpServerDefinition(command, opts) {
2177
2182
  },
2178
2183
  scope: {
2179
2184
  allAgents,
2180
- agents: explicitAgents
2185
+ agents: allAgents ? [] : explicitAgents
2181
2186
  },
2182
2187
  policy: {
2183
2188
  trust: "explicit",
2184
2189
  start: "eager"
2190
+ },
2191
+ metadata: {
2192
+ source: "manual",
2193
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
2185
2194
  }
2186
2195
  };
2187
2196
  }
@@ -2194,11 +2203,15 @@ function buildMcpServerDefinition(command, opts) {
2194
2203
  enabled: !disabled,
2195
2204
  scope: {
2196
2205
  allAgents,
2197
- agents: explicitAgents
2206
+ agents: allAgents ? [] : explicitAgents
2198
2207
  },
2199
2208
  policy: {
2200
2209
  trust: "explicit",
2201
2210
  start: "eager"
2211
+ },
2212
+ metadata: {
2213
+ source: "manual",
2214
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
2202
2215
  }
2203
2216
  };
2204
2217
  if (transport === "http") {
@@ -2257,26 +2270,28 @@ var McpCommands = class {
2257
2270
  }
2258
2271
  }
2259
2272
  async mcpAdd(name, command, opts) {
2260
- const normalizedName = normalizeMcpServerName(name);
2261
- const config2 = loadConfig4();
2262
- if (config2.mcp.servers[normalizedName]) {
2263
- reportUserInputIssue(`MCP server already exists: ${normalizedName}. Use 'mcp list' or remove it first.`);
2273
+ const mutation = new McpMutationService({
2274
+ getConfig: () => loadConfig4(),
2275
+ saveConfig: (config2) => saveConfig3(config2)
2276
+ });
2277
+ const result = mutation.addServer(name, buildMcpServerDefinition(command, opts));
2278
+ if (!result.changed) {
2279
+ reportUserInputIssue(result.message);
2264
2280
  return;
2265
2281
  }
2266
- config2.mcp.servers[normalizedName] = buildMcpServerDefinition(command, opts);
2267
- saveConfig3(config2);
2268
- console.log(`Added MCP server ${normalizedName}.`);
2282
+ console.log(result.message);
2269
2283
  }
2270
2284
  async mcpRemove(name) {
2271
- const normalizedName = normalizeMcpServerName(name);
2272
- const config2 = loadConfig4();
2273
- if (!config2.mcp.servers[normalizedName]) {
2274
- reportUserInputIssue(`Unknown MCP server: ${normalizedName}`);
2285
+ const mutation = new McpMutationService({
2286
+ getConfig: () => loadConfig4(),
2287
+ saveConfig: (config2) => saveConfig3(config2)
2288
+ });
2289
+ const result = mutation.removeServer(name);
2290
+ if (!result.changed) {
2291
+ reportUserInputIssue(result.message);
2275
2292
  return;
2276
2293
  }
2277
- delete config2.mcp.servers[normalizedName];
2278
- saveConfig3(config2);
2279
- console.log(`Removed MCP server ${normalizedName}.`);
2294
+ console.log(result.message);
2280
2295
  }
2281
2296
  async mcpEnable(name) {
2282
2297
  await this.toggleEnabled(name, true);
@@ -2288,7 +2303,7 @@ var McpCommands = class {
2288
2303
  const registry = new McpRegistryService({
2289
2304
  getConfig: () => loadConfig4()
2290
2305
  });
2291
- const doctor = new McpDoctorService({
2306
+ const doctor = new McpDoctorFacade({
2292
2307
  getConfig: () => loadConfig4(),
2293
2308
  registryService: registry
2294
2309
  });
@@ -2314,16 +2329,16 @@ var McpCommands = class {
2314
2329
  }
2315
2330
  }
2316
2331
  async toggleEnabled(name, enabled) {
2317
- const normalizedName = normalizeMcpServerName(name);
2318
- const config2 = loadConfig4();
2319
- const current = config2.mcp.servers[normalizedName];
2320
- if (!current) {
2321
- reportUserInputIssue(`Unknown MCP server: ${normalizedName}`);
2332
+ const mutation = new McpMutationService({
2333
+ getConfig: () => loadConfig4(),
2334
+ saveConfig: (config2) => saveConfig3(config2)
2335
+ });
2336
+ const result = mutation.toggleEnabled(name, enabled);
2337
+ if (!result.changed) {
2338
+ reportUserInputIssue(result.message);
2322
2339
  return;
2323
2340
  }
2324
- current.enabled = enabled;
2325
- saveConfig3(config2);
2326
- console.log(`${enabled ? "Enabled" : "Disabled"} MCP server ${normalizedName}.`);
2341
+ console.log(result.message);
2327
2342
  }
2328
2343
  };
2329
2344
  function reportUserInputIssue(message) {
@@ -2844,17 +2859,281 @@ var CronCommands = class {
2844
2859
  }
2845
2860
  };
2846
2861
 
2862
+ // src/cli/commands/remote.ts
2863
+ import { getConfigPath as getConfigPath2, getDataDir as getDataDir4, loadConfig as loadConfig7 } from "@nextclaw/core";
2864
+ import { ensureUiBridgeSecret } from "@nextclaw/server";
2865
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2866
+ import { dirname as dirname2, join as join4 } from "path";
2867
+ import { hostname, platform as readPlatform } from "os";
2868
+ function encodeBase64(bytes) {
2869
+ return Buffer.from(bytes).toString("base64");
2870
+ }
2871
+ function decodeBase64(base64) {
2872
+ if (!base64) {
2873
+ return new Uint8Array();
2874
+ }
2875
+ return new Uint8Array(Buffer.from(base64, "base64"));
2876
+ }
2877
+ function delay(ms) {
2878
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
2879
+ }
2880
+ function ensureDir(path2) {
2881
+ mkdirSync4(path2, { recursive: true });
2882
+ }
2883
+ function readJsonFile2(path2) {
2884
+ if (!existsSync6(path2)) {
2885
+ return null;
2886
+ }
2887
+ try {
2888
+ return JSON.parse(readFileSync6(path2, "utf-8"));
2889
+ } catch {
2890
+ return null;
2891
+ }
2892
+ }
2893
+ function writeJsonFile(path2, value) {
2894
+ ensureDir(dirname2(path2));
2895
+ writeFileSync4(path2, `${JSON.stringify(value, null, 2)}
2896
+ `, "utf-8");
2897
+ }
2898
+ var RemoteCommands = class {
2899
+ remoteDir = join4(getDataDir4(), "remote");
2900
+ devicePath = join4(this.remoteDir, "device.json");
2901
+ ensureDeviceInstallId() {
2902
+ const existing = readJsonFile2(this.devicePath);
2903
+ if (existing?.deviceInstallId?.trim()) {
2904
+ return existing.deviceInstallId.trim();
2905
+ }
2906
+ const deviceInstallId = crypto.randomUUID();
2907
+ ensureDir(this.remoteDir);
2908
+ writeJsonFile(this.devicePath, { deviceInstallId });
2909
+ return deviceInstallId;
2910
+ }
2911
+ resolvePlatformAccess(opts) {
2912
+ const config2 = loadConfig7(getConfigPath2());
2913
+ const providers = config2.providers;
2914
+ const nextclawProvider = providers.nextclaw;
2915
+ const token = typeof nextclawProvider?.apiKey === "string" ? nextclawProvider.apiKey.trim() : "";
2916
+ if (!token) {
2917
+ throw new Error('NextClaw platform token is missing. Run "nextclaw login" first.');
2918
+ }
2919
+ const configuredApiBase = typeof nextclawProvider?.apiBase === "string" ? nextclawProvider.apiBase.trim() : "";
2920
+ const rawApiBase = typeof opts.apiBase === "string" && opts.apiBase.trim().length > 0 ? opts.apiBase.trim() : configuredApiBase;
2921
+ if (!rawApiBase) {
2922
+ throw new Error("Platform API base is missing. Pass --api-base or run nextclaw login.");
2923
+ }
2924
+ const platformBase = rawApiBase.replace(/\/v1\/?$/i, "");
2925
+ return { platformBase, token, config: config2 };
2926
+ }
2927
+ resolveLocalOrigin(config2, opts) {
2928
+ if (typeof opts.localOrigin === "string" && opts.localOrigin.trim().length > 0) {
2929
+ return opts.localOrigin.trim().replace(/\/$/, "");
2930
+ }
2931
+ const state = readServiceState();
2932
+ if (state && isProcessRunning(state.pid) && Number.isFinite(state.uiPort)) {
2933
+ return `http://127.0.0.1:${state.uiPort}`;
2934
+ }
2935
+ const configuredPort = typeof config2.ui?.port === "number" && Number.isFinite(config2.ui.port) ? config2.ui.port : 18791;
2936
+ return `http://127.0.0.1:${configuredPort}`;
2937
+ }
2938
+ async ensureLocalUiHealthy(localOrigin) {
2939
+ const response = await fetch(`${localOrigin}/api/health`);
2940
+ if (!response.ok) {
2941
+ throw new Error(`Local UI is not healthy at ${localOrigin}. Start NextClaw first.`);
2942
+ }
2943
+ }
2944
+ async registerDevice(params) {
2945
+ const response = await fetch(`${params.platformBase}/platform/remote/devices/register`, {
2946
+ method: "POST",
2947
+ headers: {
2948
+ "content-type": "application/json",
2949
+ authorization: `Bearer ${params.token}`
2950
+ },
2951
+ body: JSON.stringify({
2952
+ deviceInstallId: params.deviceInstallId,
2953
+ displayName: params.displayName,
2954
+ platform: readPlatform(),
2955
+ appVersion: getPackageVersion(),
2956
+ localOrigin: params.localOrigin
2957
+ })
2958
+ });
2959
+ const payload = await response.json();
2960
+ if (!response.ok || !payload.ok || !payload.data?.device) {
2961
+ throw new Error(payload.error?.message ?? `Failed to register remote device (${response.status}).`);
2962
+ }
2963
+ return payload.data.device;
2964
+ }
2965
+ async requestBridgeCookie(localOrigin) {
2966
+ const response = await fetch(`${localOrigin}/api/auth/bridge`, {
2967
+ method: "POST",
2968
+ headers: {
2969
+ "x-nextclaw-ui-bridge-secret": ensureUiBridgeSecret()
2970
+ }
2971
+ });
2972
+ const payload = await response.json();
2973
+ if (!response.ok || !payload.ok) {
2974
+ throw new Error(payload.error?.message ?? `Failed to request local auth bridge (${response.status}).`);
2975
+ }
2976
+ return typeof payload.data?.cookie === "string" && payload.data.cookie.trim().length > 0 ? payload.data.cookie.trim() : null;
2977
+ }
2978
+ async handleRelayRequest(params) {
2979
+ const bridgeCookie = await this.requestBridgeCookie(params.localOrigin);
2980
+ const url = new URL(params.frame.path, params.localOrigin);
2981
+ const headers = new Headers();
2982
+ for (const [key, value] of params.frame.headers) {
2983
+ const lower = key.toLowerCase();
2984
+ if ([
2985
+ "host",
2986
+ "connection",
2987
+ "content-length",
2988
+ "cookie",
2989
+ "x-forwarded-for",
2990
+ "x-forwarded-proto",
2991
+ "cf-connecting-ip"
2992
+ ].includes(lower)) {
2993
+ continue;
2994
+ }
2995
+ headers.set(key, value);
2996
+ }
2997
+ if (bridgeCookie) {
2998
+ headers.set("cookie", bridgeCookie);
2999
+ }
3000
+ const bodyBytes = decodeBase64(params.frame.bodyBase64);
3001
+ const response = await fetch(url, {
3002
+ method: params.frame.method,
3003
+ headers,
3004
+ body: params.frame.method === "GET" || params.frame.method === "HEAD" ? void 0 : bodyBytes
3005
+ });
3006
+ const responseHeaders = Array.from(response.headers.entries()).filter(([key]) => {
3007
+ const lower = key.toLowerCase();
3008
+ return !["content-length", "connection", "transfer-encoding", "set-cookie"].includes(lower);
3009
+ });
3010
+ const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
3011
+ if (response.body && contentType.startsWith("text/event-stream")) {
3012
+ params.socket.send(JSON.stringify({
3013
+ type: "response.start",
3014
+ requestId: params.frame.requestId,
3015
+ status: response.status,
3016
+ headers: responseHeaders
3017
+ }));
3018
+ const reader = response.body.getReader();
3019
+ try {
3020
+ while (true) {
3021
+ const { value, done } = await reader.read();
3022
+ if (done) {
3023
+ break;
3024
+ }
3025
+ if (value && value.length > 0) {
3026
+ params.socket.send(JSON.stringify({
3027
+ type: "response.chunk",
3028
+ requestId: params.frame.requestId,
3029
+ bodyBase64: encodeBase64(value)
3030
+ }));
3031
+ }
3032
+ }
3033
+ } finally {
3034
+ reader.releaseLock();
3035
+ }
3036
+ params.socket.send(JSON.stringify({
3037
+ type: "response.end",
3038
+ requestId: params.frame.requestId
3039
+ }));
3040
+ return;
3041
+ }
3042
+ const responseBody = response.body ? new Uint8Array(await response.arrayBuffer()) : new Uint8Array();
3043
+ params.socket.send(JSON.stringify({
3044
+ type: "response",
3045
+ requestId: params.frame.requestId,
3046
+ status: response.status,
3047
+ headers: responseHeaders,
3048
+ bodyBase64: encodeBase64(responseBody)
3049
+ }));
3050
+ }
3051
+ async connectOnce(params) {
3052
+ await new Promise((resolve13, reject) => {
3053
+ const socket = new WebSocket(params.wsUrl);
3054
+ const pingTimer = setInterval(() => {
3055
+ if (socket.readyState === WebSocket.OPEN) {
3056
+ socket.send(JSON.stringify({ type: "ping", at: (/* @__PURE__ */ new Date()).toISOString() }));
3057
+ }
3058
+ }, 15e3);
3059
+ socket.addEventListener("open", () => {
3060
+ console.log(`\u2713 Remote connector connected: ${params.wsUrl}`);
3061
+ });
3062
+ socket.addEventListener("message", (event) => {
3063
+ void (async () => {
3064
+ let frame = null;
3065
+ try {
3066
+ frame = JSON.parse(String(event.data ?? ""));
3067
+ } catch {
3068
+ return;
3069
+ }
3070
+ if (!frame || frame.type !== "request") {
3071
+ return;
3072
+ }
3073
+ try {
3074
+ await this.handleRelayRequest({ frame, localOrigin: params.localOrigin, socket });
3075
+ } catch (error) {
3076
+ socket.send(JSON.stringify({
3077
+ type: "response.error",
3078
+ requestId: frame.requestId,
3079
+ message: error instanceof Error ? error.message : String(error)
3080
+ }));
3081
+ }
3082
+ })();
3083
+ });
3084
+ socket.addEventListener("close", () => {
3085
+ clearInterval(pingTimer);
3086
+ resolve13();
3087
+ });
3088
+ socket.addEventListener("error", () => {
3089
+ clearInterval(pingTimer);
3090
+ reject(new Error("Remote connector websocket failed."));
3091
+ });
3092
+ });
3093
+ }
3094
+ async connect(opts = {}) {
3095
+ const { platformBase, token, config: config2 } = this.resolvePlatformAccess(opts);
3096
+ const localOrigin = this.resolveLocalOrigin(config2, opts);
3097
+ await this.ensureLocalUiHealthy(localOrigin);
3098
+ const deviceInstallId = this.ensureDeviceInstallId();
3099
+ const displayName = typeof opts.name === "string" && opts.name.trim().length > 0 ? opts.name.trim() : hostname();
3100
+ const device = await this.registerDevice({
3101
+ platformBase,
3102
+ token,
3103
+ deviceInstallId,
3104
+ displayName,
3105
+ localOrigin
3106
+ });
3107
+ console.log(`\u2713 Remote device registered: ${device.displayName} (${device.id})`);
3108
+ console.log(`\u2713 Local origin: ${localOrigin}`);
3109
+ console.log(`\u2713 Platform: ${platformBase}`);
3110
+ const wsUrl = `${platformBase.replace(/^http/i, "ws")}/platform/remote/connect?deviceId=${encodeURIComponent(device.id)}&token=${encodeURIComponent(token)}`;
3111
+ do {
3112
+ try {
3113
+ await this.connectOnce({ wsUrl, localOrigin });
3114
+ } catch (error) {
3115
+ console.error(`Remote connector error: ${error instanceof Error ? error.message : String(error)}`);
3116
+ }
3117
+ if (opts.once) {
3118
+ break;
3119
+ }
3120
+ console.log("Remote connector disconnected. Reconnecting in 3s...");
3121
+ await delay(3e3);
3122
+ } while (!opts.once);
3123
+ }
3124
+ };
3125
+
2847
3126
  // src/cli/commands/diagnostics.ts
2848
3127
  import { createServer as createNetServer } from "net";
2849
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
3128
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
2850
3129
  import { resolve as resolve8 } from "path";
2851
3130
  import {
2852
3131
  APP_NAME,
2853
- getConfigPath as getConfigPath2,
2854
- getDataDir as getDataDir4,
3132
+ getConfigPath as getConfigPath3,
3133
+ getDataDir as getDataDir5,
2855
3134
  getWorkspacePath as getWorkspacePath4,
2856
3135
  hasSecretRef,
2857
- loadConfig as loadConfig7
3136
+ loadConfig as loadConfig8
2858
3137
  } from "@nextclaw/core";
2859
3138
  import { listBuiltinProviders } from "@nextclaw/runtime";
2860
3139
  var DiagnosticsCommands = class {
@@ -3015,10 +3294,10 @@ var DiagnosticsCommands = class {
3015
3294
  process.exitCode = exitCode;
3016
3295
  }
3017
3296
  async collectRuntimeStatus(params) {
3018
- const configPath = getConfigPath2();
3019
- const config2 = loadConfig7();
3297
+ const configPath = getConfigPath3();
3298
+ const config2 = loadConfig8();
3020
3299
  const workspacePath = getWorkspacePath4(config2.agents.defaults.workspace);
3021
- const serviceStatePath = resolve8(getDataDir4(), "run", "service.json");
3300
+ const serviceStatePath = resolve8(getDataDir5(), "run", "service.json");
3022
3301
  const fixActions = [];
3023
3302
  let serviceState = readServiceState();
3024
3303
  if (params.fix && serviceState && !isProcessRunning(serviceState.pid)) {
@@ -3058,11 +3337,11 @@ var DiagnosticsCommands = class {
3058
3337
  });
3059
3338
  const issues = [];
3060
3339
  const recommendations = [];
3061
- if (!existsSync6(configPath)) {
3340
+ if (!existsSync7(configPath)) {
3062
3341
  issues.push("Config file is missing.");
3063
3342
  recommendations.push(`Run ${APP_NAME} init to create config files.`);
3064
3343
  }
3065
- if (!existsSync6(workspacePath)) {
3344
+ if (!existsSync7(workspacePath)) {
3066
3345
  issues.push("Workspace directory does not exist.");
3067
3346
  recommendations.push(`Run ${APP_NAME} init to create workspace templates.`);
3068
3347
  }
@@ -3095,13 +3374,13 @@ var DiagnosticsCommands = class {
3095
3374
  return {
3096
3375
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3097
3376
  configPath,
3098
- configExists: existsSync6(configPath),
3377
+ configExists: existsSync7(configPath),
3099
3378
  workspacePath,
3100
- workspaceExists: existsSync6(workspacePath),
3379
+ workspaceExists: existsSync7(workspacePath),
3101
3380
  model: config2.agents.defaults.model,
3102
3381
  providers,
3103
3382
  serviceStatePath,
3104
- serviceStateExists: existsSync6(serviceStatePath),
3383
+ serviceStateExists: existsSync7(serviceStatePath),
3105
3384
  fixActions,
3106
3385
  process: {
3107
3386
  managedByState,
@@ -3151,11 +3430,11 @@ var DiagnosticsCommands = class {
3151
3430
  }
3152
3431
  }
3153
3432
  readLogTail(path2, maxLines = 25) {
3154
- if (!existsSync6(path2)) {
3433
+ if (!existsSync7(path2)) {
3155
3434
  return [];
3156
3435
  }
3157
3436
  try {
3158
- const lines = readFileSync6(path2, "utf-8").split(/\r?\n/).filter(Boolean);
3437
+ const lines = readFileSync7(path2, "utf-8").split(/\r?\n/).filter(Boolean);
3159
3438
  if (lines.length <= maxLines) {
3160
3439
  return lines;
3161
3440
  }
@@ -3195,8 +3474,8 @@ import {
3195
3474
  stopPluginChannelGateways as stopPluginChannelGateways2
3196
3475
  } from "@nextclaw/openclaw-compat";
3197
3476
  import { startUiServer } from "@nextclaw/server";
3198
- import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as existsSync9, mkdirSync as mkdirSync5, openSync, rmSync as rmSync4 } from "fs";
3199
- import { dirname as dirname2, join as join5, resolve as resolve10 } from "path";
3477
+ import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as existsSync11, mkdirSync as mkdirSync6, openSync } from "fs";
3478
+ import { dirname as dirname3, join as join7, resolve as resolve10 } from "path";
3200
3479
  import { spawn as spawn2 } from "child_process";
3201
3480
  import { request as httpRequest } from "http";
3202
3481
  import { request as httpsRequest } from "https";
@@ -3205,7 +3484,7 @@ import chokidar from "chokidar";
3205
3484
 
3206
3485
  // src/cli/gateway/controller.ts
3207
3486
  import { createHash } from "crypto";
3208
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
3487
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
3209
3488
  import {
3210
3489
  buildConfigSchema,
3211
3490
  ConfigSchema,
@@ -3213,12 +3492,12 @@ import {
3213
3492
  redactConfigObject
3214
3493
  } from "@nextclaw/core";
3215
3494
  var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
3216
- var readConfigSnapshot = (getConfigPath5) => {
3217
- const path2 = getConfigPath5();
3495
+ var readConfigSnapshot = (getConfigPath6) => {
3496
+ const path2 = getConfigPath6();
3218
3497
  let raw = "";
3219
3498
  let parsed = {};
3220
- if (existsSync7(path2)) {
3221
- raw = readFileSync7(path2, "utf-8");
3499
+ if (existsSync8(path2)) {
3500
+ raw = readFileSync8(path2, "utf-8");
3222
3501
  try {
3223
3502
  parsed = JSON.parse(raw);
3224
3503
  } catch {
@@ -3325,11 +3604,11 @@ var GatewayControllerImpl = class {
3325
3604
  await this.deps.requestRestart(options);
3326
3605
  return;
3327
3606
  }
3328
- const delay = typeof options?.delayMs === "number" && Number.isFinite(options.delayMs) ? Math.max(0, options.delayMs) : 100;
3607
+ const delay2 = typeof options?.delayMs === "number" && Number.isFinite(options.delayMs) ? Math.max(0, options.delayMs) : 100;
3329
3608
  console.log(`Gateway restart requested via tool${options?.reason ? ` (${options.reason})` : ""}.`);
3330
3609
  setTimeout(() => {
3331
3610
  process.exit(0);
3332
- }, delay);
3611
+ }, delay2);
3333
3612
  }
3334
3613
  status() {
3335
3614
  return {
@@ -3674,8 +3953,242 @@ var MissingProvider = class extends LLMProvider {
3674
3953
  }
3675
3954
  };
3676
3955
 
3956
+ // src/cli/commands/service-marketplace-installer.ts
3957
+ import { getWorkspacePath as getWorkspacePath5, loadConfig as loadConfig10 } from "@nextclaw/core";
3958
+ import { existsSync as existsSync9, rmSync as rmSync4 } from "fs";
3959
+ import { join as join5 } from "path";
3960
+
3961
+ // src/cli/commands/service-marketplace-helpers.ts
3962
+ var containsAbsoluteFsPath = (line) => {
3963
+ const normalized = line.trim();
3964
+ if (!normalized) {
3965
+ return false;
3966
+ }
3967
+ const lowered = normalized.toLowerCase();
3968
+ if (lowered.includes("http://") || lowered.includes("https://")) {
3969
+ return false;
3970
+ }
3971
+ if (/^[A-Za-z]:\\/.test(normalized)) {
3972
+ return true;
3973
+ }
3974
+ return /(?:^|\s)(?:~\/|\/[^\s]+)/.test(normalized);
3975
+ };
3976
+ var pickUserFacingCommandSummary = (output, fallback) => {
3977
+ const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
3978
+ if (lines.length === 0) {
3979
+ return fallback;
3980
+ }
3981
+ const visibleLines = lines.filter((line) => {
3982
+ if (/^(path|install path|source path|destination|location)\s*:/i.test(line)) {
3983
+ return false;
3984
+ }
3985
+ if (containsAbsoluteFsPath(line)) {
3986
+ return false;
3987
+ }
3988
+ return true;
3989
+ });
3990
+ if (visibleLines.length === 0) {
3991
+ return fallback;
3992
+ }
3993
+ const preferred = [...visibleLines].reverse().find(
3994
+ (line) => /\b(installed|enabled|disabled|uninstalled|published|updated|already installed|removed)\b/i.test(line)
3995
+ );
3996
+ return preferred ?? visibleLines[visibleLines.length - 1] ?? fallback;
3997
+ };
3998
+ var buildMarketplaceSkillInstallArgs = (params) => {
3999
+ const args = ["skills", "install", params.slug, "--workdir", params.workspace];
4000
+ if (params.force) {
4001
+ args.push("--force");
4002
+ }
4003
+ return args;
4004
+ };
4005
+
4006
+ // src/cli/commands/service-mcp-marketplace-ops.ts
4007
+ import { loadConfig as loadConfig9, saveConfig as saveConfig6 } from "@nextclaw/core";
4008
+ import { McpDoctorFacade as McpDoctorFacade2, McpMutationService as McpMutationService2 } from "@nextclaw/mcp";
4009
+ var ServiceMcpMarketplaceOps = class {
4010
+ constructor(options) {
4011
+ this.options = options;
4012
+ }
4013
+ async install(params) {
4014
+ if (!params.template) {
4015
+ throw new Error(`Missing MCP marketplace template for ${params.spec}`);
4016
+ }
4017
+ const template = params.template;
4018
+ const result = this.createMutationService().installFromTemplate({
4019
+ template,
4020
+ name: params.name,
4021
+ enabled: params.enabled,
4022
+ scope: {
4023
+ allAgents: params.allAgents ?? true,
4024
+ agents: params.allAgents === false ? params.agents ?? [] : []
4025
+ },
4026
+ inputs: params.inputs,
4027
+ metadata: {
4028
+ source: "marketplace",
4029
+ catalogSlug: params.spec,
4030
+ displayName: template.defaultName
4031
+ }
4032
+ });
4033
+ if (!result.changed) {
4034
+ throw new Error(result.message);
4035
+ }
4036
+ await this.options.applyLiveConfigReload?.();
4037
+ return {
4038
+ name: result.name,
4039
+ message: result.message
4040
+ };
4041
+ }
4042
+ async enable(name) {
4043
+ const result = this.createMutationService().toggleEnabled(name, true);
4044
+ if (!result.changed) {
4045
+ throw new Error(result.message);
4046
+ }
4047
+ await this.options.applyLiveConfigReload?.();
4048
+ return { message: result.message };
4049
+ }
4050
+ async disable(name) {
4051
+ const result = this.createMutationService().toggleEnabled(name, false);
4052
+ if (!result.changed) {
4053
+ throw new Error(result.message);
4054
+ }
4055
+ await this.options.applyLiveConfigReload?.();
4056
+ return { message: result.message };
4057
+ }
4058
+ async remove(name) {
4059
+ const result = this.createMutationService().removeServer(name);
4060
+ if (!result.changed) {
4061
+ throw new Error(result.message);
4062
+ }
4063
+ await this.options.applyLiveConfigReload?.();
4064
+ return { message: result.message };
4065
+ }
4066
+ async doctor(name) {
4067
+ const report = await this.createDoctorFacade().inspectOne(name);
4068
+ if (!report) {
4069
+ throw new Error(`Unknown MCP server: ${name}`);
4070
+ }
4071
+ return report;
4072
+ }
4073
+ createMutationService() {
4074
+ return new McpMutationService2({
4075
+ getConfig: () => loadConfig9(),
4076
+ saveConfig: (config2) => saveConfig6(config2)
4077
+ });
4078
+ }
4079
+ createDoctorFacade() {
4080
+ return new McpDoctorFacade2({
4081
+ getConfig: () => loadConfig9()
4082
+ });
4083
+ }
4084
+ };
4085
+
4086
+ // src/cli/commands/service-marketplace-installer.ts
4087
+ var ServiceMarketplaceInstaller = class {
4088
+ constructor(deps) {
4089
+ this.deps = deps;
4090
+ }
4091
+ createInstaller() {
4092
+ return {
4093
+ installPlugin: (spec) => this.installPlugin(spec),
4094
+ installSkill: (params) => this.installSkill(params),
4095
+ installMcp: (params) => this.installMcp(params),
4096
+ enablePlugin: (id) => this.enablePlugin(id),
4097
+ disablePlugin: (id) => this.disablePlugin(id),
4098
+ uninstallPlugin: (id) => this.uninstallPlugin(id),
4099
+ uninstallSkill: (slug) => this.uninstallSkill(slug),
4100
+ enableMcp: (name) => this.enableMcp(name),
4101
+ disableMcp: (name) => this.disableMcp(name),
4102
+ removeMcp: (name) => this.removeMcp(name),
4103
+ doctorMcp: (name) => this.doctorMcp(name)
4104
+ };
4105
+ }
4106
+ async installPlugin(spec) {
4107
+ const result = await installPluginMutation(spec);
4108
+ await this.deps.applyLiveConfigReload?.();
4109
+ return { message: result.message };
4110
+ }
4111
+ async installSkill(params) {
4112
+ if (params.kind === "builtin") {
4113
+ const result = this.deps.installBuiltinSkill(params.slug, params.force);
4114
+ if (!result) {
4115
+ throw new Error(`Builtin skill not found: ${params.slug}`);
4116
+ }
4117
+ return result;
4118
+ }
4119
+ if (params.kind && params.kind !== "marketplace") {
4120
+ throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
4121
+ }
4122
+ const workspace = getWorkspacePath5(loadConfig10().agents.defaults.workspace);
4123
+ const args = buildMarketplaceSkillInstallArgs({
4124
+ slug: params.slug,
4125
+ workspace,
4126
+ force: params.force
4127
+ });
4128
+ try {
4129
+ const output = await this.deps.runCliSubcommand(args);
4130
+ const summary = pickUserFacingCommandSummary(output, `Installed skill: ${params.slug}`);
4131
+ return { message: summary };
4132
+ } catch (error) {
4133
+ const fallback = this.deps.installBuiltinSkill(params.slug, params.force);
4134
+ if (!fallback) {
4135
+ throw error;
4136
+ }
4137
+ return fallback;
4138
+ }
4139
+ }
4140
+ async installMcp(params) {
4141
+ return await this.createMcpMarketplaceOps().install(params);
4142
+ }
4143
+ async enablePlugin(id) {
4144
+ const result = await enablePluginMutation(id);
4145
+ await this.deps.applyLiveConfigReload?.();
4146
+ return { message: result.message };
4147
+ }
4148
+ async disablePlugin(id) {
4149
+ const result = await disablePluginMutation(id);
4150
+ await this.deps.applyLiveConfigReload?.();
4151
+ return { message: result.message };
4152
+ }
4153
+ async uninstallPlugin(id) {
4154
+ await disablePluginMutation(id);
4155
+ await this.deps.applyLiveConfigReload?.();
4156
+ const result = await uninstallPluginMutation(id, { force: true });
4157
+ await this.deps.applyLiveConfigReload?.();
4158
+ return { message: result.message };
4159
+ }
4160
+ async uninstallSkill(slug) {
4161
+ const workspace = getWorkspacePath5(loadConfig10().agents.defaults.workspace);
4162
+ const targetDir = join5(workspace, "skills", slug);
4163
+ if (!existsSync9(targetDir)) {
4164
+ throw new Error(`Skill not installed in workspace: ${slug}`);
4165
+ }
4166
+ rmSync4(targetDir, { recursive: true, force: true });
4167
+ return {
4168
+ message: `Uninstalled skill: ${slug}`
4169
+ };
4170
+ }
4171
+ async enableMcp(name) {
4172
+ return await this.createMcpMarketplaceOps().enable(name);
4173
+ }
4174
+ async disableMcp(name) {
4175
+ return await this.createMcpMarketplaceOps().disable(name);
4176
+ }
4177
+ async removeMcp(name) {
4178
+ return await this.createMcpMarketplaceOps().remove(name);
4179
+ }
4180
+ async doctorMcp(name) {
4181
+ return await this.createMcpMarketplaceOps().doctor(name);
4182
+ }
4183
+ createMcpMarketplaceOps() {
4184
+ return new ServiceMcpMarketplaceOps({
4185
+ applyLiveConfigReload: this.deps.applyLiveConfigReload
4186
+ });
4187
+ }
4188
+ };
4189
+
3677
4190
  // src/cli/commands/service-plugin-reload.ts
3678
- import { getWorkspacePath as getWorkspacePath5 } from "@nextclaw/core";
4191
+ import { getWorkspacePath as getWorkspacePath6 } from "@nextclaw/core";
3679
4192
  import {
3680
4193
  getPluginChannelBindings as getPluginChannelBindings2,
3681
4194
  startPluginChannelGateways,
@@ -3741,7 +4254,7 @@ function shouldRestartChannelsForPluginReload(params) {
3741
4254
 
3742
4255
  // src/cli/commands/service-plugin-reload.ts
3743
4256
  async function reloadServicePlugins(params) {
3744
- const nextWorkspace = getWorkspacePath5(params.nextConfig.agents.defaults.workspace);
4257
+ const nextWorkspace = getWorkspacePath6(params.nextConfig.agents.defaults.workspace);
3745
4258
  const nextPluginRegistry = loadPluginRegistry(params.nextConfig, nextWorkspace);
3746
4259
  const nextExtensionRegistry = toExtensionRegistry(nextPluginRegistry);
3747
4260
  const nextPluginChannelBindings = getPluginChannelBindings2(nextPluginRegistry);
@@ -3779,7 +4292,7 @@ import {
3779
4292
  createAssistantStreamDeltaControlMessage,
3780
4293
  createAssistantStreamResetControlMessage,
3781
4294
  AgentRouteResolver,
3782
- getWorkspacePath as getWorkspacePath6,
4295
+ getWorkspacePath as getWorkspacePath7,
3783
4296
  parseAgentScopedSessionKey
3784
4297
  } from "@nextclaw/core";
3785
4298
  function normalizeAgentId(value) {
@@ -3824,7 +4337,7 @@ function resolveAgentProfiles(config2) {
3824
4337
  }
3825
4338
  return Array.from(unique.values()).map((entry) => ({
3826
4339
  id: entry.id,
3827
- workspace: getWorkspacePath6(entry.workspace ?? defaults.workspace),
4340
+ workspace: getWorkspacePath7(entry.workspace ?? defaults.workspace),
3828
4341
  model: entry.model ?? defaults.model,
3829
4342
  engine: normalizeEngineKind(entry.engine ?? defaults.engine),
3830
4343
  engineConfig: entry.engineConfig ?? toRecord(defaults.engineConfig),
@@ -4089,7 +4602,7 @@ var GatewayAgentRuntimePool = class {
4089
4602
  const normalizedAgentId = normalizeAgentId(agentId);
4090
4603
  return this.resolvedProfiles.find((profile) => profile.id === normalizedAgentId) ?? this.resolvedProfiles.find((profile) => profile.id === this.defaultAgentId) ?? this.resolvedProfiles[0] ?? {
4091
4604
  id: this.defaultAgentId,
4092
- workspace: getWorkspacePath6(this.options.config.agents.defaults.workspace),
4605
+ workspace: getWorkspacePath7(this.options.config.agents.defaults.workspace),
4093
4606
  model: this.options.config.agents.defaults.model,
4094
4607
  maxIterations: this.options.config.agents.defaults.maxToolIterations,
4095
4608
  contextTokens: this.options.config.agents.defaults.contextTokens,
@@ -4297,7 +4810,7 @@ import { createAgentClientFromServer, DefaultNcpAgentBackend } from "@nextclaw/n
4297
4810
  import {
4298
4811
  ContextBuilder,
4299
4812
  InputBudgetPruner,
4300
- getWorkspacePath as getWorkspacePath7,
4813
+ getWorkspacePath as getWorkspacePath8,
4301
4814
  parseThinkingLevel,
4302
4815
  resolveThinkingLevel
4303
4816
  } from "@nextclaw/core";
@@ -4795,7 +5308,7 @@ function resolvePrimaryAgentProfile(config2) {
4795
5308
  const profile = config2.agents.list.find((entry) => entry.id.trim() === configuredDefaultAgentId);
4796
5309
  return {
4797
5310
  agentId: configuredDefaultAgentId,
4798
- workspace: getWorkspacePath7(profile?.workspace ?? config2.agents.defaults.workspace),
5311
+ workspace: getWorkspacePath8(profile?.workspace ?? config2.agents.defaults.workspace),
4799
5312
  model: profile?.model ?? config2.agents.defaults.model,
4800
5313
  maxIterations: profile?.maxToolIterations ?? config2.agents.defaults.maxToolIterations,
4801
5314
  contextTokens: profile?.contextTokens ?? config2.agents.defaults.contextTokens,
@@ -5427,22 +5940,33 @@ var UiNcpRuntimeRegistry = class {
5427
5940
  sessionMetadata: nextSessionMetadata
5428
5941
  });
5429
5942
  }
5430
- listSessionTypes() {
5431
- const options = [...this.registrations.values()].map((registration) => ({
5432
- value: registration.kind,
5433
- label: registration.label
5434
- })).sort((left, right) => {
5435
- if (left.value === this.defaultKind) {
5436
- return -1;
5437
- }
5438
- if (right.value === this.defaultKind) {
5439
- return 1;
5440
- }
5441
- return left.value.localeCompare(right.value);
5442
- });
5943
+ async listSessionTypes() {
5944
+ const options = await Promise.all(
5945
+ [...this.registrations.values()].map(async (registration) => {
5946
+ const descriptor = await registration.describeSessionType?.();
5947
+ return {
5948
+ value: registration.kind,
5949
+ label: registration.label,
5950
+ ready: descriptor?.ready ?? true,
5951
+ reason: descriptor?.reason ?? null,
5952
+ reasonMessage: descriptor?.reasonMessage ?? null,
5953
+ recommendedModel: descriptor?.recommendedModel ?? null,
5954
+ cta: descriptor?.cta ?? null,
5955
+ ...descriptor?.supportedModels ? { supportedModels: descriptor.supportedModels } : {}
5956
+ };
5957
+ })
5958
+ );
5443
5959
  return {
5444
5960
  defaultType: this.defaultKind,
5445
- options
5961
+ options: options.sort((left, right) => {
5962
+ if (left.value === this.defaultKind) {
5963
+ return -1;
5964
+ }
5965
+ if (right.value === this.defaultKind) {
5966
+ return 1;
5967
+ }
5968
+ return left.value.localeCompare(right.value);
5969
+ })
5446
5970
  };
5447
5971
  }
5448
5972
  };
@@ -5548,7 +6072,8 @@ async function createUiNcpAgent(params) {
5548
6072
  scope.add(runtimeRegistry.register({
5549
6073
  kind: registration.kind,
5550
6074
  label: registration.label,
5551
- createRuntime: registration.createRuntime
6075
+ createRuntime: registration.createRuntime,
6076
+ describeSessionType: registration.describeSessionType
5552
6077
  }));
5553
6078
  }
5554
6079
  };
@@ -5595,60 +6120,15 @@ async function createUiNcpAgent(params) {
5595
6120
  };
5596
6121
  }
5597
6122
 
5598
- // src/cli/commands/service-marketplace-helpers.ts
5599
- var containsAbsoluteFsPath = (line) => {
5600
- const normalized = line.trim();
5601
- if (!normalized) {
5602
- return false;
5603
- }
5604
- const lowered = normalized.toLowerCase();
5605
- if (lowered.includes("http://") || lowered.includes("https://")) {
5606
- return false;
5607
- }
5608
- if (/^[A-Za-z]:\\/.test(normalized)) {
5609
- return true;
5610
- }
5611
- return /(?:^|\s)(?:~\/|\/[^\s]+)/.test(normalized);
5612
- };
5613
- var pickUserFacingCommandSummary = (output, fallback) => {
5614
- const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
5615
- if (lines.length === 0) {
5616
- return fallback;
5617
- }
5618
- const visibleLines = lines.filter((line) => {
5619
- if (/^(path|install path|source path|destination|location)\s*:/i.test(line)) {
5620
- return false;
5621
- }
5622
- if (containsAbsoluteFsPath(line)) {
5623
- return false;
5624
- }
5625
- return true;
5626
- });
5627
- if (visibleLines.length === 0) {
5628
- return fallback;
5629
- }
5630
- const preferred = [...visibleLines].reverse().find(
5631
- (line) => /\b(installed|enabled|disabled|uninstalled|published|updated|already installed|removed)\b/i.test(line)
5632
- );
5633
- return preferred ?? visibleLines[visibleLines.length - 1] ?? fallback;
5634
- };
5635
- var buildMarketplaceSkillInstallArgs = (params) => {
5636
- const args = ["skills", "install", params.slug, "--workdir", params.workspace];
5637
- if (params.force) {
5638
- args.push("--force");
5639
- }
5640
- return args;
5641
- };
5642
-
5643
6123
  // src/cli/commands/ui-chat-run-coordinator.ts
5644
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
5645
- import { join as join4 } from "path";
6124
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
6125
+ import { join as join6 } from "path";
5646
6126
  import {
5647
- getDataDir as getDataDir5,
6127
+ getDataDir as getDataDir6,
5648
6128
  parseAgentScopedSessionKey as parseAgentScopedSessionKey2,
5649
6129
  safeFilename
5650
6130
  } from "@nextclaw/core";
5651
- var RUNS_DIR = join4(getDataDir5(), "runs");
6131
+ var RUNS_DIR = join6(getDataDir6(), "runs");
5652
6132
  var NON_TERMINAL_STATES = /* @__PURE__ */ new Set(["queued", "running"]);
5653
6133
  var DEFAULT_SESSION_TYPE = "native";
5654
6134
  var SESSION_TYPE_METADATA_KEY = "session_type";
@@ -5707,7 +6187,7 @@ function hasToolSessionEvent(run) {
5707
6187
  var UiChatRunCoordinator = class {
5708
6188
  constructor(options) {
5709
6189
  this.options = options;
5710
- mkdirSync4(RUNS_DIR, { recursive: true });
6190
+ mkdirSync5(RUNS_DIR, { recursive: true });
5711
6191
  this.loadPersistedRuns();
5712
6192
  }
5713
6193
  runs = /* @__PURE__ */ new Map();
@@ -6174,7 +6654,7 @@ var UiChatRunCoordinator = class {
6174
6654
  };
6175
6655
  }
6176
6656
  getRunPath(runId) {
6177
- return join4(RUNS_DIR, `${safeFilename(runId)}.json`);
6657
+ return join6(RUNS_DIR, `${safeFilename(runId)}.json`);
6178
6658
  }
6179
6659
  persistRun(run) {
6180
6660
  const persisted = {
@@ -6192,20 +6672,20 @@ var UiChatRunCoordinator = class {
6192
6672
  ...typeof run.reply === "string" ? { reply: run.reply } : {},
6193
6673
  events: run.events
6194
6674
  };
6195
- writeFileSync4(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
6675
+ writeFileSync5(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
6196
6676
  `);
6197
6677
  }
6198
6678
  loadPersistedRuns() {
6199
- if (!existsSync8(RUNS_DIR)) {
6679
+ if (!existsSync10(RUNS_DIR)) {
6200
6680
  return;
6201
6681
  }
6202
6682
  for (const entry of readdirSync2(RUNS_DIR, { withFileTypes: true })) {
6203
6683
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
6204
6684
  continue;
6205
6685
  }
6206
- const path2 = join4(RUNS_DIR, entry.name);
6686
+ const path2 = join6(RUNS_DIR, entry.name);
6207
6687
  try {
6208
- const parsed = JSON.parse(readFileSync8(path2, "utf-8"));
6688
+ const parsed = JSON.parse(readFileSync9(path2, "utf-8"));
6209
6689
  const runId = readOptionalString(parsed.runId);
6210
6690
  const sessionKey = readOptionalString(parsed.sessionKey);
6211
6691
  if (!runId || !sessionKey) {
@@ -6264,18 +6744,18 @@ var {
6264
6744
  ChannelManager: ChannelManager2,
6265
6745
  CronService: CronService2,
6266
6746
  getApiBase,
6267
- getConfigPath: getConfigPath3,
6268
- getDataDir: getDataDir6,
6747
+ getConfigPath: getConfigPath4,
6748
+ getDataDir: getDataDir7,
6269
6749
  getProvider,
6270
6750
  getProviderName,
6271
- getWorkspacePath: getWorkspacePath8,
6751
+ getWorkspacePath: getWorkspacePath9,
6272
6752
  HeartbeatService,
6273
6753
  LiteLLMProvider,
6274
- loadConfig: loadConfig8,
6754
+ loadConfig: loadConfig11,
6275
6755
  MessageBus,
6276
6756
  ProviderManager,
6277
6757
  resolveConfigSecrets: resolveConfigSecrets2,
6278
- saveConfig: saveConfig6,
6758
+ saveConfig: saveConfig7,
6279
6759
  SessionManager,
6280
6760
  parseAgentScopedSessionKey: parseAgentScopedSessionKey3
6281
6761
  } = NextclawCore;
@@ -6295,9 +6775,9 @@ var ServiceCommands = class {
6295
6775
  async startGateway(options = {}) {
6296
6776
  this.applyLiveConfigReload = null;
6297
6777
  this.liveUiNcpAgent = null;
6298
- const runtimeConfigPath = getConfigPath3();
6299
- const config2 = resolveConfigSecrets2(loadConfig8(), { configPath: runtimeConfigPath });
6300
- const workspace = getWorkspacePath8(config2.agents.defaults.workspace);
6778
+ const runtimeConfigPath = getConfigPath4();
6779
+ const config2 = resolveConfigSecrets2(loadConfig11(), { configPath: runtimeConfigPath });
6780
+ const workspace = getWorkspacePath9(config2.agents.defaults.workspace);
6301
6781
  let pluginRegistry = loadPluginRegistry(config2, workspace);
6302
6782
  let extensionRegistry = toExtensionRegistry(pluginRegistry);
6303
6783
  logPluginDiagnostics(pluginRegistry);
@@ -6326,7 +6806,7 @@ var ServiceCommands = class {
6326
6806
  }
6327
6807
  }
6328
6808
  };
6329
- const cronStorePath = join5(getDataDir6(), "cron", "jobs.json");
6809
+ const cronStorePath = join7(getDataDir7(), "cron", "jobs.json");
6330
6810
  const cron2 = new CronService2(cronStorePath);
6331
6811
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
6332
6812
  const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
@@ -6341,7 +6821,7 @@ var ServiceCommands = class {
6341
6821
  sessionManager,
6342
6822
  providerManager,
6343
6823
  makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }) ?? this.makeMissingProvider(nextConfig),
6344
- loadConfig: () => resolveConfigSecrets2(loadConfig8(), { configPath: runtimeConfigPath }),
6824
+ loadConfig: () => resolveConfigSecrets2(loadConfig11(), { configPath: runtimeConfigPath }),
6345
6825
  getExtensionChannels: () => extensionRegistry.channels,
6346
6826
  onRestartRequired: (paths) => {
6347
6827
  void this.deps.requestRestart({
@@ -6352,14 +6832,14 @@ var ServiceCommands = class {
6352
6832
  }
6353
6833
  });
6354
6834
  this.applyLiveConfigReload = async () => {
6355
- await reloader.applyReloadPlan(resolveConfigSecrets2(loadConfig8(), { configPath: runtimeConfigPath }));
6835
+ await reloader.applyReloadPlan(resolveConfigSecrets2(loadConfig11(), { configPath: runtimeConfigPath }));
6356
6836
  };
6357
6837
  const gatewayController = new GatewayControllerImpl({
6358
6838
  reloader,
6359
6839
  cron: cron2,
6360
6840
  sessionManager,
6361
- getConfigPath: getConfigPath3,
6362
- saveConfig: saveConfig6,
6841
+ getConfigPath: getConfigPath4,
6842
+ saveConfig: saveConfig7,
6363
6843
  requestRestart: async (options2) => {
6364
6844
  await this.deps.requestRestart({
6365
6845
  reason: options2?.reason ?? "gateway tool restart",
@@ -6385,7 +6865,7 @@ var ServiceCommands = class {
6385
6865
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
6386
6866
  registry: pluginRegistry,
6387
6867
  channel,
6388
- cfg: resolveConfigSecrets2(loadConfig8(), { configPath: runtimeConfigPath }),
6868
+ cfg: resolveConfigSecrets2(loadConfig11(), { configPath: runtimeConfigPath }),
6389
6869
  accountId
6390
6870
  })
6391
6871
  });
@@ -6418,14 +6898,14 @@ var ServiceCommands = class {
6418
6898
  });
6419
6899
  let pluginChannelBindings = getPluginChannelBindings3(pluginRegistry);
6420
6900
  setPluginRuntimeBridge({
6421
- loadConfig: () => toPluginConfigView(resolveConfigSecrets2(loadConfig8(), { configPath: runtimeConfigPath }), pluginChannelBindings),
6901
+ loadConfig: () => toPluginConfigView(resolveConfigSecrets2(loadConfig11(), { configPath: runtimeConfigPath }), pluginChannelBindings),
6422
6902
  writeConfigFile: async (nextConfigView) => {
6423
6903
  if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
6424
6904
  throw new Error("plugin runtime writeConfigFile expects an object config");
6425
6905
  }
6426
- const current = loadConfig8();
6906
+ const current = loadConfig11();
6427
6907
  const next = mergePluginConfigView(current, nextConfigView, pluginChannelBindings);
6428
- saveConfig6(next);
6908
+ saveConfig7(next);
6429
6909
  },
6430
6910
  dispatchReplyWithBufferedBlockDispatcher: async ({ ctx, dispatcherOptions }) => {
6431
6911
  const bodyForAgent = typeof ctx.BodyForAgent === "string" ? ctx.BodyForAgent : "";
@@ -6499,12 +6979,12 @@ var ServiceCommands = class {
6499
6979
  providerManager,
6500
6980
  bus,
6501
6981
  gatewayController,
6502
- () => resolveConfigSecrets2(loadConfig8(), { configPath: runtimeConfigPath }),
6982
+ () => resolveConfigSecrets2(loadConfig11(), { configPath: runtimeConfigPath }),
6503
6983
  () => extensionRegistry,
6504
6984
  ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
6505
6985
  registry: pluginRegistry,
6506
6986
  channel,
6507
- cfg: resolveConfigSecrets2(loadConfig8(), { configPath: runtimeConfigPath }),
6987
+ cfg: resolveConfigSecrets2(loadConfig11(), { configPath: runtimeConfigPath }),
6508
6988
  accountId
6509
6989
  })
6510
6990
  );
@@ -6541,7 +7021,7 @@ var ServiceCommands = class {
6541
7021
  return trimmed || void 0;
6542
7022
  }
6543
7023
  watchConfigFile(reloader) {
6544
- const configPath = resolve10(getConfigPath3());
7024
+ const configPath = resolve10(getConfigPath4());
6545
7025
  const watcher = chokidar.watch(configPath, {
6546
7026
  ignoreInitial: true,
6547
7027
  awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
@@ -6662,7 +7142,7 @@ var ServiceCommands = class {
6662
7142
  });
6663
7143
  }
6664
7144
  async runForeground(options) {
6665
- const config2 = loadConfig8();
7145
+ const config2 = loadConfig11();
6666
7146
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
6667
7147
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
6668
7148
  if (options.open) {
@@ -6675,7 +7155,7 @@ var ServiceCommands = class {
6675
7155
  });
6676
7156
  }
6677
7157
  async startService(options) {
6678
- const config2 = loadConfig8();
7158
+ const config2 = loadConfig11();
6679
7159
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
6680
7160
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
6681
7161
  const apiUrl = `${uiUrl}/api`;
@@ -6736,7 +7216,7 @@ var ServiceCommands = class {
6736
7216
  }
6737
7217
  const logPath = resolveServiceLogPath();
6738
7218
  const logDir = resolve10(logPath, "..");
6739
- mkdirSync5(logDir, { recursive: true });
7219
+ mkdirSync6(logDir, { recursive: true });
6740
7220
  const logFd = openSync(logPath, "a");
6741
7221
  const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
6742
7222
  const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
@@ -7089,7 +7569,7 @@ var ServiceCommands = class {
7089
7569
  return null;
7090
7570
  }
7091
7571
  console.error("Error: No API key configured.");
7092
- console.error(`Set one in ${getConfigPath3()} under providers section`);
7572
+ console.error(`Set one in ${getConfigPath4()} under providers section`);
7093
7573
  process.exit(1);
7094
7574
  }
7095
7575
  return new LiteLLMProvider({
@@ -7208,23 +7688,21 @@ var ServiceCommands = class {
7208
7688
  })
7209
7689
  });
7210
7690
  this.liveUiNcpAgent = ncpAgent;
7691
+ const marketplaceInstaller = new ServiceMarketplaceInstaller({
7692
+ applyLiveConfigReload: this.applyLiveConfigReload ?? void 0,
7693
+ runCliSubcommand: (args) => this.runCliSubcommand(args),
7694
+ installBuiltinSkill: (slug, force) => this.installBuiltinMarketplaceSkill(slug, force)
7695
+ }).createInstaller();
7211
7696
  const uiServer = startUiServer({
7212
7697
  host: uiConfig.host,
7213
7698
  port: uiConfig.port,
7214
- configPath: getConfigPath3(),
7699
+ configPath: getConfigPath4(),
7215
7700
  productVersion: getPackageVersion(),
7216
7701
  staticDir: uiStaticDir ?? void 0,
7217
7702
  cronService,
7218
7703
  marketplace: {
7219
7704
  apiBaseUrl: process.env.NEXTCLAW_MARKETPLACE_API_BASE,
7220
- installer: {
7221
- installPlugin: (spec) => this.installMarketplacePlugin(spec),
7222
- installSkill: (params) => this.installMarketplaceSkill(params),
7223
- enablePlugin: (id) => this.enableMarketplacePlugin(id),
7224
- disablePlugin: (id) => this.disableMarketplacePlugin(id),
7225
- uninstallPlugin: (id) => this.uninstallMarketplacePlugin(id),
7226
- uninstallSkill: (slug) => this.uninstallMarketplaceSkill(slug)
7227
- }
7705
+ installer: marketplaceInstaller
7228
7706
  },
7229
7707
  ncpAgent,
7230
7708
  chatRuntime: {
@@ -7305,73 +7783,11 @@ var ServiceCommands = class {
7305
7783
  openBrowser(uiUrl);
7306
7784
  }
7307
7785
  }
7308
- async installMarketplacePlugin(spec) {
7309
- const result = await installPluginMutation(spec);
7310
- await this.applyLiveConfigReload?.();
7311
- return { message: result.message };
7312
- }
7313
- async installMarketplaceSkill(params) {
7314
- if (params.kind === "builtin") {
7315
- const result = this.installBuiltinMarketplaceSkill(params.slug, params.force);
7316
- if (!result) {
7317
- throw new Error(`Builtin skill not found: ${params.slug}`);
7318
- }
7319
- return result;
7320
- }
7321
- if (params.kind && params.kind !== "marketplace") {
7322
- throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
7323
- }
7324
- const workspace = getWorkspacePath8(loadConfig8().agents.defaults.workspace);
7325
- const args = buildMarketplaceSkillInstallArgs({
7326
- slug: params.slug,
7327
- workspace,
7328
- force: params.force
7329
- });
7330
- try {
7331
- const output = await this.runCliSubcommand(args);
7332
- const summary = pickUserFacingCommandSummary(output, `Installed skill: ${params.slug}`);
7333
- return { message: summary };
7334
- } catch (error) {
7335
- const fallback = this.installBuiltinMarketplaceSkill(params.slug, params.force);
7336
- if (!fallback) {
7337
- throw error;
7338
- }
7339
- return fallback;
7340
- }
7341
- }
7342
- async enableMarketplacePlugin(id) {
7343
- const result = await enablePluginMutation(id);
7344
- await this.applyLiveConfigReload?.();
7345
- return { message: result.message };
7346
- }
7347
- async disableMarketplacePlugin(id) {
7348
- const result = await disablePluginMutation(id);
7349
- await this.applyLiveConfigReload?.();
7350
- return { message: result.message };
7351
- }
7352
- async uninstallMarketplacePlugin(id) {
7353
- await disablePluginMutation(id);
7354
- await this.applyLiveConfigReload?.();
7355
- const result = await uninstallPluginMutation(id, { force: true });
7356
- await this.applyLiveConfigReload?.();
7357
- return { message: result.message };
7358
- }
7359
- async uninstallMarketplaceSkill(slug) {
7360
- const workspace = getWorkspacePath8(loadConfig8().agents.defaults.workspace);
7361
- const targetDir = join5(workspace, "skills", slug);
7362
- if (!existsSync9(targetDir)) {
7363
- throw new Error(`Skill not installed in workspace: ${slug}`);
7364
- }
7365
- rmSync4(targetDir, { recursive: true, force: true });
7366
- return {
7367
- message: `Uninstalled skill: ${slug}`
7368
- };
7369
- }
7370
7786
  installBuiltinMarketplaceSkill(slug, force) {
7371
- const workspace = getWorkspacePath8(loadConfig8().agents.defaults.workspace);
7372
- const destination = join5(workspace, "skills", slug);
7373
- const destinationSkillFile = join5(destination, "SKILL.md");
7374
- if (existsSync9(destinationSkillFile) && !force) {
7787
+ const workspace = getWorkspacePath9(loadConfig11().agents.defaults.workspace);
7788
+ const destination = join7(workspace, "skills", slug);
7789
+ const destinationSkillFile = join7(destination, "SKILL.md");
7790
+ if (existsSync11(destinationSkillFile) && !force) {
7375
7791
  return {
7376
7792
  message: `${slug} is already installed`
7377
7793
  };
@@ -7379,15 +7795,15 @@ var ServiceCommands = class {
7379
7795
  const loader = createSkillsLoader(workspace);
7380
7796
  const builtin = (loader?.listSkills(false) ?? []).find((skill) => skill.name === slug && skill.source === "builtin");
7381
7797
  if (!builtin) {
7382
- if (existsSync9(destinationSkillFile)) {
7798
+ if (existsSync11(destinationSkillFile)) {
7383
7799
  return {
7384
7800
  message: `${slug} is already installed`
7385
7801
  };
7386
7802
  }
7387
7803
  return null;
7388
7804
  }
7389
- mkdirSync5(join5(workspace, "skills"), { recursive: true });
7390
- cpSync2(dirname2(builtin.path), destination, { recursive: true, force: true });
7805
+ mkdirSync6(join7(workspace, "skills"), { recursive: true });
7806
+ cpSync2(dirname3(builtin.path), destination, { recursive: true, force: true });
7391
7807
  return {
7392
7808
  message: `Installed skill: ${slug}`
7393
7809
  };
@@ -7446,11 +7862,11 @@ ${stderr}`.trim();
7446
7862
  };
7447
7863
 
7448
7864
  // src/cli/workspace.ts
7449
- import { cpSync as cpSync3, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
7865
+ import { cpSync as cpSync3, existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
7450
7866
  import { createRequire as createRequire2 } from "module";
7451
- import { dirname as dirname3, join as join6, resolve as resolve11 } from "path";
7867
+ import { dirname as dirname4, join as join8, resolve as resolve11 } from "path";
7452
7868
  import { fileURLToPath as fileURLToPath3 } from "url";
7453
- import { APP_NAME as APP_NAME3, getDataDir as getDataDir7 } from "@nextclaw/core";
7869
+ import { APP_NAME as APP_NAME3, getDataDir as getDataDir8 } from "@nextclaw/core";
7454
7870
  import { spawnSync as spawnSync3 } from "child_process";
7455
7871
  var WorkspaceManager = class {
7456
7872
  constructor(logo) {
@@ -7478,30 +7894,30 @@ var WorkspaceManager = class {
7478
7894
  { source: "memory/MEMORY.md", target: "memory/MEMORY.md" }
7479
7895
  ];
7480
7896
  for (const entry of templateFiles) {
7481
- const filePath = join6(workspace, entry.target);
7482
- if (!force && existsSync10(filePath)) {
7897
+ const filePath = join8(workspace, entry.target);
7898
+ if (!force && existsSync12(filePath)) {
7483
7899
  continue;
7484
7900
  }
7485
- const templatePath = join6(templateDir, entry.source);
7486
- if (!existsSync10(templatePath)) {
7901
+ const templatePath = join8(templateDir, entry.source);
7902
+ if (!existsSync12(templatePath)) {
7487
7903
  console.warn(`Warning: Template file missing: ${templatePath}`);
7488
7904
  continue;
7489
7905
  }
7490
- const raw = readFileSync9(templatePath, "utf-8");
7906
+ const raw = readFileSync10(templatePath, "utf-8");
7491
7907
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
7492
- mkdirSync6(dirname3(filePath), { recursive: true });
7493
- writeFileSync5(filePath, content);
7908
+ mkdirSync7(dirname4(filePath), { recursive: true });
7909
+ writeFileSync6(filePath, content);
7494
7910
  created.push(entry.target);
7495
7911
  }
7496
- const memoryDir = join6(workspace, "memory");
7497
- if (!existsSync10(memoryDir)) {
7498
- mkdirSync6(memoryDir, { recursive: true });
7499
- created.push(join6("memory", ""));
7912
+ const memoryDir = join8(workspace, "memory");
7913
+ if (!existsSync12(memoryDir)) {
7914
+ mkdirSync7(memoryDir, { recursive: true });
7915
+ created.push(join8("memory", ""));
7500
7916
  }
7501
- const skillsDir = join6(workspace, "skills");
7502
- if (!existsSync10(skillsDir)) {
7503
- mkdirSync6(skillsDir, { recursive: true });
7504
- created.push(join6("skills", ""));
7917
+ const skillsDir = join8(workspace, "skills");
7918
+ if (!existsSync12(skillsDir)) {
7919
+ mkdirSync7(skillsDir, { recursive: true });
7920
+ created.push(join8("skills", ""));
7505
7921
  }
7506
7922
  const seeded = this.seedBuiltinSkills(skillsDir, { force });
7507
7923
  if (seeded > 0) {
@@ -7520,12 +7936,12 @@ var WorkspaceManager = class {
7520
7936
  if (!entry.isDirectory()) {
7521
7937
  continue;
7522
7938
  }
7523
- const src = join6(sourceDir, entry.name);
7524
- if (!existsSync10(join6(src, "SKILL.md"))) {
7939
+ const src = join8(sourceDir, entry.name);
7940
+ if (!existsSync12(join8(src, "SKILL.md"))) {
7525
7941
  continue;
7526
7942
  }
7527
- const dest = join6(targetDir, entry.name);
7528
- if (!force && existsSync10(dest)) {
7943
+ const dest = join8(targetDir, entry.name);
7944
+ if (!force && existsSync12(dest)) {
7529
7945
  continue;
7530
7946
  }
7531
7947
  try {
@@ -7542,13 +7958,13 @@ var WorkspaceManager = class {
7542
7958
  try {
7543
7959
  const require3 = createRequire2(import.meta.url);
7544
7960
  const entry = require3.resolve("@nextclaw/core");
7545
- const pkgRoot = resolve11(dirname3(entry), "..");
7546
- const distSkills = join6(pkgRoot, "dist", "skills");
7547
- if (existsSync10(distSkills)) {
7961
+ const pkgRoot = resolve11(dirname4(entry), "..");
7962
+ const distSkills = join8(pkgRoot, "dist", "skills");
7963
+ if (existsSync12(distSkills)) {
7548
7964
  return distSkills;
7549
7965
  }
7550
- const srcSkills = join6(pkgRoot, "src", "agent", "skills");
7551
- if (existsSync10(srcSkills)) {
7966
+ const srcSkills = join8(pkgRoot, "src", "agent", "skills");
7967
+ if (existsSync12(srcSkills)) {
7552
7968
  return srcSkills;
7553
7969
  }
7554
7970
  return null;
@@ -7563,17 +7979,17 @@ var WorkspaceManager = class {
7563
7979
  }
7564
7980
  const cliDir = resolve11(fileURLToPath3(new URL(".", import.meta.url)));
7565
7981
  const pkgRoot = resolve11(cliDir, "..", "..");
7566
- const candidates = [join6(pkgRoot, "templates")];
7982
+ const candidates = [join8(pkgRoot, "templates")];
7567
7983
  for (const candidate of candidates) {
7568
- if (existsSync10(candidate)) {
7984
+ if (existsSync12(candidate)) {
7569
7985
  return candidate;
7570
7986
  }
7571
7987
  }
7572
7988
  return null;
7573
7989
  }
7574
7990
  getBridgeDir() {
7575
- const userBridge = join6(getDataDir7(), "bridge");
7576
- if (existsSync10(join6(userBridge, "dist", "index.js"))) {
7991
+ const userBridge = join8(getDataDir8(), "bridge");
7992
+ if (existsSync12(join8(userBridge, "dist", "index.js"))) {
7577
7993
  return userBridge;
7578
7994
  }
7579
7995
  if (!which("npm")) {
@@ -7582,12 +7998,12 @@ var WorkspaceManager = class {
7582
7998
  }
7583
7999
  const cliDir = resolve11(fileURLToPath3(new URL(".", import.meta.url)));
7584
8000
  const pkgRoot = resolve11(cliDir, "..", "..");
7585
- const pkgBridge = join6(pkgRoot, "bridge");
7586
- const srcBridge = join6(pkgRoot, "..", "..", "bridge");
8001
+ const pkgBridge = join8(pkgRoot, "bridge");
8002
+ const srcBridge = join8(pkgRoot, "..", "..", "bridge");
7587
8003
  let source = null;
7588
- if (existsSync10(join6(pkgBridge, "package.json"))) {
8004
+ if (existsSync12(join8(pkgBridge, "package.json"))) {
7589
8005
  source = pkgBridge;
7590
- } else if (existsSync10(join6(srcBridge, "package.json"))) {
8006
+ } else if (existsSync12(join8(srcBridge, "package.json"))) {
7591
8007
  source = srcBridge;
7592
8008
  }
7593
8009
  if (!source) {
@@ -7595,8 +8011,8 @@ var WorkspaceManager = class {
7595
8011
  process.exit(1);
7596
8012
  }
7597
8013
  console.log(`${this.logo} Setting up bridge...`);
7598
- mkdirSync6(resolve11(userBridge, ".."), { recursive: true });
7599
- if (existsSync10(userBridge)) {
8014
+ mkdirSync7(resolve11(userBridge, ".."), { recursive: true });
8015
+ if (existsSync12(userBridge)) {
7600
8016
  rmSync5(userBridge, { recursive: true, force: true });
7601
8017
  }
7602
8018
  cpSync3(source, userBridge, {
@@ -7632,7 +8048,7 @@ function resolveSkillsInstallWorkdir(params) {
7632
8048
  if (params.explicitWorkdir) {
7633
8049
  return expandHome2(params.explicitWorkdir);
7634
8050
  }
7635
- return getWorkspacePath9(params.configuredWorkspace);
8051
+ return getWorkspacePath10(params.configuredWorkspace);
7636
8052
  }
7637
8053
  var CliRuntime = class {
7638
8054
  logo;
@@ -7647,6 +8063,7 @@ var CliRuntime = class {
7647
8063
  pluginCommands;
7648
8064
  channelCommands;
7649
8065
  cronCommands;
8066
+ remoteCommands;
7650
8067
  diagnosticsCommands;
7651
8068
  constructor(options = {}) {
7652
8069
  this.logo = options.logo ?? LOGO;
@@ -7668,6 +8085,7 @@ var CliRuntime = class {
7668
8085
  requestRestart: (params) => this.requestRestart(params)
7669
8086
  });
7670
8087
  this.cronCommands = new CronCommands();
8088
+ this.remoteCommands = new RemoteCommands();
7671
8089
  this.diagnosticsCommands = new DiagnosticsCommands({ logo: this.logo });
7672
8090
  this.restartCoordinator = new RestartCoordinator({
7673
8091
  readServiceState,
@@ -7733,7 +8151,7 @@ var CliRuntime = class {
7733
8151
  const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
7734
8152
  const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath4(new URL("./index.js", import.meta.url));
7735
8153
  const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
7736
- const serviceStatePath = resolve12(getDataDir8(), "run", "service.json");
8154
+ const serviceStatePath = resolve12(getDataDir9(), "run", "service.json");
7737
8155
  const helperScript = [
7738
8156
  'const { spawnSync } = require("node:child_process");',
7739
8157
  'const { readFileSync } = require("node:fs");',
@@ -7861,18 +8279,18 @@ var CliRuntime = class {
7861
8279
  const source = options.source ?? "init";
7862
8280
  const prefix = options.auto ? "Auto init" : "Init";
7863
8281
  const force = Boolean(options.force);
7864
- const configPath = getConfigPath4();
8282
+ const configPath = getConfigPath5();
7865
8283
  let createdConfig = false;
7866
- if (!existsSync11(configPath)) {
8284
+ if (!existsSync13(configPath)) {
7867
8285
  const config3 = ConfigSchema2.parse({});
7868
- saveConfig7(config3);
8286
+ saveConfig8(config3);
7869
8287
  createdConfig = true;
7870
8288
  }
7871
- const config2 = loadConfig9();
8289
+ const config2 = loadConfig12();
7872
8290
  const workspaceSetting = config2.agents.defaults.workspace;
7873
- const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join7(getDataDir8(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
7874
- const workspaceExisted = existsSync11(workspacePath);
7875
- mkdirSync7(workspacePath, { recursive: true });
8291
+ const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join9(getDataDir9(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
8292
+ const workspaceExisted = existsSync13(workspacePath);
8293
+ mkdirSync8(workspacePath, { recursive: true });
7876
8294
  const templateResult = this.workspaceManager.createWorkspaceTemplates(
7877
8295
  workspacePath,
7878
8296
  { force }
@@ -7903,8 +8321,8 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
7903
8321
  }
7904
8322
  async login(opts = {}) {
7905
8323
  await this.init({ source: "login", auto: true });
7906
- const configPath = getConfigPath4();
7907
- const config2 = loadConfig9(configPath);
8324
+ const configPath = getConfigPath5();
8325
+ const config2 = loadConfig12(configPath);
7908
8326
  const providers = config2.providers;
7909
8327
  const nextclawProvider = providers.nextclaw ?? {
7910
8328
  displayName: "",
@@ -7966,11 +8384,14 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
7966
8384
  nextclawProvider.apiBase = v1Base;
7967
8385
  nextclawProvider.apiKey = token;
7968
8386
  providers.nextclaw = nextclawProvider;
7969
- saveConfig7(config2, configPath);
8387
+ saveConfig8(config2, configPath);
7970
8388
  console.log(`\u2713 Logged in to NextClaw platform (${platformBase})`);
7971
8389
  console.log(`\u2713 Account: ${email} (${role})`);
7972
8390
  console.log(`\u2713 Token saved into providers.nextclaw.apiKey`);
7973
8391
  }
8392
+ async remoteConnect(opts = {}) {
8393
+ await this.remoteCommands.connect(opts);
8394
+ }
7974
8395
  async gateway(opts) {
7975
8396
  const uiOverrides = {
7976
8397
  host: FORCED_PUBLIC_UI_HOST
@@ -8060,16 +8481,16 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8060
8481
  await this.serviceCommands.stopService();
8061
8482
  }
8062
8483
  async agent(opts) {
8063
- const configPath = getConfigPath4();
8064
- const config2 = resolveConfigSecrets3(loadConfig9(), { configPath });
8065
- const workspace = getWorkspacePath9(config2.agents.defaults.workspace);
8484
+ const configPath = getConfigPath5();
8485
+ const config2 = resolveConfigSecrets3(loadConfig12(), { configPath });
8486
+ const workspace = getWorkspacePath10(config2.agents.defaults.workspace);
8066
8487
  const pluginRegistry = loadPluginRegistry(config2, workspace);
8067
8488
  const extensionRegistry = toExtensionRegistry(pluginRegistry);
8068
8489
  logPluginDiagnostics(pluginRegistry);
8069
8490
  const pluginChannelBindings = getPluginChannelBindings4(pluginRegistry);
8070
8491
  setPluginRuntimeBridge2({
8071
8492
  loadConfig: () => toPluginConfigView(
8072
- resolveConfigSecrets3(loadConfig9(), { configPath }),
8493
+ resolveConfigSecrets3(loadConfig12(), { configPath }),
8073
8494
  pluginChannelBindings
8074
8495
  ),
8075
8496
  writeConfigFile: async (nextConfigView) => {
@@ -8078,13 +8499,13 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8078
8499
  "plugin runtime writeConfigFile expects an object config"
8079
8500
  );
8080
8501
  }
8081
- const current = loadConfig9();
8502
+ const current = loadConfig12();
8082
8503
  const next = mergePluginConfigView(
8083
8504
  current,
8084
8505
  nextConfigView,
8085
8506
  pluginChannelBindings
8086
8507
  );
8087
- saveConfig7(next);
8508
+ saveConfig8(next);
8088
8509
  }
8089
8510
  });
8090
8511
  try {
@@ -8110,7 +8531,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8110
8531
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints2({
8111
8532
  registry: pluginRegistry,
8112
8533
  channel,
8113
- cfg: resolveConfigSecrets3(loadConfig9(), { configPath }),
8534
+ cfg: resolveConfigSecrets3(loadConfig12(), { configPath }),
8114
8535
  accountId
8115
8536
  })
8116
8537
  });
@@ -8129,10 +8550,10 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8129
8550
  `${this.logo} Interactive mode (type exit or Ctrl+C to quit)
8130
8551
  `
8131
8552
  );
8132
- const historyFile = join7(getDataDir8(), "history", "cli_history");
8553
+ const historyFile = join9(getDataDir9(), "history", "cli_history");
8133
8554
  const historyDir = resolve12(historyFile, "..");
8134
- mkdirSync7(historyDir, { recursive: true });
8135
- const history = existsSync11(historyFile) ? readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean) : [];
8555
+ mkdirSync8(historyDir, { recursive: true });
8556
+ const history = existsSync13(historyFile) ? readFileSync11(historyFile, "utf-8").split("\n").filter(Boolean) : [];
8136
8557
  const rl = createInterface2({
8137
8558
  input: process.stdin,
8138
8559
  output: process.stdout
@@ -8141,7 +8562,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8141
8562
  const merged = history.concat(
8142
8563
  rl.history ?? []
8143
8564
  );
8144
- writeFileSync6(historyFile, merged.join("\n"));
8565
+ writeFileSync7(historyFile, merged.join("\n"));
8145
8566
  process.exit(0);
8146
8567
  });
8147
8568
  let running = true;
@@ -8305,7 +8726,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
8305
8726
  await this.diagnosticsCommands.doctor(opts);
8306
8727
  }
8307
8728
  async skillsInstall(options) {
8308
- const config2 = loadConfig9();
8729
+ const config2 = loadConfig12();
8309
8730
  const workdir = resolveSkillsInstallWorkdir({
8310
8731
  explicitWorkdir: options.workdir,
8311
8732
  configuredWorkspace: config2.agents.defaults.workspace
@@ -8373,6 +8794,8 @@ program.name(APP_NAME5).description(`${LOGO} ${APP_NAME5} - ${APP_TAGLINE}`).ver
8373
8794
  program.command("onboard").description(`Initialize ${APP_NAME5} configuration and workspace`).action(async () => runtime.onboard());
8374
8795
  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) }));
8375
8796
  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));
8797
+ var remote = program.command("remote").description("Manage remote access");
8798
+ 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));
8376
8799
  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));
8377
8800
  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));
8378
8801
  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));