nextclaw 0.9.10 → 0.9.11
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 +116 -260
- package/package.json +1 -1
- package/templates/USAGE.md +0 -9
package/dist/cli/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2,
|
|
27
27
|
setPluginRuntimeBridge as setPluginRuntimeBridge2
|
|
28
28
|
} from "@nextclaw/openclaw-compat";
|
|
29
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as
|
|
29
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
30
30
|
import { join as join7, resolve as resolve9 } from "path";
|
|
31
31
|
import { createInterface as createInterface2 } from "readline";
|
|
32
32
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -1831,8 +1831,8 @@ import { join as join3 } from "path";
|
|
|
1831
1831
|
var CronCommands = class {
|
|
1832
1832
|
cronList(opts) {
|
|
1833
1833
|
const storePath = join3(getDataDir3(), "cron", "jobs.json");
|
|
1834
|
-
const
|
|
1835
|
-
const jobs =
|
|
1834
|
+
const service = new CronService(storePath);
|
|
1835
|
+
const jobs = service.listJobs(Boolean(opts.all));
|
|
1836
1836
|
if (!jobs.length) {
|
|
1837
1837
|
console.log("No scheduled jobs.");
|
|
1838
1838
|
return;
|
|
@@ -1851,7 +1851,7 @@ var CronCommands = class {
|
|
|
1851
1851
|
}
|
|
1852
1852
|
cronAdd(opts) {
|
|
1853
1853
|
const storePath = join3(getDataDir3(), "cron", "jobs.json");
|
|
1854
|
-
const
|
|
1854
|
+
const service = new CronService(storePath);
|
|
1855
1855
|
let schedule = null;
|
|
1856
1856
|
if (opts.every) {
|
|
1857
1857
|
schedule = { kind: "every", everyMs: Number(opts.every) * 1e3 };
|
|
@@ -1864,7 +1864,7 @@ var CronCommands = class {
|
|
|
1864
1864
|
console.error("Error: Must specify --every, --cron, or --at");
|
|
1865
1865
|
return;
|
|
1866
1866
|
}
|
|
1867
|
-
const job =
|
|
1867
|
+
const job = service.addJob({
|
|
1868
1868
|
name: opts.name,
|
|
1869
1869
|
schedule,
|
|
1870
1870
|
message: opts.message,
|
|
@@ -1876,8 +1876,8 @@ var CronCommands = class {
|
|
|
1876
1876
|
}
|
|
1877
1877
|
cronRemove(jobId) {
|
|
1878
1878
|
const storePath = join3(getDataDir3(), "cron", "jobs.json");
|
|
1879
|
-
const
|
|
1880
|
-
if (
|
|
1879
|
+
const service = new CronService(storePath);
|
|
1880
|
+
if (service.removeJob(jobId)) {
|
|
1881
1881
|
console.log(`\u2713 Removed job ${jobId}`);
|
|
1882
1882
|
} else {
|
|
1883
1883
|
console.log(`Job ${jobId} not found`);
|
|
@@ -1885,8 +1885,8 @@ var CronCommands = class {
|
|
|
1885
1885
|
}
|
|
1886
1886
|
cronEnable(jobId, opts) {
|
|
1887
1887
|
const storePath = join3(getDataDir3(), "cron", "jobs.json");
|
|
1888
|
-
const
|
|
1889
|
-
const job =
|
|
1888
|
+
const service = new CronService(storePath);
|
|
1889
|
+
const job = service.enableJob(jobId, !opts.disable);
|
|
1890
1890
|
if (job) {
|
|
1891
1891
|
console.log(`\u2713 Job '${job.name}' ${opts.disable ? "disabled" : "enabled"}`);
|
|
1892
1892
|
} else {
|
|
@@ -1895,8 +1895,8 @@ var CronCommands = class {
|
|
|
1895
1895
|
}
|
|
1896
1896
|
async cronRun(jobId, opts) {
|
|
1897
1897
|
const storePath = join3(getDataDir3(), "cron", "jobs.json");
|
|
1898
|
-
const
|
|
1899
|
-
const ok = await
|
|
1898
|
+
const service = new CronService(storePath);
|
|
1899
|
+
const ok = await service.runJob(jobId, Boolean(opts.force));
|
|
1900
1900
|
console.log(ok ? "\u2713 Job executed" : `Failed to run job ${jobId}`);
|
|
1901
1901
|
}
|
|
1902
1902
|
};
|
|
@@ -2252,12 +2252,12 @@ import {
|
|
|
2252
2252
|
stopPluginChannelGateways
|
|
2253
2253
|
} from "@nextclaw/openclaw-compat";
|
|
2254
2254
|
import { startUiServer } from "@nextclaw/server";
|
|
2255
|
-
import { appendFileSync, closeSync, cpSync, existsSync as existsSync8, mkdirSync as mkdirSync4, openSync, rmSync as rmSync3
|
|
2255
|
+
import { appendFileSync, closeSync, cpSync, existsSync as existsSync8, mkdirSync as mkdirSync4, mkdtempSync, openSync, rmSync as rmSync3 } from "fs";
|
|
2256
2256
|
import { dirname, isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve7 } from "path";
|
|
2257
|
-
import {
|
|
2257
|
+
import { tmpdir } from "os";
|
|
2258
|
+
import { spawn as spawn2 } from "child_process";
|
|
2258
2259
|
import { request as httpRequest } from "http";
|
|
2259
2260
|
import { request as httpsRequest } from "https";
|
|
2260
|
-
import { homedir, userInfo } from "os";
|
|
2261
2261
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2262
2262
|
import chokidar from "chokidar";
|
|
2263
2263
|
|
|
@@ -4035,85 +4035,6 @@ var ServiceCommands = class {
|
|
|
4035
4035
|
clearServiceState();
|
|
4036
4036
|
console.log(`\u2713 ${APP_NAME2} stopped`);
|
|
4037
4037
|
}
|
|
4038
|
-
async installSystemdService(options) {
|
|
4039
|
-
if (process.platform !== "linux") {
|
|
4040
|
-
console.error("Error: systemd installation is only supported on Linux.");
|
|
4041
|
-
return;
|
|
4042
|
-
}
|
|
4043
|
-
if (typeof process.getuid === "function" && process.getuid() !== 0) {
|
|
4044
|
-
console.error("Error: Run this command as root (for example: sudo nextclaw service install-systemd).");
|
|
4045
|
-
return;
|
|
4046
|
-
}
|
|
4047
|
-
const serviceName = this.resolveSystemdServiceName(options.name);
|
|
4048
|
-
const config2 = loadConfig6();
|
|
4049
|
-
const uiConfig = resolveUiConfig(config2, { enabled: true, host: "0.0.0.0" });
|
|
4050
|
-
const uiPort = this.parseSystemdUiPort(options.uiPort, uiConfig.port);
|
|
4051
|
-
if (uiPort === null) {
|
|
4052
|
-
console.error("Error: Invalid --ui-port. Provide a positive integer.");
|
|
4053
|
-
return;
|
|
4054
|
-
}
|
|
4055
|
-
const systemctlAvailable = this.runSystemCommand("systemctl", ["--version"]);
|
|
4056
|
-
if (!systemctlAvailable.ok) {
|
|
4057
|
-
console.error("Error: systemctl is not available on this machine.");
|
|
4058
|
-
return;
|
|
4059
|
-
}
|
|
4060
|
-
const runUser = this.resolveSystemdRunUser();
|
|
4061
|
-
const runHome = this.resolveSystemdRunHome(runUser);
|
|
4062
|
-
const servicePath = `/etc/systemd/system/${serviceName}.service`;
|
|
4063
|
-
const cliPath = fileURLToPath2(new URL("../index.js", import.meta.url));
|
|
4064
|
-
const execArgs = [process.execPath, ...process.execArgv, cliPath, "serve", "--ui-port", String(uiPort)];
|
|
4065
|
-
const unit = this.buildSystemdUnit({
|
|
4066
|
-
runUser,
|
|
4067
|
-
runHome,
|
|
4068
|
-
execArgs
|
|
4069
|
-
});
|
|
4070
|
-
writeFileSync4(servicePath, unit, "utf-8");
|
|
4071
|
-
const daemonReload = this.runSystemCommand("systemctl", ["daemon-reload"]);
|
|
4072
|
-
if (!daemonReload.ok) {
|
|
4073
|
-
console.error(`Error: Failed to reload systemd. ${daemonReload.stderr}`.trim());
|
|
4074
|
-
return;
|
|
4075
|
-
}
|
|
4076
|
-
const enableStart = this.runSystemCommand("systemctl", ["enable", "--now", `${serviceName}.service`]);
|
|
4077
|
-
if (!enableStart.ok) {
|
|
4078
|
-
console.error(`Error: Failed to enable/start ${serviceName}.service. ${enableStart.stderr}`.trim());
|
|
4079
|
-
return;
|
|
4080
|
-
}
|
|
4081
|
-
const active = this.runSystemCommand("systemctl", ["is-active", `${serviceName}.service`]);
|
|
4082
|
-
if (!active.ok || active.stdout.trim() !== "active") {
|
|
4083
|
-
console.error(`Error: ${serviceName}.service is not active. ${active.stderr || active.stdout}`.trim());
|
|
4084
|
-
return;
|
|
4085
|
-
}
|
|
4086
|
-
console.log(`\u2713 Installed systemd service: ${serviceName}.service`);
|
|
4087
|
-
console.log(`Run user: ${runUser}`);
|
|
4088
|
-
console.log(`UI port: ${uiPort}`);
|
|
4089
|
-
console.log(`Unit file: ${servicePath}`);
|
|
4090
|
-
console.log(`Health: http://127.0.0.1:${uiPort}/api/health`);
|
|
4091
|
-
console.log(`Logs: journalctl -u ${serviceName}.service -f`);
|
|
4092
|
-
}
|
|
4093
|
-
async uninstallSystemdService(options) {
|
|
4094
|
-
if (process.platform !== "linux") {
|
|
4095
|
-
console.error("Error: systemd removal is only supported on Linux.");
|
|
4096
|
-
return;
|
|
4097
|
-
}
|
|
4098
|
-
if (typeof process.getuid === "function" && process.getuid() !== 0) {
|
|
4099
|
-
console.error("Error: Run this command as root.");
|
|
4100
|
-
return;
|
|
4101
|
-
}
|
|
4102
|
-
const serviceName = this.resolveSystemdServiceName(options.name);
|
|
4103
|
-
const servicePath = `/etc/systemd/system/${serviceName}.service`;
|
|
4104
|
-
if (!existsSync8(servicePath)) {
|
|
4105
|
-
console.error(`Error: ${servicePath} does not exist.`);
|
|
4106
|
-
return;
|
|
4107
|
-
}
|
|
4108
|
-
this.runSystemCommand("systemctl", ["disable", "--now", `${serviceName}.service`]);
|
|
4109
|
-
rmSync3(servicePath, { force: true });
|
|
4110
|
-
const daemonReload = this.runSystemCommand("systemctl", ["daemon-reload"]);
|
|
4111
|
-
if (!daemonReload.ok) {
|
|
4112
|
-
console.error(`Warning: Removed unit file but failed to reload systemd. ${daemonReload.stderr}`.trim());
|
|
4113
|
-
return;
|
|
4114
|
-
}
|
|
4115
|
-
console.log(`\u2713 Removed systemd service: ${serviceName}.service`);
|
|
4116
|
-
}
|
|
4117
4038
|
async waitForBackgroundServiceReady(params) {
|
|
4118
4039
|
const startedAt = Date.now();
|
|
4119
4040
|
let lastProbeError = null;
|
|
@@ -4144,78 +4065,6 @@ var ServiceCommands = class {
|
|
|
4144
4065
|
const resolved = fromOverride ?? fromEnv ?? fallback;
|
|
4145
4066
|
return Math.max(3e3, resolved);
|
|
4146
4067
|
}
|
|
4147
|
-
resolveSystemdServiceName(rawName) {
|
|
4148
|
-
const trimmed = rawName?.trim() || APP_NAME2;
|
|
4149
|
-
return trimmed.endsWith(".service") ? trimmed.slice(0, -".service".length) : trimmed;
|
|
4150
|
-
}
|
|
4151
|
-
parseSystemdUiPort(rawPort, fallbackPort) {
|
|
4152
|
-
if (rawPort === void 0 || rawPort === null || rawPort === "") {
|
|
4153
|
-
return fallbackPort;
|
|
4154
|
-
}
|
|
4155
|
-
const parsed = Number(rawPort);
|
|
4156
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
4157
|
-
return null;
|
|
4158
|
-
}
|
|
4159
|
-
return parsed;
|
|
4160
|
-
}
|
|
4161
|
-
resolveSystemdRunUser() {
|
|
4162
|
-
const sudoUser = process.env.SUDO_USER?.trim();
|
|
4163
|
-
if (sudoUser && sudoUser !== "root") {
|
|
4164
|
-
return sudoUser;
|
|
4165
|
-
}
|
|
4166
|
-
return userInfo().username;
|
|
4167
|
-
}
|
|
4168
|
-
resolveSystemdRunHome(runUser) {
|
|
4169
|
-
const passwd = this.runSystemCommand("getent", ["passwd", runUser]);
|
|
4170
|
-
if (passwd.ok) {
|
|
4171
|
-
const fields = passwd.stdout.trim().split(":");
|
|
4172
|
-
if (fields.length >= 6 && fields[5]) {
|
|
4173
|
-
return fields[5];
|
|
4174
|
-
}
|
|
4175
|
-
}
|
|
4176
|
-
return process.env.HOME?.trim() || homedir();
|
|
4177
|
-
}
|
|
4178
|
-
buildSystemdUnit(params) {
|
|
4179
|
-
const execStart = params.execArgs.map((arg) => this.escapeSystemdArg(arg)).join(" ");
|
|
4180
|
-
return [
|
|
4181
|
-
"[Unit]",
|
|
4182
|
-
`Description=${APP_NAME2} gateway + UI`,
|
|
4183
|
-
"After=network-online.target",
|
|
4184
|
-
"Wants=network-online.target",
|
|
4185
|
-
"",
|
|
4186
|
-
"[Service]",
|
|
4187
|
-
"Type=simple",
|
|
4188
|
-
`User=${params.runUser}`,
|
|
4189
|
-
`WorkingDirectory=${params.runHome}`,
|
|
4190
|
-
`Environment=HOME=${params.runHome}`,
|
|
4191
|
-
"Environment=NODE_ENV=production",
|
|
4192
|
-
`ExecStart=${execStart}`,
|
|
4193
|
-
"Restart=always",
|
|
4194
|
-
"RestartSec=3",
|
|
4195
|
-
"TimeoutStopSec=20",
|
|
4196
|
-
"",
|
|
4197
|
-
"[Install]",
|
|
4198
|
-
"WantedBy=multi-user.target",
|
|
4199
|
-
""
|
|
4200
|
-
].join("\n");
|
|
4201
|
-
}
|
|
4202
|
-
escapeSystemdArg(value) {
|
|
4203
|
-
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) {
|
|
4204
|
-
return value;
|
|
4205
|
-
}
|
|
4206
|
-
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
4207
|
-
}
|
|
4208
|
-
runSystemCommand(command, args) {
|
|
4209
|
-
const result = spawnSync4(command, args, {
|
|
4210
|
-
encoding: "utf-8",
|
|
4211
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4212
|
-
});
|
|
4213
|
-
return {
|
|
4214
|
-
ok: result.status === 0,
|
|
4215
|
-
stdout: result.stdout ?? "",
|
|
4216
|
-
stderr: result.stderr ?? ""
|
|
4217
|
-
};
|
|
4218
|
-
}
|
|
4219
4068
|
appendStartupStage(logPath, message) {
|
|
4220
4069
|
try {
|
|
4221
4070
|
appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [startup] ${message}
|
|
@@ -4519,54 +4368,107 @@ var ServiceCommands = class {
|
|
|
4519
4368
|
if (existsSync8(destination) && params.force) {
|
|
4520
4369
|
rmSync3(destination, { recursive: true, force: true });
|
|
4521
4370
|
}
|
|
4522
|
-
const
|
|
4523
|
-
|
|
4524
|
-
|
|
4371
|
+
const materialized = await this.materializeMarketplaceGitSkillSource({ source, skillName });
|
|
4372
|
+
try {
|
|
4373
|
+
const installSkillFile = join5(materialized.skillDir, "SKILL.md");
|
|
4374
|
+
if (!existsSync8(installSkillFile)) {
|
|
4375
|
+
throw new Error(`git skill source does not contain SKILL.md for ${skillName}`);
|
|
4376
|
+
}
|
|
4377
|
+
mkdirSync4(dirname(destination), { recursive: true });
|
|
4378
|
+
cpSync(materialized.skillDir, destination, { recursive: true, force: true });
|
|
4379
|
+
return {
|
|
4380
|
+
message: `Installed skill: ${skillName}`,
|
|
4381
|
+
output: [
|
|
4382
|
+
`Source: ${source}`,
|
|
4383
|
+
`Materialized skill: ${materialized.skillDir}`,
|
|
4384
|
+
`Workspace target: ${destination}`,
|
|
4385
|
+
materialized.commandOutput
|
|
4386
|
+
].filter(Boolean).join("\n")
|
|
4387
|
+
};
|
|
4388
|
+
} finally {
|
|
4389
|
+
rmSync3(materialized.tempRoot, { recursive: true, force: true });
|
|
4525
4390
|
}
|
|
4526
|
-
|
|
4527
|
-
|
|
4391
|
+
}
|
|
4392
|
+
async materializeMarketplaceGitSkillSource(params) {
|
|
4393
|
+
const parsed = this.parseMarketplaceGitSkillSource(params.source);
|
|
4394
|
+
const gitPath = findExecutableOnPath("git");
|
|
4395
|
+
if (!gitPath) {
|
|
4396
|
+
throw new Error("git is required to install marketplace git skills");
|
|
4528
4397
|
}
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4398
|
+
const tempRoot = mkdtempSync(join5(tmpdir(), "nextclaw-marketplace-skill-"));
|
|
4399
|
+
const repoDir = join5(tempRoot, "repo");
|
|
4400
|
+
try {
|
|
4401
|
+
const cloneArgs = ["clone", "--depth", "1", "--filter=blob:none", "--sparse"];
|
|
4402
|
+
if (parsed.ref) {
|
|
4403
|
+
cloneArgs.push("--branch", parsed.ref);
|
|
4535
4404
|
}
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4405
|
+
cloneArgs.push(parsed.repoUrl, repoDir);
|
|
4406
|
+
const cloneResult = await this.runCommand(gitPath, cloneArgs, {
|
|
4407
|
+
cwd: tempRoot,
|
|
4408
|
+
timeoutMs: 18e4
|
|
4409
|
+
});
|
|
4410
|
+
const sparseResult = await this.runCommand(gitPath, ["-C", repoDir, "sparse-checkout", "set", parsed.skillPath], {
|
|
4411
|
+
cwd: tempRoot,
|
|
4412
|
+
timeoutMs: 6e4
|
|
4413
|
+
});
|
|
4414
|
+
const skillDir = join5(repoDir, parsed.skillPath);
|
|
4415
|
+
return {
|
|
4416
|
+
tempRoot,
|
|
4417
|
+
skillDir,
|
|
4418
|
+
commandOutput: [
|
|
4419
|
+
`Git repository: ${parsed.repoUrl}`,
|
|
4420
|
+
`Git path: ${parsed.skillPath}`,
|
|
4421
|
+
this.mergeCommandOutput(cloneResult.stdout, cloneResult.stderr),
|
|
4422
|
+
this.mergeCommandOutput(sparseResult.stdout, sparseResult.stderr)
|
|
4423
|
+
].filter(Boolean).join("\n")
|
|
4424
|
+
};
|
|
4425
|
+
} catch (error) {
|
|
4426
|
+
rmSync3(tempRoot, { recursive: true, force: true });
|
|
4427
|
+
throw error;
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
parseMarketplaceGitSkillSource(source) {
|
|
4431
|
+
const trimmed = source.trim();
|
|
4432
|
+
if (!trimmed) {
|
|
4433
|
+
throw new Error("Git skill source is required");
|
|
4549
4434
|
}
|
|
4550
|
-
if (
|
|
4551
|
-
|
|
4435
|
+
if (trimmed.includes("://")) {
|
|
4436
|
+
const parsedUrl = new URL(trimmed);
|
|
4437
|
+
if (parsedUrl.hostname !== "github.com") {
|
|
4438
|
+
throw new Error(`Unsupported git skill source host: ${parsedUrl.hostname}`);
|
|
4439
|
+
}
|
|
4440
|
+
const parts2 = parsedUrl.pathname.split("/").filter(Boolean);
|
|
4441
|
+
if (parts2.length < 5 || parts2[2] !== "tree" && parts2[2] !== "blob") {
|
|
4442
|
+
throw new Error(`Unsupported GitHub skill source URL: ${source}`);
|
|
4443
|
+
}
|
|
4444
|
+
const [owner2, repoRaw, , ref2, ...pathParts2] = parts2;
|
|
4445
|
+
const repo2 = repoRaw.replace(/\.git$/i, "");
|
|
4446
|
+
if (!owner2 || !repo2 || !ref2 || pathParts2.length === 0) {
|
|
4447
|
+
throw new Error(`Unsupported GitHub skill source URL: ${source}`);
|
|
4448
|
+
}
|
|
4449
|
+
return {
|
|
4450
|
+
repoUrl: `https://github.com/${owner2}/${repo2}.git`,
|
|
4451
|
+
skillPath: pathParts2.join("/"),
|
|
4452
|
+
ref: ref2
|
|
4453
|
+
};
|
|
4552
4454
|
}
|
|
4553
|
-
const
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
throw new Error(`skild install did not produce a valid skill directory for ${skillName}`);
|
|
4455
|
+
const parts = trimmed.split("/").filter(Boolean);
|
|
4456
|
+
if (parts.length < 3) {
|
|
4457
|
+
throw new Error(`Unsupported git skill source: ${source}`);
|
|
4557
4458
|
}
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4459
|
+
const owner = parts[0] ?? "";
|
|
4460
|
+
const repoWithOptionalRef = parts[1] ?? "";
|
|
4461
|
+
const pathParts = parts.slice(2);
|
|
4462
|
+
const atIndex = repoWithOptionalRef.indexOf("@");
|
|
4463
|
+
const repo = (atIndex >= 0 ? repoWithOptionalRef.slice(0, atIndex) : repoWithOptionalRef).replace(/\.git$/i, "");
|
|
4464
|
+
const ref = atIndex >= 0 ? repoWithOptionalRef.slice(atIndex + 1) : void 0;
|
|
4465
|
+
if (!owner || !repo || pathParts.length === 0) {
|
|
4466
|
+
throw new Error(`Unsupported git skill source: ${source}`);
|
|
4561
4467
|
}
|
|
4562
4468
|
return {
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
`Installed via skild: ${installDir}`,
|
|
4567
|
-
`Workspace target: ${destination}`,
|
|
4568
|
-
this.mergeCommandOutput(result.stdout, result.stderr)
|
|
4569
|
-
].filter(Boolean).join("\n")
|
|
4469
|
+
repoUrl: `https://github.com/${owner}/${repo}.git`,
|
|
4470
|
+
skillPath: pathParts.join("/"),
|
|
4471
|
+
ref: ref && ref.trim().length > 0 ? ref.trim() : void 0
|
|
4570
4472
|
};
|
|
4571
4473
|
}
|
|
4572
4474
|
async enableMarketplacePlugin(id) {
|
|
@@ -4587,17 +4489,13 @@ var ServiceCommands = class {
|
|
|
4587
4489
|
async uninstallMarketplaceSkill(slug) {
|
|
4588
4490
|
const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
|
|
4589
4491
|
const targetDir = join5(workspace, "skills", slug);
|
|
4590
|
-
|
|
4591
|
-
const existingTargets = [targetDir, skildDir].filter((path) => existsSync8(path));
|
|
4592
|
-
if (existingTargets.length === 0) {
|
|
4492
|
+
if (!existsSync8(targetDir)) {
|
|
4593
4493
|
throw new Error(`Skill not installed in workspace: ${slug}`);
|
|
4594
4494
|
}
|
|
4595
|
-
|
|
4596
|
-
rmSync3(path, { recursive: true, force: true });
|
|
4597
|
-
}
|
|
4495
|
+
rmSync3(targetDir, { recursive: true, force: true });
|
|
4598
4496
|
return {
|
|
4599
4497
|
message: `Uninstalled skill: ${slug}`,
|
|
4600
|
-
output:
|
|
4498
|
+
output: `Removed ${targetDir}`
|
|
4601
4499
|
};
|
|
4602
4500
|
}
|
|
4603
4501
|
installBuiltinMarketplaceSkill(slug, force) {
|
|
@@ -4659,39 +4557,6 @@ var ServiceCommands = class {
|
|
|
4659
4557
|
}
|
|
4660
4558
|
return destination;
|
|
4661
4559
|
}
|
|
4662
|
-
parseSkildJsonOutput(stdout) {
|
|
4663
|
-
const trimmed = stdout.trim();
|
|
4664
|
-
if (!trimmed) {
|
|
4665
|
-
throw new Error("skild returned empty output");
|
|
4666
|
-
}
|
|
4667
|
-
const maybeJson = (() => {
|
|
4668
|
-
const start = trimmed.indexOf("{");
|
|
4669
|
-
const end = trimmed.lastIndexOf("}");
|
|
4670
|
-
if (start >= 0 && end > start) {
|
|
4671
|
-
return trimmed.slice(start, end + 1);
|
|
4672
|
-
}
|
|
4673
|
-
return trimmed;
|
|
4674
|
-
})();
|
|
4675
|
-
try {
|
|
4676
|
-
const parsed = JSON.parse(maybeJson);
|
|
4677
|
-
if (parsed === null) {
|
|
4678
|
-
return null;
|
|
4679
|
-
}
|
|
4680
|
-
if (Array.isArray(parsed)) {
|
|
4681
|
-
const firstObject = parsed.find((item) => item && typeof item === "object" && !Array.isArray(item));
|
|
4682
|
-
if (firstObject) {
|
|
4683
|
-
return firstObject;
|
|
4684
|
-
}
|
|
4685
|
-
throw new Error("skild json output array does not contain an object");
|
|
4686
|
-
}
|
|
4687
|
-
if (typeof parsed !== "object") {
|
|
4688
|
-
throw new Error("skild json output is not an object");
|
|
4689
|
-
}
|
|
4690
|
-
return parsed;
|
|
4691
|
-
} catch (error) {
|
|
4692
|
-
throw new Error(`failed to parse skild --json output: ${String(error)}`);
|
|
4693
|
-
}
|
|
4694
|
-
}
|
|
4695
4560
|
mergeCommandOutput(stdout, stderr) {
|
|
4696
4561
|
return `${stdout}
|
|
4697
4562
|
${stderr}`.trim();
|
|
@@ -4763,12 +4628,12 @@ ${stderr}`.trim();
|
|
|
4763
4628
|
};
|
|
4764
4629
|
|
|
4765
4630
|
// src/cli/workspace.ts
|
|
4766
|
-
import { cpSync as cpSync2, existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync2, rmSync as rmSync4, writeFileSync as
|
|
4631
|
+
import { cpSync as cpSync2, existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync2, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
4767
4632
|
import { createRequire } from "module";
|
|
4768
4633
|
import { dirname as dirname2, join as join6, resolve as resolve8 } from "path";
|
|
4769
4634
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4770
4635
|
import { APP_NAME as APP_NAME3, getDataDir as getDataDir7 } from "@nextclaw/core";
|
|
4771
|
-
import { spawnSync as
|
|
4636
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
4772
4637
|
var WorkspaceManager = class {
|
|
4773
4638
|
constructor(logo) {
|
|
4774
4639
|
this.logo = logo;
|
|
@@ -4807,7 +4672,7 @@ var WorkspaceManager = class {
|
|
|
4807
4672
|
const raw = readFileSync7(templatePath, "utf-8");
|
|
4808
4673
|
const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
|
|
4809
4674
|
mkdirSync5(dirname2(filePath), { recursive: true });
|
|
4810
|
-
|
|
4675
|
+
writeFileSync4(filePath, content);
|
|
4811
4676
|
created.push(entry.target);
|
|
4812
4677
|
}
|
|
4813
4678
|
const memoryDir = join6(workspace, "memory");
|
|
@@ -4915,7 +4780,7 @@ var WorkspaceManager = class {
|
|
|
4915
4780
|
recursive: true,
|
|
4916
4781
|
filter: (src) => !src.includes("node_modules") && !src.includes("dist")
|
|
4917
4782
|
});
|
|
4918
|
-
const install =
|
|
4783
|
+
const install = spawnSync4("npm", ["install"], { cwd: userBridge, stdio: "pipe" });
|
|
4919
4784
|
if (install.status !== 0) {
|
|
4920
4785
|
console.error(`Bridge install failed: ${install.status ?? 1}`);
|
|
4921
4786
|
if (install.stderr) {
|
|
@@ -4923,7 +4788,7 @@ var WorkspaceManager = class {
|
|
|
4923
4788
|
}
|
|
4924
4789
|
process.exit(1);
|
|
4925
4790
|
}
|
|
4926
|
-
const build =
|
|
4791
|
+
const build = spawnSync4("npm", ["run", "build"], { cwd: userBridge, stdio: "pipe" });
|
|
4927
4792
|
if (build.status !== 0) {
|
|
4928
4793
|
console.error(`Bridge build failed: ${build.status ?? 1}`);
|
|
4929
4794
|
if (build.stderr) {
|
|
@@ -5293,12 +5158,6 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
5293
5158
|
async stop() {
|
|
5294
5159
|
await this.serviceCommands.stopService();
|
|
5295
5160
|
}
|
|
5296
|
-
async serviceInstallSystemd(opts) {
|
|
5297
|
-
await this.serviceCommands.installSystemdService(opts);
|
|
5298
|
-
}
|
|
5299
|
-
async serviceUninstallSystemd(opts) {
|
|
5300
|
-
await this.serviceCommands.uninstallSystemdService(opts);
|
|
5301
|
-
}
|
|
5302
5161
|
async agent(opts) {
|
|
5303
5162
|
const configPath = getConfigPath4();
|
|
5304
5163
|
const config2 = resolveConfigSecrets3(loadConfig7(), { configPath });
|
|
@@ -5381,7 +5240,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
5381
5240
|
const merged = history.concat(
|
|
5382
5241
|
rl.history ?? []
|
|
5383
5242
|
);
|
|
5384
|
-
|
|
5243
|
+
writeFileSync5(historyFile, merged.join("\n"));
|
|
5385
5244
|
process.exit(0);
|
|
5386
5245
|
});
|
|
5387
5246
|
let running = true;
|
|
@@ -5561,9 +5420,6 @@ program.command("start").description(`Start the ${APP_NAME5} gateway + UI in the
|
|
|
5561
5420
|
program.command("restart").description(`Restart the ${APP_NAME5} background service`).option("--ui-port <port>", "UI port").option("--start-timeout <ms>", "Maximum wait time for startup readiness in milliseconds").option("--open", "Open browser after restart", false).action(async (opts) => runtime.restart(opts));
|
|
5562
5421
|
program.command("serve").description(`Run the ${APP_NAME5} gateway + UI in the foreground`).option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).action(async (opts) => runtime.serve(opts));
|
|
5563
5422
|
program.command("stop").description(`Stop the ${APP_NAME5} background service`).action(async () => runtime.stop());
|
|
5564
|
-
var service = program.command("service").description("Manage OS service integration");
|
|
5565
|
-
service.command("install-systemd").description(`Install a systemd unit for ${APP_NAME5} on Linux servers`).option("--name <name>", "Systemd unit name", APP_NAME5).option("--ui-port <port>", "UI port").action(async (opts) => runtime.serviceInstallSystemd(opts));
|
|
5566
|
-
service.command("uninstall-systemd").description(`Remove the systemd unit for ${APP_NAME5}`).option("--name <name>", "Systemd unit name", APP_NAME5).action(async (opts) => runtime.serviceUninstallSystemd(opts));
|
|
5567
5423
|
program.command("agent").description("Interact with the agent directly").option("-m, --message <message>", "Message to send to the agent").option("-s, --session <session>", "Session ID", "cli:default").option("--model <model>", "Session model override for this run").option("--no-markdown", "Disable Markdown rendering").action(async (opts) => runtime.agent(opts));
|
|
5568
5424
|
program.command("update").description(`Update ${APP_NAME5}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
|
|
5569
5425
|
var registerClawHubInstall = (cmd) => {
|
package/package.json
CHANGED
package/templates/USAGE.md
CHANGED
|
@@ -418,8 +418,6 @@ Created under the workspace:
|
|
|
418
418
|
| `nextclaw start` | Start gateway + UI in the background |
|
|
419
419
|
| `nextclaw restart` | Restart the background service with optional start flags |
|
|
420
420
|
| `nextclaw stop` | Stop the background service |
|
|
421
|
-
| `sudo nextclaw service install-systemd` | Install a managed Linux `systemd` service for public/server deployment |
|
|
422
|
-
| `sudo nextclaw service uninstall-systemd` | Remove the managed Linux `systemd` service |
|
|
423
421
|
| `nextclaw ui` | Start UI and gateway in the foreground |
|
|
424
422
|
| `nextclaw gateway` | Start gateway only (for channels) |
|
|
425
423
|
| `nextclaw serve` | Run gateway + UI in the foreground (no background) |
|
|
@@ -460,13 +458,6 @@ Gateway options (when running `nextclaw gateway` or `nextclaw start`):
|
|
|
460
458
|
|
|
461
459
|
If service is already running, new UI port flags do not hot-apply; use `nextclaw restart ...` to apply them.
|
|
462
460
|
|
|
463
|
-
Linux server deployment tip:
|
|
464
|
-
|
|
465
|
-
- If you put Nginx/Caddy/Traefik in front of NextClaw, do not rely on a one-time `nextclaw start` only.
|
|
466
|
-
- `nextclaw start` is a background convenience command, not a crash/reboot supervisor.
|
|
467
|
-
- On Linux servers, install a managed unit with `sudo nextclaw service install-systemd` so the UI/API comes back automatically after reboot or unexpected exit.
|
|
468
|
-
- Verify with `systemctl status nextclaw`, `journalctl -u nextclaw -f`, and `curl http://127.0.0.1:18791/api/health`.
|
|
469
|
-
|
|
470
461
|
Status/diagnostics tips:
|
|
471
462
|
|
|
472
463
|
- `nextclaw status` shows runtime truth (process + health + config summary).
|