nextclaw 0.4.15 → 0.4.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
MessageBus,
|
|
22
22
|
AgentLoop,
|
|
23
23
|
LiteLLMProvider,
|
|
24
|
+
LLMProvider,
|
|
24
25
|
ProviderManager,
|
|
25
26
|
ChannelManager,
|
|
26
27
|
SessionManager,
|
|
@@ -81,7 +82,7 @@ import {
|
|
|
81
82
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
82
83
|
import { join, resolve } from "path";
|
|
83
84
|
import { spawn } from "child_process";
|
|
84
|
-
import {
|
|
85
|
+
import { isIP } from "net";
|
|
85
86
|
import { fileURLToPath } from "url";
|
|
86
87
|
import { getDataDir, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
|
|
87
88
|
function resolveUiConfig(config2, overrides) {
|
|
@@ -127,48 +128,9 @@ async function resolvePublicIp(timeoutMs = 1500) {
|
|
|
127
128
|
}
|
|
128
129
|
return null;
|
|
129
130
|
}
|
|
130
|
-
function isDevRuntime() {
|
|
131
|
-
return import.meta.url.includes("/src/cli/") || process.env.NEXTCLAW_DEV === "1";
|
|
132
|
-
}
|
|
133
|
-
function normalizeHostForPortCheck(host) {
|
|
134
|
-
return host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
135
|
-
}
|
|
136
|
-
async function findAvailablePort(port, host, attempts = 20) {
|
|
137
|
-
const basePort = Number.isFinite(port) ? port : 0;
|
|
138
|
-
let candidate = basePort;
|
|
139
|
-
for (let i = 0; i < attempts; i += 1) {
|
|
140
|
-
const ok = await isPortAvailable(candidate, host);
|
|
141
|
-
if (ok) {
|
|
142
|
-
return candidate;
|
|
143
|
-
}
|
|
144
|
-
candidate += 1;
|
|
145
|
-
}
|
|
146
|
-
return basePort;
|
|
147
|
-
}
|
|
148
|
-
async function isPortAvailable(port, host) {
|
|
149
|
-
const checkHost = normalizeHostForPortCheck(host);
|
|
150
|
-
return await canBindPort(port, checkHost);
|
|
151
|
-
}
|
|
152
|
-
async function canBindPort(port, host) {
|
|
153
|
-
return await new Promise((resolve5) => {
|
|
154
|
-
const server = createServer();
|
|
155
|
-
server.unref();
|
|
156
|
-
server.once("error", () => resolve5(false));
|
|
157
|
-
server.listen({ port, host }, () => {
|
|
158
|
-
server.close(() => resolve5(true));
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
131
|
function buildServeArgs(options) {
|
|
163
132
|
const cliPath = fileURLToPath(new URL("./index.js", import.meta.url));
|
|
164
|
-
|
|
165
|
-
if (options.frontend) {
|
|
166
|
-
args.push("--frontend");
|
|
167
|
-
}
|
|
168
|
-
if (Number.isFinite(options.frontendPort)) {
|
|
169
|
-
args.push("--frontend-port", String(options.frontendPort));
|
|
170
|
-
}
|
|
171
|
-
return args;
|
|
133
|
+
return [cliPath, "serve", "--ui-host", options.uiHost, "--ui-port", String(options.uiPort)];
|
|
172
134
|
}
|
|
173
135
|
function readServiceState() {
|
|
174
136
|
const path = resolveServiceStatePath();
|
|
@@ -297,64 +259,6 @@ function getPackageVersion() {
|
|
|
297
259
|
const cliDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
298
260
|
return resolveVersionFromPackageTree(cliDir, "nextclaw") ?? resolveVersionFromPackageTree(cliDir) ?? getCorePackageVersion();
|
|
299
261
|
}
|
|
300
|
-
function startUiFrontend(options) {
|
|
301
|
-
const uiDir = options.dir ?? resolveUiFrontendDir();
|
|
302
|
-
if (!uiDir) {
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
const runner = resolveUiFrontendRunner();
|
|
306
|
-
if (!runner) {
|
|
307
|
-
console.log("Warning: pnpm/npm not found. Skipping UI frontend.");
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
const args = [...runner.args];
|
|
311
|
-
if (options.port) {
|
|
312
|
-
if (runner.useArgSeparator) {
|
|
313
|
-
args.push("--");
|
|
314
|
-
}
|
|
315
|
-
args.push("--port", String(options.port));
|
|
316
|
-
}
|
|
317
|
-
const env = { ...process.env, VITE_API_BASE: options.apiBase };
|
|
318
|
-
const child = spawn(runner.cmd, args, { cwd: uiDir, stdio: "inherit", env });
|
|
319
|
-
child.on("exit", (code) => {
|
|
320
|
-
if (code && code !== 0) {
|
|
321
|
-
console.log(`UI frontend exited with code ${code}`);
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
const url = `http://127.0.0.1:${options.port}`;
|
|
325
|
-
console.log(`\u2713 UI frontend: ${url}`);
|
|
326
|
-
return { url, dir: uiDir };
|
|
327
|
-
}
|
|
328
|
-
function resolveUiFrontendRunner() {
|
|
329
|
-
if (which("pnpm")) {
|
|
330
|
-
return { cmd: "pnpm", args: ["dev"], useArgSeparator: false };
|
|
331
|
-
}
|
|
332
|
-
if (which("npm")) {
|
|
333
|
-
return { cmd: "npm", args: ["run", "dev"], useArgSeparator: true };
|
|
334
|
-
}
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
function resolveUiFrontendDir() {
|
|
338
|
-
const candidates = [];
|
|
339
|
-
const envDir = process.env.NEXTCLAW_UI_DIR;
|
|
340
|
-
if (envDir) {
|
|
341
|
-
candidates.push(envDir);
|
|
342
|
-
}
|
|
343
|
-
const cwd = process.cwd();
|
|
344
|
-
candidates.push(join(cwd, "packages", "nextclaw-ui"));
|
|
345
|
-
candidates.push(join(cwd, "nextclaw-ui"));
|
|
346
|
-
const cliDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
347
|
-
const pkgRoot = resolve(cliDir, "..", "..");
|
|
348
|
-
candidates.push(join(pkgRoot, "..", "nextclaw-ui"));
|
|
349
|
-
candidates.push(join(pkgRoot, "..", "..", "packages", "nextclaw-ui"));
|
|
350
|
-
candidates.push(join(pkgRoot, "..", "..", "nextclaw-ui"));
|
|
351
|
-
for (const dir of candidates) {
|
|
352
|
-
if (existsSync(join(dir, "package.json"))) {
|
|
353
|
-
return dir;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
262
|
function printAgentResponse(response) {
|
|
359
263
|
console.log("\n" + response + "\n");
|
|
360
264
|
}
|
|
@@ -460,17 +364,21 @@ var mergeDeep = (base, patch) => {
|
|
|
460
364
|
}
|
|
461
365
|
return next;
|
|
462
366
|
};
|
|
463
|
-
var scheduleRestart = (delayMs, reason) => {
|
|
464
|
-
const delay = typeof delayMs === "number" && Number.isFinite(delayMs) ? Math.max(0, delayMs) : 100;
|
|
465
|
-
console.log(`Gateway restart requested via tool${reason ? ` (${reason})` : ""}.`);
|
|
466
|
-
setTimeout(() => {
|
|
467
|
-
process.exit(0);
|
|
468
|
-
}, delay);
|
|
469
|
-
};
|
|
470
367
|
var GatewayControllerImpl = class {
|
|
471
368
|
constructor(deps) {
|
|
472
369
|
this.deps = deps;
|
|
473
370
|
}
|
|
371
|
+
async requestRestart(options) {
|
|
372
|
+
if (this.deps.requestRestart) {
|
|
373
|
+
await this.deps.requestRestart(options);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const delay = typeof options?.delayMs === "number" && Number.isFinite(options.delayMs) ? Math.max(0, options.delayMs) : 100;
|
|
377
|
+
console.log(`Gateway restart requested via tool${options?.reason ? ` (${options.reason})` : ""}.`);
|
|
378
|
+
setTimeout(() => {
|
|
379
|
+
process.exit(0);
|
|
380
|
+
}, delay);
|
|
381
|
+
}
|
|
474
382
|
status() {
|
|
475
383
|
return {
|
|
476
384
|
channels: this.deps.reloader.getChannels().enabledChannels,
|
|
@@ -482,7 +390,7 @@ var GatewayControllerImpl = class {
|
|
|
482
390
|
return this.deps.reloader.reloadConfig(reason);
|
|
483
391
|
}
|
|
484
392
|
async restart(options) {
|
|
485
|
-
|
|
393
|
+
await this.requestRestart(options);
|
|
486
394
|
return "Restart scheduled";
|
|
487
395
|
}
|
|
488
396
|
async getConfig() {
|
|
@@ -527,7 +435,7 @@ var GatewayControllerImpl = class {
|
|
|
527
435
|
}
|
|
528
436
|
this.deps.saveConfig(validated);
|
|
529
437
|
const delayMs = params.restartDelayMs ?? 0;
|
|
530
|
-
|
|
438
|
+
await this.requestRestart({ delayMs, reason: "config.apply" });
|
|
531
439
|
return {
|
|
532
440
|
ok: true,
|
|
533
441
|
note: params.note ?? null,
|
|
@@ -563,7 +471,7 @@ var GatewayControllerImpl = class {
|
|
|
563
471
|
}
|
|
564
472
|
this.deps.saveConfig(validated);
|
|
565
473
|
const delayMs = params.restartDelayMs ?? 0;
|
|
566
|
-
|
|
474
|
+
await this.requestRestart({ delayMs, reason: "config.patch" });
|
|
567
475
|
return {
|
|
568
476
|
ok: true,
|
|
569
477
|
note: params.note ?? null,
|
|
@@ -578,7 +486,7 @@ var GatewayControllerImpl = class {
|
|
|
578
486
|
return { ok: false, error: result.error ?? "update failed", steps: result.steps };
|
|
579
487
|
}
|
|
580
488
|
const delayMs = params.restartDelayMs ?? 0;
|
|
581
|
-
|
|
489
|
+
await this.requestRestart({ delayMs, reason: "update.run" });
|
|
582
490
|
return {
|
|
583
491
|
ok: true,
|
|
584
492
|
note: params.note ?? null,
|
|
@@ -589,6 +497,63 @@ var GatewayControllerImpl = class {
|
|
|
589
497
|
}
|
|
590
498
|
};
|
|
591
499
|
|
|
500
|
+
// src/cli/restart-coordinator.ts
|
|
501
|
+
var RestartCoordinator = class {
|
|
502
|
+
constructor(deps) {
|
|
503
|
+
this.deps = deps;
|
|
504
|
+
}
|
|
505
|
+
restartingService = false;
|
|
506
|
+
exitScheduled = false;
|
|
507
|
+
async requestRestart(request) {
|
|
508
|
+
const reason = request.reason.trim() || "config changed";
|
|
509
|
+
const strategy = request.strategy ?? "background-service-or-manual";
|
|
510
|
+
if (strategy !== "exit-process") {
|
|
511
|
+
const state = this.deps.readServiceState();
|
|
512
|
+
const serviceRunning = Boolean(state && this.deps.isProcessRunning(state.pid));
|
|
513
|
+
const managedByCurrentProcess = Boolean(state && state.pid === this.deps.currentPid());
|
|
514
|
+
if (serviceRunning && !managedByCurrentProcess) {
|
|
515
|
+
if (this.restartingService) {
|
|
516
|
+
return {
|
|
517
|
+
status: "restart-in-progress",
|
|
518
|
+
message: "Restart already in progress; skipping duplicate request."
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
this.restartingService = true;
|
|
522
|
+
try {
|
|
523
|
+
const restarted = await this.deps.restartBackgroundService(reason);
|
|
524
|
+
if (restarted) {
|
|
525
|
+
return {
|
|
526
|
+
status: "service-restarted",
|
|
527
|
+
message: `Restarted background service to apply changes (${reason}).`
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
} finally {
|
|
531
|
+
this.restartingService = false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (strategy === "background-service-or-exit" || strategy === "exit-process") {
|
|
536
|
+
if (this.exitScheduled) {
|
|
537
|
+
return {
|
|
538
|
+
status: "exit-scheduled",
|
|
539
|
+
message: "Restart already scheduled; skipping duplicate request."
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
const delay = typeof request.delayMs === "number" && Number.isFinite(request.delayMs) ? Math.max(0, Math.floor(request.delayMs)) : 100;
|
|
543
|
+
this.exitScheduled = true;
|
|
544
|
+
this.deps.scheduleProcessExit(delay, reason);
|
|
545
|
+
return {
|
|
546
|
+
status: "exit-scheduled",
|
|
547
|
+
message: `Restart scheduled (${reason}).`
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
status: "manual-required",
|
|
552
|
+
message: request.manualMessage ?? "Restart the gateway to apply changes."
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
|
|
592
557
|
// src/cli/skills/clawhub.ts
|
|
593
558
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
594
559
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -847,6 +812,21 @@ function unsetAtConfigPath(root, pathSegments) {
|
|
|
847
812
|
delete record[last];
|
|
848
813
|
return true;
|
|
849
814
|
}
|
|
815
|
+
var MissingProvider = class extends LLMProvider {
|
|
816
|
+
constructor(defaultModel) {
|
|
817
|
+
super(null, null);
|
|
818
|
+
this.defaultModel = defaultModel;
|
|
819
|
+
}
|
|
820
|
+
setDefaultModel(model) {
|
|
821
|
+
this.defaultModel = model;
|
|
822
|
+
}
|
|
823
|
+
async chat() {
|
|
824
|
+
throw new Error("No API key configured yet. Configure provider credentials in UI and retry.");
|
|
825
|
+
}
|
|
826
|
+
getDefaultModel() {
|
|
827
|
+
return this.defaultModel;
|
|
828
|
+
}
|
|
829
|
+
};
|
|
850
830
|
var ConfigReloader = class {
|
|
851
831
|
constructor(options) {
|
|
852
832
|
this.options = options;
|
|
@@ -863,6 +843,9 @@ var ConfigReloader = class {
|
|
|
863
843
|
getChannels() {
|
|
864
844
|
return this.channels;
|
|
865
845
|
}
|
|
846
|
+
setApplyAgentRuntimeConfig(callback) {
|
|
847
|
+
this.options.applyAgentRuntimeConfig = callback;
|
|
848
|
+
}
|
|
866
849
|
async applyReloadPlan(nextConfig) {
|
|
867
850
|
const changedPaths = diffConfigPaths(this.currentConfig, nextConfig);
|
|
868
851
|
if (!changedPaths.length) {
|
|
@@ -872,9 +855,15 @@ var ConfigReloader = class {
|
|
|
872
855
|
const plan = buildReloadPlan(changedPaths);
|
|
873
856
|
if (plan.restartChannels) {
|
|
874
857
|
await this.reloadChannels(nextConfig);
|
|
858
|
+
console.log("Config reload: channels restarted.");
|
|
875
859
|
}
|
|
876
860
|
if (plan.reloadProviders) {
|
|
877
861
|
await this.reloadProvider(nextConfig);
|
|
862
|
+
console.log("Config reload: provider settings applied.");
|
|
863
|
+
}
|
|
864
|
+
if (plan.reloadAgent) {
|
|
865
|
+
this.options.applyAgentRuntimeConfig?.(nextConfig);
|
|
866
|
+
console.log("Config reload: agent defaults applied.");
|
|
878
867
|
}
|
|
879
868
|
if (plan.restartRequired.length > 0) {
|
|
880
869
|
this.options.onRestartRequired(plan.restartRequired);
|
|
@@ -961,12 +950,75 @@ var ConfigReloader = class {
|
|
|
961
950
|
};
|
|
962
951
|
var CliRuntime = class {
|
|
963
952
|
logo;
|
|
953
|
+
restartCoordinator;
|
|
954
|
+
serviceRestartTask = null;
|
|
964
955
|
constructor(options = {}) {
|
|
965
956
|
this.logo = options.logo ?? LOGO;
|
|
957
|
+
this.restartCoordinator = new RestartCoordinator({
|
|
958
|
+
readServiceState,
|
|
959
|
+
isProcessRunning,
|
|
960
|
+
currentPid: () => process.pid,
|
|
961
|
+
restartBackgroundService: async (reason) => this.restartBackgroundService(reason),
|
|
962
|
+
scheduleProcessExit: (delayMs, reason) => this.scheduleProcessExit(delayMs, reason)
|
|
963
|
+
});
|
|
966
964
|
}
|
|
967
965
|
get version() {
|
|
968
966
|
return getPackageVersion();
|
|
969
967
|
}
|
|
968
|
+
scheduleProcessExit(delayMs, reason) {
|
|
969
|
+
console.warn(`Gateway restart requested (${reason}).`);
|
|
970
|
+
setTimeout(() => {
|
|
971
|
+
process.exit(0);
|
|
972
|
+
}, delayMs);
|
|
973
|
+
}
|
|
974
|
+
async restartBackgroundService(reason) {
|
|
975
|
+
if (this.serviceRestartTask) {
|
|
976
|
+
return this.serviceRestartTask;
|
|
977
|
+
}
|
|
978
|
+
this.serviceRestartTask = (async () => {
|
|
979
|
+
const state = readServiceState();
|
|
980
|
+
if (!state || !isProcessRunning(state.pid) || state.pid === process.pid) {
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
const uiHost = state.uiHost ?? "127.0.0.1";
|
|
984
|
+
const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 18791;
|
|
985
|
+
console.log(`Applying changes (${reason}): restarting ${APP_NAME} background service...`);
|
|
986
|
+
await this.stopService();
|
|
987
|
+
await this.startService({
|
|
988
|
+
uiOverrides: {
|
|
989
|
+
enabled: true,
|
|
990
|
+
host: uiHost,
|
|
991
|
+
port: uiPort
|
|
992
|
+
},
|
|
993
|
+
open: false
|
|
994
|
+
});
|
|
995
|
+
return true;
|
|
996
|
+
})();
|
|
997
|
+
try {
|
|
998
|
+
return await this.serviceRestartTask;
|
|
999
|
+
} finally {
|
|
1000
|
+
this.serviceRestartTask = null;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
async requestRestart(params) {
|
|
1004
|
+
const result = await this.restartCoordinator.requestRestart({
|
|
1005
|
+
reason: params.reason,
|
|
1006
|
+
strategy: params.strategy,
|
|
1007
|
+
delayMs: params.delayMs,
|
|
1008
|
+
manualMessage: params.manualMessage
|
|
1009
|
+
});
|
|
1010
|
+
if (result.status === "manual-required" || result.status === "restart-in-progress") {
|
|
1011
|
+
console.log(result.message);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
if (result.status === "service-restarted") {
|
|
1015
|
+
if (!params.silentOnServiceRestart) {
|
|
1016
|
+
console.log(result.message);
|
|
1017
|
+
}
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
console.warn(result.message);
|
|
1021
|
+
}
|
|
970
1022
|
async onboard() {
|
|
971
1023
|
console.warn(`Warning: ${APP_NAME} onboard is deprecated. Use "${APP_NAME} init" instead.`);
|
|
972
1024
|
await this.init({ source: "onboard" });
|
|
@@ -1063,35 +1115,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1063
1115
|
if (opts.public && !opts.uiHost) {
|
|
1064
1116
|
uiOverrides.host = "0.0.0.0";
|
|
1065
1117
|
}
|
|
1066
|
-
const devMode = isDevRuntime();
|
|
1067
|
-
if (devMode) {
|
|
1068
|
-
const requestedUiPort = Number.isFinite(Number(opts.uiPort)) ? Number(opts.uiPort) : 18792;
|
|
1069
|
-
const requestedFrontendPort = Number.isFinite(Number(opts.frontendPort)) ? Number(opts.frontendPort) : 5174;
|
|
1070
|
-
const uiHost = uiOverrides.host ?? "127.0.0.1";
|
|
1071
|
-
const devUiPort = await findAvailablePort(requestedUiPort, uiHost);
|
|
1072
|
-
const shouldStartFrontend = opts.frontend === void 0 ? true : Boolean(opts.frontend);
|
|
1073
|
-
const devFrontendPort = shouldStartFrontend ? await findAvailablePort(requestedFrontendPort, "127.0.0.1") : requestedFrontendPort;
|
|
1074
|
-
uiOverrides.port = devUiPort;
|
|
1075
|
-
if (requestedUiPort !== devUiPort) {
|
|
1076
|
-
console.log(`Dev mode: UI port ${requestedUiPort} is in use, switched to ${devUiPort}.`);
|
|
1077
|
-
}
|
|
1078
|
-
if (shouldStartFrontend && requestedFrontendPort !== devFrontendPort) {
|
|
1079
|
-
console.log(`Dev mode: Frontend port ${requestedFrontendPort} is in use, switched to ${devFrontendPort}.`);
|
|
1080
|
-
}
|
|
1081
|
-
console.log(`Dev mode: UI ${devUiPort}, Frontend ${devFrontendPort}`);
|
|
1082
|
-
console.log("Dev mode runs in the foreground (Ctrl+C to stop).");
|
|
1083
|
-
await this.runForeground({
|
|
1084
|
-
uiOverrides,
|
|
1085
|
-
frontend: shouldStartFrontend,
|
|
1086
|
-
frontendPort: devFrontendPort,
|
|
1087
|
-
open: Boolean(opts.open)
|
|
1088
|
-
});
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
1118
|
await this.startService({
|
|
1092
1119
|
uiOverrides,
|
|
1093
|
-
frontend: Boolean(opts.frontend),
|
|
1094
|
-
frontendPort: Number(opts.frontendPort),
|
|
1095
1120
|
open: Boolean(opts.open)
|
|
1096
1121
|
});
|
|
1097
1122
|
}
|
|
@@ -1122,29 +1147,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1122
1147
|
if (opts.public && !opts.uiHost) {
|
|
1123
1148
|
uiOverrides.host = "0.0.0.0";
|
|
1124
1149
|
}
|
|
1125
|
-
const devMode = isDevRuntime();
|
|
1126
|
-
if (devMode && uiOverrides.port === void 0) {
|
|
1127
|
-
uiOverrides.port = 18792;
|
|
1128
|
-
}
|
|
1129
|
-
const shouldStartFrontend = Boolean(opts.frontend);
|
|
1130
|
-
const defaultFrontendPort = devMode ? 5174 : 5173;
|
|
1131
|
-
const requestedFrontendPort = Number.isFinite(Number(opts.frontendPort)) ? Number(opts.frontendPort) : defaultFrontendPort;
|
|
1132
|
-
if (devMode && uiOverrides.port !== void 0) {
|
|
1133
|
-
const uiHost = uiOverrides.host ?? "127.0.0.1";
|
|
1134
|
-
const uiPort = await findAvailablePort(uiOverrides.port, uiHost);
|
|
1135
|
-
if (uiPort !== uiOverrides.port) {
|
|
1136
|
-
console.log(`Dev mode: UI port ${uiOverrides.port} is in use, switched to ${uiPort}.`);
|
|
1137
|
-
uiOverrides.port = uiPort;
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
const frontendPort = devMode && shouldStartFrontend ? await findAvailablePort(requestedFrontendPort, "127.0.0.1") : requestedFrontendPort;
|
|
1141
|
-
if (devMode && shouldStartFrontend && frontendPort !== requestedFrontendPort) {
|
|
1142
|
-
console.log(`Dev mode: Frontend port ${requestedFrontendPort} is in use, switched to ${frontendPort}.`);
|
|
1143
|
-
}
|
|
1144
1150
|
await this.runForeground({
|
|
1145
1151
|
uiOverrides,
|
|
1146
|
-
frontend: shouldStartFrontend,
|
|
1147
|
-
frontendPort,
|
|
1148
1152
|
open: Boolean(opts.open)
|
|
1149
1153
|
});
|
|
1150
1154
|
}
|
|
@@ -1403,7 +1407,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1403
1407
|
}
|
|
1404
1408
|
console.log(JSON.stringify(result.value ?? null, null, 2));
|
|
1405
1409
|
}
|
|
1406
|
-
configSet(pathExpr, value, opts = {}) {
|
|
1410
|
+
async configSet(pathExpr, value, opts = {}) {
|
|
1407
1411
|
let parsedPath;
|
|
1408
1412
|
try {
|
|
1409
1413
|
parsedPath = parseRequiredConfigPath(pathExpr);
|
|
@@ -1429,9 +1433,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1429
1433
|
return;
|
|
1430
1434
|
}
|
|
1431
1435
|
saveConfig(config2);
|
|
1432
|
-
|
|
1436
|
+
await this.requestRestart({
|
|
1437
|
+
reason: `config.set ${pathExpr}`,
|
|
1438
|
+
manualMessage: `Updated ${pathExpr}. Restart the gateway to apply.`
|
|
1439
|
+
});
|
|
1433
1440
|
}
|
|
1434
|
-
configUnset(pathExpr) {
|
|
1441
|
+
async configUnset(pathExpr) {
|
|
1435
1442
|
let parsedPath;
|
|
1436
1443
|
try {
|
|
1437
1444
|
parsedPath = parseRequiredConfigPath(pathExpr);
|
|
@@ -1448,19 +1455,28 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1448
1455
|
return;
|
|
1449
1456
|
}
|
|
1450
1457
|
saveConfig(config2);
|
|
1451
|
-
|
|
1458
|
+
await this.requestRestart({
|
|
1459
|
+
reason: `config.unset ${pathExpr}`,
|
|
1460
|
+
manualMessage: `Removed ${pathExpr}. Restart the gateway to apply.`
|
|
1461
|
+
});
|
|
1452
1462
|
}
|
|
1453
|
-
pluginsEnable(id) {
|
|
1463
|
+
async pluginsEnable(id) {
|
|
1454
1464
|
const config2 = loadConfig();
|
|
1455
1465
|
const next = enablePluginInConfig(config2, id);
|
|
1456
1466
|
saveConfig(next);
|
|
1457
|
-
|
|
1467
|
+
await this.requestRestart({
|
|
1468
|
+
reason: `plugin enabled: ${id}`,
|
|
1469
|
+
manualMessage: `Enabled plugin "${id}". Restart the gateway to apply.`
|
|
1470
|
+
});
|
|
1458
1471
|
}
|
|
1459
|
-
pluginsDisable(id) {
|
|
1472
|
+
async pluginsDisable(id) {
|
|
1460
1473
|
const config2 = loadConfig();
|
|
1461
1474
|
const next = disablePluginInConfig(config2, id);
|
|
1462
1475
|
saveConfig(next);
|
|
1463
|
-
|
|
1476
|
+
await this.requestRestart({
|
|
1477
|
+
reason: `plugin disabled: ${id}`,
|
|
1478
|
+
manualMessage: `Disabled plugin "${id}". Restart the gateway to apply.`
|
|
1479
|
+
});
|
|
1464
1480
|
}
|
|
1465
1481
|
async pluginsUninstall(id, opts = {}) {
|
|
1466
1482
|
const config2 = loadConfig();
|
|
@@ -1557,7 +1573,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1557
1573
|
removed.push("directory");
|
|
1558
1574
|
}
|
|
1559
1575
|
console.log(`Uninstalled plugin "${pluginId}". Removed: ${removed.length > 0 ? removed.join(", ") : "nothing"}.`);
|
|
1560
|
-
|
|
1576
|
+
await this.requestRestart({
|
|
1577
|
+
reason: `plugin uninstalled: ${pluginId}`,
|
|
1578
|
+
manualMessage: "Restart the gateway to apply changes."
|
|
1579
|
+
});
|
|
1561
1580
|
}
|
|
1562
1581
|
async pluginsInstall(pathOrSpec, opts = {}) {
|
|
1563
1582
|
const fileSpec = this.resolveFileNpmSpecToLocalPath(pathOrSpec);
|
|
@@ -1586,7 +1605,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1586
1605
|
});
|
|
1587
1606
|
saveConfig(next3);
|
|
1588
1607
|
console.log(`Linked plugin path: ${resolved}`);
|
|
1589
|
-
|
|
1608
|
+
await this.requestRestart({
|
|
1609
|
+
reason: `plugin linked: ${probe.pluginId}`,
|
|
1610
|
+
manualMessage: "Restart the gateway to load plugins."
|
|
1611
|
+
});
|
|
1590
1612
|
return;
|
|
1591
1613
|
}
|
|
1592
1614
|
const result2 = await installPluginFromPath({
|
|
@@ -1610,7 +1632,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1610
1632
|
});
|
|
1611
1633
|
saveConfig(next2);
|
|
1612
1634
|
console.log(`Installed plugin: ${result2.pluginId}`);
|
|
1613
|
-
|
|
1635
|
+
await this.requestRestart({
|
|
1636
|
+
reason: `plugin installed: ${result2.pluginId}`,
|
|
1637
|
+
manualMessage: "Restart the gateway to load plugins."
|
|
1638
|
+
});
|
|
1614
1639
|
return;
|
|
1615
1640
|
}
|
|
1616
1641
|
if (opts.link) {
|
|
@@ -1642,7 +1667,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1642
1667
|
});
|
|
1643
1668
|
saveConfig(next);
|
|
1644
1669
|
console.log(`Installed plugin: ${result.pluginId}`);
|
|
1645
|
-
|
|
1670
|
+
await this.requestRestart({
|
|
1671
|
+
reason: `plugin installed: ${result.pluginId}`,
|
|
1672
|
+
manualMessage: "Restart the gateway to load plugins."
|
|
1673
|
+
});
|
|
1646
1674
|
}
|
|
1647
1675
|
pluginsDoctor() {
|
|
1648
1676
|
const config2 = loadConfig();
|
|
@@ -1732,7 +1760,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1732
1760
|
console.error(`Bridge failed: ${result.status ?? 1}`);
|
|
1733
1761
|
}
|
|
1734
1762
|
}
|
|
1735
|
-
channelsAdd(opts) {
|
|
1763
|
+
async channelsAdd(opts) {
|
|
1736
1764
|
const channelId = opts.channel?.trim();
|
|
1737
1765
|
if (!channelId) {
|
|
1738
1766
|
console.error("--channel is required");
|
|
@@ -1783,7 +1811,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1783
1811
|
next = enablePluginInConfig(next, binding.pluginId);
|
|
1784
1812
|
saveConfig(next);
|
|
1785
1813
|
console.log(`Configured channel "${binding.channelId}" via plugin "${binding.pluginId}".`);
|
|
1786
|
-
|
|
1814
|
+
await this.requestRestart({
|
|
1815
|
+
reason: `channel configured via plugin: ${binding.pluginId}`,
|
|
1816
|
+
manualMessage: "Restart the gateway to apply changes."
|
|
1817
|
+
});
|
|
1787
1818
|
}
|
|
1788
1819
|
toPluginConfigView(config2, bindings) {
|
|
1789
1820
|
const view = JSON.parse(JSON.stringify(config2));
|
|
@@ -1986,7 +2017,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1986
2017
|
this.logPluginDiagnostics(pluginRegistry);
|
|
1987
2018
|
const bus = new MessageBus();
|
|
1988
2019
|
const provider = options.allowMissingProvider === true ? this.makeProvider(config2, { allowMissing: true }) : this.makeProvider(config2);
|
|
1989
|
-
const providerManager =
|
|
2020
|
+
const providerManager = new ProviderManager(provider ?? this.makeMissingProvider(config2));
|
|
1990
2021
|
const sessionManager = new SessionManager(workspace);
|
|
1991
2022
|
const cronStorePath = join3(getDataDir2(), "cron", "jobs.json");
|
|
1992
2023
|
const cron2 = new CronService(cronStorePath);
|
|
@@ -1994,11 +2025,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1994
2025
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
1995
2026
|
const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
|
|
1996
2027
|
if (!provider) {
|
|
1997
|
-
|
|
1998
|
-
console.log("Warning: No API key configured. UI server only.");
|
|
1999
|
-
await new Promise(() => {
|
|
2000
|
-
});
|
|
2001
|
-
return;
|
|
2028
|
+
console.warn("Warning: No API key configured. The gateway is running, but agent replies are disabled until provider config is set.");
|
|
2002
2029
|
}
|
|
2003
2030
|
const channels2 = new ChannelManager(config2, bus, sessionManager, extensionRegistry.channels);
|
|
2004
2031
|
const reloader = new ConfigReloader({
|
|
@@ -2007,11 +2034,15 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2007
2034
|
bus,
|
|
2008
2035
|
sessionManager,
|
|
2009
2036
|
providerManager,
|
|
2010
|
-
makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }),
|
|
2037
|
+
makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }) ?? this.makeMissingProvider(nextConfig),
|
|
2011
2038
|
loadConfig,
|
|
2012
2039
|
getExtensionChannels: () => extensionRegistry.channels,
|
|
2013
2040
|
onRestartRequired: (paths) => {
|
|
2014
|
-
|
|
2041
|
+
void this.requestRestart({
|
|
2042
|
+
reason: `config reload requires restart: ${paths.join(", ")}`,
|
|
2043
|
+
manualMessage: `Config changes require restart: ${paths.join(", ")}`,
|
|
2044
|
+
strategy: "background-service-or-manual"
|
|
2045
|
+
});
|
|
2015
2046
|
}
|
|
2016
2047
|
});
|
|
2017
2048
|
const gatewayController = new GatewayControllerImpl({
|
|
@@ -2019,11 +2050,20 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2019
2050
|
cron: cron2,
|
|
2020
2051
|
getConfigPath,
|
|
2021
2052
|
saveConfig,
|
|
2022
|
-
getPluginUiMetadata: () => pluginUiMetadata
|
|
2053
|
+
getPluginUiMetadata: () => pluginUiMetadata,
|
|
2054
|
+
requestRestart: async (options2) => {
|
|
2055
|
+
await this.requestRestart({
|
|
2056
|
+
reason: options2?.reason ?? "gateway tool restart",
|
|
2057
|
+
manualMessage: "Restart the gateway to apply changes.",
|
|
2058
|
+
strategy: "background-service-or-exit",
|
|
2059
|
+
delayMs: options2?.delayMs,
|
|
2060
|
+
silentOnServiceRestart: true
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2023
2063
|
});
|
|
2024
2064
|
const agent = new AgentLoop({
|
|
2025
2065
|
bus,
|
|
2026
|
-
providerManager
|
|
2066
|
+
providerManager,
|
|
2027
2067
|
workspace,
|
|
2028
2068
|
model: config2.agents.defaults.model,
|
|
2029
2069
|
maxIterations: config2.agents.defaults.maxToolIterations,
|
|
@@ -2043,6 +2083,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2043
2083
|
accountId
|
|
2044
2084
|
})
|
|
2045
2085
|
});
|
|
2086
|
+
reloader.setApplyAgentRuntimeConfig((nextConfig) => agent.applyRuntimeConfig(nextConfig));
|
|
2046
2087
|
const pluginChannelBindings = getPluginChannelBindings(pluginRegistry);
|
|
2047
2088
|
setPluginRuntimeBridge({
|
|
2048
2089
|
loadConfig: () => this.toPluginConfigView(loadConfig(), pluginChannelBindings),
|
|
@@ -2191,34 +2232,14 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2191
2232
|
async runForeground(options) {
|
|
2192
2233
|
const config2 = loadConfig();
|
|
2193
2234
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
2194
|
-
const
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
const staticDir = resolveUiStaticDir();
|
|
2198
|
-
let frontendUrl = null;
|
|
2199
|
-
if (shouldStartFrontend && frontendDir) {
|
|
2200
|
-
const frontend = startUiFrontend({
|
|
2201
|
-
apiBase: resolveUiApiBase(uiConfig.host, uiConfig.port),
|
|
2202
|
-
port: frontendPort,
|
|
2203
|
-
dir: frontendDir
|
|
2204
|
-
});
|
|
2205
|
-
frontendUrl = frontend?.url ?? null;
|
|
2206
|
-
} else if (shouldStartFrontend && !frontendDir) {
|
|
2207
|
-
console.log("Warning: UI frontend not found. Start it separately.");
|
|
2208
|
-
}
|
|
2209
|
-
if (!frontendUrl && staticDir) {
|
|
2210
|
-
frontendUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
2211
|
-
}
|
|
2212
|
-
if (options.open && frontendUrl) {
|
|
2213
|
-
openBrowser(frontendUrl);
|
|
2214
|
-
} else if (options.open && !frontendUrl) {
|
|
2215
|
-
console.log("Warning: UI frontend not started. Browser not opened.");
|
|
2235
|
+
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
2236
|
+
if (options.open) {
|
|
2237
|
+
openBrowser(uiUrl);
|
|
2216
2238
|
}
|
|
2217
|
-
const uiStaticDir = shouldStartFrontend && frontendDir ? null : staticDir;
|
|
2218
2239
|
await this.startGateway({
|
|
2219
2240
|
uiOverrides: options.uiOverrides,
|
|
2220
2241
|
allowMissingProvider: true,
|
|
2221
|
-
uiStaticDir
|
|
2242
|
+
uiStaticDir: resolveUiStaticDir()
|
|
2222
2243
|
});
|
|
2223
2244
|
}
|
|
2224
2245
|
async startService(options) {
|
|
@@ -2232,6 +2253,28 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2232
2253
|
console.log(`\u2713 ${APP_NAME} is already running (PID ${existing.pid})`);
|
|
2233
2254
|
console.log(`UI: ${existing.uiUrl}`);
|
|
2234
2255
|
console.log(`API: ${existing.apiUrl}`);
|
|
2256
|
+
const parsedUi = (() => {
|
|
2257
|
+
try {
|
|
2258
|
+
const parsed = new URL(existing.uiUrl);
|
|
2259
|
+
const port = Number(parsed.port || 80);
|
|
2260
|
+
return {
|
|
2261
|
+
host: existing.uiHost ?? parsed.hostname,
|
|
2262
|
+
port: Number.isFinite(port) ? port : existing.uiPort ?? 18791
|
|
2263
|
+
};
|
|
2264
|
+
} catch {
|
|
2265
|
+
return {
|
|
2266
|
+
host: existing.uiHost ?? "127.0.0.1",
|
|
2267
|
+
port: existing.uiPort ?? 18791
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
})();
|
|
2271
|
+
await this.printPublicUiUrls(parsedUi.host, parsedUi.port);
|
|
2272
|
+
if (parsedUi.host !== uiConfig.host || parsedUi.port !== uiConfig.port) {
|
|
2273
|
+
console.log(
|
|
2274
|
+
`Note: requested UI bind (${uiConfig.host}:${uiConfig.port}) differs from running service (${parsedUi.host}:${parsedUi.port}).`
|
|
2275
|
+
);
|
|
2276
|
+
console.log(`Run: ${APP_NAME} restart${uiConfig.host === "0.0.0.0" ? " --public" : ""}`);
|
|
2277
|
+
}
|
|
2235
2278
|
console.log(`Logs: ${existing.logPath}`);
|
|
2236
2279
|
console.log(`Stop: ${APP_NAME} stop`);
|
|
2237
2280
|
return;
|
|
@@ -2239,8 +2282,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2239
2282
|
if (existing) {
|
|
2240
2283
|
clearServiceState();
|
|
2241
2284
|
}
|
|
2242
|
-
if (!staticDir
|
|
2243
|
-
console.log("Warning: UI frontend not found
|
|
2285
|
+
if (!staticDir) {
|
|
2286
|
+
console.log("Warning: UI frontend not found in package assets.");
|
|
2244
2287
|
}
|
|
2245
2288
|
const logPath = resolveServiceLogPath();
|
|
2246
2289
|
const logDir = resolve4(logPath, "..");
|
|
@@ -2248,9 +2291,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2248
2291
|
const logFd = openSync(logPath, "a");
|
|
2249
2292
|
const serveArgs = buildServeArgs({
|
|
2250
2293
|
uiHost: uiConfig.host,
|
|
2251
|
-
uiPort: uiConfig.port
|
|
2252
|
-
frontend: options.frontend,
|
|
2253
|
-
frontendPort: options.frontendPort
|
|
2294
|
+
uiPort: uiConfig.port
|
|
2254
2295
|
});
|
|
2255
2296
|
const child = spawn2(process.execPath, [...process.execArgv, ...serveArgs], {
|
|
2256
2297
|
env: process.env,
|
|
@@ -2268,6 +2309,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2268
2309
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2269
2310
|
uiUrl,
|
|
2270
2311
|
apiUrl,
|
|
2312
|
+
uiHost: uiConfig.host,
|
|
2313
|
+
uiPort: uiConfig.port,
|
|
2271
2314
|
logPath
|
|
2272
2315
|
};
|
|
2273
2316
|
writeServiceState(state);
|
|
@@ -2324,6 +2367,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2324
2367
|
const normalized = answer.trim().toLowerCase();
|
|
2325
2368
|
return normalized === "y" || normalized === "yes";
|
|
2326
2369
|
}
|
|
2370
|
+
makeMissingProvider(config2) {
|
|
2371
|
+
return new MissingProvider(config2.agents.defaults.model);
|
|
2372
|
+
}
|
|
2327
2373
|
makeProvider(config2, options) {
|
|
2328
2374
|
const provider = getProvider(config2);
|
|
2329
2375
|
const model = config2.agents.defaults.model;
|
|
@@ -2548,9 +2594,9 @@ program.command("onboard").description(`Initialize ${APP_NAME2} configuration an
|
|
|
2548
2594
|
program.command("init").description(`Initialize ${APP_NAME2} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
|
|
2549
2595
|
program.command("gateway").description(`Start the ${APP_NAME2} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.gateway(opts));
|
|
2550
2596
|
program.command("ui").description(`Start the ${APP_NAME2} UI with gateway`).option("--host <host>", "UI host").option("--port <port>", "UI port").option("--no-open", "Disable opening browser").option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.ui(opts));
|
|
2551
|
-
program.command("start").description(`Start the ${APP_NAME2} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--
|
|
2552
|
-
program.command("restart").description(`Restart the ${APP_NAME2} background service`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--
|
|
2553
|
-
program.command("serve").description(`Run the ${APP_NAME2} gateway + UI in the foreground`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--
|
|
2597
|
+
program.command("start").description(`Start the ${APP_NAME2} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.start(opts));
|
|
2598
|
+
program.command("restart").description(`Restart the ${APP_NAME2} background service`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--open", "Open browser after restart", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.restart(opts));
|
|
2599
|
+
program.command("serve").description(`Run the ${APP_NAME2} gateway + UI in the foreground`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).option("--public", "Expose UI on 0.0.0.0 and print public URL", false).action(async (opts) => runtime.serve(opts));
|
|
2554
2600
|
program.command("stop").description(`Stop the ${APP_NAME2} background service`).action(async () => runtime.stop());
|
|
2555
2601
|
program.command("agent").description("Interact with the agent directly").option("-m, --message <message>", "Message to send to the agent").option("-s, --session <session>", "Session ID", "cli:default").option("--no-markdown", "Disable Markdown rendering").action(async (opts) => runtime.agent(opts));
|
|
2556
2602
|
program.command("update").description(`Update ${APP_NAME2}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
|