nextclaw 0.9.14 → 0.9.16

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 +603 -426
  2. package/package.json +3 -3
  3. package/templates/USAGE.md +3 -2
  4. package/ui-dist/assets/{ChannelsList-DH5fzlPu.js → ChannelsList-DRqUf_f1.js} +1 -1
  5. package/ui-dist/assets/{ChatPage-BrLCnJSb.js → ChatPage-XNOO4MdH.js} +12 -12
  6. package/ui-dist/assets/{DocBrowser-DPQHJVsZ.js → DocBrowser-B_7djIEq.js} +1 -1
  7. package/ui-dist/assets/{LogoBadge-FEb4_vSq.js → LogoBadge-CEQerLPb.js} +1 -1
  8. package/ui-dist/assets/{MarketplacePage-BAVXYeZA.js → MarketplacePage-DBzY0jNC.js} +3 -3
  9. package/ui-dist/assets/{ModelConfig-BqPXe7nw.js → ModelConfig-BIcnKQ9L.js} +1 -1
  10. package/ui-dist/assets/{ProvidersList-vpKPuIxV.js → ProvidersList-K0TZvjGO.js} +1 -1
  11. package/ui-dist/assets/{RuntimeConfig-DTYSU4_d.js → RuntimeConfig-DlOB3OyD.js} +1 -1
  12. package/ui-dist/assets/{SecretsConfig-nNzs3YDm.js → SecretsConfig-Dl7ISZSg.js} +1 -1
  13. package/ui-dist/assets/{SessionsConfig-CHjeyqEQ.js → SessionsConfig-BQrPKyRq.js} +1 -1
  14. package/ui-dist/assets/{card-73MmEZi7.js → card-B-c2OgfM.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-gXBW3ns8.js} +5 -5
  17. package/ui-dist/assets/{input-1MCMs6Yf.js → input-vcZIRBEV.js} +1 -1
  18. package/ui-dist/assets/{label-C4Q8RlBJ.js → label-DZa1Y6BS.js} +1 -1
  19. package/ui-dist/assets/{page-layout-CK0vcVmV.js → page-layout-DVoD2FFE.js} +1 -1
  20. package/ui-dist/assets/{session-run-status-BaNlKvi6.js → session-run-status-C0z5CFQq.js} +1 -1
  21. package/ui-dist/assets/{switch-Bf8w_cF1.js → switch-DNEnGqWo.js} +1 -1
  22. package/ui-dist/assets/{tabs-custom-B6Gw8gax.js → tabs-custom-B7PsWX6R.js} +1 -1
  23. package/ui-dist/assets/{useConfirmDialog-B5CZ4EDN.js → useConfirmDialog-PNI-ofd5.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 });
229
+ }
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"));
224
239
  }
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}`);
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
@@ -2779,6 +2971,7 @@ var GatewayAgentRuntimePool = class {
2779
2971
  runtimes = /* @__PURE__ */ new Map();
2780
2972
  running = false;
2781
2973
  defaultAgentId = "main";
2974
+ onSystemSessionUpdated = null;
2782
2975
  get primaryAgentId() {
2783
2976
  return this.defaultAgentId;
2784
2977
  }
@@ -2795,6 +2988,9 @@ var GatewayAgentRuntimePool = class {
2795
2988
  this.options.extensionRegistry = extensionRegistry;
2796
2989
  this.rebuild(this.options.config);
2797
2990
  }
2991
+ setSystemSessionUpdatedHandler(handler) {
2992
+ this.onSystemSessionUpdated = handler;
2993
+ }
2798
2994
  async processDirect(params) {
2799
2995
  const { message, route } = this.resolveDirectRoute({
2800
2996
  content: params.content,
@@ -2804,6 +3000,14 @@ var GatewayAgentRuntimePool = class {
2804
3000
  metadata: params.metadata,
2805
3001
  agentId: params.agentId
2806
3002
  });
3003
+ const commandResult = await this.executeDirectCommand(params.content, {
3004
+ channel: message.channel,
3005
+ chatId: message.chatId,
3006
+ sessionKey: route.sessionKey
3007
+ });
3008
+ if (commandResult) {
3009
+ return commandResult;
3010
+ }
2807
3011
  const runtime2 = this.resolveRuntime(route.agentId);
2808
3012
  return runtime2.engine.processDirect({
2809
3013
  content: params.content,
@@ -2816,6 +3020,42 @@ var GatewayAgentRuntimePool = class {
2816
3020
  onSessionEvent: params.onSessionEvent
2817
3021
  });
2818
3022
  }
3023
+ async executeDirectCommand(rawContent, ctx) {
3024
+ const trimmed = rawContent.trim();
3025
+ if (!trimmed.startsWith("/")) {
3026
+ return null;
3027
+ }
3028
+ const registry = new CommandRegistry(this.options.config, this.options.sessionManager);
3029
+ const executeText = registry.executeText;
3030
+ if (typeof executeText === "function") {
3031
+ const result2 = await executeText.call(registry, rawContent, {
3032
+ channel: ctx.channel,
3033
+ chatId: ctx.chatId,
3034
+ senderId: "user",
3035
+ sessionKey: ctx.sessionKey
3036
+ });
3037
+ return result2?.content ?? null;
3038
+ }
3039
+ const commandRaw = trimmed.slice(1).trim();
3040
+ if (!commandRaw) {
3041
+ return null;
3042
+ }
3043
+ const [nameToken, ...restTokens] = commandRaw.split(/\s+/);
3044
+ const commandName = nameToken.trim().toLowerCase();
3045
+ if (!commandName) {
3046
+ return null;
3047
+ }
3048
+ const commandTail = restTokens.join(" ").trim();
3049
+ const specs = registry.listSlashCommands();
3050
+ const args = parseCommandArgsFromText(commandName, commandTail, specs);
3051
+ const result = await registry.execute(commandName, args, {
3052
+ channel: ctx.channel,
3053
+ chatId: ctx.chatId,
3054
+ senderId: "user",
3055
+ sessionKey: ctx.sessionKey
3056
+ });
3057
+ return result?.content ?? null;
3058
+ }
2819
3059
  supportsTurnAbort(params) {
2820
3060
  const { route } = this.resolveDirectRoute({
2821
3061
  content: "",
@@ -2856,6 +3096,12 @@ var GatewayAgentRuntimePool = class {
2856
3096
  sessionKey: route.sessionKey,
2857
3097
  publishResponse: true
2858
3098
  });
3099
+ if (message.channel === "system") {
3100
+ this.onSystemSessionUpdated?.({
3101
+ sessionKey: route.sessionKey,
3102
+ message
3103
+ });
3104
+ }
2859
3105
  } catch (error) {
2860
3106
  await this.options.bus.publishOutbound({
2861
3107
  channel: message.channel,
@@ -2988,9 +3234,57 @@ var GatewayAgentRuntimePool = class {
2988
3234
  this.runtimes = nextRuntimes;
2989
3235
  }
2990
3236
  };
3237
+ function parseCommandArgsFromText(commandName, rawTail, specs) {
3238
+ if (!rawTail) {
3239
+ return {};
3240
+ }
3241
+ const command = specs.find((item) => item.name.trim().toLowerCase() === commandName);
3242
+ const options = command?.options;
3243
+ if (!options || options.length === 0) {
3244
+ return {};
3245
+ }
3246
+ const tokens = rawTail.split(/\s+/).filter(Boolean);
3247
+ const args = {};
3248
+ let cursor = 0;
3249
+ for (let i = 0; i < options.length; i += 1) {
3250
+ if (cursor >= tokens.length) {
3251
+ break;
3252
+ }
3253
+ const option = options[i];
3254
+ const isLastOption = i === options.length - 1;
3255
+ const rawValue = isLastOption ? tokens.slice(cursor).join(" ") : tokens[cursor];
3256
+ cursor += isLastOption ? tokens.length - cursor : 1;
3257
+ const parsedValue = parseCommandOptionValue(option.type, rawValue);
3258
+ if (parsedValue !== void 0) {
3259
+ args[option.name] = parsedValue;
3260
+ }
3261
+ }
3262
+ return args;
3263
+ }
3264
+ function parseCommandOptionValue(type, rawValue) {
3265
+ const value = rawValue.trim();
3266
+ if (!value) {
3267
+ return void 0;
3268
+ }
3269
+ if (type === "number") {
3270
+ const parsed = Number(value);
3271
+ return Number.isFinite(parsed) ? parsed : void 0;
3272
+ }
3273
+ if (type === "boolean") {
3274
+ const lowered = value.toLowerCase();
3275
+ if (["1", "true", "yes", "on"].includes(lowered)) {
3276
+ return true;
3277
+ }
3278
+ if (["0", "false", "no", "off"].includes(lowered)) {
3279
+ return false;
3280
+ }
3281
+ return void 0;
3282
+ }
3283
+ return value;
3284
+ }
2991
3285
 
2992
3286
  // 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";
3287
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2994
3288
  import { join as join4 } from "path";
2995
3289
  import {
2996
3290
  getDataDir as getDataDir5,
@@ -3004,7 +3298,7 @@ function createRunId() {
3004
3298
  const rand = Math.random().toString(36).slice(2, 10);
3005
3299
  return `run-${now}-${rand}`;
3006
3300
  }
3007
- function isRecord(value) {
3301
+ function isRecord2(value) {
3008
3302
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3009
3303
  }
3010
3304
  function readOptionalString(value) {
@@ -3035,7 +3329,7 @@ function cloneEvent(event) {
3035
3329
  var UiChatRunCoordinator = class {
3036
3330
  constructor(options) {
3037
3331
  this.options = options;
3038
- mkdirSync3(RUNS_DIR, { recursive: true });
3332
+ mkdirSync4(RUNS_DIR, { recursive: true });
3039
3333
  this.loadPersistedRuns();
3040
3334
  }
3041
3335
  runs = /* @__PURE__ */ new Map();
@@ -3172,7 +3466,7 @@ var UiChatRunCoordinator = class {
3172
3466
  const parsedAgentId = parseAgentScopedSessionKey2(sessionKey)?.agentId;
3173
3467
  const agentId = explicitAgentId ?? readOptionalString(parsedAgentId);
3174
3468
  const model = readOptionalString(input.model);
3175
- const metadata = isRecord(input.metadata) ? { ...input.metadata } : {};
3469
+ const metadata = isRecord2(input.metadata) ? { ...input.metadata } : {};
3176
3470
  if (model) {
3177
3471
  metadata.model = model;
3178
3472
  }
@@ -3414,20 +3708,20 @@ var UiChatRunCoordinator = class {
3414
3708
  ...typeof run.reply === "string" ? { reply: run.reply } : {},
3415
3709
  events: run.events
3416
3710
  };
3417
- writeFileSync3(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
3711
+ writeFileSync4(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
3418
3712
  `);
3419
3713
  }
3420
3714
  loadPersistedRuns() {
3421
3715
  if (!existsSync7(RUNS_DIR)) {
3422
3716
  return;
3423
3717
  }
3424
- for (const entry of readdirSync(RUNS_DIR, { withFileTypes: true })) {
3718
+ for (const entry of readdirSync2(RUNS_DIR, { withFileTypes: true })) {
3425
3719
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
3426
3720
  continue;
3427
3721
  }
3428
3722
  const path = join4(RUNS_DIR, entry.name);
3429
3723
  try {
3430
- const parsed = JSON.parse(readFileSync6(path, "utf-8"));
3724
+ const parsed = JSON.parse(readFileSync7(path, "utf-8"));
3431
3725
  const runId = readOptionalString(parsed.runId);
3432
3726
  const sessionKey = readOptionalString(parsed.sessionKey);
3433
3727
  if (!runId || !sessionKey) {
@@ -3508,6 +3802,56 @@ function createSkillsLoader(workspace) {
3508
3802
  }
3509
3803
  return new ctor(workspace);
3510
3804
  }
3805
+ function containsAbsoluteFsPath(line) {
3806
+ const normalized = line.trim();
3807
+ if (!normalized) {
3808
+ return false;
3809
+ }
3810
+ const lowered = normalized.toLowerCase();
3811
+ if (lowered.includes("http://") || lowered.includes("https://")) {
3812
+ return false;
3813
+ }
3814
+ if (/^[A-Za-z]:\\/.test(normalized)) {
3815
+ return true;
3816
+ }
3817
+ return /(?:^|\s)(?:~\/|\/[^\s]+)/.test(normalized);
3818
+ }
3819
+ function pickUserFacingCommandSummary(output, fallback) {
3820
+ const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
3821
+ if (lines.length === 0) {
3822
+ return fallback;
3823
+ }
3824
+ const visibleLines = lines.filter((line) => {
3825
+ if (/^(path|install path|source path|destination|location)\s*:/i.test(line)) {
3826
+ return false;
3827
+ }
3828
+ if (containsAbsoluteFsPath(line)) {
3829
+ return false;
3830
+ }
3831
+ return true;
3832
+ });
3833
+ if (visibleLines.length === 0) {
3834
+ return fallback;
3835
+ }
3836
+ const preferred = [...visibleLines].reverse().find(
3837
+ (line) => /\b(installed|enabled|disabled|uninstalled|published|updated|already installed|removed)\b/i.test(line)
3838
+ );
3839
+ return preferred ?? visibleLines[visibleLines.length - 1] ?? fallback;
3840
+ }
3841
+ function buildMarketplaceSkillInstallArgs(params) {
3842
+ const args = ["skills", "install", params.slug, "--workdir", params.workspace];
3843
+ if (params.force) {
3844
+ args.push("--force");
3845
+ }
3846
+ return args;
3847
+ }
3848
+ function resolveCliSubcommandEntry(params) {
3849
+ const argvEntry = params.argvEntry?.trim();
3850
+ if (argvEntry) {
3851
+ return resolve7(argvEntry);
3852
+ }
3853
+ return fileURLToPath2(new URL("../index.js", params.importMetaUrl));
3854
+ }
3511
3855
  var ServiceCommands = class {
3512
3856
  constructor(deps) {
3513
3857
  this.deps = deps;
@@ -3911,7 +4255,7 @@ var ServiceCommands = class {
3911
4255
  }
3912
4256
  const logPath = resolveServiceLogPath();
3913
4257
  const logDir = resolve7(logPath, "..");
3914
- mkdirSync4(logDir, { recursive: true });
4258
+ mkdirSync5(logDir, { recursive: true });
3915
4259
  const logFd = openSync(logPath, "a");
3916
4260
  const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
3917
4261
  const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
@@ -4216,6 +4560,21 @@ var ServiceCommands = class {
4216
4560
  ...params.model ? { model: params.model } : {}
4217
4561
  });
4218
4562
  let publishUiEvent = null;
4563
+ runtimePool.setSystemSessionUpdatedHandler(({ sessionKey, message }) => {
4564
+ if (!publishUiEvent) {
4565
+ return;
4566
+ }
4567
+ const isUiRoute = message.chatId.startsWith("ui:");
4568
+ if (!isUiRoute) {
4569
+ return;
4570
+ }
4571
+ publishUiEvent({
4572
+ type: "session.updated",
4573
+ payload: {
4574
+ sessionKey
4575
+ }
4576
+ });
4577
+ });
4219
4578
  const runCoordinator = new UiChatRunCoordinator({
4220
4579
  runtimePool,
4221
4580
  sessionManager,
@@ -4311,8 +4670,8 @@ var ServiceCommands = class {
4311
4670
  }
4312
4671
  async installMarketplacePlugin(spec) {
4313
4672
  const output = await this.runCliSubcommand(["plugins", "install", spec]);
4314
- const summary = this.pickLastOutputLine(output) ?? `Installed plugin: ${spec}`;
4315
- return { message: summary, output };
4673
+ const summary = pickUserFacingCommandSummary(output, `Installed plugin: ${spec}`);
4674
+ return { message: summary };
4316
4675
  }
4317
4676
  async installMarketplaceSkill(params) {
4318
4677
  if (params.kind === "builtin") {
@@ -4322,23 +4681,19 @@ var ServiceCommands = class {
4322
4681
  }
4323
4682
  return result;
4324
4683
  }
4325
- if (params.kind === "git") {
4326
- return await this.installGitMarketplaceSkill(params);
4327
- }
4328
- 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
- if (params.force) {
4336
- args.push("--force");
4684
+ if (params.kind && params.kind !== "marketplace") {
4685
+ throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
4337
4686
  }
4687
+ const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
4688
+ const args = buildMarketplaceSkillInstallArgs({
4689
+ slug: params.slug,
4690
+ workspace,
4691
+ force: params.force
4692
+ });
4338
4693
  try {
4339
4694
  const output = await this.runCliSubcommand(args);
4340
- const summary = this.pickLastOutputLine(output) ?? `Installed skill: ${params.slug}`;
4341
- return { message: summary, output };
4695
+ const summary = pickUserFacingCommandSummary(output, `Installed skill: ${params.slug}`);
4696
+ return { message: summary };
4342
4697
  } catch (error) {
4343
4698
  const fallback = this.installBuiltinMarketplaceSkill(params.slug, params.force);
4344
4699
  if (!fallback) {
@@ -4347,258 +4702,20 @@ var ServiceCommands = class {
4347
4702
  return fallback;
4348
4703
  }
4349
4704
  }
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
4705
  async enableMarketplacePlugin(id) {
4589
4706
  const output = await this.runCliSubcommand(["plugins", "enable", id]);
4590
- const summary = this.pickLastOutputLine(output) ?? `Enabled plugin: ${id}`;
4591
- return { message: summary, output };
4707
+ const summary = pickUserFacingCommandSummary(output, `Enabled plugin: ${id}`);
4708
+ return { message: summary };
4592
4709
  }
4593
4710
  async disableMarketplacePlugin(id) {
4594
4711
  const output = await this.runCliSubcommand(["plugins", "disable", id]);
4595
- const summary = this.pickLastOutputLine(output) ?? `Disabled plugin: ${id}`;
4596
- return { message: summary, output };
4712
+ const summary = pickUserFacingCommandSummary(output, `Disabled plugin: ${id}`);
4713
+ return { message: summary };
4597
4714
  }
4598
4715
  async uninstallMarketplacePlugin(id) {
4599
4716
  const output = await this.runCliSubcommand(["plugins", "uninstall", id, "--force"]);
4600
- const summary = this.pickLastOutputLine(output) ?? `Uninstalled plugin: ${id}`;
4601
- return { message: summary, output };
4717
+ const summary = pickUserFacingCommandSummary(output, `Uninstalled plugin: ${id}`);
4718
+ return { message: summary };
4602
4719
  }
4603
4720
  async uninstallMarketplaceSkill(slug) {
4604
4721
  const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
@@ -4606,10 +4723,9 @@ var ServiceCommands = class {
4606
4723
  if (!existsSync8(targetDir)) {
4607
4724
  throw new Error(`Skill not installed in workspace: ${slug}`);
4608
4725
  }
4609
- rmSync3(targetDir, { recursive: true, force: true });
4726
+ rmSync4(targetDir, { recursive: true, force: true });
4610
4727
  return {
4611
- message: `Uninstalled skill: ${slug}`,
4612
- output: `Removed ${targetDir}`
4728
+ message: `Uninstalled skill: ${slug}`
4613
4729
  };
4614
4730
  }
4615
4731
  installBuiltinMarketplaceSkill(slug, force) {
@@ -4618,8 +4734,7 @@ var ServiceCommands = class {
4618
4734
  const destinationSkillFile = join5(destination, "SKILL.md");
4619
4735
  if (existsSync8(destinationSkillFile) && !force) {
4620
4736
  return {
4621
- message: `${slug} is already installed`,
4622
- output: destination
4737
+ message: `${slug} is already installed`
4623
4738
  };
4624
4739
  }
4625
4740
  const loader = createSkillsLoader(workspace);
@@ -4627,77 +4742,31 @@ var ServiceCommands = class {
4627
4742
  if (!builtin) {
4628
4743
  if (existsSync8(destinationSkillFile)) {
4629
4744
  return {
4630
- message: `${slug} is already installed`,
4631
- output: destination
4745
+ message: `${slug} is already installed`
4632
4746
  };
4633
4747
  }
4634
4748
  return null;
4635
4749
  }
4636
- mkdirSync4(join5(workspace, "skills"), { recursive: true });
4637
- cpSync(dirname(builtin.path), destination, { recursive: true, force: true });
4750
+ mkdirSync5(join5(workspace, "skills"), { recursive: true });
4751
+ cpSync2(dirname2(builtin.path), destination, { recursive: true, force: true });
4638
4752
  return {
4639
- message: `Installed skill: ${slug}`,
4640
- output: `Copied builtin skill to ${destination}`
4753
+ message: `Installed skill: ${slug}`
4641
4754
  };
4642
4755
  }
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
4756
  mergeCommandOutput(stdout, stderr) {
4675
4757
  return `${stdout}
4676
4758
  ${stderr}`.trim();
4677
4759
  }
4678
4760
  runCliSubcommand(args, timeoutMs = 18e4) {
4679
- const cliEntry = fileURLToPath2(new URL("../index.js", import.meta.url));
4761
+ const cliEntry = resolveCliSubcommandEntry({
4762
+ argvEntry: process.argv[1],
4763
+ importMetaUrl: import.meta.url
4764
+ });
4680
4765
  return this.runCommand(process.execPath, [...process.execArgv, cliEntry, ...args], {
4681
4766
  cwd: process.cwd(),
4682
4767
  timeoutMs
4683
4768
  }).then((result) => this.mergeCommandOutput(result.stdout, result.stderr));
4684
4769
  }
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
4770
  runCommand(command, args, options = {}) {
4702
4771
  const timeoutMs = options.timeoutMs ?? 18e4;
4703
4772
  return new Promise((resolvePromise, rejectPromise) => {
@@ -4735,19 +4804,15 @@ ${stderr}`.trim();
4735
4804
  });
4736
4805
  });
4737
4806
  }
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
4807
  };
4743
4808
 
4744
4809
  // 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";
4810
+ 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
4811
  import { createRequire } from "module";
4747
- import { dirname as dirname2, join as join6, resolve as resolve8 } from "path";
4812
+ import { dirname as dirname3, join as join6, resolve as resolve8 } from "path";
4748
4813
  import { fileURLToPath as fileURLToPath3 } from "url";
4749
4814
  import { APP_NAME as APP_NAME3, getDataDir as getDataDir7 } from "@nextclaw/core";
4750
- import { spawnSync as spawnSync4 } from "child_process";
4815
+ import { spawnSync as spawnSync3 } from "child_process";
4751
4816
  var WorkspaceManager = class {
4752
4817
  constructor(logo) {
4753
4818
  this.logo = logo;
@@ -4783,20 +4848,20 @@ var WorkspaceManager = class {
4783
4848
  console.warn(`Warning: Template file missing: ${templatePath}`);
4784
4849
  continue;
4785
4850
  }
4786
- const raw = readFileSync7(templatePath, "utf-8");
4851
+ const raw = readFileSync8(templatePath, "utf-8");
4787
4852
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
4788
- mkdirSync5(dirname2(filePath), { recursive: true });
4853
+ mkdirSync6(dirname3(filePath), { recursive: true });
4789
4854
  writeFileSync5(filePath, content);
4790
4855
  created.push(entry.target);
4791
4856
  }
4792
4857
  const memoryDir = join6(workspace, "memory");
4793
4858
  if (!existsSync9(memoryDir)) {
4794
- mkdirSync5(memoryDir, { recursive: true });
4859
+ mkdirSync6(memoryDir, { recursive: true });
4795
4860
  created.push(join6("memory", ""));
4796
4861
  }
4797
4862
  const skillsDir = join6(workspace, "skills");
4798
4863
  if (!existsSync9(skillsDir)) {
4799
- mkdirSync5(skillsDir, { recursive: true });
4864
+ mkdirSync6(skillsDir, { recursive: true });
4800
4865
  created.push(join6("skills", ""));
4801
4866
  }
4802
4867
  const seeded = this.seedBuiltinSkills(skillsDir, { force });
@@ -4812,7 +4877,7 @@ var WorkspaceManager = class {
4812
4877
  }
4813
4878
  const force = Boolean(options.force);
4814
4879
  let seeded = 0;
4815
- for (const entry of readdirSync2(sourceDir, { withFileTypes: true })) {
4880
+ for (const entry of readdirSync3(sourceDir, { withFileTypes: true })) {
4816
4881
  if (!entry.isDirectory()) {
4817
4882
  continue;
4818
4883
  }
@@ -4824,7 +4889,7 @@ var WorkspaceManager = class {
4824
4889
  if (!force && existsSync9(dest)) {
4825
4890
  continue;
4826
4891
  }
4827
- cpSync2(src, dest, { recursive: true, force: true });
4892
+ cpSync3(src, dest, { recursive: true, force: true });
4828
4893
  seeded += 1;
4829
4894
  }
4830
4895
  return seeded;
@@ -4833,7 +4898,7 @@ var WorkspaceManager = class {
4833
4898
  try {
4834
4899
  const require2 = createRequire(import.meta.url);
4835
4900
  const entry = require2.resolve("@nextclaw/core");
4836
- const pkgRoot = resolve8(dirname2(entry), "..");
4901
+ const pkgRoot = resolve8(dirname3(entry), "..");
4837
4902
  const distSkills = join6(pkgRoot, "dist", "skills");
4838
4903
  if (existsSync9(distSkills)) {
4839
4904
  return distSkills;
@@ -4886,15 +4951,15 @@ var WorkspaceManager = class {
4886
4951
  process.exit(1);
4887
4952
  }
4888
4953
  console.log(`${this.logo} Setting up bridge...`);
4889
- mkdirSync5(resolve8(userBridge, ".."), { recursive: true });
4954
+ mkdirSync6(resolve8(userBridge, ".."), { recursive: true });
4890
4955
  if (existsSync9(userBridge)) {
4891
- rmSync4(userBridge, { recursive: true, force: true });
4956
+ rmSync5(userBridge, { recursive: true, force: true });
4892
4957
  }
4893
- cpSync2(source, userBridge, {
4958
+ cpSync3(source, userBridge, {
4894
4959
  recursive: true,
4895
4960
  filter: (src) => !src.includes("node_modules") && !src.includes("dist")
4896
4961
  });
4897
- const install = spawnSync4("npm", ["install"], { cwd: userBridge, stdio: "pipe" });
4962
+ const install = spawnSync3("npm", ["install"], { cwd: userBridge, stdio: "pipe" });
4898
4963
  if (install.status !== 0) {
4899
4964
  console.error(`Bridge install failed: ${install.status ?? 1}`);
4900
4965
  if (install.stderr) {
@@ -4902,7 +4967,7 @@ var WorkspaceManager = class {
4902
4967
  }
4903
4968
  process.exit(1);
4904
4969
  }
4905
- const build = spawnSync4("npm", ["run", "build"], { cwd: userBridge, stdio: "pipe" });
4970
+ const build = spawnSync3("npm", ["run", "build"], { cwd: userBridge, stdio: "pipe" });
4906
4971
  if (build.status !== 0) {
4907
4972
  console.error(`Bridge build failed: ${build.status ?? 1}`);
4908
4973
  if (build.stderr) {
@@ -4919,6 +4984,12 @@ var WorkspaceManager = class {
4919
4984
  var LOGO = "\u{1F916}";
4920
4985
  var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "/exit", "/quit", ":q"]);
4921
4986
  var FORCED_PUBLIC_UI_HOST = "0.0.0.0";
4987
+ function resolveSkillsInstallWorkdir(params) {
4988
+ if (params.explicitWorkdir) {
4989
+ return expandHome2(params.explicitWorkdir);
4990
+ }
4991
+ return getWorkspacePath6(params.configuredWorkspace);
4992
+ }
4922
4993
  var CliRuntime = class {
4923
4994
  logo;
4924
4995
  restartCoordinator;
@@ -5155,7 +5226,7 @@ var CliRuntime = class {
5155
5226
  const workspaceSetting = config2.agents.defaults.workspace;
5156
5227
  const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join7(getDataDir8(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
5157
5228
  const workspaceExisted = existsSync10(workspacePath);
5158
- mkdirSync6(workspacePath, { recursive: true });
5229
+ mkdirSync7(workspacePath, { recursive: true });
5159
5230
  const templateResult = this.workspaceManager.createWorkspaceTemplates(
5160
5231
  workspacePath,
5161
5232
  { force }
@@ -5184,6 +5255,76 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5184
5255
  );
5185
5256
  }
5186
5257
  }
5258
+ async login(opts = {}) {
5259
+ await this.init({ source: "login", auto: true });
5260
+ const configPath = getConfigPath4();
5261
+ const config2 = loadConfig7(configPath);
5262
+ const providers = config2.providers;
5263
+ const nextclawProvider = providers.nextclaw ?? {
5264
+ displayName: "",
5265
+ apiKey: "",
5266
+ apiBase: null,
5267
+ extraHeaders: null,
5268
+ wireApi: "auto",
5269
+ models: []
5270
+ };
5271
+ const configuredApiBase = typeof nextclawProvider.apiBase === "string" && nextclawProvider.apiBase.trim().length > 0 ? nextclawProvider.apiBase.trim() : "https://ai-gateway-api.nextclaw.io/v1";
5272
+ const requestedApiBase = typeof opts.apiBase === "string" && opts.apiBase.trim().length > 0 ? opts.apiBase.trim() : configuredApiBase;
5273
+ const platformBase = requestedApiBase.replace(/\/v1\/?$/i, "");
5274
+ const v1Base = `${platformBase}/v1`;
5275
+ let email = typeof opts.email === "string" ? opts.email.trim() : "";
5276
+ let password = typeof opts.password === "string" ? opts.password : "";
5277
+ if (!email || !password) {
5278
+ const rl = createInterface2({
5279
+ input: process.stdin,
5280
+ output: process.stdout
5281
+ });
5282
+ try {
5283
+ if (!email) {
5284
+ email = (await prompt(rl, "Email: ")).trim();
5285
+ }
5286
+ if (!password) {
5287
+ password = await prompt(rl, "Password: ");
5288
+ }
5289
+ } finally {
5290
+ rl.close();
5291
+ }
5292
+ }
5293
+ if (!email || !password) {
5294
+ throw new Error("Email and password are required.");
5295
+ }
5296
+ const endpoint = opts.register ? `${platformBase}/platform/auth/register` : `${platformBase}/platform/auth/login`;
5297
+ const response = await fetch(endpoint, {
5298
+ method: "POST",
5299
+ headers: {
5300
+ "Content-Type": "application/json"
5301
+ },
5302
+ body: JSON.stringify({ email, password })
5303
+ });
5304
+ const raw = await response.text();
5305
+ let parsed = null;
5306
+ try {
5307
+ parsed = JSON.parse(raw);
5308
+ } catch {
5309
+ parsed = null;
5310
+ }
5311
+ if (!response.ok) {
5312
+ const maybeMessage = typeof parsed === "object" && parsed && "error" in parsed && typeof parsed.error?.message === "string" ? parsed.error.message : raw || `Request failed (${response.status})`;
5313
+ throw new Error(maybeMessage);
5314
+ }
5315
+ const token = typeof parsed === "object" && parsed && "data" in parsed && typeof parsed.data?.token === "string" ? parsed.data.token : "";
5316
+ const role = typeof parsed === "object" && parsed && "data" in parsed && typeof parsed.data?.user?.role === "string" ? parsed.data.user.role : "user";
5317
+ if (!token) {
5318
+ throw new Error("Login succeeded but token is missing.");
5319
+ }
5320
+ nextclawProvider.apiBase = v1Base;
5321
+ nextclawProvider.apiKey = token;
5322
+ providers.nextclaw = nextclawProvider;
5323
+ saveConfig6(config2, configPath);
5324
+ console.log(`\u2713 Logged in to NextClaw platform (${platformBase})`);
5325
+ console.log(`\u2713 Account: ${email} (${role})`);
5326
+ console.log(`\u2713 Token saved into providers.nextclaw.apiKey`);
5327
+ }
5187
5328
  async gateway(opts) {
5188
5329
  const uiOverrides = {
5189
5330
  host: FORCED_PUBLIC_UI_HOST
@@ -5344,8 +5485,8 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5344
5485
  );
5345
5486
  const historyFile = join7(getDataDir8(), "history", "cli_history");
5346
5487
  const historyDir = resolve9(historyFile, "..");
5347
- mkdirSync6(historyDir, { recursive: true });
5348
- const history = existsSync10(historyFile) ? readFileSync8(historyFile, "utf-8").split("\n").filter(Boolean) : [];
5488
+ mkdirSync7(historyDir, { recursive: true });
5489
+ const history = existsSync10(historyFile) ? readFileSync9(historyFile, "utf-8").split("\n").filter(Boolean) : [];
5349
5490
  const rl = createInterface2({
5350
5491
  input: process.stdin,
5351
5492
  output: process.stdout
@@ -5500,26 +5641,63 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5500
5641
  await this.diagnosticsCommands.doctor(opts);
5501
5642
  }
5502
5643
  async skillsInstall(options) {
5503
- const workdir = options.workdir ? expandHome2(options.workdir) : getWorkspacePath6();
5504
- const result = await installClawHubSkill({
5644
+ const config2 = loadConfig7();
5645
+ const workdir = resolveSkillsInstallWorkdir({
5646
+ explicitWorkdir: options.workdir,
5647
+ configuredWorkspace: config2.agents.defaults.workspace
5648
+ });
5649
+ const result = await installMarketplaceSkill({
5505
5650
  slug: options.slug,
5506
- version: options.version,
5507
- registry: options.registry,
5508
5651
  workdir,
5509
5652
  dir: options.dir,
5510
- force: options.force
5653
+ force: options.force,
5654
+ apiBaseUrl: options.apiBaseUrl
5511
5655
  });
5512
- const versionLabel = result.version ?? "latest";
5513
5656
  if (result.alreadyInstalled) {
5514
5657
  console.log(`\u2713 ${result.slug} is already installed`);
5515
5658
  } else {
5516
- console.log(`\u2713 Installed ${result.slug}@${versionLabel}`);
5517
- }
5518
- if (result.registry) {
5519
- console.log(` Registry: ${result.registry}`);
5659
+ console.log(`\u2713 Installed ${result.slug} (${result.source})`);
5520
5660
  }
5521
5661
  console.log(` Path: ${result.destinationDir}`);
5522
5662
  }
5663
+ async skillsPublish(options) {
5664
+ const result = await publishMarketplaceSkill({
5665
+ skillDir: expandHome2(options.dir),
5666
+ slug: options.slug,
5667
+ name: options.name,
5668
+ summary: options.summary,
5669
+ description: options.description,
5670
+ author: options.author,
5671
+ tags: options.tag,
5672
+ sourceRepo: options.sourceRepo,
5673
+ homepage: options.homepage,
5674
+ publishedAt: options.publishedAt,
5675
+ updatedAt: options.updatedAt,
5676
+ apiBaseUrl: options.apiBaseUrl,
5677
+ token: options.token
5678
+ });
5679
+ console.log(result.created ? `\u2713 Published new skill: ${result.slug}` : `\u2713 Updated skill: ${result.slug}`);
5680
+ console.log(` Files: ${result.fileCount}`);
5681
+ }
5682
+ async skillsUpdate(options) {
5683
+ const result = await publishMarketplaceSkill({
5684
+ skillDir: expandHome2(options.dir),
5685
+ slug: options.slug,
5686
+ name: options.name,
5687
+ summary: options.summary,
5688
+ description: options.description,
5689
+ author: options.author,
5690
+ tags: options.tag,
5691
+ sourceRepo: options.sourceRepo,
5692
+ homepage: options.homepage,
5693
+ updatedAt: options.updatedAt,
5694
+ apiBaseUrl: options.apiBaseUrl,
5695
+ token: options.token,
5696
+ requireExisting: true
5697
+ });
5698
+ console.log(`\u2713 Updated skill: ${result.slug}`);
5699
+ console.log(` Files: ${result.fileCount}`);
5700
+ }
5523
5701
  };
5524
5702
 
5525
5703
  // src/cli/index.ts
@@ -5528,6 +5706,7 @@ var runtime = new CliRuntime({ logo: LOGO });
5528
5706
  program.name(APP_NAME5).description(`${LOGO} ${APP_NAME5} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
5529
5707
  program.command("onboard").description(`Initialize ${APP_NAME5} configuration and workspace`).action(async () => runtime.onboard());
5530
5708
  program.command("init").description(`Initialize ${APP_NAME5} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
5709
+ program.command("login").description("Login to NextClaw platform and save token into providers.nextclaw.apiKey").option("--api-base <url>", "Platform API base (supports /v1 suffix)").option("--email <email>", "Login email").option("--password <password>", "Login password").option("--register", "Register first, then login", false).action(async (opts) => runtime.login(opts));
5531
5710
  program.command("gateway").description(`Start the ${APP_NAME5} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).action(async (opts) => runtime.gateway(opts));
5532
5711
  program.command("ui").description(`Start the ${APP_NAME5} UI with gateway`).option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
5533
5712
  program.command("start").description(`Start the ${APP_NAME5} gateway + UI in the background`).option("--ui-port <port>", "UI port").option("--start-timeout <ms>", "Maximum wait time for startup readiness in milliseconds").option("--open", "Open browser after start", false).action(async (opts) => runtime.start(opts));
@@ -5536,13 +5715,11 @@ program.command("serve").description(`Run the ${APP_NAME5} gateway + UI in the f
5536
5715
  program.command("stop").description(`Stop the ${APP_NAME5} background service`).action(async () => runtime.stop());
5537
5716
  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
5717
  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
5718
  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);
5719
+ 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 }));
5720
+ var withRepeatableTag = (value, previous = []) => [...previous, value];
5721
+ 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 }));
5722
+ 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
5723
  var plugins = program.command("plugins").description("Manage OpenClaw-compatible plugins");
5547
5724
  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
5725
  plugins.command("info <id>").description("Show plugin details").option("--json", "Print JSON").action((id, opts) => runtime.pluginsInfo(id, opts));