nextclaw 0.9.11 → 0.9.13

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
@@ -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 writeFileSync5 } from "fs";
29
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } 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";
@@ -1925,7 +1925,7 @@ var DiagnosticsCommands = class {
1925
1925
  });
1926
1926
  if (opts.json) {
1927
1927
  console.log(JSON.stringify(report, null, 2));
1928
- process.exitCode = report.exitCode;
1928
+ process.exitCode = 0;
1929
1929
  return;
1930
1930
  }
1931
1931
  console.log(`${this.deps.logo} ${APP_NAME} Status`);
@@ -1978,7 +1978,7 @@ var DiagnosticsCommands = class {
1978
1978
  console.log(line);
1979
1979
  }
1980
1980
  }
1981
- process.exitCode = report.exitCode;
1981
+ process.exitCode = 0;
1982
1982
  }
1983
1983
  async doctor(opts = {}) {
1984
1984
  const report = await this.collectRuntimeStatus({
@@ -2148,7 +2148,7 @@ var DiagnosticsCommands = class {
2148
2148
  }
2149
2149
  const logTail = params.verbose ? this.readLogTail(serviceState?.logPath ?? resolveServiceLogPath(), 25) : [];
2150
2150
  const level = running ? managedHealth.state === "ok" ? issues.length > 0 ? "degraded" : "healthy" : "degraded" : "stopped";
2151
- const exitCode = level === "healthy" ? 0 : level === "degraded" ? 1 : 2;
2151
+ const exitCode = 0;
2152
2152
  return {
2153
2153
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2154
2154
  configPath,
@@ -2252,7 +2252,7 @@ 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, mkdtempSync, openSync, rmSync as rmSync3 } 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
2257
  import { tmpdir } from "os";
2258
2258
  import { spawn as spawn2 } from "child_process";
@@ -4391,33 +4391,51 @@ var ServiceCommands = class {
4391
4391
  }
4392
4392
  async materializeMarketplaceGitSkillSource(params) {
4393
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");
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");
4397
4404
  }
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) {
4398
4415
  const tempRoot = mkdtempSync(join5(tmpdir(), "nextclaw-marketplace-skill-"));
4399
4416
  const repoDir = join5(tempRoot, "repo");
4400
4417
  try {
4401
4418
  const cloneArgs = ["clone", "--depth", "1", "--filter=blob:none", "--sparse"];
4402
- if (parsed.ref) {
4403
- cloneArgs.push("--branch", parsed.ref);
4419
+ if (params.parsed.ref) {
4420
+ cloneArgs.push("--branch", params.parsed.ref);
4404
4421
  }
4405
- cloneArgs.push(parsed.repoUrl, repoDir);
4406
- const cloneResult = await this.runCommand(gitPath, cloneArgs, {
4422
+ cloneArgs.push(params.parsed.repoUrl, repoDir);
4423
+ const cloneResult = await this.runCommand(params.gitPath, cloneArgs, {
4407
4424
  cwd: tempRoot,
4408
4425
  timeoutMs: 18e4
4409
4426
  });
4410
- const sparseResult = await this.runCommand(gitPath, ["-C", repoDir, "sparse-checkout", "set", parsed.skillPath], {
4427
+ const sparseResult = await this.runCommand(params.gitPath, ["-C", repoDir, "sparse-checkout", "set", params.parsed.skillPath], {
4411
4428
  cwd: tempRoot,
4412
4429
  timeoutMs: 6e4
4413
4430
  });
4414
- const skillDir = join5(repoDir, parsed.skillPath);
4431
+ const skillDir = join5(repoDir, params.parsed.skillPath);
4415
4432
  return {
4416
4433
  tempRoot,
4417
4434
  skillDir,
4418
4435
  commandOutput: [
4419
- `Git repository: ${parsed.repoUrl}`,
4420
- `Git path: ${parsed.skillPath}`,
4436
+ "Installer path: git-sparse",
4437
+ `Git repository: ${params.parsed.repoUrl}`,
4438
+ `Git path: ${params.parsed.skillPath}`,
4421
4439
  this.mergeCommandOutput(cloneResult.stdout, cloneResult.stderr),
4422
4440
  this.mergeCommandOutput(sparseResult.stdout, sparseResult.stderr)
4423
4441
  ].filter(Boolean).join("\n")
@@ -4427,6 +4445,98 @@ var ServiceCommands = class {
4427
4445
  throw error;
4428
4446
  }
4429
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;
4492
+ }
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);
4523
+ }
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}`);
4529
+ }
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}`);
4536
+ }
4537
+ const bytes = await response.arrayBuffer();
4538
+ return Buffer.from(bytes);
4539
+ }
4430
4540
  parseMarketplaceGitSkillSource(source) {
4431
4541
  const trimmed = source.trim();
4432
4542
  if (!trimmed) {
@@ -4447,6 +4557,8 @@ var ServiceCommands = class {
4447
4557
  throw new Error(`Unsupported GitHub skill source URL: ${source}`);
4448
4558
  }
4449
4559
  return {
4560
+ owner: owner2,
4561
+ repo: repo2,
4450
4562
  repoUrl: `https://github.com/${owner2}/${repo2}.git`,
4451
4563
  skillPath: pathParts2.join("/"),
4452
4564
  ref: ref2
@@ -4466,6 +4578,8 @@ var ServiceCommands = class {
4466
4578
  throw new Error(`Unsupported git skill source: ${source}`);
4467
4579
  }
4468
4580
  return {
4581
+ owner,
4582
+ repo,
4469
4583
  repoUrl: `https://github.com/${owner}/${repo}.git`,
4470
4584
  skillPath: pathParts.join("/"),
4471
4585
  ref: ref && ref.trim().length > 0 ? ref.trim() : void 0
@@ -4628,7 +4742,7 @@ ${stderr}`.trim();
4628
4742
  };
4629
4743
 
4630
4744
  // src/cli/workspace.ts
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";
4745
+ import { cpSync as cpSync2, existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync2, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
4632
4746
  import { createRequire } from "module";
4633
4747
  import { dirname as dirname2, join as join6, resolve as resolve8 } from "path";
4634
4748
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -4672,7 +4786,7 @@ var WorkspaceManager = class {
4672
4786
  const raw = readFileSync7(templatePath, "utf-8");
4673
4787
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
4674
4788
  mkdirSync5(dirname2(filePath), { recursive: true });
4675
- writeFileSync4(filePath, content);
4789
+ writeFileSync5(filePath, content);
4676
4790
  created.push(entry.target);
4677
4791
  }
4678
4792
  const memoryDir = join6(workspace, "memory");
@@ -5240,7 +5354,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5240
5354
  const merged = history.concat(
5241
5355
  rl.history ?? []
5242
5356
  );
5243
- writeFileSync5(historyFile, merged.join("\n"));
5357
+ writeFileSync6(historyFile, merged.join("\n"));
5244
5358
  process.exit(0);
5245
5359
  });
5246
5360
  let running = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
4
4
  "description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -8,13 +8,14 @@ This guide covers installation, configuration, channels, tools, automation, and
8
8
 
9
9
  ## AI Self-Management Contract
10
10
 
11
- When NextClaw AI needs to operate the product itself (status/doctor/channels/config/cron), follow these rules:
11
+ When NextClaw AI needs to operate the product itself (version/status/doctor/channels/config/cron), follow these rules:
12
12
 
13
13
  1. **Read this guide first** (`USAGE.md`) before executing management commands.
14
- 2. **Prefer machine-readable output** (`--json`) whenever available.
15
- 3. **Close the loop after changes** with `nextclaw status --json` (and `nextclaw doctor --json` when needed).
16
- 4. **Be explicit about restart semantics** (hot-apply, auto-restart, or manual restart required).
17
- 5. **Never invent commands**; use documented commands or `nextclaw --help` / `nextclaw <subcommand> --help`.
14
+ 2. **Use the exact command for the intent**: use `nextclaw --version` for version lookup; do not infer version from `status`.
15
+ 3. **Prefer machine-readable output** (`--json`) whenever available.
16
+ 4. **Close the loop after changes** with `nextclaw status --json` (and `nextclaw doctor --json` when needed).
17
+ 5. **Be explicit about restart semantics** (hot-apply, auto-restart, or manual restart required).
18
+ 6. **Never invent commands**; use documented commands or `nextclaw --help` / `nextclaw <subcommand> --help`.
18
19
 
19
20
  ---
20
21
 
@@ -421,6 +422,7 @@ Created under the workspace:
421
422
  | `nextclaw ui` | Start UI and gateway in the foreground |
422
423
  | `nextclaw gateway` | Start gateway only (for channels) |
423
424
  | `nextclaw serve` | Run gateway + UI in the foreground (no background) |
425
+ | `nextclaw --version` | Show the installed NextClaw version |
424
426
  | `nextclaw agent -m "message"` | Send a one-off message to the agent |
425
427
  | `nextclaw agent` | Interactive chat in the terminal |
426
428
  | `nextclaw agent --session <id> --model <model>` | Use a session-specific model/provider route (sticky for that session) |
@@ -460,8 +462,9 @@ If service is already running, new UI port flags do not hot-apply; use `nextclaw
460
462
 
461
463
  Status/diagnostics tips:
462
464
 
465
+ - `nextclaw --version` is the only supported way to query the installed CLI version.
463
466
  - `nextclaw status` shows runtime truth (process + health + config summary).
464
- - `nextclaw status --json` outputs machine-readable status and sets exit code (`0` healthy, `1` degraded, `2` stopped).
467
+ - `nextclaw status --json` outputs machine-readable status and exits `0` when the command itself succeeds; use the JSON `level` field (`healthy` / `degraded` / `stopped`) to interpret runtime state.
465
468
  - `nextclaw status --fix` safely clears stale service state if PID is dead.
466
469
  - `nextclaw doctor` runs additional checks (state coherence, health, port availability, provider readiness).
467
470