@nextclaw/server 0.5.13 → 0.5.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +41 -17
  2. package/dist/index.js +341 -180
  3. package/package.json +3 -3
package/dist/index.d.ts CHANGED
@@ -324,9 +324,8 @@ type MarketplaceInstalledView = {
324
324
  specs: string[];
325
325
  records: MarketplaceInstalledRecord[];
326
326
  };
327
- type MarketplaceInstallRequest = {
328
- type: MarketplaceItemType;
329
- spec: string;
327
+ type MarketplaceInstallSkillParams = {
328
+ slug: string;
330
329
  kind?: MarketplaceInstallKind;
331
330
  skill?: string;
332
331
  installPath?: string;
@@ -334,14 +333,13 @@ type MarketplaceInstallRequest = {
334
333
  registry?: string;
335
334
  force?: boolean;
336
335
  };
337
- type MarketplaceInstallResult = {
338
- type: MarketplaceItemType;
336
+ type MarketplacePluginInstallRequest = {
337
+ type?: "plugin";
339
338
  spec: string;
340
- message: string;
341
- output?: string;
342
339
  };
343
- type MarketplaceInstallSkillParams = {
344
- slug: string;
340
+ type MarketplaceSkillInstallRequest = {
341
+ type?: "skill";
342
+ spec: string;
345
343
  kind?: MarketplaceInstallKind;
346
344
  skill?: string;
347
345
  installPath?: string;
@@ -349,16 +347,42 @@ type MarketplaceInstallSkillParams = {
349
347
  registry?: string;
350
348
  force?: boolean;
351
349
  };
352
- type MarketplaceManageAction = "enable" | "disable" | "uninstall";
353
- type MarketplaceManageRequest = {
354
- type: MarketplaceItemType;
355
- action: MarketplaceManageAction;
350
+ type MarketplacePluginInstallResult = {
351
+ type: "plugin";
352
+ spec: string;
353
+ message: string;
354
+ output?: string;
355
+ };
356
+ type MarketplaceSkillInstallResult = {
357
+ type: "skill";
358
+ spec: string;
359
+ message: string;
360
+ output?: string;
361
+ };
362
+ type MarketplacePluginManageAction = "enable" | "disable" | "uninstall";
363
+ type MarketplaceSkillManageAction = "uninstall";
364
+ type MarketplacePluginManageRequest = {
365
+ type?: "plugin";
366
+ action: MarketplacePluginManageAction;
356
367
  id?: string;
357
368
  spec?: string;
358
369
  };
359
- type MarketplaceManageResult = {
360
- type: MarketplaceItemType;
361
- action: MarketplaceManageAction;
370
+ type MarketplaceSkillManageRequest = {
371
+ type?: "skill";
372
+ action: MarketplaceSkillManageAction;
373
+ id?: string;
374
+ spec?: string;
375
+ };
376
+ type MarketplacePluginManageResult = {
377
+ type: "plugin";
378
+ action: MarketplacePluginManageAction;
379
+ id: string;
380
+ message: string;
381
+ output?: string;
382
+ };
383
+ type MarketplaceSkillManageResult = {
384
+ type: "skill";
385
+ action: MarketplaceSkillManageAction;
362
386
  id: string;
363
387
  message: string;
364
388
  output?: string;
@@ -467,4 +491,4 @@ declare function patchSession(configPath: string, key: string, patch: SessionPat
467
491
  declare function deleteSession(configPath: string, key: string): boolean;
468
492
  declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
469
493
 
470
- export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type BindingPeerView, type ChannelSpecView, 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 MarketplaceInstallRequest, type MarketplaceInstallResult, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplaceManageAction, type MarketplaceManageRequest, type MarketplaceManageResult, type MarketplaceRecommendationView, type MarketplaceSort, type ProviderConfigUpdate, type ProviderConfigView, type ProviderSpecView, type RuntimeConfigUpdate, type SessionConfigView, type SessionEntryView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createUiRouter, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, updateChannel, updateModel, updateProvider, updateRuntime };
494
+ export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type BindingPeerView, type ChannelSpecView, 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 ProviderSpecView, type RuntimeConfigUpdate, type SessionConfigView, type SessionEntryView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createUiRouter, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, updateChannel, updateModel, updateProvider, updateRuntime };
package/dist/index.js CHANGED
@@ -931,10 +931,19 @@ function collectInstalledSkillRecords(options) {
931
931
  records
932
932
  };
933
933
  }
934
- function collectMarketplaceInstalledView(options, type) {
935
- const installed = type === "plugin" ? collectInstalledPluginRecords(options) : collectInstalledSkillRecords(options);
934
+ function collectPluginMarketplaceInstalledView(options) {
935
+ const installed = collectInstalledPluginRecords(options);
936
936
  return {
937
- type,
937
+ type: "plugin",
938
+ total: installed.records.length,
939
+ specs: installed.specs,
940
+ records: installed.records
941
+ };
942
+ }
943
+ function collectSkillMarketplaceInstalledView(options) {
944
+ const installed = collectInstalledSkillRecords(options);
945
+ return {
946
+ type: "skill",
938
947
  total: installed.records.length,
939
948
  specs: installed.specs,
940
949
  records: installed.records
@@ -993,9 +1002,12 @@ function collectKnownSkillNames(options) {
993
1002
  const loader = createSkillsLoader(getWorkspacePathFromConfig3(config));
994
1003
  return new Set((loader?.listSkills(false) ?? []).map((skill) => skill.name));
995
1004
  }
996
- function isSupportedMarketplaceItem(item, knownSkillNames) {
997
- if (item.type === "plugin") {
998
- return item.install.kind === "npm" && isSupportedMarketplacePluginSpec(item.install.spec);
1005
+ function isSupportedMarketplacePluginItem(item) {
1006
+ return item.type === "plugin" && item.install.kind === "npm" && isSupportedMarketplacePluginSpec(item.install.spec);
1007
+ }
1008
+ function isSupportedMarketplaceSkillItem(item, knownSkillNames) {
1009
+ if (item.type !== "skill") {
1010
+ return false;
999
1011
  }
1000
1012
  if (item.install.kind === "git") {
1001
1013
  return true;
@@ -1036,228 +1048,377 @@ async function fetchAllMarketplaceItems(params) {
1036
1048
  }
1037
1049
  };
1038
1050
  }
1039
- async function installMarketplaceItem(params) {
1040
- const type = params.body.type;
1051
+ async function fetchAllPluginMarketplaceItems(params) {
1052
+ return await fetchAllMarketplaceItems({
1053
+ baseUrl: params.baseUrl,
1054
+ segment: "plugins",
1055
+ query: params.query
1056
+ });
1057
+ }
1058
+ async function fetchAllSkillMarketplaceItems(params) {
1059
+ return await fetchAllMarketplaceItems({
1060
+ baseUrl: params.baseUrl,
1061
+ segment: "skills",
1062
+ query: params.query
1063
+ });
1064
+ }
1065
+ async function installMarketplacePlugin(params) {
1041
1066
  const spec = typeof params.body.spec === "string" ? params.body.spec.trim() : "";
1042
- if (type !== "plugin" && type !== "skill" || !spec) {
1043
- throw new Error("INVALID_BODY:type and non-empty spec are required");
1067
+ if (!spec) {
1068
+ throw new Error("INVALID_BODY:non-empty spec is required");
1044
1069
  }
1045
1070
  const installer = params.options.marketplace?.installer;
1046
1071
  if (!installer) {
1047
1072
  throw new Error("NOT_AVAILABLE:marketplace installer is not configured");
1048
1073
  }
1049
- let result;
1050
- if (type === "plugin") {
1051
- if (!installer.installPlugin) {
1052
- throw new Error("NOT_AVAILABLE:plugin installer is not configured");
1053
- }
1054
- result = await installer.installPlugin(spec);
1055
- } else {
1056
- if (!installer.installSkill) {
1057
- throw new Error("NOT_AVAILABLE:skill installer is not configured");
1058
- }
1059
- result = await installer.installSkill({
1060
- slug: spec,
1061
- kind: params.body.kind,
1062
- skill: params.body.skill,
1063
- installPath: params.body.installPath,
1064
- version: params.body.version,
1065
- registry: params.body.registry,
1066
- force: params.body.force
1067
- });
1074
+ if (!installer.installPlugin) {
1075
+ throw new Error("NOT_AVAILABLE:plugin installer is not configured");
1068
1076
  }
1069
- params.options.publish({ type: "config.updated", payload: { path: type === "plugin" ? "plugins" : "skills" } });
1077
+ const result = await installer.installPlugin(spec);
1078
+ params.options.publish({ type: "config.updated", payload: { path: "plugins" } });
1070
1079
  return {
1071
- type,
1080
+ type: "plugin",
1072
1081
  spec,
1073
1082
  message: result.message,
1074
1083
  output: result.output
1075
1084
  };
1076
1085
  }
1077
- async function manageMarketplaceItem(params) {
1078
- const type = params.body.type;
1086
+ async function installMarketplaceSkill(params) {
1087
+ const spec = typeof params.body.spec === "string" ? params.body.spec.trim() : "";
1088
+ if (!spec) {
1089
+ throw new Error("INVALID_BODY:non-empty spec is required");
1090
+ }
1091
+ const installer = params.options.marketplace?.installer;
1092
+ if (!installer) {
1093
+ throw new Error("NOT_AVAILABLE:marketplace installer is not configured");
1094
+ }
1095
+ if (!installer.installSkill) {
1096
+ throw new Error("NOT_AVAILABLE:skill installer is not configured");
1097
+ }
1098
+ const result = await installer.installSkill({
1099
+ slug: spec,
1100
+ kind: params.body.kind,
1101
+ skill: params.body.skill,
1102
+ installPath: params.body.installPath,
1103
+ version: params.body.version,
1104
+ registry: params.body.registry,
1105
+ force: params.body.force
1106
+ });
1107
+ params.options.publish({ type: "config.updated", payload: { path: "skills" } });
1108
+ return {
1109
+ type: "skill",
1110
+ spec,
1111
+ message: result.message,
1112
+ output: result.output
1113
+ };
1114
+ }
1115
+ async function manageMarketplacePlugin(params) {
1079
1116
  const action = params.body.action;
1080
1117
  const requestedTargetId = typeof params.body.id === "string" && params.body.id.trim().length > 0 ? params.body.id.trim() : typeof params.body.spec === "string" && params.body.spec.trim().length > 0 ? params.body.spec.trim() : "";
1081
1118
  const rawSpec = typeof params.body.spec === "string" ? params.body.spec.trim() : "";
1082
- const targetId = type === "plugin" ? resolvePluginManageTargetId(params.options, requestedTargetId, rawSpec) : requestedTargetId;
1083
- if (type !== "plugin" && type !== "skill" || action !== "enable" && action !== "disable" && action !== "uninstall" || !targetId) {
1084
- throw new Error("INVALID_BODY:type, action and non-empty id/spec are required");
1119
+ const targetId = resolvePluginManageTargetId(params.options, requestedTargetId, rawSpec);
1120
+ if (action !== "enable" && action !== "disable" && action !== "uninstall" || !targetId) {
1121
+ throw new Error("INVALID_BODY:action and non-empty id/spec are required");
1085
1122
  }
1086
1123
  const installer = params.options.marketplace?.installer;
1087
1124
  if (!installer) {
1088
1125
  throw new Error("NOT_AVAILABLE:marketplace installer is not configured");
1089
1126
  }
1090
1127
  let result;
1091
- if (type === "plugin") {
1092
- if (action === "enable") {
1093
- if (!installer.enablePlugin) {
1094
- throw new Error("NOT_AVAILABLE:plugin enable is not configured");
1095
- }
1096
- result = await installer.enablePlugin(targetId);
1097
- } else if (action === "disable") {
1098
- if (!installer.disablePlugin) {
1099
- throw new Error("NOT_AVAILABLE:plugin disable is not configured");
1100
- }
1101
- result = await installer.disablePlugin(targetId);
1102
- } else {
1103
- if (!installer.uninstallPlugin) {
1104
- throw new Error("NOT_AVAILABLE:plugin uninstall is not configured");
1105
- }
1106
- result = await installer.uninstallPlugin(targetId);
1128
+ if (action === "enable") {
1129
+ if (!installer.enablePlugin) {
1130
+ throw new Error("NOT_AVAILABLE:plugin enable is not configured");
1107
1131
  }
1108
- } else {
1109
- if (action !== "uninstall") {
1110
- throw new Error("NOT_AVAILABLE:skill only supports uninstall action");
1132
+ result = await installer.enablePlugin(targetId);
1133
+ } else if (action === "disable") {
1134
+ if (!installer.disablePlugin) {
1135
+ throw new Error("NOT_AVAILABLE:plugin disable is not configured");
1111
1136
  }
1112
- if (!installer.uninstallSkill) {
1113
- throw new Error("NOT_AVAILABLE:skill uninstall is not configured");
1137
+ result = await installer.disablePlugin(targetId);
1138
+ } else {
1139
+ if (!installer.uninstallPlugin) {
1140
+ throw new Error("NOT_AVAILABLE:plugin uninstall is not configured");
1114
1141
  }
1115
- result = await installer.uninstallSkill(targetId);
1142
+ result = await installer.uninstallPlugin(targetId);
1116
1143
  }
1117
- params.options.publish({ type: "config.updated", payload: { path: type === "plugin" ? "plugins" : "skills" } });
1144
+ params.options.publish({ type: "config.updated", payload: { path: "plugins" } });
1118
1145
  return {
1119
- type,
1146
+ type: "plugin",
1120
1147
  action,
1121
1148
  id: targetId,
1122
1149
  message: result.message,
1123
1150
  output: result.output
1124
1151
  };
1125
1152
  }
1126
- function registerMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1127
- const typedMarketplaceRoutes = [
1128
- { segment: "plugins", type: "plugin" },
1129
- { segment: "skills", type: "skill" }
1130
- ];
1131
- for (const route of typedMarketplaceRoutes) {
1132
- app.get(`/api/marketplace/${route.segment}/installed`, (c) => {
1133
- return c.json(ok(collectMarketplaceInstalledView(options, route.type)));
1134
- });
1135
- app.get(`/api/marketplace/${route.segment}/items`, async (c) => {
1136
- const query = c.req.query();
1137
- const result = await fetchAllMarketplaceItems({
1138
- baseUrl: marketplaceBaseUrl,
1139
- segment: route.segment,
1140
- query: {
1141
- q: query.q,
1142
- tag: query.tag,
1143
- sort: query.sort,
1144
- page: query.page,
1145
- pageSize: query.pageSize
1146
- }
1147
- });
1148
- if (!result.ok) {
1149
- return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1153
+ async function manageMarketplaceSkill(params) {
1154
+ const action = params.body.action;
1155
+ const targetId = typeof params.body.id === "string" && params.body.id.trim().length > 0 ? params.body.id.trim() : typeof params.body.spec === "string" && params.body.spec.trim().length > 0 ? params.body.spec.trim() : "";
1156
+ if (action !== "uninstall" || !targetId) {
1157
+ throw new Error("INVALID_BODY:skill manage requires uninstall action and non-empty id/spec");
1158
+ }
1159
+ const installer = params.options.marketplace?.installer;
1160
+ if (!installer) {
1161
+ throw new Error("NOT_AVAILABLE:marketplace installer is not configured");
1162
+ }
1163
+ if (!installer.uninstallSkill) {
1164
+ throw new Error("NOT_AVAILABLE:skill uninstall is not configured");
1165
+ }
1166
+ const result = await installer.uninstallSkill(targetId);
1167
+ params.options.publish({ type: "config.updated", payload: { path: "skills" } });
1168
+ return {
1169
+ type: "skill",
1170
+ action,
1171
+ id: targetId,
1172
+ message: result.message,
1173
+ output: result.output
1174
+ };
1175
+ }
1176
+ function registerPluginMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1177
+ app.get("/api/marketplace/plugins/installed", (c) => {
1178
+ return c.json(ok(collectPluginMarketplaceInstalledView(options)));
1179
+ });
1180
+ app.get("/api/marketplace/plugins/items", async (c) => {
1181
+ const query = c.req.query();
1182
+ const result = await fetchAllPluginMarketplaceItems({
1183
+ baseUrl: marketplaceBaseUrl,
1184
+ query: {
1185
+ q: query.q,
1186
+ tag: query.tag,
1187
+ sort: query.sort,
1188
+ page: query.page,
1189
+ pageSize: query.pageSize
1150
1190
  }
1151
- const knownSkillNames = collectKnownSkillNames(options);
1152
- const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceItem(item, knownSkillNames));
1153
- const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
1154
- const requestedPage = toPositiveInt(query.page, 1);
1155
- const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
1156
- const currentPage = totalPages === 0 ? 1 : Math.min(requestedPage, totalPages);
1157
- return c.json(ok({
1158
- total: filteredItems.length,
1159
- page: currentPage,
1160
- pageSize,
1161
- totalPages,
1162
- sort: result.data.sort,
1163
- query: result.data.query,
1164
- items: filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize)
1165
- }));
1166
1191
  });
1167
- app.get(`/api/marketplace/${route.segment}/items/:slug`, async (c) => {
1168
- const slug = encodeURIComponent(c.req.param("slug"));
1169
- const result = await fetchMarketplaceData({
1170
- baseUrl: marketplaceBaseUrl,
1171
- path: `/api/v1/${route.segment}/items/${slug}`
1192
+ if (!result.ok) {
1193
+ return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1194
+ }
1195
+ const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplacePluginItem(item));
1196
+ const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
1197
+ const requestedPage = toPositiveInt(query.page, 1);
1198
+ const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
1199
+ const currentPage = totalPages === 0 ? 1 : Math.min(requestedPage, totalPages);
1200
+ return c.json(ok({
1201
+ total: filteredItems.length,
1202
+ page: currentPage,
1203
+ pageSize,
1204
+ totalPages,
1205
+ sort: result.data.sort,
1206
+ query: result.data.query,
1207
+ items: filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize)
1208
+ }));
1209
+ });
1210
+ app.get("/api/marketplace/plugins/items/:slug", async (c) => {
1211
+ const slug = encodeURIComponent(c.req.param("slug"));
1212
+ const result = await fetchMarketplaceData({
1213
+ baseUrl: marketplaceBaseUrl,
1214
+ path: `/api/v1/plugins/items/${slug}`
1215
+ });
1216
+ if (!result.ok) {
1217
+ return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1218
+ }
1219
+ const sanitized = sanitizeMarketplaceItem(result.data);
1220
+ if (!isSupportedMarketplacePluginItem(sanitized)) {
1221
+ return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
1222
+ }
1223
+ return c.json(ok(sanitized));
1224
+ });
1225
+ app.post("/api/marketplace/plugins/install", async (c) => {
1226
+ const body = await readJson(c.req.raw);
1227
+ if (!body.ok || !body.data || typeof body.data !== "object") {
1228
+ return c.json(err("INVALID_BODY", "invalid json body"), 400);
1229
+ }
1230
+ if (body.data.type && body.data.type !== "plugin") {
1231
+ return c.json(err("INVALID_BODY", "body.type does not match route type"), 400);
1232
+ }
1233
+ try {
1234
+ const payload = await installMarketplacePlugin({
1235
+ options,
1236
+ body: body.data
1172
1237
  });
1173
- if (!result.ok) {
1174
- return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1238
+ return c.json(ok(payload));
1239
+ } catch (error) {
1240
+ const message = String(error);
1241
+ if (message.startsWith("INVALID_BODY:")) {
1242
+ return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
1175
1243
  }
1176
- const knownSkillNames = collectKnownSkillNames(options);
1177
- const sanitized = sanitizeMarketplaceItem(result.data);
1178
- if (!isSupportedMarketplaceItem(sanitized, knownSkillNames)) {
1179
- return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
1244
+ if (message.startsWith("NOT_AVAILABLE:")) {
1245
+ return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
1180
1246
  }
1181
- return c.json(ok(sanitized));
1182
- });
1183
- app.post(`/api/marketplace/${route.segment}/install`, async (c) => {
1184
- const body = await readJson(c.req.raw);
1185
- if (!body.ok || !body.data || typeof body.data !== "object") {
1186
- return c.json(err("INVALID_BODY", "invalid json body"), 400);
1247
+ return c.json(err("INSTALL_FAILED", message), 400);
1248
+ }
1249
+ });
1250
+ app.post("/api/marketplace/plugins/manage", async (c) => {
1251
+ const body = await readJson(c.req.raw);
1252
+ if (!body.ok || !body.data || typeof body.data !== "object") {
1253
+ return c.json(err("INVALID_BODY", "invalid json body"), 400);
1254
+ }
1255
+ if (body.data.type && body.data.type !== "plugin") {
1256
+ return c.json(err("INVALID_BODY", "body.type does not match route type"), 400);
1257
+ }
1258
+ try {
1259
+ const payload = await manageMarketplacePlugin({
1260
+ options,
1261
+ body: body.data
1262
+ });
1263
+ return c.json(ok(payload));
1264
+ } catch (error) {
1265
+ const message = String(error);
1266
+ if (message.startsWith("INVALID_BODY:")) {
1267
+ return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
1187
1268
  }
1188
- if (body.data.type && body.data.type !== route.type) {
1189
- return c.json(err("INVALID_BODY", "body.type does not match route type"), 400);
1269
+ if (message.startsWith("NOT_AVAILABLE:")) {
1270
+ return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
1190
1271
  }
1191
- try {
1192
- const payload = await installMarketplaceItem({
1193
- options,
1194
- body: {
1195
- ...body.data,
1196
- type: route.type
1197
- }
1198
- });
1199
- return c.json(ok(payload));
1200
- } catch (error) {
1201
- const message = String(error);
1202
- if (message.startsWith("INVALID_BODY:")) {
1203
- return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
1204
- }
1205
- if (message.startsWith("NOT_AVAILABLE:")) {
1206
- return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
1207
- }
1208
- return c.json(err("INSTALL_FAILED", message), 400);
1272
+ return c.json(err("MANAGE_FAILED", message), 400);
1273
+ }
1274
+ });
1275
+ app.get("/api/marketplace/plugins/recommendations", async (c) => {
1276
+ const query = c.req.query();
1277
+ const result = await fetchMarketplaceData({
1278
+ baseUrl: marketplaceBaseUrl,
1279
+ path: "/api/v1/plugins/recommendations",
1280
+ query: {
1281
+ scene: query.scene,
1282
+ limit: query.limit
1209
1283
  }
1210
1284
  });
1211
- app.post(`/api/marketplace/${route.segment}/manage`, async (c) => {
1212
- const body = await readJson(c.req.raw);
1213
- if (!body.ok || !body.data || typeof body.data !== "object") {
1214
- return c.json(err("INVALID_BODY", "invalid json body"), 400);
1285
+ if (!result.ok) {
1286
+ return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1287
+ }
1288
+ const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplacePluginItem(item));
1289
+ return c.json(ok({
1290
+ ...result.data,
1291
+ total: filteredItems.length,
1292
+ items: filteredItems
1293
+ }));
1294
+ });
1295
+ }
1296
+ function registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1297
+ app.get("/api/marketplace/skills/installed", (c) => {
1298
+ return c.json(ok(collectSkillMarketplaceInstalledView(options)));
1299
+ });
1300
+ app.get("/api/marketplace/skills/items", async (c) => {
1301
+ const query = c.req.query();
1302
+ const result = await fetchAllSkillMarketplaceItems({
1303
+ baseUrl: marketplaceBaseUrl,
1304
+ query: {
1305
+ q: query.q,
1306
+ tag: query.tag,
1307
+ sort: query.sort,
1308
+ page: query.page,
1309
+ pageSize: query.pageSize
1215
1310
  }
1216
- if (body.data.type && body.data.type !== route.type) {
1217
- return c.json(err("INVALID_BODY", "body.type does not match route type"), 400);
1311
+ });
1312
+ if (!result.ok) {
1313
+ return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1314
+ }
1315
+ const knownSkillNames = collectKnownSkillNames(options);
1316
+ const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
1317
+ const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
1318
+ const requestedPage = toPositiveInt(query.page, 1);
1319
+ const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
1320
+ const currentPage = totalPages === 0 ? 1 : Math.min(requestedPage, totalPages);
1321
+ return c.json(ok({
1322
+ total: filteredItems.length,
1323
+ page: currentPage,
1324
+ pageSize,
1325
+ totalPages,
1326
+ sort: result.data.sort,
1327
+ query: result.data.query,
1328
+ items: filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize)
1329
+ }));
1330
+ });
1331
+ app.get("/api/marketplace/skills/items/:slug", async (c) => {
1332
+ const slug = encodeURIComponent(c.req.param("slug"));
1333
+ const result = await fetchMarketplaceData({
1334
+ baseUrl: marketplaceBaseUrl,
1335
+ path: `/api/v1/skills/items/${slug}`
1336
+ });
1337
+ if (!result.ok) {
1338
+ return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1339
+ }
1340
+ const knownSkillNames = collectKnownSkillNames(options);
1341
+ const sanitized = sanitizeMarketplaceItem(result.data);
1342
+ if (!isSupportedMarketplaceSkillItem(sanitized, knownSkillNames)) {
1343
+ return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
1344
+ }
1345
+ return c.json(ok(sanitized));
1346
+ });
1347
+ app.post("/api/marketplace/skills/install", async (c) => {
1348
+ const body = await readJson(c.req.raw);
1349
+ if (!body.ok || !body.data || typeof body.data !== "object") {
1350
+ return c.json(err("INVALID_BODY", "invalid json body"), 400);
1351
+ }
1352
+ if (body.data.type && body.data.type !== "skill") {
1353
+ return c.json(err("INVALID_BODY", "body.type does not match route type"), 400);
1354
+ }
1355
+ try {
1356
+ const payload = await installMarketplaceSkill({
1357
+ options,
1358
+ body: body.data
1359
+ });
1360
+ return c.json(ok(payload));
1361
+ } catch (error) {
1362
+ const message = String(error);
1363
+ if (message.startsWith("INVALID_BODY:")) {
1364
+ return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
1218
1365
  }
1219
- try {
1220
- const payload = await manageMarketplaceItem({
1221
- options,
1222
- body: {
1223
- ...body.data,
1224
- type: route.type
1225
- }
1226
- });
1227
- return c.json(ok(payload));
1228
- } catch (error) {
1229
- const message = String(error);
1230
- if (message.startsWith("INVALID_BODY:")) {
1231
- return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
1232
- }
1233
- if (message.startsWith("NOT_AVAILABLE:")) {
1234
- return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
1235
- }
1236
- return c.json(err("MANAGE_FAILED", message), 400);
1366
+ if (message.startsWith("NOT_AVAILABLE:")) {
1367
+ return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
1237
1368
  }
1238
- });
1239
- app.get(`/api/marketplace/${route.segment}/recommendations`, async (c) => {
1240
- const query = c.req.query();
1241
- const result = await fetchMarketplaceData({
1242
- baseUrl: marketplaceBaseUrl,
1243
- path: `/api/v1/${route.segment}/recommendations`,
1244
- query: {
1245
- scene: query.scene,
1246
- limit: query.limit
1247
- }
1369
+ return c.json(err("INSTALL_FAILED", message), 400);
1370
+ }
1371
+ });
1372
+ app.post("/api/marketplace/skills/manage", async (c) => {
1373
+ const body = await readJson(c.req.raw);
1374
+ if (!body.ok || !body.data || typeof body.data !== "object") {
1375
+ return c.json(err("INVALID_BODY", "invalid json body"), 400);
1376
+ }
1377
+ if (body.data.type && body.data.type !== "skill") {
1378
+ return c.json(err("INVALID_BODY", "body.type does not match route type"), 400);
1379
+ }
1380
+ try {
1381
+ const payload = await manageMarketplaceSkill({
1382
+ options,
1383
+ body: body.data
1248
1384
  });
1249
- if (!result.ok) {
1250
- return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1385
+ return c.json(ok(payload));
1386
+ } catch (error) {
1387
+ const message = String(error);
1388
+ if (message.startsWith("INVALID_BODY:")) {
1389
+ return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
1390
+ }
1391
+ if (message.startsWith("NOT_AVAILABLE:")) {
1392
+ return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
1393
+ }
1394
+ return c.json(err("MANAGE_FAILED", message), 400);
1395
+ }
1396
+ });
1397
+ app.get("/api/marketplace/skills/recommendations", async (c) => {
1398
+ const query = c.req.query();
1399
+ const result = await fetchMarketplaceData({
1400
+ baseUrl: marketplaceBaseUrl,
1401
+ path: "/api/v1/skills/recommendations",
1402
+ query: {
1403
+ scene: query.scene,
1404
+ limit: query.limit
1251
1405
  }
1252
- const knownSkillNames = collectKnownSkillNames(options);
1253
- const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceItem(item, knownSkillNames));
1254
- return c.json(ok({
1255
- ...result.data,
1256
- total: filteredItems.length,
1257
- items: filteredItems
1258
- }));
1259
1406
  });
1260
- }
1407
+ if (!result.ok) {
1408
+ return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1409
+ }
1410
+ const knownSkillNames = collectKnownSkillNames(options);
1411
+ const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceSkillItem(item, knownSkillNames));
1412
+ return c.json(ok({
1413
+ ...result.data,
1414
+ total: filteredItems.length,
1415
+ items: filteredItems
1416
+ }));
1417
+ });
1418
+ }
1419
+ function registerMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1420
+ registerPluginMarketplaceRoutes(app, options, marketplaceBaseUrl);
1421
+ registerSkillMarketplaceRoutes(app, options, marketplaceBaseUrl);
1261
1422
  }
1262
1423
  function createUiRouter(options) {
1263
1424
  const app = new Hono();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
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.26",
18
+ "@nextclaw/openclaw-compat": "^0.1.28",
19
19
  "hono": "^4.6.2",
20
20
  "ws": "^8.18.0",
21
- "@nextclaw/core": "^0.6.32"
21
+ "@nextclaw/core": "^0.6.34"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^20.17.6",