@nextclaw/server 0.5.10 → 0.5.12

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
@@ -299,6 +299,7 @@ type MarketplaceListView = {
299
299
  items: MarketplaceItemSummary[];
300
300
  };
301
301
  type MarketplaceRecommendationView = {
302
+ type: MarketplaceItemType;
302
303
  sceneId: string;
303
304
  title: string;
304
305
  description?: string;
@@ -318,9 +319,9 @@ type MarketplaceInstalledRecord = {
318
319
  installPath?: string;
319
320
  };
320
321
  type MarketplaceInstalledView = {
322
+ type: MarketplaceItemType;
321
323
  total: number;
322
- pluginSpecs: string[];
323
- skillSpecs: string[];
324
+ specs: string[];
324
325
  records: MarketplaceInstalledRecord[];
325
326
  };
326
327
  type MarketplaceInstallRequest = {
package/dist/index.js CHANGED
@@ -801,7 +801,7 @@ async function fetchMarketplaceData(params) {
801
801
  };
802
802
  }
803
803
  }
804
- function collectMarketplaceInstalledView(options) {
804
+ function collectInstalledPluginRecords(options) {
805
805
  const config = loadConfigOrDefault(options.configPath);
806
806
  const pluginRecordsMap = config.plugins.installs ?? {};
807
807
  const pluginEntries = config.plugins.entries ?? {};
@@ -900,15 +900,22 @@ function collectMarketplaceInstalledView(options) {
900
900
  }
901
901
  }
902
902
  const dedupedPluginRecords = dedupeInstalledPluginRecordsByCanonicalSpec(pluginRecords);
903
- const pluginSpecSet = new Set(dedupedPluginRecords.map((record) => record.spec));
903
+ dedupedPluginRecords.sort((left, right) => {
904
+ return left.spec.localeCompare(right.spec);
905
+ });
906
+ return {
907
+ specs: dedupedPluginRecords.map((record) => record.spec),
908
+ records: dedupedPluginRecords
909
+ };
910
+ }
911
+ function collectInstalledSkillRecords(options) {
912
+ const config = loadConfigOrDefault(options.configPath);
904
913
  const workspacePath = getWorkspacePathFromConfig3(config);
905
914
  const skillsLoader = createSkillsLoader(workspacePath);
906
915
  const availableSkillSet = new Set((skillsLoader?.listSkills(true) ?? []).map((skill) => skill.name));
907
916
  const listedSkills = skillsLoader?.listSkills(false) ?? [];
908
- const skillSpecSet = /* @__PURE__ */ new Set();
909
- const skillRecords = listedSkills.map((skill) => {
917
+ const records = listedSkills.map((skill) => {
910
918
  const enabled = availableSkillSet.has(skill.name);
911
- skillSpecSet.add(skill.name);
912
919
  return {
913
920
  type: "skill",
914
921
  id: skill.name,
@@ -918,20 +925,21 @@ function collectMarketplaceInstalledView(options) {
918
925
  enabled,
919
926
  runtimeStatus: enabled ? "enabled" : "disabled"
920
927
  };
921
- });
922
- const records = [...dedupedPluginRecords, ...skillRecords].sort((left, right) => {
923
- if (left.type !== right.type) {
924
- return left.type.localeCompare(right.type);
925
- }
926
- return left.spec.localeCompare(right.spec);
927
- });
928
+ }).sort((left, right) => left.spec.localeCompare(right.spec));
928
929
  return {
929
- total: records.length,
930
- pluginSpecs: Array.from(pluginSpecSet).sort((left, right) => left.localeCompare(right)),
931
- skillSpecs: Array.from(skillSpecSet).sort((left, right) => left.localeCompare(right)),
930
+ specs: records.map((record) => record.spec),
932
931
  records
933
932
  };
934
933
  }
934
+ function collectMarketplaceInstalledView(options, type) {
935
+ const installed = type === "plugin" ? collectInstalledPluginRecords(options) : collectInstalledSkillRecords(options);
936
+ return {
937
+ type,
938
+ total: installed.records.length,
939
+ specs: installed.specs,
940
+ records: installed.records
941
+ };
942
+ }
935
943
  function resolvePluginManageTargetId(options, rawTargetId, rawSpec) {
936
944
  const targetId = rawTargetId.trim();
937
945
  if (!targetId && !rawSpec) {
@@ -939,8 +947,7 @@ function resolvePluginManageTargetId(options, rawTargetId, rawSpec) {
939
947
  }
940
948
  const normalizedTarget = targetId ? normalizePluginNpmSpec(targetId).toLowerCase() : "";
941
949
  const normalizedSpec = rawSpec ? normalizePluginNpmSpec(rawSpec).toLowerCase() : "";
942
- const installed = collectMarketplaceInstalledView(options);
943
- const pluginRecords = installed.records.filter((record) => record.type === "plugin");
950
+ const pluginRecords = collectInstalledPluginRecords(options).records;
944
951
  const lowerTargetId = targetId.toLowerCase();
945
952
  for (const record of pluginRecords) {
946
953
  const recordId = record.id?.trim();
@@ -1001,7 +1008,7 @@ async function fetchAllMarketplaceItems(params) {
1001
1008
  while (remotePage <= remoteTotalPages && remotePage <= MARKETPLACE_REMOTE_MAX_PAGES) {
1002
1009
  const result = await fetchMarketplaceData({
1003
1010
  baseUrl: params.baseUrl,
1004
- path: "/api/v1/items",
1011
+ path: `/api/v1/${params.segment}/items`,
1005
1012
  query: {
1006
1013
  ...params.query,
1007
1014
  page: String(remotePage),
@@ -1112,120 +1119,140 @@ async function manageMarketplaceItem(params) {
1112
1119
  };
1113
1120
  }
1114
1121
  function registerMarketplaceRoutes(app, options, marketplaceBaseUrl) {
1115
- app.get("/api/marketplace/installed", (c) => {
1116
- return c.json(ok(collectMarketplaceInstalledView(options)));
1117
- });
1118
- app.get("/api/marketplace/items", async (c) => {
1119
- const query = c.req.query();
1120
- const result = await fetchAllMarketplaceItems({
1121
- baseUrl: marketplaceBaseUrl,
1122
- query: {
1123
- q: query.q,
1124
- type: query.type,
1125
- tag: query.tag,
1126
- sort: query.sort,
1127
- page: query.page,
1128
- pageSize: query.pageSize
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);
1129
1145
  }
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
+ }));
1130
1161
  });
1131
- if (!result.ok) {
1132
- return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1133
- }
1134
- const knownSkillNames = collectKnownSkillNames(options);
1135
- const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceItem(item, knownSkillNames));
1136
- const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
1137
- const requestedPage = toPositiveInt(query.page, 1);
1138
- const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
1139
- const currentPage = totalPages === 0 ? 1 : Math.min(requestedPage, totalPages);
1140
- return c.json(ok({
1141
- total: filteredItems.length,
1142
- page: currentPage,
1143
- pageSize,
1144
- totalPages,
1145
- sort: result.data.sort,
1146
- query: result.data.query,
1147
- items: filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize)
1148
- }));
1149
- });
1150
- app.get("/api/marketplace/items/:slug", async (c) => {
1151
- const slug = encodeURIComponent(c.req.param("slug"));
1152
- const type = c.req.query("type");
1153
- const result = await fetchMarketplaceData({
1154
- baseUrl: marketplaceBaseUrl,
1155
- path: `/api/v1/items/${slug}`,
1156
- query: {
1157
- type
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}`
1167
+ });
1168
+ if (!result.ok) {
1169
+ return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1170
+ }
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);
1158
1175
  }
1176
+ return c.json(ok(sanitized));
1159
1177
  });
1160
- if (!result.ok) {
1161
- return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1162
- }
1163
- const knownSkillNames = collectKnownSkillNames(options);
1164
- const sanitized = sanitizeMarketplaceItem(result.data);
1165
- if (!isSupportedMarketplaceItem(sanitized, knownSkillNames)) {
1166
- return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
1167
- }
1168
- return c.json(ok(sanitized));
1169
- });
1170
- app.get("/api/marketplace/recommendations", async (c) => {
1171
- const query = c.req.query();
1172
- const result = await fetchMarketplaceData({
1173
- baseUrl: marketplaceBaseUrl,
1174
- path: "/api/v1/recommendations",
1175
- query: {
1176
- scene: query.scene,
1177
- limit: query.limit
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);
1182
+ }
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);
1185
+ }
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);
1178
1204
  }
1179
1205
  });
1180
- if (!result.ok) {
1181
- return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1182
- }
1183
- const knownSkillNames = collectKnownSkillNames(options);
1184
- const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceItem(item, knownSkillNames));
1185
- return c.json(ok({
1186
- ...result.data,
1187
- total: filteredItems.length,
1188
- items: filteredItems
1189
- }));
1190
- });
1191
- app.post("/api/marketplace/install", async (c) => {
1192
- const body = await readJson(c.req.raw);
1193
- if (!body.ok || !body.data || typeof body.data !== "object") {
1194
- return c.json(err("INVALID_BODY", "invalid json body"), 400);
1195
- }
1196
- try {
1197
- const payload = await installMarketplaceItem({ options, body: body.data });
1198
- return c.json(ok(payload));
1199
- } catch (error) {
1200
- const message = String(error);
1201
- if (message.startsWith("INVALID_BODY:")) {
1202
- return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
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);
1203
1210
  }
1204
- if (message.startsWith("NOT_AVAILABLE:")) {
1205
- return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
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);
1206
1213
  }
1207
- return c.json(err("INSTALL_FAILED", message), 400);
1208
- }
1209
- });
1210
- app.post("/api/marketplace/manage", async (c) => {
1211
- const body = await readJson(c.req.raw);
1212
- if (!body.ok || !body.data || typeof body.data !== "object") {
1213
- return c.json(err("INVALID_BODY", "invalid json body"), 400);
1214
- }
1215
- try {
1216
- const payload = await manageMarketplaceItem({ options, body: body.data });
1217
- return c.json(ok(payload));
1218
- } catch (error) {
1219
- const message = String(error);
1220
- if (message.startsWith("INVALID_BODY:")) {
1221
- return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
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);
1222
1232
  }
1223
- if (message.startsWith("NOT_AVAILABLE:")) {
1224
- return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
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
+ }
1243
+ });
1244
+ if (!result.ok) {
1245
+ return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
1225
1246
  }
1226
- return c.json(err("MANAGE_FAILED", message), 400);
1227
- }
1228
- });
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
+ });
1255
+ }
1229
1256
  }
1230
1257
  function createUiRouter(options) {
1231
1258
  const app = new Hono();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.5.10",
3
+ "version": "0.5.12",
4
4
  "private": false,
5
5
  "description": "Nextclaw UI/API server.",
6
6
  "type": "module",