nextclaw 0.9.14 → 0.9.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/dist/cli/index.js +472 -420
  2. package/package.json +3 -3
  3. package/templates/USAGE.md +3 -2
  4. package/ui-dist/assets/{ChannelsList-DH5fzlPu.js → ChannelsList-DACqpUYZ.js} +1 -1
  5. package/ui-dist/assets/{ChatPage-BrLCnJSb.js → ChatPage-iji0RkTR.js} +12 -12
  6. package/ui-dist/assets/{DocBrowser-DPQHJVsZ.js → DocBrowser-D7mjKkGe.js} +1 -1
  7. package/ui-dist/assets/{LogoBadge-FEb4_vSq.js → LogoBadge-BlDT-g9R.js} +1 -1
  8. package/ui-dist/assets/{MarketplacePage-BAVXYeZA.js → MarketplacePage-CZq3jVgg.js} +3 -3
  9. package/ui-dist/assets/{ModelConfig-BqPXe7nw.js → ModelConfig-DwRU5qrw.js} +1 -1
  10. package/ui-dist/assets/{ProvidersList-vpKPuIxV.js → ProvidersList-DFxN3pjx.js} +1 -1
  11. package/ui-dist/assets/{RuntimeConfig-DTYSU4_d.js → RuntimeConfig-C7BRLGSC.js} +1 -1
  12. package/ui-dist/assets/{SecretsConfig-nNzs3YDm.js → SecretsConfig-D5xZh7VF.js} +1 -1
  13. package/ui-dist/assets/{SessionsConfig-CHjeyqEQ.js → SessionsConfig-ovpj_otA.js} +1 -1
  14. package/ui-dist/assets/{card-73MmEZi7.js → card-Bf4CtrW8.js} +1 -1
  15. package/ui-dist/assets/{index-DI6BuShn.css → index-C_DhisNo.css} +1 -1
  16. package/ui-dist/assets/{index-CTLvVlk8.js → index-dKTqKCJo.js} +2 -2
  17. package/ui-dist/assets/{input-1MCMs6Yf.js → input-CaKJyoWZ.js} +1 -1
  18. package/ui-dist/assets/{label-C4Q8RlBJ.js → label-BaXSWTKI.js} +1 -1
  19. package/ui-dist/assets/{page-layout-CK0vcVmV.js → page-layout-DA6PFRtQ.js} +1 -1
  20. package/ui-dist/assets/{session-run-status-BaNlKvi6.js → session-run-status-CllIZxNf.js} +1 -1
  21. package/ui-dist/assets/{switch-Bf8w_cF1.js → switch-Cvd5wZs-.js} +1 -1
  22. package/ui-dist/assets/{tabs-custom-B6Gw8gax.js → tabs-custom-0PybLkXs.js} +1 -1
  23. package/ui-dist/assets/{useConfirmDialog-B5CZ4EDN.js → useConfirmDialog-DdtpSju1.js} +1 -1
  24. package/ui-dist/index.html +2 -2
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 writeFileSync6 } from "fs";
29
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync9, 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";
@@ -185,15 +185,13 @@ function parseSessionKey(sessionKey) {
185
185
  };
186
186
  }
187
187
 
188
- // src/cli/skills/clawhub.ts
189
- import { spawnSync } from "child_process";
190
- import { existsSync as existsSync2 } from "fs";
191
- import { isAbsolute, join, resolve as resolve2 } from "path";
192
- async function installClawHubSkill(options) {
193
- const slug = options.slug.trim();
194
- if (!slug) {
195
- throw new Error("Skill slug is required.");
196
- }
188
+ // src/cli/skills/marketplace.ts
189
+ import { cpSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
190
+ import { basename, dirname, isAbsolute, join, relative, resolve as resolve2 } from "path";
191
+ import { SkillsLoader } from "@nextclaw/core";
192
+ var DEFAULT_MARKETPLACE_API_BASE = "https://marketplace-api.nextclaw.io";
193
+ async function installMarketplaceSkill(options) {
194
+ const slug = validateSkillSlug(options.slug.trim(), "slug");
197
195
  const workdir = resolve2(options.workdir);
198
196
  if (!existsSync2(workdir)) {
199
197
  throw new Error(`Workdir does not exist: ${workdir}`);
@@ -205,62 +203,256 @@ async function installClawHubSkill(options) {
205
203
  if (existsSync2(skillFile)) {
206
204
  return {
207
205
  slug,
208
- version: options.version,
209
- registry: options.registry,
210
206
  destinationDir,
211
- alreadyInstalled: true
207
+ alreadyInstalled: true,
208
+ source: "marketplace"
212
209
  };
213
210
  }
214
211
  throw new Error(`Skill directory already exists: ${destinationDir} (use --force)`);
215
212
  }
216
- const args = buildClawHubArgs(slug, options);
217
- const result = spawnSync("npx", args, {
218
- cwd: workdir,
219
- stdio: "pipe",
220
- env: process.env
221
- });
222
- if (result.error) {
223
- throw new Error(`Failed to run npx clawhub: ${String(result.error)}`);
213
+ const apiBase = resolveMarketplaceApiBase(options.apiBaseUrl);
214
+ const item = await fetchMarketplaceSkillItem(apiBase, slug);
215
+ if (item.install.kind === "builtin") {
216
+ if (existsSync2(destinationDir) && options.force) {
217
+ rmSync2(destinationDir, { recursive: true, force: true });
218
+ }
219
+ installBuiltinSkill(workdir, destinationDir, slug);
220
+ return {
221
+ slug,
222
+ destinationDir,
223
+ source: "builtin"
224
+ };
225
+ }
226
+ const filesPayload = await fetchMarketplaceSkillFiles(apiBase, slug);
227
+ if (existsSync2(destinationDir) && options.force) {
228
+ rmSync2(destinationDir, { recursive: true, force: true });
224
229
  }
225
- if (result.status !== 0) {
226
- const stdout = result.stdout ? String(result.stdout).trim() : "";
227
- const stderr = result.stderr ? String(result.stderr).trim() : "";
228
- const details = [stderr, stdout].filter(Boolean).join("\n");
229
- throw new Error(details || `clawhub install failed with code ${result.status ?? 1}`);
230
+ mkdirSync2(destinationDir, { recursive: true });
231
+ for (const file of filesPayload.files) {
232
+ const targetPath = resolve2(destinationDir, ...file.path.split("/"));
233
+ const rel = relative(destinationDir, targetPath);
234
+ if (rel.startsWith("..") || isAbsolute(rel)) {
235
+ throw new Error(`Invalid marketplace file path: ${file.path}`);
236
+ }
237
+ mkdirSync2(dirname(targetPath), { recursive: true });
238
+ writeFileSync2(targetPath, Buffer.from(file.contentBase64, "base64"));
239
+ }
240
+ if (!existsSync2(join(destinationDir, "SKILL.md"))) {
241
+ throw new Error(`Marketplace skill ${slug} does not include SKILL.md`);
230
242
  }
231
243
  return {
232
244
  slug,
233
- version: options.version,
234
- registry: options.registry,
235
- destinationDir
245
+ destinationDir,
246
+ source: "marketplace"
236
247
  };
237
248
  }
238
- function buildClawHubArgs(slug, options) {
239
- const args = ["--yes", "clawhub", "install", slug];
240
- if (options.version) {
241
- args.push("--version", options.version);
249
+ async function publishMarketplaceSkill(options) {
250
+ const skillDir = resolve2(options.skillDir);
251
+ if (!existsSync2(skillDir)) {
252
+ throw new Error(`Skill directory not found: ${skillDir}`);
253
+ }
254
+ const files = collectFiles(skillDir);
255
+ if (!files.some((file) => file.path === "SKILL.md")) {
256
+ throw new Error(`Skill directory must include SKILL.md: ${skillDir}`);
257
+ }
258
+ const parsedFrontmatter = parseSkillFrontmatter(readFileSync2(join(skillDir, "SKILL.md"), "utf8"));
259
+ const slug = validateSkillSlug(options.slug?.trim() || basename(skillDir), "slug");
260
+ const name = options.name?.trim() || parsedFrontmatter.name || slug;
261
+ const description = options.description?.trim() || parsedFrontmatter.description;
262
+ const summary = options.summary?.trim() || parsedFrontmatter.summary || description || `${slug} skill`;
263
+ const author = options.author?.trim() || parsedFrontmatter.author || "nextclaw";
264
+ const tags = normalizeTags(options.tags && options.tags.length > 0 ? options.tags : parsedFrontmatter.tags);
265
+ const apiBase = resolveMarketplaceApiBase(options.apiBaseUrl);
266
+ const token = resolveMarketplaceAdminToken(options.token);
267
+ if (options.requireExisting) {
268
+ await fetchMarketplaceSkillItem(apiBase, slug);
269
+ }
270
+ const response = await fetch(`${apiBase}/api/v1/admin/skills/upsert`, {
271
+ method: "POST",
272
+ headers: {
273
+ "content-type": "application/json",
274
+ ...token ? { Authorization: `Bearer ${token}` } : {}
275
+ },
276
+ body: JSON.stringify({
277
+ slug,
278
+ name,
279
+ summary,
280
+ description,
281
+ author,
282
+ tags,
283
+ sourceRepo: options.sourceRepo?.trim() || void 0,
284
+ homepage: options.homepage?.trim() || void 0,
285
+ publishedAt: options.publishedAt?.trim() || void 0,
286
+ updatedAt: options.updatedAt?.trim() || void 0,
287
+ files
288
+ })
289
+ });
290
+ const payload = await readMarketplaceEnvelope(response);
291
+ if (!payload.ok || !payload.data) {
292
+ const message = payload.error?.message || `marketplace publish failed: HTTP ${response.status}`;
293
+ throw new Error(message);
242
294
  }
243
- if (options.registry) {
244
- args.push("--registry", options.registry);
295
+ return {
296
+ created: payload.data.created,
297
+ slug,
298
+ fileCount: payload.data.fileCount
299
+ };
300
+ }
301
+ function collectFiles(rootDir) {
302
+ const output = [];
303
+ const walk = (dir, prefix) => {
304
+ const entries = readdirSync(dir, { withFileTypes: true });
305
+ for (const entry of entries) {
306
+ const absolute = join(dir, entry.name);
307
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
308
+ if (entry.isDirectory()) {
309
+ walk(absolute, relativePath);
310
+ continue;
311
+ }
312
+ if (!entry.isFile()) {
313
+ continue;
314
+ }
315
+ const content = readFileSync2(absolute);
316
+ output.push({
317
+ path: relativePath,
318
+ contentBase64: content.toString("base64")
319
+ });
320
+ }
321
+ };
322
+ walk(rootDir, "");
323
+ return output;
324
+ }
325
+ function installBuiltinSkill(workdir, destinationDir, skillName) {
326
+ const loader = new SkillsLoader(workdir);
327
+ const builtin = loader.listSkills(false).find((skill) => skill.name === skillName && skill.source === "builtin");
328
+ if (!builtin) {
329
+ throw new Error(`Builtin skill not found in local core bundle: ${skillName}`);
245
330
  }
246
- if (options.workdir) {
247
- args.push("--workdir", options.workdir);
331
+ cpSync(dirname(builtin.path), destinationDir, { recursive: true, force: true });
332
+ }
333
+ async function fetchMarketplaceSkillItem(apiBase, slug) {
334
+ const response = await fetch(`${apiBase}/api/v1/skills/items/${encodeURIComponent(slug)}`, {
335
+ headers: {
336
+ Accept: "application/json"
337
+ }
338
+ });
339
+ const payload = await readMarketplaceEnvelope(response);
340
+ if (!payload.ok || !payload.data) {
341
+ const message = payload.error?.message || `marketplace skill fetch failed: ${response.status}`;
342
+ throw new Error(message);
248
343
  }
249
- if (options.dir) {
250
- args.push("--dir", options.dir);
344
+ const kind = payload.data.install?.kind;
345
+ if (kind !== "builtin" && kind !== "marketplace") {
346
+ throw new Error(`Unsupported skill install kind from marketplace: ${String(kind)}`);
251
347
  }
252
- if (options.force) {
253
- args.push("--force");
348
+ return {
349
+ install: {
350
+ kind
351
+ }
352
+ };
353
+ }
354
+ async function fetchMarketplaceSkillFiles(apiBase, slug) {
355
+ const response = await fetch(`${apiBase}/api/v1/skills/items/${encodeURIComponent(slug)}/files`, {
356
+ headers: {
357
+ Accept: "application/json"
358
+ }
359
+ });
360
+ const payload = await readMarketplaceEnvelope(response);
361
+ if (!payload.ok || !payload.data) {
362
+ const message = payload.error?.message || `marketplace skill file fetch failed: ${response.status}`;
363
+ throw new Error(message);
254
364
  }
255
- return args;
365
+ return payload.data;
366
+ }
367
+ async function readMarketplaceEnvelope(response) {
368
+ const raw = await response.text();
369
+ let payload;
370
+ try {
371
+ payload = raw.length > 0 ? JSON.parse(raw) : null;
372
+ } catch {
373
+ throw new Error(`Invalid marketplace response: ${response.status}`);
374
+ }
375
+ if (!isRecord(payload) || typeof payload.ok !== "boolean") {
376
+ throw new Error(`Invalid marketplace response shape: ${response.status}`);
377
+ }
378
+ return payload;
379
+ }
380
+ function resolveMarketplaceApiBase(explicitBase) {
381
+ const raw = explicitBase?.trim() || process.env.NEXTCLAW_MARKETPLACE_API_BASE?.trim() || DEFAULT_MARKETPLACE_API_BASE;
382
+ return raw.replace(/\/+$/, "");
383
+ }
384
+ function resolveMarketplaceAdminToken(explicitToken) {
385
+ const token = explicitToken?.trim() || process.env.NEXTCLAW_MARKETPLACE_ADMIN_TOKEN?.trim();
386
+ return token && token.length > 0 ? token : void 0;
387
+ }
388
+ function validateSkillSlug(raw, fieldName) {
389
+ if (!/^[A-Za-z0-9._-]+$/.test(raw)) {
390
+ throw new Error(`Invalid ${fieldName}: ${raw}`);
391
+ }
392
+ return raw;
393
+ }
394
+ function normalizeTags(rawTags) {
395
+ const seen = /* @__PURE__ */ new Set();
396
+ const output = [];
397
+ for (const rawTag of rawTags ?? []) {
398
+ const tag = rawTag.trim();
399
+ if (!tag || seen.has(tag)) {
400
+ continue;
401
+ }
402
+ seen.add(tag);
403
+ output.push(tag);
404
+ }
405
+ return output.length > 0 ? output : ["skill"];
406
+ }
407
+ function parseSkillFrontmatter(raw) {
408
+ const normalized = raw.replace(/\r\n/g, "\n");
409
+ const match = normalized.match(/^---\n([\s\S]*?)\n---/);
410
+ if (!match || !match[1]) {
411
+ return {};
412
+ }
413
+ const metadata = /* @__PURE__ */ new Map();
414
+ for (const line of match[1].split("\n")) {
415
+ const parsed = line.match(/^([A-Za-z0-9_-]+):\s*(.+)$/);
416
+ if (!parsed) {
417
+ continue;
418
+ }
419
+ const key = parsed[1]?.trim().toLowerCase();
420
+ const value = parsed[2]?.trim();
421
+ if (!key || !value) {
422
+ continue;
423
+ }
424
+ metadata.set(key, trimYamlString(value));
425
+ }
426
+ const rawTags = metadata.get("tags");
427
+ let tags;
428
+ if (rawTags) {
429
+ if (rawTags.startsWith("[") && rawTags.endsWith("]")) {
430
+ tags = rawTags.slice(1, -1).split(",").map((entry) => trimYamlString(entry)).filter(Boolean);
431
+ } else {
432
+ tags = rawTags.split(",").map((entry) => entry.trim()).filter(Boolean);
433
+ }
434
+ }
435
+ return {
436
+ name: metadata.get("name"),
437
+ summary: metadata.get("summary"),
438
+ description: metadata.get("description"),
439
+ author: metadata.get("author"),
440
+ tags
441
+ };
442
+ }
443
+ function trimYamlString(raw) {
444
+ return raw.replace(/^['"]/, "").replace(/['"]$/, "").trim();
445
+ }
446
+ function isRecord(value) {
447
+ return typeof value === "object" && value !== null && !Array.isArray(value);
256
448
  }
257
449
 
258
450
  // src/cli/update/runner.ts
259
- import { spawnSync as spawnSync2 } from "child_process";
451
+ import { spawnSync } from "child_process";
260
452
  import { resolve as resolve4 } from "path";
261
453
 
262
454
  // src/cli/utils.ts
263
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
455
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, rmSync as rmSync3 } from "fs";
264
456
  import { join as join2, resolve as resolve3 } from "path";
265
457
  import { spawn } from "child_process";
266
458
  import { isIP } from "net";
@@ -319,7 +511,7 @@ function readServiceState() {
319
511
  return null;
320
512
  }
321
513
  try {
322
- const raw = readFileSync2(path, "utf-8");
514
+ const raw = readFileSync3(path, "utf-8");
323
515
  return JSON.parse(raw);
324
516
  } catch {
325
517
  return null;
@@ -327,13 +519,13 @@ function readServiceState() {
327
519
  }
328
520
  function writeServiceState(state) {
329
521
  const path = resolveServiceStatePath();
330
- mkdirSync2(resolve3(path, ".."), { recursive: true });
331
- writeFileSync2(path, JSON.stringify(state, null, 2));
522
+ mkdirSync3(resolve3(path, ".."), { recursive: true });
523
+ writeFileSync3(path, JSON.stringify(state, null, 2));
332
524
  }
333
525
  function clearServiceState() {
334
526
  const path = resolveServiceStatePath();
335
527
  if (existsSync3(path)) {
336
- rmSync2(path, { force: true });
528
+ rmSync3(path, { force: true });
337
529
  }
338
530
  }
339
531
  function resolveServiceStatePath() {
@@ -467,7 +659,7 @@ function resolveVersionFromPackageTree(startDir, expectedName) {
467
659
  const pkgPath = join2(current, "package.json");
468
660
  if (existsSync3(pkgPath)) {
469
661
  try {
470
- const raw = readFileSync2(pkgPath, "utf-8");
662
+ const raw = readFileSync3(pkgPath, "utf-8");
471
663
  const parsed = JSON.parse(raw);
472
664
  if (typeof parsed.version === "string") {
473
665
  if (!expectedName || parsed.name === expectedName) {
@@ -514,7 +706,7 @@ function runSelfUpdate(options = {}) {
514
706
  return { cmd: process.env.SHELL || "sh", args: ["-c", command] };
515
707
  };
516
708
  const runStep = (cmd, args, cwd) => {
517
- const result = spawnSync2(cmd, args, {
709
+ const result = spawnSync(cmd, args, {
518
710
  cwd,
519
711
  encoding: "utf-8",
520
712
  timeout: timeoutMs,
@@ -1389,7 +1581,7 @@ var ConfigCommands = class {
1389
1581
  };
1390
1582
 
1391
1583
  // src/cli/commands/secrets.ts
1392
- import { readFileSync as readFileSync3 } from "fs";
1584
+ import { readFileSync as readFileSync4 } from "fs";
1393
1585
  import {
1394
1586
  buildReloadPlan as buildReloadPlan2,
1395
1587
  diffConfigPaths as diffConfigPaths2,
@@ -1632,7 +1824,7 @@ var SecretsCommands = class {
1632
1824
  nextConfig.secrets.enabled = false;
1633
1825
  }
1634
1826
  if (opts.file) {
1635
- const raw = readFileSync3(opts.file, "utf-8");
1827
+ const raw = readFileSync4(opts.file, "utf-8");
1636
1828
  const patch = parseApplyFile(raw);
1637
1829
  if (patch.defaults) {
1638
1830
  nextConfig.secrets.defaults = patch.defaults;
@@ -1712,7 +1904,7 @@ var SecretsCommands = class {
1712
1904
  };
1713
1905
 
1714
1906
  // src/cli/commands/channels.ts
1715
- import { spawnSync as spawnSync3 } from "child_process";
1907
+ import { spawnSync as spawnSync2 } from "child_process";
1716
1908
  import { getWorkspacePath as getWorkspacePath2, loadConfig as loadConfig4, saveConfig as saveConfig4 } from "@nextclaw/core";
1717
1909
  import { BUILTIN_CHANNEL_PLUGIN_IDS, builtinProviderIds as builtinProviderIds2 } from "@nextclaw/runtime";
1718
1910
  import { buildPluginStatusReport as buildPluginStatusReport2, enablePluginInConfig as enablePluginInConfig2, getPluginChannelBindings } from "@nextclaw/openclaw-compat";
@@ -1762,7 +1954,7 @@ var ChannelCommands = class {
1762
1954
  const bridgeDir = this.deps.getBridgeDir();
1763
1955
  console.log(`${this.deps.logo} Starting bridge...`);
1764
1956
  console.log("Scan the QR code to connect.\n");
1765
- const result = spawnSync3("npm", ["start"], { cwd: bridgeDir, stdio: "inherit" });
1957
+ const result = spawnSync2("npm", ["start"], { cwd: bridgeDir, stdio: "inherit" });
1766
1958
  if (result.status !== 0) {
1767
1959
  console.error(`Bridge failed: ${result.status ?? 1}`);
1768
1960
  }
@@ -1903,7 +2095,7 @@ var CronCommands = class {
1903
2095
 
1904
2096
  // src/cli/commands/diagnostics.ts
1905
2097
  import { createServer as createNetServer } from "net";
1906
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
2098
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1907
2099
  import { resolve as resolve6 } from "path";
1908
2100
  import {
1909
2101
  APP_NAME,
@@ -2212,7 +2404,7 @@ var DiagnosticsCommands = class {
2212
2404
  return [];
2213
2405
  }
2214
2406
  try {
2215
- const lines = readFileSync4(path, "utf-8").split(/\r?\n/).filter(Boolean);
2407
+ const lines = readFileSync5(path, "utf-8").split(/\r?\n/).filter(Boolean);
2216
2408
  if (lines.length <= maxLines) {
2217
2409
  return lines;
2218
2410
  }
@@ -2252,9 +2444,8 @@ import {
2252
2444
  stopPluginChannelGateways
2253
2445
  } from "@nextclaw/openclaw-compat";
2254
2446
  import { startUiServer } from "@nextclaw/server";
2255
- import { appendFileSync, closeSync, cpSync, existsSync as existsSync8, mkdirSync as mkdirSync4, mkdtempSync, openSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
2256
- import { dirname, isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve7 } from "path";
2257
- import { tmpdir } from "os";
2447
+ import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, openSync, rmSync as rmSync4 } from "fs";
2448
+ import { dirname as dirname2, join as join5, resolve as resolve7 } from "path";
2258
2449
  import { spawn as spawn2 } from "child_process";
2259
2450
  import { request as httpRequest } from "http";
2260
2451
  import { request as httpsRequest } from "https";
@@ -2263,7 +2454,7 @@ import chokidar from "chokidar";
2263
2454
 
2264
2455
  // src/cli/gateway/controller.ts
2265
2456
  import { createHash } from "crypto";
2266
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
2457
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
2267
2458
  import {
2268
2459
  buildConfigSchema,
2269
2460
  ConfigSchema,
@@ -2276,7 +2467,7 @@ var readConfigSnapshot = (getConfigPath5) => {
2276
2467
  let raw = "";
2277
2468
  let parsed = {};
2278
2469
  if (existsSync6(path)) {
2279
- raw = readFileSync5(path, "utf-8");
2470
+ raw = readFileSync6(path, "utf-8");
2280
2471
  try {
2281
2472
  parsed = JSON.parse(raw);
2282
2473
  } catch {
@@ -2715,6 +2906,7 @@ var MissingProvider = class extends LLMProvider {
2715
2906
  // src/cli/commands/agent-runtime-pool.ts
2716
2907
  import {
2717
2908
  NativeAgentEngine,
2909
+ CommandRegistry,
2718
2910
  AgentRouteResolver,
2719
2911
  getWorkspacePath as getWorkspacePath4,
2720
2912
  parseAgentScopedSessionKey
@@ -2804,6 +2996,14 @@ var GatewayAgentRuntimePool = class {
2804
2996
  metadata: params.metadata,
2805
2997
  agentId: params.agentId
2806
2998
  });
2999
+ const commandResult = await this.executeDirectCommand(params.content, {
3000
+ channel: message.channel,
3001
+ chatId: message.chatId,
3002
+ sessionKey: route.sessionKey
3003
+ });
3004
+ if (commandResult) {
3005
+ return commandResult;
3006
+ }
2807
3007
  const runtime2 = this.resolveRuntime(route.agentId);
2808
3008
  return runtime2.engine.processDirect({
2809
3009
  content: params.content,
@@ -2816,6 +3016,42 @@ var GatewayAgentRuntimePool = class {
2816
3016
  onSessionEvent: params.onSessionEvent
2817
3017
  });
2818
3018
  }
3019
+ async executeDirectCommand(rawContent, ctx) {
3020
+ const trimmed = rawContent.trim();
3021
+ if (!trimmed.startsWith("/")) {
3022
+ return null;
3023
+ }
3024
+ const registry = new CommandRegistry(this.options.config, this.options.sessionManager);
3025
+ const executeText = registry.executeText;
3026
+ if (typeof executeText === "function") {
3027
+ const result2 = await executeText.call(registry, rawContent, {
3028
+ channel: ctx.channel,
3029
+ chatId: ctx.chatId,
3030
+ senderId: "user",
3031
+ sessionKey: ctx.sessionKey
3032
+ });
3033
+ return result2?.content ?? null;
3034
+ }
3035
+ const commandRaw = trimmed.slice(1).trim();
3036
+ if (!commandRaw) {
3037
+ return null;
3038
+ }
3039
+ const [nameToken, ...restTokens] = commandRaw.split(/\s+/);
3040
+ const commandName = nameToken.trim().toLowerCase();
3041
+ if (!commandName) {
3042
+ return null;
3043
+ }
3044
+ const commandTail = restTokens.join(" ").trim();
3045
+ const specs = registry.listSlashCommands();
3046
+ const args = parseCommandArgsFromText(commandName, commandTail, specs);
3047
+ const result = await registry.execute(commandName, args, {
3048
+ channel: ctx.channel,
3049
+ chatId: ctx.chatId,
3050
+ senderId: "user",
3051
+ sessionKey: ctx.sessionKey
3052
+ });
3053
+ return result?.content ?? null;
3054
+ }
2819
3055
  supportsTurnAbort(params) {
2820
3056
  const { route } = this.resolveDirectRoute({
2821
3057
  content: "",
@@ -2988,9 +3224,57 @@ var GatewayAgentRuntimePool = class {
2988
3224
  this.runtimes = nextRuntimes;
2989
3225
  }
2990
3226
  };
3227
+ function parseCommandArgsFromText(commandName, rawTail, specs) {
3228
+ if (!rawTail) {
3229
+ return {};
3230
+ }
3231
+ const command = specs.find((item) => item.name.trim().toLowerCase() === commandName);
3232
+ const options = command?.options;
3233
+ if (!options || options.length === 0) {
3234
+ return {};
3235
+ }
3236
+ const tokens = rawTail.split(/\s+/).filter(Boolean);
3237
+ const args = {};
3238
+ let cursor = 0;
3239
+ for (let i = 0; i < options.length; i += 1) {
3240
+ if (cursor >= tokens.length) {
3241
+ break;
3242
+ }
3243
+ const option = options[i];
3244
+ const isLastOption = i === options.length - 1;
3245
+ const rawValue = isLastOption ? tokens.slice(cursor).join(" ") : tokens[cursor];
3246
+ cursor += isLastOption ? tokens.length - cursor : 1;
3247
+ const parsedValue = parseCommandOptionValue(option.type, rawValue);
3248
+ if (parsedValue !== void 0) {
3249
+ args[option.name] = parsedValue;
3250
+ }
3251
+ }
3252
+ return args;
3253
+ }
3254
+ function parseCommandOptionValue(type, rawValue) {
3255
+ const value = rawValue.trim();
3256
+ if (!value) {
3257
+ return void 0;
3258
+ }
3259
+ if (type === "number") {
3260
+ const parsed = Number(value);
3261
+ return Number.isFinite(parsed) ? parsed : void 0;
3262
+ }
3263
+ if (type === "boolean") {
3264
+ const lowered = value.toLowerCase();
3265
+ if (["1", "true", "yes", "on"].includes(lowered)) {
3266
+ return true;
3267
+ }
3268
+ if (["0", "false", "no", "off"].includes(lowered)) {
3269
+ return false;
3270
+ }
3271
+ return void 0;
3272
+ }
3273
+ return value;
3274
+ }
2991
3275
 
2992
3276
  // src/cli/commands/ui-chat-run-coordinator.ts
2993
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
3277
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2994
3278
  import { join as join4 } from "path";
2995
3279
  import {
2996
3280
  getDataDir as getDataDir5,
@@ -3004,7 +3288,7 @@ function createRunId() {
3004
3288
  const rand = Math.random().toString(36).slice(2, 10);
3005
3289
  return `run-${now}-${rand}`;
3006
3290
  }
3007
- function isRecord(value) {
3291
+ function isRecord2(value) {
3008
3292
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3009
3293
  }
3010
3294
  function readOptionalString(value) {
@@ -3035,7 +3319,7 @@ function cloneEvent(event) {
3035
3319
  var UiChatRunCoordinator = class {
3036
3320
  constructor(options) {
3037
3321
  this.options = options;
3038
- mkdirSync3(RUNS_DIR, { recursive: true });
3322
+ mkdirSync4(RUNS_DIR, { recursive: true });
3039
3323
  this.loadPersistedRuns();
3040
3324
  }
3041
3325
  runs = /* @__PURE__ */ new Map();
@@ -3172,7 +3456,7 @@ var UiChatRunCoordinator = class {
3172
3456
  const parsedAgentId = parseAgentScopedSessionKey2(sessionKey)?.agentId;
3173
3457
  const agentId = explicitAgentId ?? readOptionalString(parsedAgentId);
3174
3458
  const model = readOptionalString(input.model);
3175
- const metadata = isRecord(input.metadata) ? { ...input.metadata } : {};
3459
+ const metadata = isRecord2(input.metadata) ? { ...input.metadata } : {};
3176
3460
  if (model) {
3177
3461
  metadata.model = model;
3178
3462
  }
@@ -3414,20 +3698,20 @@ var UiChatRunCoordinator = class {
3414
3698
  ...typeof run.reply === "string" ? { reply: run.reply } : {},
3415
3699
  events: run.events
3416
3700
  };
3417
- writeFileSync3(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
3701
+ writeFileSync4(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
3418
3702
  `);
3419
3703
  }
3420
3704
  loadPersistedRuns() {
3421
3705
  if (!existsSync7(RUNS_DIR)) {
3422
3706
  return;
3423
3707
  }
3424
- for (const entry of readdirSync(RUNS_DIR, { withFileTypes: true })) {
3708
+ for (const entry of readdirSync2(RUNS_DIR, { withFileTypes: true })) {
3425
3709
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
3426
3710
  continue;
3427
3711
  }
3428
3712
  const path = join4(RUNS_DIR, entry.name);
3429
3713
  try {
3430
- const parsed = JSON.parse(readFileSync6(path, "utf-8"));
3714
+ const parsed = JSON.parse(readFileSync7(path, "utf-8"));
3431
3715
  const runId = readOptionalString(parsed.runId);
3432
3716
  const sessionKey = readOptionalString(parsed.sessionKey);
3433
3717
  if (!runId || !sessionKey) {
@@ -3508,6 +3792,42 @@ function createSkillsLoader(workspace) {
3508
3792
  }
3509
3793
  return new ctor(workspace);
3510
3794
  }
3795
+ function containsAbsoluteFsPath(line) {
3796
+ const normalized = line.trim();
3797
+ if (!normalized) {
3798
+ return false;
3799
+ }
3800
+ const lowered = normalized.toLowerCase();
3801
+ if (lowered.includes("http://") || lowered.includes("https://")) {
3802
+ return false;
3803
+ }
3804
+ if (/^[A-Za-z]:\\/.test(normalized)) {
3805
+ return true;
3806
+ }
3807
+ return /(?:^|\s)(?:~\/|\/[^\s]+)/.test(normalized);
3808
+ }
3809
+ function pickUserFacingCommandSummary(output, fallback) {
3810
+ const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
3811
+ if (lines.length === 0) {
3812
+ return fallback;
3813
+ }
3814
+ const visibleLines = lines.filter((line) => {
3815
+ if (/^(path|install path|source path|destination|location)\s*:/i.test(line)) {
3816
+ return false;
3817
+ }
3818
+ if (containsAbsoluteFsPath(line)) {
3819
+ return false;
3820
+ }
3821
+ return true;
3822
+ });
3823
+ if (visibleLines.length === 0) {
3824
+ return fallback;
3825
+ }
3826
+ const preferred = [...visibleLines].reverse().find(
3827
+ (line) => /\b(installed|enabled|disabled|uninstalled|published|updated|already installed|removed)\b/i.test(line)
3828
+ );
3829
+ return preferred ?? visibleLines[visibleLines.length - 1] ?? fallback;
3830
+ }
3511
3831
  var ServiceCommands = class {
3512
3832
  constructor(deps) {
3513
3833
  this.deps = deps;
@@ -3911,7 +4231,7 @@ var ServiceCommands = class {
3911
4231
  }
3912
4232
  const logPath = resolveServiceLogPath();
3913
4233
  const logDir = resolve7(logPath, "..");
3914
- mkdirSync4(logDir, { recursive: true });
4234
+ mkdirSync5(logDir, { recursive: true });
3915
4235
  const logFd = openSync(logPath, "a");
3916
4236
  const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
3917
4237
  const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
@@ -4311,8 +4631,8 @@ var ServiceCommands = class {
4311
4631
  }
4312
4632
  async installMarketplacePlugin(spec) {
4313
4633
  const output = await this.runCliSubcommand(["plugins", "install", spec]);
4314
- const summary = this.pickLastOutputLine(output) ?? `Installed plugin: ${spec}`;
4315
- return { message: summary, output };
4634
+ const summary = pickUserFacingCommandSummary(output, `Installed plugin: ${spec}`);
4635
+ return { message: summary };
4316
4636
  }
4317
4637
  async installMarketplaceSkill(params) {
4318
4638
  if (params.kind === "builtin") {
@@ -4322,23 +4642,17 @@ var ServiceCommands = class {
4322
4642
  }
4323
4643
  return result;
4324
4644
  }
4325
- if (params.kind === "git") {
4326
- return await this.installGitMarketplaceSkill(params);
4645
+ if (params.kind && params.kind !== "marketplace") {
4646
+ throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
4327
4647
  }
4328
4648
  const args = ["skills", "install", params.slug];
4329
- if (params.version) {
4330
- args.push("--version", params.version);
4331
- }
4332
- if (params.registry) {
4333
- args.push("--registry", params.registry);
4334
- }
4335
4649
  if (params.force) {
4336
4650
  args.push("--force");
4337
4651
  }
4338
4652
  try {
4339
4653
  const output = await this.runCliSubcommand(args);
4340
- const summary = this.pickLastOutputLine(output) ?? `Installed skill: ${params.slug}`;
4341
- return { message: summary, output };
4654
+ const summary = pickUserFacingCommandSummary(output, `Installed skill: ${params.slug}`);
4655
+ return { message: summary };
4342
4656
  } catch (error) {
4343
4657
  const fallback = this.installBuiltinMarketplaceSkill(params.slug, params.force);
4344
4658
  if (!fallback) {
@@ -4347,258 +4661,20 @@ var ServiceCommands = class {
4347
4661
  return fallback;
4348
4662
  }
4349
4663
  }
4350
- async installGitMarketplaceSkill(params) {
4351
- const source = params.slug.trim();
4352
- if (!source) {
4353
- throw new Error("Git skill source is required");
4354
- }
4355
- const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
4356
- const skillName = this.resolveGitSkillName(params.skill, source);
4357
- const destination = this.resolveSkillInstallPath(workspace, params.installPath, skillName);
4358
- const destinationSkillFile = join5(destination, "SKILL.md");
4359
- if (existsSync8(destinationSkillFile) && !params.force) {
4360
- return {
4361
- message: `${skillName} is already installed`,
4362
- output: destination
4363
- };
4364
- }
4365
- if (existsSync8(destination) && !params.force) {
4366
- throw new Error(`Skill install path already exists: ${destination} (use force to overwrite)`);
4367
- }
4368
- if (existsSync8(destination) && params.force) {
4369
- rmSync3(destination, { recursive: true, force: true });
4370
- }
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 });
4390
- }
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");
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) {
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,
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;
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
- }
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}`);
4579
- }
4580
- return {
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
4586
- };
4587
- }
4588
4664
  async enableMarketplacePlugin(id) {
4589
4665
  const output = await this.runCliSubcommand(["plugins", "enable", id]);
4590
- const summary = this.pickLastOutputLine(output) ?? `Enabled plugin: ${id}`;
4591
- return { message: summary, output };
4666
+ const summary = pickUserFacingCommandSummary(output, `Enabled plugin: ${id}`);
4667
+ return { message: summary };
4592
4668
  }
4593
4669
  async disableMarketplacePlugin(id) {
4594
4670
  const output = await this.runCliSubcommand(["plugins", "disable", id]);
4595
- const summary = this.pickLastOutputLine(output) ?? `Disabled plugin: ${id}`;
4596
- return { message: summary, output };
4671
+ const summary = pickUserFacingCommandSummary(output, `Disabled plugin: ${id}`);
4672
+ return { message: summary };
4597
4673
  }
4598
4674
  async uninstallMarketplacePlugin(id) {
4599
4675
  const output = await this.runCliSubcommand(["plugins", "uninstall", id, "--force"]);
4600
- const summary = this.pickLastOutputLine(output) ?? `Uninstalled plugin: ${id}`;
4601
- return { message: summary, output };
4676
+ const summary = pickUserFacingCommandSummary(output, `Uninstalled plugin: ${id}`);
4677
+ return { message: summary };
4602
4678
  }
4603
4679
  async uninstallMarketplaceSkill(slug) {
4604
4680
  const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
@@ -4606,10 +4682,9 @@ var ServiceCommands = class {
4606
4682
  if (!existsSync8(targetDir)) {
4607
4683
  throw new Error(`Skill not installed in workspace: ${slug}`);
4608
4684
  }
4609
- rmSync3(targetDir, { recursive: true, force: true });
4685
+ rmSync4(targetDir, { recursive: true, force: true });
4610
4686
  return {
4611
- message: `Uninstalled skill: ${slug}`,
4612
- output: `Removed ${targetDir}`
4687
+ message: `Uninstalled skill: ${slug}`
4613
4688
  };
4614
4689
  }
4615
4690
  installBuiltinMarketplaceSkill(slug, force) {
@@ -4618,8 +4693,7 @@ var ServiceCommands = class {
4618
4693
  const destinationSkillFile = join5(destination, "SKILL.md");
4619
4694
  if (existsSync8(destinationSkillFile) && !force) {
4620
4695
  return {
4621
- message: `${slug} is already installed`,
4622
- output: destination
4696
+ message: `${slug} is already installed`
4623
4697
  };
4624
4698
  }
4625
4699
  const loader = createSkillsLoader(workspace);
@@ -4627,50 +4701,17 @@ var ServiceCommands = class {
4627
4701
  if (!builtin) {
4628
4702
  if (existsSync8(destinationSkillFile)) {
4629
4703
  return {
4630
- message: `${slug} is already installed`,
4631
- output: destination
4704
+ message: `${slug} is already installed`
4632
4705
  };
4633
4706
  }
4634
4707
  return null;
4635
4708
  }
4636
- mkdirSync4(join5(workspace, "skills"), { recursive: true });
4637
- cpSync(dirname(builtin.path), destination, { recursive: true, force: true });
4709
+ mkdirSync5(join5(workspace, "skills"), { recursive: true });
4710
+ cpSync2(dirname2(builtin.path), destination, { recursive: true, force: true });
4638
4711
  return {
4639
- message: `Installed skill: ${slug}`,
4640
- output: `Copied builtin skill to ${destination}`
4712
+ message: `Installed skill: ${slug}`
4641
4713
  };
4642
4714
  }
4643
- resolveGitSkillName(skill, source) {
4644
- const fromRequest = typeof skill === "string" ? skill.trim() : "";
4645
- if (fromRequest) {
4646
- return this.validateSkillName(fromRequest);
4647
- }
4648
- const normalizedSource = source.replace(/[?#].*$/, "").replace(/\/+$/, "");
4649
- const parts = normalizedSource.split("/").filter(Boolean);
4650
- const inferred = parts.length > 0 ? parts[parts.length - 1] : "";
4651
- if (!inferred) {
4652
- throw new Error("Git skill install requires a specific skill name");
4653
- }
4654
- return this.validateSkillName(inferred);
4655
- }
4656
- validateSkillName(skillName) {
4657
- if (!/^[A-Za-z0-9._-]+$/.test(skillName)) {
4658
- throw new Error(`Invalid skill name: ${skillName}`);
4659
- }
4660
- return skillName;
4661
- }
4662
- resolveSkillInstallPath(workspace, installPath, skillName) {
4663
- const requested = typeof installPath === "string" && installPath.trim().length > 0 ? installPath.trim() : join5("skills", skillName);
4664
- if (isAbsolute2(requested)) {
4665
- throw new Error("installPath must be relative to workspace");
4666
- }
4667
- const destination = resolve7(workspace, requested);
4668
- const rel = relative(workspace, destination);
4669
- if (rel.startsWith("..") || isAbsolute2(rel)) {
4670
- throw new Error("installPath escapes workspace");
4671
- }
4672
- return destination;
4673
- }
4674
4715
  mergeCommandOutput(stdout, stderr) {
4675
4716
  return `${stdout}
4676
4717
  ${stderr}`.trim();
@@ -4682,22 +4723,6 @@ ${stderr}`.trim();
4682
4723
  timeoutMs
4683
4724
  }).then((result) => this.mergeCommandOutput(result.stdout, result.stderr));
4684
4725
  }
4685
- async runCommandWithFallback(commandCandidates, args, options = {}) {
4686
- let lastError = null;
4687
- for (const command of commandCandidates) {
4688
- try {
4689
- return await this.runCommand(command, args, options);
4690
- } catch (error) {
4691
- const message = String(error);
4692
- lastError = error instanceof Error ? error : new Error(message);
4693
- if (message.startsWith("failed to start command:")) {
4694
- continue;
4695
- }
4696
- throw error;
4697
- }
4698
- }
4699
- throw lastError ?? new Error("failed to start command");
4700
- }
4701
4726
  runCommand(command, args, options = {}) {
4702
4727
  const timeoutMs = options.timeoutMs ?? 18e4;
4703
4728
  return new Promise((resolvePromise, rejectPromise) => {
@@ -4735,19 +4760,15 @@ ${stderr}`.trim();
4735
4760
  });
4736
4761
  });
4737
4762
  }
4738
- pickLastOutputLine(output) {
4739
- const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
4740
- return lines.length > 0 ? lines[lines.length - 1] : null;
4741
- }
4742
4763
  };
4743
4764
 
4744
4765
  // src/cli/workspace.ts
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";
4766
+ import { cpSync as cpSync3, existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync8, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
4746
4767
  import { createRequire } from "module";
4747
- import { dirname as dirname2, join as join6, resolve as resolve8 } from "path";
4768
+ import { dirname as dirname3, join as join6, resolve as resolve8 } from "path";
4748
4769
  import { fileURLToPath as fileURLToPath3 } from "url";
4749
4770
  import { APP_NAME as APP_NAME3, getDataDir as getDataDir7 } from "@nextclaw/core";
4750
- import { spawnSync as spawnSync4 } from "child_process";
4771
+ import { spawnSync as spawnSync3 } from "child_process";
4751
4772
  var WorkspaceManager = class {
4752
4773
  constructor(logo) {
4753
4774
  this.logo = logo;
@@ -4783,20 +4804,20 @@ var WorkspaceManager = class {
4783
4804
  console.warn(`Warning: Template file missing: ${templatePath}`);
4784
4805
  continue;
4785
4806
  }
4786
- const raw = readFileSync7(templatePath, "utf-8");
4807
+ const raw = readFileSync8(templatePath, "utf-8");
4787
4808
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
4788
- mkdirSync5(dirname2(filePath), { recursive: true });
4809
+ mkdirSync6(dirname3(filePath), { recursive: true });
4789
4810
  writeFileSync5(filePath, content);
4790
4811
  created.push(entry.target);
4791
4812
  }
4792
4813
  const memoryDir = join6(workspace, "memory");
4793
4814
  if (!existsSync9(memoryDir)) {
4794
- mkdirSync5(memoryDir, { recursive: true });
4815
+ mkdirSync6(memoryDir, { recursive: true });
4795
4816
  created.push(join6("memory", ""));
4796
4817
  }
4797
4818
  const skillsDir = join6(workspace, "skills");
4798
4819
  if (!existsSync9(skillsDir)) {
4799
- mkdirSync5(skillsDir, { recursive: true });
4820
+ mkdirSync6(skillsDir, { recursive: true });
4800
4821
  created.push(join6("skills", ""));
4801
4822
  }
4802
4823
  const seeded = this.seedBuiltinSkills(skillsDir, { force });
@@ -4812,7 +4833,7 @@ var WorkspaceManager = class {
4812
4833
  }
4813
4834
  const force = Boolean(options.force);
4814
4835
  let seeded = 0;
4815
- for (const entry of readdirSync2(sourceDir, { withFileTypes: true })) {
4836
+ for (const entry of readdirSync3(sourceDir, { withFileTypes: true })) {
4816
4837
  if (!entry.isDirectory()) {
4817
4838
  continue;
4818
4839
  }
@@ -4824,7 +4845,7 @@ var WorkspaceManager = class {
4824
4845
  if (!force && existsSync9(dest)) {
4825
4846
  continue;
4826
4847
  }
4827
- cpSync2(src, dest, { recursive: true, force: true });
4848
+ cpSync3(src, dest, { recursive: true, force: true });
4828
4849
  seeded += 1;
4829
4850
  }
4830
4851
  return seeded;
@@ -4833,7 +4854,7 @@ var WorkspaceManager = class {
4833
4854
  try {
4834
4855
  const require2 = createRequire(import.meta.url);
4835
4856
  const entry = require2.resolve("@nextclaw/core");
4836
- const pkgRoot = resolve8(dirname2(entry), "..");
4857
+ const pkgRoot = resolve8(dirname3(entry), "..");
4837
4858
  const distSkills = join6(pkgRoot, "dist", "skills");
4838
4859
  if (existsSync9(distSkills)) {
4839
4860
  return distSkills;
@@ -4886,15 +4907,15 @@ var WorkspaceManager = class {
4886
4907
  process.exit(1);
4887
4908
  }
4888
4909
  console.log(`${this.logo} Setting up bridge...`);
4889
- mkdirSync5(resolve8(userBridge, ".."), { recursive: true });
4910
+ mkdirSync6(resolve8(userBridge, ".."), { recursive: true });
4890
4911
  if (existsSync9(userBridge)) {
4891
- rmSync4(userBridge, { recursive: true, force: true });
4912
+ rmSync5(userBridge, { recursive: true, force: true });
4892
4913
  }
4893
- cpSync2(source, userBridge, {
4914
+ cpSync3(source, userBridge, {
4894
4915
  recursive: true,
4895
4916
  filter: (src) => !src.includes("node_modules") && !src.includes("dist")
4896
4917
  });
4897
- const install = spawnSync4("npm", ["install"], { cwd: userBridge, stdio: "pipe" });
4918
+ const install = spawnSync3("npm", ["install"], { cwd: userBridge, stdio: "pipe" });
4898
4919
  if (install.status !== 0) {
4899
4920
  console.error(`Bridge install failed: ${install.status ?? 1}`);
4900
4921
  if (install.stderr) {
@@ -4902,7 +4923,7 @@ var WorkspaceManager = class {
4902
4923
  }
4903
4924
  process.exit(1);
4904
4925
  }
4905
- const build = spawnSync4("npm", ["run", "build"], { cwd: userBridge, stdio: "pipe" });
4926
+ const build = spawnSync3("npm", ["run", "build"], { cwd: userBridge, stdio: "pipe" });
4906
4927
  if (build.status !== 0) {
4907
4928
  console.error(`Bridge build failed: ${build.status ?? 1}`);
4908
4929
  if (build.stderr) {
@@ -5155,7 +5176,7 @@ var CliRuntime = class {
5155
5176
  const workspaceSetting = config2.agents.defaults.workspace;
5156
5177
  const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join7(getDataDir8(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
5157
5178
  const workspaceExisted = existsSync10(workspacePath);
5158
- mkdirSync6(workspacePath, { recursive: true });
5179
+ mkdirSync7(workspacePath, { recursive: true });
5159
5180
  const templateResult = this.workspaceManager.createWorkspaceTemplates(
5160
5181
  workspacePath,
5161
5182
  { force }
@@ -5344,8 +5365,8 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5344
5365
  );
5345
5366
  const historyFile = join7(getDataDir8(), "history", "cli_history");
5346
5367
  const historyDir = resolve9(historyFile, "..");
5347
- mkdirSync6(historyDir, { recursive: true });
5348
- const history = existsSync10(historyFile) ? readFileSync8(historyFile, "utf-8").split("\n").filter(Boolean) : [];
5368
+ mkdirSync7(historyDir, { recursive: true });
5369
+ const history = existsSync10(historyFile) ? readFileSync9(historyFile, "utf-8").split("\n").filter(Boolean) : [];
5349
5370
  const rl = createInterface2({
5350
5371
  input: process.stdin,
5351
5372
  output: process.stdout
@@ -5501,25 +5522,58 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5501
5522
  }
5502
5523
  async skillsInstall(options) {
5503
5524
  const workdir = options.workdir ? expandHome2(options.workdir) : getWorkspacePath6();
5504
- const result = await installClawHubSkill({
5525
+ const result = await installMarketplaceSkill({
5505
5526
  slug: options.slug,
5506
- version: options.version,
5507
- registry: options.registry,
5508
5527
  workdir,
5509
5528
  dir: options.dir,
5510
- force: options.force
5529
+ force: options.force,
5530
+ apiBaseUrl: options.apiBaseUrl
5511
5531
  });
5512
- const versionLabel = result.version ?? "latest";
5513
5532
  if (result.alreadyInstalled) {
5514
5533
  console.log(`\u2713 ${result.slug} is already installed`);
5515
5534
  } else {
5516
- console.log(`\u2713 Installed ${result.slug}@${versionLabel}`);
5517
- }
5518
- if (result.registry) {
5519
- console.log(` Registry: ${result.registry}`);
5535
+ console.log(`\u2713 Installed ${result.slug} (${result.source})`);
5520
5536
  }
5521
5537
  console.log(` Path: ${result.destinationDir}`);
5522
5538
  }
5539
+ async skillsPublish(options) {
5540
+ const result = await publishMarketplaceSkill({
5541
+ skillDir: expandHome2(options.dir),
5542
+ slug: options.slug,
5543
+ name: options.name,
5544
+ summary: options.summary,
5545
+ description: options.description,
5546
+ author: options.author,
5547
+ tags: options.tag,
5548
+ sourceRepo: options.sourceRepo,
5549
+ homepage: options.homepage,
5550
+ publishedAt: options.publishedAt,
5551
+ updatedAt: options.updatedAt,
5552
+ apiBaseUrl: options.apiBaseUrl,
5553
+ token: options.token
5554
+ });
5555
+ console.log(result.created ? `\u2713 Published new skill: ${result.slug}` : `\u2713 Updated skill: ${result.slug}`);
5556
+ console.log(` Files: ${result.fileCount}`);
5557
+ }
5558
+ async skillsUpdate(options) {
5559
+ const result = await publishMarketplaceSkill({
5560
+ skillDir: expandHome2(options.dir),
5561
+ slug: options.slug,
5562
+ name: options.name,
5563
+ summary: options.summary,
5564
+ description: options.description,
5565
+ author: options.author,
5566
+ tags: options.tag,
5567
+ sourceRepo: options.sourceRepo,
5568
+ homepage: options.homepage,
5569
+ updatedAt: options.updatedAt,
5570
+ apiBaseUrl: options.apiBaseUrl,
5571
+ token: options.token,
5572
+ requireExisting: true
5573
+ });
5574
+ console.log(`\u2713 Updated skill: ${result.slug}`);
5575
+ console.log(` Files: ${result.fileCount}`);
5576
+ }
5523
5577
  };
5524
5578
 
5525
5579
  // src/cli/index.ts
@@ -5536,13 +5590,11 @@ program.command("serve").description(`Run the ${APP_NAME5} gateway + UI in the f
5536
5590
  program.command("stop").description(`Stop the ${APP_NAME5} background service`).action(async () => runtime.stop());
5537
5591
  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));
5538
5592
  program.command("update").description(`Update ${APP_NAME5}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
5539
- var registerClawHubInstall = (cmd) => {
5540
- cmd.command("install <slug>").description("Install a skill from ClawHub").option("--version <version>", "Skill version (default: latest)").option("--registry <url>", "ClawHub registry base URL").option("--workdir <dir>", "Workspace directory to install into").option("--dir <dir>", "Skills directory name (default: skills)").option("-f, --force", "Overwrite existing skill files", false).action(async (slug, opts) => runtime.skillsInstall({ slug, ...opts }));
5541
- };
5542
5593
  var skills = program.command("skills").description("Manage skills");
5543
- registerClawHubInstall(skills);
5544
- var clawhub = program.command("clawhub").description("Install skills from ClawHub");
5545
- registerClawHubInstall(clawhub);
5594
+ skills.command("install <slug>").description("Install a skill from NextClaw marketplace").option("--api-base <url>", "Marketplace API base URL").option("--workdir <dir>", "Workspace directory to install into").option("--dir <dir>", "Skills directory name (default: skills)").option("-f, --force", "Overwrite existing skill files", false).action(async (slug, opts) => runtime.skillsInstall({ slug, ...opts, apiBaseUrl: opts.apiBase }));
5595
+ var withRepeatableTag = (value, previous = []) => [...previous, value];
5596
+ skills.command("publish <dir>").description("Upload or create a skill in marketplace").option("--slug <slug>", "Skill slug (default: directory name)").option("--name <name>", "Skill display name").option("--summary <summary>", "Skill summary").option("--description <description>", "Skill description").option("--author <author>", "Skill author").option("--tag <tag>", "Skill tag (repeatable)", withRepeatableTag, []).option("--source-repo <url>", "Source repository URL").option("--homepage <url>", "Homepage URL").option("--published-at <datetime>", "Published time (ISO datetime)").option("--updated-at <datetime>", "Updated time (ISO datetime)").option("--api-base <url>", "Marketplace API base URL").option("--token <token>", "Marketplace admin token").action(async (dir, opts) => runtime.skillsPublish({ dir, ...opts, apiBaseUrl: opts.apiBase }));
5597
+ skills.command("update <dir>").description("Update an existing skill in marketplace").option("--slug <slug>", "Skill slug (default: directory name)").option("--name <name>", "Skill display name").option("--summary <summary>", "Skill summary").option("--description <description>", "Skill description").option("--author <author>", "Skill author").option("--tag <tag>", "Skill tag (repeatable)", withRepeatableTag, []).option("--source-repo <url>", "Source repository URL").option("--homepage <url>", "Homepage URL").option("--updated-at <datetime>", "Updated time (ISO datetime)").option("--api-base <url>", "Marketplace API base URL").option("--token <token>", "Marketplace admin token").action(async (dir, opts) => runtime.skillsUpdate({ dir, ...opts, apiBaseUrl: opts.apiBase }));
5546
5598
  var plugins = program.command("plugins").description("Manage OpenClaw-compatible plugins");
5547
5599
  plugins.command("list").description("List discovered plugins").option("--json", "Print JSON").option("--enabled", "Only show enabled plugins", false).option("--verbose", "Show detailed entries", false).action((opts) => runtime.pluginsList(opts));
5548
5600
  plugins.command("info <id>").description("Show plugin details").option("--json", "Print JSON").action((id, opts) => runtime.pluginsInfo(id, opts));