nextclaw 0.9.10 → 0.9.12

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
@@ -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 service2 = new CronService(storePath);
1835
- const jobs = service2.listJobs(Boolean(opts.all));
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 service2 = new CronService(storePath);
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 = service2.addJob({
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 service2 = new CronService(storePath);
1880
- if (service2.removeJob(jobId)) {
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 service2 = new CronService(storePath);
1889
- const job = service2.enableJob(jobId, !opts.disable);
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 service2 = new CronService(storePath);
1899
- const ok = await service2.runJob(jobId, Boolean(opts.force));
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, writeFileSync as writeFileSync4 } from "fs";
2255
+ import { appendFileSync, closeSync, cpSync, existsSync as existsSync8, mkdirSync as mkdirSync4, mkdtempSync, openSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
2256
2256
  import { dirname, isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve7 } from "path";
2257
- import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
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,221 @@ var ServiceCommands = class {
4519
4368
  if (existsSync8(destination) && params.force) {
4520
4369
  rmSync3(destination, { recursive: true, force: true });
4521
4370
  }
4522
- const skildArgs = ["--yes", "skild", "install", source, "--target", "agents", "--local", "--json", "--skill", skillName];
4523
- if (params.registry) {
4524
- skildArgs.push("--registry", params.registry);
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
- if (params.force) {
4527
- skildArgs.push("--force");
4391
+ }
4392
+ async materializeMarketplaceGitSkillSource(params) {
4393
+ const parsed = this.parseMarketplaceGitSkillSource(params.source);
4394
+ const gitPath = this.resolveGitExecutablePath();
4395
+ const fallbackNotes = [];
4396
+ if (gitPath) {
4397
+ try {
4398
+ return await this.materializeMarketplaceGitSkillViaGit({ gitPath, parsed });
4399
+ } catch (error) {
4400
+ fallbackNotes.push(`Git fast path failed: ${String(error)}`);
4401
+ }
4402
+ } else {
4403
+ fallbackNotes.push("Git fast path unavailable: git executable not found");
4528
4404
  }
4529
- let result = await this.runCommandWithFallback(
4530
- ["npx", "/opt/homebrew/bin/npx", "/usr/local/bin/npx"],
4531
- skildArgs,
4532
- {
4533
- cwd: workspace,
4405
+ const httpResult = await this.materializeMarketplaceGitSkillViaGithubApi(parsed);
4406
+ return {
4407
+ ...httpResult,
4408
+ commandOutput: [...fallbackNotes, httpResult.commandOutput].filter(Boolean).join("\n")
4409
+ };
4410
+ }
4411
+ resolveGitExecutablePath() {
4412
+ return findExecutableOnPath("git");
4413
+ }
4414
+ async materializeMarketplaceGitSkillViaGit(params) {
4415
+ const tempRoot = mkdtempSync(join5(tmpdir(), "nextclaw-marketplace-skill-"));
4416
+ const repoDir = join5(tempRoot, "repo");
4417
+ try {
4418
+ const cloneArgs = ["clone", "--depth", "1", "--filter=blob:none", "--sparse"];
4419
+ if (params.parsed.ref) {
4420
+ cloneArgs.push("--branch", params.parsed.ref);
4421
+ }
4422
+ cloneArgs.push(params.parsed.repoUrl, repoDir);
4423
+ const cloneResult = await this.runCommand(params.gitPath, cloneArgs, {
4424
+ cwd: tempRoot,
4534
4425
  timeoutMs: 18e4
4426
+ });
4427
+ const sparseResult = await this.runCommand(params.gitPath, ["-C", repoDir, "sparse-checkout", "set", params.parsed.skillPath], {
4428
+ cwd: tempRoot,
4429
+ timeoutMs: 6e4
4430
+ });
4431
+ const skillDir = join5(repoDir, params.parsed.skillPath);
4432
+ return {
4433
+ tempRoot,
4434
+ skillDir,
4435
+ commandOutput: [
4436
+ "Installer path: git-sparse",
4437
+ `Git repository: ${params.parsed.repoUrl}`,
4438
+ `Git path: ${params.parsed.skillPath}`,
4439
+ this.mergeCommandOutput(cloneResult.stdout, cloneResult.stderr),
4440
+ this.mergeCommandOutput(sparseResult.stdout, sparseResult.stderr)
4441
+ ].filter(Boolean).join("\n")
4442
+ };
4443
+ } catch (error) {
4444
+ rmSync3(tempRoot, { recursive: true, force: true });
4445
+ throw error;
4446
+ }
4447
+ }
4448
+ async materializeMarketplaceGitSkillViaGithubApi(parsed) {
4449
+ const tempRoot = mkdtempSync(join5(tmpdir(), "nextclaw-marketplace-skill-"));
4450
+ const skillDir = join5(tempRoot, "skill");
4451
+ const downloadedFiles = [];
4452
+ try {
4453
+ mkdirSync4(skillDir, { recursive: true });
4454
+ await this.downloadGithubDirectoryToPath({
4455
+ parsed,
4456
+ remotePath: parsed.skillPath,
4457
+ localDir: skillDir,
4458
+ relativePrefix: "",
4459
+ downloadedFiles
4460
+ });
4461
+ return {
4462
+ tempRoot,
4463
+ skillDir,
4464
+ commandOutput: [
4465
+ "Installer path: github-http",
4466
+ `Git repository: ${parsed.repoUrl}`,
4467
+ `Git path: ${parsed.skillPath}`,
4468
+ `Downloaded files: ${downloadedFiles.length}`
4469
+ ].join("\n")
4470
+ };
4471
+ } catch (error) {
4472
+ rmSync3(tempRoot, { recursive: true, force: true });
4473
+ throw error;
4474
+ }
4475
+ }
4476
+ async downloadGithubDirectoryToPath(params) {
4477
+ const encodedPath = params.remotePath.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
4478
+ const apiUrl = new URL(`https://api.github.com/repos/${params.parsed.owner}/${params.parsed.repo}/contents/${encodedPath}`);
4479
+ if (params.parsed.ref) {
4480
+ apiUrl.searchParams.set("ref", params.parsed.ref);
4481
+ }
4482
+ const payload = await this.fetchJsonWithHeaders(apiUrl.toString(), {
4483
+ Accept: "application/vnd.github+json",
4484
+ "User-Agent": `${APP_NAME2}/${getPackageVersion()}`
4485
+ });
4486
+ if (!Array.isArray(payload)) {
4487
+ throw new Error(`GitHub path is not a directory: ${params.remotePath}`);
4488
+ }
4489
+ for (const entry of payload) {
4490
+ if (!entry || typeof entry !== "object") {
4491
+ continue;
4535
4492
  }
4536
- );
4537
- let payload = this.parseSkildJsonOutput(result.stdout);
4538
- if (!payload) {
4539
- const forceArgs = skildArgs.includes("--force") ? skildArgs : [...skildArgs, "--force"];
4540
- result = await this.runCommandWithFallback(
4541
- ["npx", "/opt/homebrew/bin/npx", "/usr/local/bin/npx"],
4542
- forceArgs,
4543
- {
4544
- cwd: workspace,
4545
- timeoutMs: 18e4
4546
- }
4547
- );
4548
- payload = this.parseSkildJsonOutput(result.stdout);
4493
+ const item = entry;
4494
+ const itemName = typeof item.name === "string" ? item.name.trim() : "";
4495
+ const itemPath = typeof item.path === "string" ? item.path.trim() : "";
4496
+ if (!itemName || !itemPath) {
4497
+ continue;
4498
+ }
4499
+ if (item.type === "dir") {
4500
+ const childLocalDir = join5(params.localDir, itemName);
4501
+ mkdirSync4(childLocalDir, { recursive: true });
4502
+ await this.downloadGithubDirectoryToPath({
4503
+ parsed: params.parsed,
4504
+ remotePath: itemPath,
4505
+ localDir: childLocalDir,
4506
+ relativePrefix: params.relativePrefix ? `${params.relativePrefix}/${itemName}` : itemName,
4507
+ downloadedFiles: params.downloadedFiles
4508
+ });
4509
+ continue;
4510
+ }
4511
+ if (item.type !== "file") {
4512
+ throw new Error(`Unsupported GitHub skill entry type: ${item.type ?? "unknown"}`);
4513
+ }
4514
+ if (!item.download_url) {
4515
+ throw new Error(`GitHub skill file missing download_url: ${itemPath}`);
4516
+ }
4517
+ const content = await this.fetchBinaryWithHeaders(item.download_url, {
4518
+ "User-Agent": `${APP_NAME2}/${getPackageVersion()}`
4519
+ });
4520
+ const localFile = join5(params.localDir, itemName);
4521
+ writeFileSync4(localFile, content);
4522
+ params.downloadedFiles.push(params.relativePrefix ? `${params.relativePrefix}/${itemName}` : itemName);
4549
4523
  }
4550
- if (!payload) {
4551
- throw new Error("skild returned null json payload even after force reinstall");
4524
+ }
4525
+ async fetchJsonWithHeaders(url, headers) {
4526
+ const response = await fetch(url, { headers });
4527
+ if (!response.ok) {
4528
+ throw new Error(`HTTP ${response.status} when fetching ${url}`);
4552
4529
  }
4553
- const installDir = typeof payload.installDir === "string" ? payload.installDir.trim() : "";
4554
- const installSkillFile = installDir ? join5(installDir, "SKILL.md") : "";
4555
- if (!installDir || !existsSync8(installSkillFile)) {
4556
- throw new Error(`skild install did not produce a valid skill directory for ${skillName}`);
4530
+ return await response.json();
4531
+ }
4532
+ async fetchBinaryWithHeaders(url, headers) {
4533
+ const response = await fetch(url, { headers });
4534
+ if (!response.ok) {
4535
+ throw new Error(`HTTP ${response.status} when downloading ${url}`);
4557
4536
  }
4558
- mkdirSync4(dirname(destination), { recursive: true });
4559
- if (resolve7(installDir) !== resolve7(destination)) {
4560
- cpSync(installDir, destination, { recursive: true, force: true });
4537
+ const bytes = await response.arrayBuffer();
4538
+ return Buffer.from(bytes);
4539
+ }
4540
+ parseMarketplaceGitSkillSource(source) {
4541
+ const trimmed = source.trim();
4542
+ if (!trimmed) {
4543
+ throw new Error("Git skill source is required");
4544
+ }
4545
+ if (trimmed.includes("://")) {
4546
+ const parsedUrl = new URL(trimmed);
4547
+ if (parsedUrl.hostname !== "github.com") {
4548
+ throw new Error(`Unsupported git skill source host: ${parsedUrl.hostname}`);
4549
+ }
4550
+ const parts2 = parsedUrl.pathname.split("/").filter(Boolean);
4551
+ if (parts2.length < 5 || parts2[2] !== "tree" && parts2[2] !== "blob") {
4552
+ throw new Error(`Unsupported GitHub skill source URL: ${source}`);
4553
+ }
4554
+ const [owner2, repoRaw, , ref2, ...pathParts2] = parts2;
4555
+ const repo2 = repoRaw.replace(/\.git$/i, "");
4556
+ if (!owner2 || !repo2 || !ref2 || pathParts2.length === 0) {
4557
+ throw new Error(`Unsupported GitHub skill source URL: ${source}`);
4558
+ }
4559
+ return {
4560
+ owner: owner2,
4561
+ repo: repo2,
4562
+ repoUrl: `https://github.com/${owner2}/${repo2}.git`,
4563
+ skillPath: pathParts2.join("/"),
4564
+ ref: ref2
4565
+ };
4566
+ }
4567
+ const parts = trimmed.split("/").filter(Boolean);
4568
+ if (parts.length < 3) {
4569
+ throw new Error(`Unsupported git skill source: ${source}`);
4570
+ }
4571
+ const owner = parts[0] ?? "";
4572
+ const repoWithOptionalRef = parts[1] ?? "";
4573
+ const pathParts = parts.slice(2);
4574
+ const atIndex = repoWithOptionalRef.indexOf("@");
4575
+ const repo = (atIndex >= 0 ? repoWithOptionalRef.slice(0, atIndex) : repoWithOptionalRef).replace(/\.git$/i, "");
4576
+ const ref = atIndex >= 0 ? repoWithOptionalRef.slice(atIndex + 1) : void 0;
4577
+ if (!owner || !repo || pathParts.length === 0) {
4578
+ throw new Error(`Unsupported git skill source: ${source}`);
4561
4579
  }
4562
4580
  return {
4563
- message: `Installed skill: ${skillName}`,
4564
- output: [
4565
- `Source: ${source}`,
4566
- `Installed via skild: ${installDir}`,
4567
- `Workspace target: ${destination}`,
4568
- this.mergeCommandOutput(result.stdout, result.stderr)
4569
- ].filter(Boolean).join("\n")
4581
+ owner,
4582
+ repo,
4583
+ repoUrl: `https://github.com/${owner}/${repo}.git`,
4584
+ skillPath: pathParts.join("/"),
4585
+ ref: ref && ref.trim().length > 0 ? ref.trim() : void 0
4570
4586
  };
4571
4587
  }
4572
4588
  async enableMarketplacePlugin(id) {
@@ -4587,17 +4603,13 @@ var ServiceCommands = class {
4587
4603
  async uninstallMarketplaceSkill(slug) {
4588
4604
  const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
4589
4605
  const targetDir = join5(workspace, "skills", slug);
4590
- const skildDir = join5(workspace, ".agents", "skills", slug);
4591
- const existingTargets = [targetDir, skildDir].filter((path) => existsSync8(path));
4592
- if (existingTargets.length === 0) {
4606
+ if (!existsSync8(targetDir)) {
4593
4607
  throw new Error(`Skill not installed in workspace: ${slug}`);
4594
4608
  }
4595
- for (const path of existingTargets) {
4596
- rmSync3(path, { recursive: true, force: true });
4597
- }
4609
+ rmSync3(targetDir, { recursive: true, force: true });
4598
4610
  return {
4599
4611
  message: `Uninstalled skill: ${slug}`,
4600
- output: existingTargets.map((path) => `Removed ${path}`).join("\n")
4612
+ output: `Removed ${targetDir}`
4601
4613
  };
4602
4614
  }
4603
4615
  installBuiltinMarketplaceSkill(slug, force) {
@@ -4659,39 +4671,6 @@ var ServiceCommands = class {
4659
4671
  }
4660
4672
  return destination;
4661
4673
  }
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
4674
  mergeCommandOutput(stdout, stderr) {
4696
4675
  return `${stdout}
4697
4676
  ${stderr}`.trim();
@@ -4768,7 +4747,7 @@ import { createRequire } from "module";
4768
4747
  import { dirname as dirname2, join as join6, resolve as resolve8 } from "path";
4769
4748
  import { fileURLToPath as fileURLToPath3 } from "url";
4770
4749
  import { APP_NAME as APP_NAME3, getDataDir as getDataDir7 } from "@nextclaw/core";
4771
- import { spawnSync as spawnSync5 } from "child_process";
4750
+ import { spawnSync as spawnSync4 } from "child_process";
4772
4751
  var WorkspaceManager = class {
4773
4752
  constructor(logo) {
4774
4753
  this.logo = logo;
@@ -4915,7 +4894,7 @@ var WorkspaceManager = class {
4915
4894
  recursive: true,
4916
4895
  filter: (src) => !src.includes("node_modules") && !src.includes("dist")
4917
4896
  });
4918
- const install = spawnSync5("npm", ["install"], { cwd: userBridge, stdio: "pipe" });
4897
+ const install = spawnSync4("npm", ["install"], { cwd: userBridge, stdio: "pipe" });
4919
4898
  if (install.status !== 0) {
4920
4899
  console.error(`Bridge install failed: ${install.status ?? 1}`);
4921
4900
  if (install.stderr) {
@@ -4923,7 +4902,7 @@ var WorkspaceManager = class {
4923
4902
  }
4924
4903
  process.exit(1);
4925
4904
  }
4926
- const build = spawnSync5("npm", ["run", "build"], { cwd: userBridge, stdio: "pipe" });
4905
+ const build = spawnSync4("npm", ["run", "build"], { cwd: userBridge, stdio: "pipe" });
4927
4906
  if (build.status !== 0) {
4928
4907
  console.error(`Bridge build failed: ${build.status ?? 1}`);
4929
4908
  if (build.stderr) {
@@ -5293,12 +5272,6 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5293
5272
  async stop() {
5294
5273
  await this.serviceCommands.stopService();
5295
5274
  }
5296
- async serviceInstallSystemd(opts) {
5297
- await this.serviceCommands.installSystemdService(opts);
5298
- }
5299
- async serviceUninstallSystemd(opts) {
5300
- await this.serviceCommands.uninstallSystemdService(opts);
5301
- }
5302
5275
  async agent(opts) {
5303
5276
  const configPath = getConfigPath4();
5304
5277
  const config2 = resolveConfigSecrets3(loadConfig7(), { configPath });
@@ -5561,9 +5534,6 @@ program.command("start").description(`Start the ${APP_NAME5} gateway + UI in the
5561
5534
  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
5535
  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
5536
  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
5537
  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
5538
  program.command("update").description(`Update ${APP_NAME5}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
5569
5539
  var registerClawHubInstall = (cmd) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -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).