@nextclaw/server 0.5.12 → 0.5.14

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 +45 -17
  2. package/dist/index.js +344 -178
  3. package/package.json +3 -3
package/dist/index.d.ts CHANGED
@@ -324,37 +324,65 @@ 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;
330
+ skill?: string;
331
+ installPath?: string;
331
332
  version?: string;
332
333
  registry?: string;
333
334
  force?: boolean;
334
335
  };
335
- type MarketplaceInstallResult = {
336
- type: MarketplaceItemType;
336
+ type MarketplacePluginInstallRequest = {
337
+ type?: "plugin";
337
338
  spec: string;
338
- message: string;
339
- output?: string;
340
339
  };
341
- type MarketplaceInstallSkillParams = {
342
- slug: string;
340
+ type MarketplaceSkillInstallRequest = {
341
+ type?: "skill";
342
+ spec: string;
343
343
  kind?: MarketplaceInstallKind;
344
+ skill?: string;
345
+ installPath?: string;
344
346
  version?: string;
345
347
  registry?: string;
346
348
  force?: boolean;
347
349
  };
348
- type MarketplaceManageAction = "enable" | "disable" | "uninstall";
349
- type MarketplaceManageRequest = {
350
- type: MarketplaceItemType;
351
- 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;
352
367
  id?: string;
353
368
  spec?: string;
354
369
  };
355
- type MarketplaceManageResult = {
356
- type: MarketplaceItemType;
357
- 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;
358
386
  id: string;
359
387
  message: string;
360
388
  output?: string;
@@ -463,4 +491,4 @@ declare function patchSession(configPath: string, key: string, patch: SessionPat
463
491
  declare function deleteSession(configPath: string, key: string): boolean;
464
492
  declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
465
493
 
466
- 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,15 @@ 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;
1011
+ }
1012
+ if (item.install.kind === "git") {
1013
+ return true;
999
1014
  }
1000
1015
  return item.install.kind === "builtin" && knownSkillNames.has(item.install.spec);
1001
1016
  }
@@ -1033,226 +1048,377 @@ async function fetchAllMarketplaceItems(params) {
1033
1048
  }
1034
1049
  };
1035
1050
  }
1036
- async function installMarketplaceItem(params) {
1037
- 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) {
1038
1066
  const spec = typeof params.body.spec === "string" ? params.body.spec.trim() : "";
1039
- if (type !== "plugin" && type !== "skill" || !spec) {
1040
- 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");
1041
1069
  }
1042
1070
  const installer = params.options.marketplace?.installer;
1043
1071
  if (!installer) {
1044
1072
  throw new Error("NOT_AVAILABLE:marketplace installer is not configured");
1045
1073
  }
1046
- let result;
1047
- if (type === "plugin") {
1048
- if (!installer.installPlugin) {
1049
- throw new Error("NOT_AVAILABLE:plugin installer is not configured");
1050
- }
1051
- result = await installer.installPlugin(spec);
1052
- } else {
1053
- if (!installer.installSkill) {
1054
- throw new Error("NOT_AVAILABLE:skill installer is not configured");
1055
- }
1056
- result = await installer.installSkill({
1057
- slug: spec,
1058
- kind: params.body.kind,
1059
- version: params.body.version,
1060
- registry: params.body.registry,
1061
- force: params.body.force
1062
- });
1074
+ if (!installer.installPlugin) {
1075
+ throw new Error("NOT_AVAILABLE:plugin installer is not configured");
1063
1076
  }
1064
- 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" } });
1065
1079
  return {
1066
- type,
1080
+ type: "plugin",
1067
1081
  spec,
1068
1082
  message: result.message,
1069
1083
  output: result.output
1070
1084
  };
1071
1085
  }
1072
- async function manageMarketplaceItem(params) {
1073
- 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) {
1074
1116
  const action = params.body.action;
1075
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() : "";
1076
1118
  const rawSpec = typeof params.body.spec === "string" ? params.body.spec.trim() : "";
1077
- const targetId = type === "plugin" ? resolvePluginManageTargetId(params.options, requestedTargetId, rawSpec) : requestedTargetId;
1078
- if (type !== "plugin" && type !== "skill" || action !== "enable" && action !== "disable" && action !== "uninstall" || !targetId) {
1079
- 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");
1080
1122
  }
1081
1123
  const installer = params.options.marketplace?.installer;
1082
1124
  if (!installer) {
1083
1125
  throw new Error("NOT_AVAILABLE:marketplace installer is not configured");
1084
1126
  }
1085
1127
  let result;
1086
- if (type === "plugin") {
1087
- if (action === "enable") {
1088
- if (!installer.enablePlugin) {
1089
- throw new Error("NOT_AVAILABLE:plugin enable is not configured");
1090
- }
1091
- result = await installer.enablePlugin(targetId);
1092
- } else if (action === "disable") {
1093
- if (!installer.disablePlugin) {
1094
- throw new Error("NOT_AVAILABLE:plugin disable is not configured");
1095
- }
1096
- result = await installer.disablePlugin(targetId);
1097
- } else {
1098
- if (!installer.uninstallPlugin) {
1099
- throw new Error("NOT_AVAILABLE:plugin uninstall is not configured");
1100
- }
1101
- result = await installer.uninstallPlugin(targetId);
1128
+ if (action === "enable") {
1129
+ if (!installer.enablePlugin) {
1130
+ throw new Error("NOT_AVAILABLE:plugin enable is not configured");
1102
1131
  }
1103
- } else {
1104
- if (action !== "uninstall") {
1105
- 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");
1106
1136
  }
1107
- if (!installer.uninstallSkill) {
1108
- 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");
1109
1141
  }
1110
- result = await installer.uninstallSkill(targetId);
1142
+ result = await installer.uninstallPlugin(targetId);
1111
1143
  }
1112
- params.options.publish({ type: "config.updated", payload: { path: type === "plugin" ? "plugins" : "skills" } });
1144
+ params.options.publish({ type: "config.updated", payload: { path: "plugins" } });
1113
1145
  return {
1114
- type,
1146
+ type: "plugin",
1115
1147
  action,
1116
1148
  id: targetId,
1117
1149
  message: result.message,
1118
1150
  output: result.output
1119
1151
  };
1120
1152
  }
1121
- function registerMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1122
- const typedMarketplaceRoutes = [
1123
- { segment: "plugins", type: "plugin" },
1124
- { segment: "skills", type: "skill" }
1125
- ];
1126
- for (const route of typedMarketplaceRoutes) {
1127
- app.get(`/api/marketplace/${route.segment}/installed`, (c) => {
1128
- return c.json(ok(collectMarketplaceInstalledView(options, route.type)));
1129
- });
1130
- app.get(`/api/marketplace/${route.segment}/items`, async (c) => {
1131
- const query = c.req.query();
1132
- const result = await fetchAllMarketplaceItems({
1133
- baseUrl: marketplaceBaseUrl,
1134
- segment: route.segment,
1135
- query: {
1136
- q: query.q,
1137
- tag: query.tag,
1138
- sort: query.sort,
1139
- page: query.page,
1140
- pageSize: query.pageSize
1141
- }
1142
- });
1143
- if (!result.ok) {
1144
- 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
1145
1190
  }
1146
- const knownSkillNames = collectKnownSkillNames(options);
1147
- const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceItem(item, knownSkillNames));
1148
- const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
1149
- const requestedPage = toPositiveInt(query.page, 1);
1150
- const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
1151
- const currentPage = totalPages === 0 ? 1 : Math.min(requestedPage, totalPages);
1152
- return c.json(ok({
1153
- total: filteredItems.length,
1154
- page: currentPage,
1155
- pageSize,
1156
- totalPages,
1157
- sort: result.data.sort,
1158
- query: result.data.query,
1159
- items: filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize)
1160
- }));
1161
1191
  });
1162
- app.get(`/api/marketplace/${route.segment}/items/:slug`, async (c) => {
1163
- const slug = encodeURIComponent(c.req.param("slug"));
1164
- const result = await fetchMarketplaceData({
1165
- baseUrl: marketplaceBaseUrl,
1166
- 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
1167
1237
  });
1168
- if (!result.ok) {
1169
- 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);
1170
1243
  }
1171
- const knownSkillNames = collectKnownSkillNames(options);
1172
- const sanitized = sanitizeMarketplaceItem(result.data);
1173
- if (!isSupportedMarketplaceItem(sanitized, knownSkillNames)) {
1174
- 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);
1175
1246
  }
1176
- return c.json(ok(sanitized));
1177
- });
1178
- app.post(`/api/marketplace/${route.segment}/install`, async (c) => {
1179
- const body = await readJson(c.req.raw);
1180
- if (!body.ok || !body.data || typeof body.data !== "object") {
1181
- 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);
1182
1268
  }
1183
- if (body.data.type && body.data.type !== route.type) {
1184
- 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);
1185
1271
  }
1186
- try {
1187
- const payload = await installMarketplaceItem({
1188
- options,
1189
- body: {
1190
- ...body.data,
1191
- type: route.type
1192
- }
1193
- });
1194
- return c.json(ok(payload));
1195
- } catch (error) {
1196
- const message = String(error);
1197
- if (message.startsWith("INVALID_BODY:")) {
1198
- return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
1199
- }
1200
- if (message.startsWith("NOT_AVAILABLE:")) {
1201
- return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
1202
- }
1203
- 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
1204
1283
  }
1205
1284
  });
1206
- app.post(`/api/marketplace/${route.segment}/manage`, async (c) => {
1207
- const body = await readJson(c.req.raw);
1208
- if (!body.ok || !body.data || typeof body.data !== "object") {
1209
- 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
1210
1310
  }
1211
- if (body.data.type && body.data.type !== route.type) {
1212
- 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);
1213
1365
  }
1214
- try {
1215
- const payload = await manageMarketplaceItem({
1216
- options,
1217
- body: {
1218
- ...body.data,
1219
- type: route.type
1220
- }
1221
- });
1222
- return c.json(ok(payload));
1223
- } catch (error) {
1224
- const message = String(error);
1225
- if (message.startsWith("INVALID_BODY:")) {
1226
- return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
1227
- }
1228
- if (message.startsWith("NOT_AVAILABLE:")) {
1229
- return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
1230
- }
1231
- 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);
1232
1368
  }
1233
- });
1234
- app.get(`/api/marketplace/${route.segment}/recommendations`, async (c) => {
1235
- const query = c.req.query();
1236
- const result = await fetchMarketplaceData({
1237
- baseUrl: marketplaceBaseUrl,
1238
- path: `/api/v1/${route.segment}/recommendations`,
1239
- query: {
1240
- scene: query.scene,
1241
- limit: query.limit
1242
- }
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
1243
1384
  });
1244
- if (!result.ok) {
1245
- 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
1246
1405
  }
1247
- const knownSkillNames = collectKnownSkillNames(options);
1248
- const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceItem(item, knownSkillNames));
1249
- return c.json(ok({
1250
- ...result.data,
1251
- total: filteredItems.length,
1252
- items: filteredItems
1253
- }));
1254
1406
  });
1255
- }
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);
1256
1422
  }
1257
1423
  function createUiRouter(options) {
1258
1424
  const app = new Hono();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.5.12",
3
+ "version": "0.5.14",
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.27",
19
19
  "hono": "^4.6.2",
20
20
  "ws": "^8.18.0",
21
- "@nextclaw/core": "^0.6.32"
21
+ "@nextclaw/core": "^0.6.33"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^20.17.6",