@nextclaw/server 0.5.24 → 0.5.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -387,12 +387,14 @@ type MarketplaceInstallSpec = {
387
387
  spec: string;
388
388
  command: string;
389
389
  };
390
+ type MarketplaceLocalizedTextMap = Record<string, string>;
390
391
  type MarketplaceItemSummary = {
391
392
  id: string;
392
393
  slug: string;
393
394
  type: MarketplaceItemType;
394
395
  name: string;
395
396
  summary: string;
397
+ summaryI18n: MarketplaceLocalizedTextMap;
396
398
  tags: string[];
397
399
  author: string;
398
400
  install: MarketplaceInstallSpec;
@@ -400,10 +402,33 @@ type MarketplaceItemSummary = {
400
402
  };
401
403
  type MarketplaceItemView = MarketplaceItemSummary & {
402
404
  description?: string;
405
+ descriptionI18n?: MarketplaceLocalizedTextMap;
403
406
  sourceRepo?: string;
404
407
  homepage?: string;
405
408
  publishedAt: string;
406
409
  };
410
+ type MarketplaceSkillContentView = {
411
+ type: "skill";
412
+ slug: string;
413
+ name: string;
414
+ install: MarketplaceInstallSpec;
415
+ source: "workspace" | "builtin" | "git" | "remote";
416
+ raw: string;
417
+ metadataRaw?: string;
418
+ bodyRaw: string;
419
+ sourceUrl?: string;
420
+ };
421
+ type MarketplacePluginContentView = {
422
+ type: "plugin";
423
+ slug: string;
424
+ name: string;
425
+ install: MarketplaceInstallSpec;
426
+ source: "npm" | "repo" | "remote";
427
+ raw?: string;
428
+ bodyRaw?: string;
429
+ metadataRaw?: string;
430
+ sourceUrl?: string;
431
+ };
407
432
  type MarketplaceListView = {
408
433
  total: number;
409
434
  page: number;
@@ -610,4 +635,4 @@ declare function deleteSession(configPath: string, key: string): boolean;
610
635
  declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
611
636
  declare function updateSecrets(configPath: string, patch: SecretsConfigUpdate): SecretsView;
612
637
 
613
- export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type BindingPeerView, type ChannelSpecView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderConfigUpdate, type ProviderConfigView, type ProviderConnectionTestRequest, type ProviderConnectionTestResult, type ProviderSpecView, type RuntimeConfigUpdate, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createUiRouter, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
638
+ export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type BindingPeerView, type ChannelSpecView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplaceLocalizedTextMap, type MarketplacePluginContentView, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillContentView, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderConfigUpdate, type ProviderConfigView, type ProviderConnectionTestRequest, type ProviderConnectionTestResult, type ProviderSpecView, type RuntimeConfigUpdate, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createUiRouter, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
package/dist/index.js CHANGED
@@ -5,11 +5,12 @@ import { cors } from "hono/cors";
5
5
  import { serve } from "@hono/node-server";
6
6
  import { WebSocketServer, WebSocket } from "ws";
7
7
  import { existsSync, readFileSync } from "fs";
8
- import { readFile, stat } from "fs/promises";
8
+ import { readFile as readFile2, stat } from "fs/promises";
9
9
  import { join } from "path";
10
10
 
11
11
  // src/ui/router.ts
12
12
  import { Hono } from "hono";
13
+ import { readFile } from "fs/promises";
13
14
  import * as NextclawCore from "@nextclaw/core";
14
15
  import { buildPluginStatusReport } from "@nextclaw/openclaw-compat";
15
16
 
@@ -34,7 +35,7 @@ var MASK_MIN_LENGTH = 8;
34
35
  var EXTRA_SENSITIVE_PATH_PATTERNS = [/authorization/i, /cookie/i, /session/i, /bearer/i];
35
36
  var PROVIDER_TEST_MODEL_FALLBACKS = {
36
37
  openai: "gpt-5-mini",
37
- deepseek: "deepseek-v3.2",
38
+ deepseek: "deepseek-chat",
38
39
  gemini: "gemini-3-flash-preview",
39
40
  zhipu: "glm-5",
40
41
  dashscope: "qwen3.5-flash",
@@ -60,22 +61,12 @@ var PREFERRED_PROVIDER_ORDER_INDEX = new Map(
60
61
  PREFERRED_PROVIDER_ORDER.map((name, index) => [name, index])
61
62
  );
62
63
  var DOCS_BASE_URL = "https://docs.nextclaw.io";
63
- var CHANNEL_DEFAULT_TUTORIAL_URL = `${DOCS_BASE_URL}/guide/channels`;
64
64
  var CHANNEL_TUTORIAL_URLS = {
65
- telegram: { default: CHANNEL_DEFAULT_TUTORIAL_URL },
66
- whatsapp: { default: CHANNEL_DEFAULT_TUTORIAL_URL },
67
- discord: { default: CHANNEL_DEFAULT_TUTORIAL_URL },
68
65
  feishu: {
69
66
  default: `${DOCS_BASE_URL}/guide/tutorials/feishu`,
70
67
  en: `${DOCS_BASE_URL}/en/guide/tutorials/feishu`,
71
68
  zh: `${DOCS_BASE_URL}/zh/guide/tutorials/feishu`
72
- },
73
- mochat: { default: CHANNEL_DEFAULT_TUTORIAL_URL },
74
- dingtalk: { default: CHANNEL_DEFAULT_TUTORIAL_URL },
75
- wecom: { default: CHANNEL_DEFAULT_TUTORIAL_URL },
76
- email: { default: CHANNEL_DEFAULT_TUTORIAL_URL },
77
- slack: { default: CHANNEL_DEFAULT_TUTORIAL_URL },
78
- qq: { default: CHANNEL_DEFAULT_TUTORIAL_URL }
69
+ }
79
70
  };
80
71
  function matchesExtraSensitivePath(path) {
81
72
  if (path === "session" || path.startsWith("session.")) {
@@ -378,8 +369,8 @@ function buildConfigMeta(config) {
378
369
  return left.name.localeCompare(right.name);
379
370
  });
380
371
  const channels = Object.keys(config.channels).map((name) => {
381
- const tutorialUrls = CHANNEL_TUTORIAL_URLS[name] ?? { default: CHANNEL_DEFAULT_TUTORIAL_URL };
382
- const tutorialUrl = tutorialUrls.default ?? tutorialUrls.en ?? tutorialUrls.zh;
372
+ const tutorialUrls = CHANNEL_TUTORIAL_URLS[name];
373
+ const tutorialUrl = tutorialUrls?.default ?? tutorialUrls?.en ?? tutorialUrls?.zh;
383
374
  return {
384
375
  name,
385
376
  displayName: name,
@@ -1297,6 +1288,141 @@ function sanitizeMarketplaceItem(item) {
1297
1288
  delete next.metrics;
1298
1289
  return next;
1299
1290
  }
1291
+ var MARKETPLACE_ZH_COPY_BY_SLUG = {
1292
+ weather: {
1293
+ summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u5929\u6C14\u67E5\u8BE2\u5DE5\u4F5C\u6D41\u3002",
1294
+ description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u5FEB\u901F\u5929\u6C14\u67E5\u8BE2\u5DE5\u4F5C\u6D41\u3002"
1295
+ },
1296
+ summarize: {
1297
+ summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u7ED3\u6784\u5316\u6458\u8981\u3002",
1298
+ description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u6587\u4EF6\u4E0E\u957F\u6587\u672C\u7684\u6458\u8981\u5DE5\u4F5C\u6D41\u3002"
1299
+ },
1300
+ github: {
1301
+ summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E GitHub \u5DE5\u4F5C\u6D41\u3002",
1302
+ description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B Issue\u3001PR \u4E0E\u4ED3\u5E93\u76F8\u5173\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
1303
+ },
1304
+ tmux: {
1305
+ summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u7EC8\u7AEF/Tmux \u534F\u4F5C\u5DE5\u4F5C\u6D41\u3002",
1306
+ description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u57FA\u4E8E Tmux \u7684\u4EFB\u52A1\u6267\u884C\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
1307
+ },
1308
+ gog: {
1309
+ summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u56FE\u8C31\u5BFC\u5411\u751F\u6210\u5DE5\u4F5C\u6D41\u3002",
1310
+ description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u56FE\u8C31\u4E0E\u89C4\u5212\u5BFC\u5411\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
1311
+ },
1312
+ pdf: {
1313
+ summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E PDF \u8BFB\u53D6/\u5408\u5E76/\u62C6\u5206/OCR \u5DE5\u4F5C\u6D41\u3002",
1314
+ description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u8BFB\u53D6\u3001\u63D0\u53D6\u3001\u5408\u5E76\u3001\u62C6\u5206\u3001\u65CB\u8F6C\u5E76\u5BF9 PDF \u6267\u884C OCR \u5904\u7406\u3002"
1315
+ },
1316
+ docx: {
1317
+ summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u521B\u5EFA\u548C\u7F16\u8F91 Word \u6587\u6863\u3002",
1318
+ description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u521B\u5EFA\u3001\u8BFB\u53D6\u3001\u7F16\u8F91\u5E76\u91CD\u6784 .docx \u6587\u6863\u3002"
1319
+ },
1320
+ pptx: {
1321
+ summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u6F14\u793A\u6587\u7A3F\u64CD\u4F5C\u3002",
1322
+ description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u521B\u5EFA\u3001\u89E3\u6790\u3001\u7F16\u8F91\u5E76\u91CD\u7EC4 .pptx \u6F14\u793A\u6587\u7A3F\u3002"
1323
+ },
1324
+ xlsx: {
1325
+ summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u8868\u683C\u6587\u6863\u5DE5\u4F5C\u6D41\u3002",
1326
+ description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u6253\u5F00\u3001\u7F16\u8F91\u3001\u6E05\u6D17\u5E76\u8F6C\u6362 .xlsx \u4E0E .csv \u7B49\u8868\u683C\u6587\u4EF6\u3002"
1327
+ },
1328
+ bird: {
1329
+ summary: "OpenClaw \u793E\u533A\u6280\u80FD\uFF0C\u7528\u4E8E X/Twitter \u8BFB\u53D6/\u641C\u7D22/\u53D1\u5E03\u5DE5\u4F5C\u6D41\u3002",
1330
+ description: "\u4F7F\u7528 bird CLI \u5728\u4EE3\u7406\u5DE5\u4F5C\u6D41\u4E2D\u8BFB\u53D6\u7EBF\u7A0B\u3001\u641C\u7D22\u5E16\u5B50\u5E76\u8D77\u8349\u63A8\u6587/\u56DE\u590D\u3002"
1331
+ },
1332
+ "cloudflare-deploy": {
1333
+ summary: "OpenAI \u7CBE\u9009\u6280\u80FD\uFF0C\u7528\u4E8E\u5728 Cloudflare \u4E0A\u90E8\u7F72\u5E94\u7528\u4E0E\u57FA\u7840\u8BBE\u65BD\u3002",
1334
+ description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u9009\u62E9 Cloudflare \u4EA7\u54C1\u5E76\u90E8\u7F72 Workers\u3001Pages \u53CA\u76F8\u5173\u670D\u52A1\u3002"
1335
+ },
1336
+ "channel-plugin-discord": {
1337
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Discord \u6E20\u9053\u96C6\u6210\u3002",
1338
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Discord \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
1339
+ },
1340
+ "channel-plugin-telegram": {
1341
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Telegram \u6E20\u9053\u96C6\u6210\u3002",
1342
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Telegram \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
1343
+ },
1344
+ "channel-plugin-slack": {
1345
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Slack \u6E20\u9053\u96C6\u6210\u3002",
1346
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Slack \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
1347
+ },
1348
+ "channel-plugin-wecom": {
1349
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E\u4F01\u4E1A\u5FAE\u4FE1\u6E20\u9053\u96C6\u6210\u3002",
1350
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B\u4F01\u4E1A\u5FAE\u4FE1\u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
1351
+ },
1352
+ "channel-plugin-email": {
1353
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Email \u6E20\u9053\u96C6\u6210\u3002",
1354
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Email \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
1355
+ },
1356
+ "channel-plugin-whatsapp": {
1357
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E WhatsApp \u6E20\u9053\u96C6\u6210\u3002",
1358
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B WhatsApp \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
1359
+ },
1360
+ "channel-plugin-clawbay": {
1361
+ summary: "Clawbay \u5B98\u65B9\u6E20\u9053\u63D2\u4EF6\uFF0C\u7528\u4E8E NextClaw \u96C6\u6210\u3002",
1362
+ description: "\u901A\u8FC7\u63D2\u4EF6\u8FD0\u884C\u65F6\u4E3A NextClaw \u63D0\u4F9B Clawbay \u6E20\u9053\u80FD\u529B\u3002"
1363
+ }
1364
+ };
1365
+ function readLocalizedMap(value) {
1366
+ const localized = {};
1367
+ if (!isRecord(value)) {
1368
+ return localized;
1369
+ }
1370
+ for (const [key, entry] of Object.entries(value)) {
1371
+ if (typeof entry !== "string" || entry.trim().length === 0) {
1372
+ continue;
1373
+ }
1374
+ localized[key] = entry.trim();
1375
+ }
1376
+ return localized;
1377
+ }
1378
+ function normalizeLocaleTag(value) {
1379
+ return value.trim().toLowerCase().replace(/_/g, "-");
1380
+ }
1381
+ function pickLocaleFamilyValue(localized, localeFamily) {
1382
+ const normalizedFamily = normalizeLocaleTag(localeFamily).split("-")[0];
1383
+ if (!normalizedFamily) {
1384
+ return void 0;
1385
+ }
1386
+ let familyMatch;
1387
+ for (const [locale, text] of Object.entries(localized)) {
1388
+ const normalizedLocale = normalizeLocaleTag(locale);
1389
+ if (!normalizedLocale) {
1390
+ continue;
1391
+ }
1392
+ if (normalizedLocale === normalizedFamily) {
1393
+ return text;
1394
+ }
1395
+ if (!familyMatch && normalizedLocale.startsWith(`${normalizedFamily}-`)) {
1396
+ familyMatch = text;
1397
+ }
1398
+ }
1399
+ return familyMatch;
1400
+ }
1401
+ function normalizeLocalizedTextMap(primaryText, localized, zhFallback) {
1402
+ const next = readLocalizedMap(localized);
1403
+ if (!next.en) {
1404
+ next.en = pickLocaleFamilyValue(next, "en") ?? primaryText;
1405
+ }
1406
+ if (!next.zh) {
1407
+ next.zh = pickLocaleFamilyValue(next, "zh") ?? (zhFallback && zhFallback.trim().length > 0 ? zhFallback.trim() : next.en);
1408
+ }
1409
+ return next;
1410
+ }
1411
+ function normalizeMarketplaceItemForUi(item) {
1412
+ const zhCopy = MARKETPLACE_ZH_COPY_BY_SLUG[item.slug];
1413
+ const next = {
1414
+ ...item,
1415
+ summaryI18n: normalizeLocalizedTextMap(item.summary, item.summaryI18n, zhCopy?.summary)
1416
+ };
1417
+ if ("description" in item && typeof item.description === "string" && item.description.trim().length > 0) {
1418
+ next.descriptionI18n = normalizeLocalizedTextMap(
1419
+ item.description,
1420
+ item.descriptionI18n,
1421
+ zhCopy?.description
1422
+ );
1423
+ }
1424
+ return next;
1425
+ }
1300
1426
  function toPositiveInt(raw, fallback) {
1301
1427
  if (!raw) {
1302
1428
  return fallback;
@@ -1324,6 +1450,192 @@ function isSupportedMarketplaceSkillItem(item, knownSkillNames) {
1324
1450
  }
1325
1451
  return item.install.kind === "builtin" && knownSkillNames.has(item.install.spec);
1326
1452
  }
1453
+ function splitMarkdownFrontmatter(raw) {
1454
+ const normalized = raw.replace(/\r\n/g, "\n");
1455
+ const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
1456
+ if (!match) {
1457
+ return { bodyRaw: normalized };
1458
+ }
1459
+ return {
1460
+ metadataRaw: match[1]?.trim() || void 0,
1461
+ bodyRaw: match[2] ?? ""
1462
+ };
1463
+ }
1464
+ async function loadLocalSkillMarkdown(options, skillName) {
1465
+ const config = loadConfigOrDefault(options.configPath);
1466
+ const loader = createSkillsLoader(getWorkspacePathFromConfig3(config));
1467
+ if (!loader) {
1468
+ return null;
1469
+ }
1470
+ const skillInfo = loader.listSkills(false).find((skill) => skill.name === skillName);
1471
+ if (!skillInfo) {
1472
+ return null;
1473
+ }
1474
+ try {
1475
+ const raw = await readFile(skillInfo.path, "utf-8");
1476
+ return {
1477
+ raw,
1478
+ source: skillInfo.source
1479
+ };
1480
+ } catch {
1481
+ return null;
1482
+ }
1483
+ }
1484
+ function parseGitSkillSpec(rawSpec) {
1485
+ const spec = rawSpec.trim();
1486
+ if (!spec) {
1487
+ return null;
1488
+ }
1489
+ const segments = spec.split("/").filter(Boolean);
1490
+ if (segments.length < 3) {
1491
+ return null;
1492
+ }
1493
+ return {
1494
+ owner: segments[0] ?? "",
1495
+ repo: segments[1] ?? "",
1496
+ skillPath: segments.slice(2).join("/")
1497
+ };
1498
+ }
1499
+ async function fetchTextWithFallback(urls) {
1500
+ for (const url of urls) {
1501
+ try {
1502
+ const response = await fetch(url, {
1503
+ method: "GET",
1504
+ headers: {
1505
+ Accept: "text/plain, text/markdown, application/json"
1506
+ }
1507
+ });
1508
+ if (!response.ok) {
1509
+ continue;
1510
+ }
1511
+ const text = await response.text();
1512
+ if (text.trim().length === 0) {
1513
+ continue;
1514
+ }
1515
+ return { text, url };
1516
+ } catch {
1517
+ continue;
1518
+ }
1519
+ }
1520
+ return null;
1521
+ }
1522
+ async function loadGitSkillMarkdownFromSpec(rawSpec) {
1523
+ const parsed = parseGitSkillSpec(rawSpec);
1524
+ if (!parsed) {
1525
+ return null;
1526
+ }
1527
+ const candidates = [
1528
+ `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/main/${parsed.skillPath}/SKILL.md`,
1529
+ `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/master/${parsed.skillPath}/SKILL.md`
1530
+ ];
1531
+ const result = await fetchTextWithFallback(candidates);
1532
+ if (!result) {
1533
+ return null;
1534
+ }
1535
+ return {
1536
+ raw: result.text,
1537
+ sourceUrl: result.url
1538
+ };
1539
+ }
1540
+ async function loadPluginReadmeFromNpm(spec) {
1541
+ const encodedSpec = encodeURIComponent(spec);
1542
+ const registryUrl = `https://registry.npmjs.org/${encodedSpec}`;
1543
+ try {
1544
+ const response = await fetch(registryUrl, {
1545
+ headers: {
1546
+ Accept: "application/json"
1547
+ }
1548
+ });
1549
+ if (!response.ok) {
1550
+ return null;
1551
+ }
1552
+ const payload = await response.json();
1553
+ const readme = typeof payload.readme === "string" ? payload.readme : "";
1554
+ const latest = isRecord(payload["dist-tags"]) && typeof payload["dist-tags"].latest === "string" ? payload["dist-tags"].latest : void 0;
1555
+ const metadata = {
1556
+ name: typeof payload.name === "string" ? payload.name : spec,
1557
+ version: latest,
1558
+ description: typeof payload.description === "string" ? payload.description : void 0,
1559
+ homepage: typeof payload.homepage === "string" ? payload.homepage : void 0
1560
+ };
1561
+ if (readme.trim().length === 0) {
1562
+ return null;
1563
+ }
1564
+ return {
1565
+ readme,
1566
+ sourceUrl: registryUrl,
1567
+ metadataRaw: JSON.stringify(metadata, null, 2)
1568
+ };
1569
+ } catch {
1570
+ return null;
1571
+ }
1572
+ }
1573
+ async function buildSkillContentView(options, item) {
1574
+ const local = await loadLocalSkillMarkdown(options, item.install.spec);
1575
+ if (local) {
1576
+ const split = splitMarkdownFrontmatter(local.raw);
1577
+ return {
1578
+ type: "skill",
1579
+ slug: item.slug,
1580
+ name: item.name,
1581
+ install: item.install,
1582
+ source: local.source,
1583
+ raw: local.raw,
1584
+ metadataRaw: split.metadataRaw,
1585
+ bodyRaw: split.bodyRaw
1586
+ };
1587
+ }
1588
+ if (item.install.kind === "git") {
1589
+ const remote = await loadGitSkillMarkdownFromSpec(item.install.spec);
1590
+ if (remote) {
1591
+ const split = splitMarkdownFrontmatter(remote.raw);
1592
+ return {
1593
+ type: "skill",
1594
+ slug: item.slug,
1595
+ name: item.name,
1596
+ install: item.install,
1597
+ source: "git",
1598
+ raw: remote.raw,
1599
+ metadataRaw: split.metadataRaw,
1600
+ bodyRaw: split.bodyRaw,
1601
+ sourceUrl: remote.sourceUrl
1602
+ };
1603
+ }
1604
+ }
1605
+ return null;
1606
+ }
1607
+ async function buildPluginContentView(item) {
1608
+ if (item.install.kind === "npm") {
1609
+ const npm = await loadPluginReadmeFromNpm(item.install.spec);
1610
+ if (npm) {
1611
+ return {
1612
+ type: "plugin",
1613
+ slug: item.slug,
1614
+ name: item.name,
1615
+ install: item.install,
1616
+ source: "npm",
1617
+ raw: npm.readme,
1618
+ bodyRaw: npm.readme,
1619
+ metadataRaw: npm.metadataRaw,
1620
+ sourceUrl: npm.sourceUrl
1621
+ };
1622
+ }
1623
+ }
1624
+ return {
1625
+ type: "plugin",
1626
+ slug: item.slug,
1627
+ name: item.name,
1628
+ install: item.install,
1629
+ source: "remote",
1630
+ bodyRaw: item.description || item.summary || "",
1631
+ metadataRaw: JSON.stringify({
1632
+ name: item.name,
1633
+ author: item.author,
1634
+ sourceRepo: item.sourceRepo,
1635
+ homepage: item.homepage
1636
+ }, null, 2)
1637
+ };
1638
+ }
1327
1639
  async function fetchAllMarketplaceItems(params) {
1328
1640
  const allItems = [];
1329
1641
  let remotePage = 1;
@@ -1502,7 +1814,7 @@ function registerPluginMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1502
1814
  if (!result.ok) {
1503
1815
  return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1504
1816
  }
1505
- const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplacePluginItem(item));
1817
+ const filteredItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item))).filter((item) => isSupportedMarketplacePluginItem(item));
1506
1818
  const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
1507
1819
  const requestedPage = toPositiveInt(query.page, 1);
1508
1820
  const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
@@ -1526,12 +1838,28 @@ function registerPluginMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1526
1838
  if (!result.ok) {
1527
1839
  return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1528
1840
  }
1529
- const sanitized = sanitizeMarketplaceItem(result.data);
1841
+ const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
1530
1842
  if (!isSupportedMarketplacePluginItem(sanitized)) {
1531
1843
  return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
1532
1844
  }
1533
1845
  return c.json(ok(sanitized));
1534
1846
  });
1847
+ app.get("/api/marketplace/plugins/items/:slug/content", async (c) => {
1848
+ const slug = encodeURIComponent(c.req.param("slug"));
1849
+ const result = await fetchMarketplaceData({
1850
+ baseUrl: marketplaceBaseUrl,
1851
+ path: `/api/v1/plugins/items/${slug}`
1852
+ });
1853
+ if (!result.ok) {
1854
+ return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1855
+ }
1856
+ const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
1857
+ if (!isSupportedMarketplacePluginItem(sanitized)) {
1858
+ return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
1859
+ }
1860
+ const content = await buildPluginContentView(sanitized);
1861
+ return c.json(ok(content));
1862
+ });
1535
1863
  app.post("/api/marketplace/plugins/install", async (c) => {
1536
1864
  const body = await readJson(c.req.raw);
1537
1865
  if (!body.ok || !body.data || typeof body.data !== "object") {
@@ -1595,7 +1923,7 @@ function registerPluginMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1595
1923
  if (!result.ok) {
1596
1924
  return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1597
1925
  }
1598
- const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplacePluginItem(item));
1926
+ const filteredItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item))).filter((item) => isSupportedMarketplacePluginItem(item));
1599
1927
  return c.json(ok({
1600
1928
  ...result.data,
1601
1929
  total: filteredItems.length,
@@ -1623,7 +1951,7 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1623
1951
  return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1624
1952
  }
1625
1953
  const knownSkillNames = collectKnownSkillNames(options);
1626
- const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
1954
+ const filteredItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item))).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
1627
1955
  const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
1628
1956
  const requestedPage = toPositiveInt(query.page, 1);
1629
1957
  const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
@@ -1648,12 +1976,32 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1648
1976
  return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1649
1977
  }
1650
1978
  const knownSkillNames = collectKnownSkillNames(options);
1651
- const sanitized = sanitizeMarketplaceItem(result.data);
1979
+ const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
1652
1980
  if (!isSupportedMarketplaceSkillItem(sanitized, knownSkillNames)) {
1653
1981
  return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
1654
1982
  }
1655
1983
  return c.json(ok(sanitized));
1656
1984
  });
1985
+ app.get("/api/marketplace/skills/items/:slug/content", async (c) => {
1986
+ const slug = encodeURIComponent(c.req.param("slug"));
1987
+ const result = await fetchMarketplaceData({
1988
+ baseUrl: marketplaceBaseUrl,
1989
+ path: `/api/v1/skills/items/${slug}`
1990
+ });
1991
+ if (!result.ok) {
1992
+ return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1993
+ }
1994
+ const knownSkillNames = collectKnownSkillNames(options);
1995
+ const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(result.data));
1996
+ if (!isSupportedMarketplaceSkillItem(sanitized, knownSkillNames)) {
1997
+ return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
1998
+ }
1999
+ const content = await buildSkillContentView(options, sanitized);
2000
+ if (!content) {
2001
+ return c.json(err("NOT_FOUND", "skill markdown content not found"), 404);
2002
+ }
2003
+ return c.json(ok(content));
2004
+ });
1657
2005
  app.post("/api/marketplace/skills/install", async (c) => {
1658
2006
  const body = await readJson(c.req.raw);
1659
2007
  if (!body.ok || !body.data || typeof body.data !== "object") {
@@ -1718,7 +2066,7 @@ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1718
2066
  return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1719
2067
  }
1720
2068
  const knownSkillNames = collectKnownSkillNames(options);
1721
- const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
2069
+ const filteredItems = result.data.items.map((item) => normalizeMarketplaceItemForUi(sanitizeMarketplaceItem(item))).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
1722
2070
  return c.json(ok({
1723
2071
  ...result.data,
1724
2072
  total: filteredItems.length,
@@ -2163,7 +2511,7 @@ function startUiServer(options) {
2163
2511
  join,
2164
2512
  getContent: async (path) => {
2165
2513
  try {
2166
- return await readFile(path);
2514
+ return await readFile2(path);
2167
2515
  } catch {
2168
2516
  return null;
2169
2517
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.5.24",
3
+ "version": "0.5.26",
4
4
  "private": false,
5
5
  "description": "Nextclaw UI/API server.",
6
6
  "type": "module",
@@ -15,10 +15,10 @@
15
15
  ],
16
16
  "dependencies": {
17
17
  "@hono/node-server": "^1.13.3",
18
- "@nextclaw/openclaw-compat": "^0.1.30",
18
+ "@nextclaw/openclaw-compat": "^0.1.31",
19
19
  "hono": "^4.6.2",
20
20
  "ws": "^8.18.0",
21
- "@nextclaw/core": "^0.6.39"
21
+ "@nextclaw/core": "^0.6.42"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^20.17.6",