@nextclaw/server 0.5.9 → 0.5.11
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 +2 -2
- package/dist/index.js +139 -106
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -318,9 +318,9 @@ type MarketplaceInstalledRecord = {
|
|
|
318
318
|
installPath?: string;
|
|
319
319
|
};
|
|
320
320
|
type MarketplaceInstalledView = {
|
|
321
|
+
type: MarketplaceItemType;
|
|
321
322
|
total: number;
|
|
322
|
-
|
|
323
|
-
skillSpecs: string[];
|
|
323
|
+
specs: string[];
|
|
324
324
|
records: MarketplaceInstalledRecord[];
|
|
325
325
|
};
|
|
326
326
|
type MarketplaceInstallRequest = {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/ui/server.ts
|
|
2
2
|
import { Hono as Hono2 } from "hono";
|
|
3
|
+
import { compress } from "hono/compress";
|
|
3
4
|
import { cors } from "hono/cors";
|
|
4
5
|
import { serve } from "@hono/node-server";
|
|
5
6
|
import { WebSocketServer, WebSocket } from "ws";
|
|
@@ -800,7 +801,7 @@ async function fetchMarketplaceData(params) {
|
|
|
800
801
|
};
|
|
801
802
|
}
|
|
802
803
|
}
|
|
803
|
-
function
|
|
804
|
+
function collectInstalledPluginRecords(options) {
|
|
804
805
|
const config = loadConfigOrDefault(options.configPath);
|
|
805
806
|
const pluginRecordsMap = config.plugins.installs ?? {};
|
|
806
807
|
const pluginEntries = config.plugins.entries ?? {};
|
|
@@ -899,15 +900,22 @@ function collectMarketplaceInstalledView(options) {
|
|
|
899
900
|
}
|
|
900
901
|
}
|
|
901
902
|
const dedupedPluginRecords = dedupeInstalledPluginRecordsByCanonicalSpec(pluginRecords);
|
|
902
|
-
|
|
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);
|
|
903
913
|
const workspacePath = getWorkspacePathFromConfig3(config);
|
|
904
914
|
const skillsLoader = createSkillsLoader(workspacePath);
|
|
905
915
|
const availableSkillSet = new Set((skillsLoader?.listSkills(true) ?? []).map((skill) => skill.name));
|
|
906
916
|
const listedSkills = skillsLoader?.listSkills(false) ?? [];
|
|
907
|
-
const
|
|
908
|
-
const skillRecords = listedSkills.map((skill) => {
|
|
917
|
+
const records = listedSkills.map((skill) => {
|
|
909
918
|
const enabled = availableSkillSet.has(skill.name);
|
|
910
|
-
skillSpecSet.add(skill.name);
|
|
911
919
|
return {
|
|
912
920
|
type: "skill",
|
|
913
921
|
id: skill.name,
|
|
@@ -917,20 +925,21 @@ function collectMarketplaceInstalledView(options) {
|
|
|
917
925
|
enabled,
|
|
918
926
|
runtimeStatus: enabled ? "enabled" : "disabled"
|
|
919
927
|
};
|
|
920
|
-
});
|
|
921
|
-
const records = [...dedupedPluginRecords, ...skillRecords].sort((left, right) => {
|
|
922
|
-
if (left.type !== right.type) {
|
|
923
|
-
return left.type.localeCompare(right.type);
|
|
924
|
-
}
|
|
925
|
-
return left.spec.localeCompare(right.spec);
|
|
926
|
-
});
|
|
928
|
+
}).sort((left, right) => left.spec.localeCompare(right.spec));
|
|
927
929
|
return {
|
|
928
|
-
|
|
929
|
-
pluginSpecs: Array.from(pluginSpecSet).sort((left, right) => left.localeCompare(right)),
|
|
930
|
-
skillSpecs: Array.from(skillSpecSet).sort((left, right) => left.localeCompare(right)),
|
|
930
|
+
specs: records.map((record) => record.spec),
|
|
931
931
|
records
|
|
932
932
|
};
|
|
933
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
|
+
}
|
|
934
943
|
function resolvePluginManageTargetId(options, rawTargetId, rawSpec) {
|
|
935
944
|
const targetId = rawTargetId.trim();
|
|
936
945
|
if (!targetId && !rawSpec) {
|
|
@@ -938,8 +947,7 @@ function resolvePluginManageTargetId(options, rawTargetId, rawSpec) {
|
|
|
938
947
|
}
|
|
939
948
|
const normalizedTarget = targetId ? normalizePluginNpmSpec(targetId).toLowerCase() : "";
|
|
940
949
|
const normalizedSpec = rawSpec ? normalizePluginNpmSpec(rawSpec).toLowerCase() : "";
|
|
941
|
-
const
|
|
942
|
-
const pluginRecords = installed.records.filter((record) => record.type === "plugin");
|
|
950
|
+
const pluginRecords = collectInstalledPluginRecords(options).records;
|
|
943
951
|
const lowerTargetId = targetId.toLowerCase();
|
|
944
952
|
for (const record of pluginRecords) {
|
|
945
953
|
const recordId = record.id?.trim();
|
|
@@ -1003,6 +1011,7 @@ async function fetchAllMarketplaceItems(params) {
|
|
|
1003
1011
|
path: "/api/v1/items",
|
|
1004
1012
|
query: {
|
|
1005
1013
|
...params.query,
|
|
1014
|
+
type: params.type,
|
|
1006
1015
|
page: String(remotePage),
|
|
1007
1016
|
pageSize: String(MARKETPLACE_REMOTE_PAGE_SIZE)
|
|
1008
1017
|
}
|
|
@@ -1111,61 +1120,122 @@ async function manageMarketplaceItem(params) {
|
|
|
1111
1120
|
};
|
|
1112
1121
|
}
|
|
1113
1122
|
function registerMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1123
|
+
const typedMarketplaceRoutes = [
|
|
1124
|
+
{ segment: "plugins", type: "plugin" },
|
|
1125
|
+
{ segment: "skills", type: "skill" }
|
|
1126
|
+
];
|
|
1127
|
+
for (const route of typedMarketplaceRoutes) {
|
|
1128
|
+
app.get(`/api/marketplace/${route.segment}/installed`, (c) => {
|
|
1129
|
+
return c.json(ok(collectMarketplaceInstalledView(options, route.type)));
|
|
1130
|
+
});
|
|
1131
|
+
app.get(`/api/marketplace/${route.segment}/items`, async (c) => {
|
|
1132
|
+
const query = c.req.query();
|
|
1133
|
+
const result = await fetchAllMarketplaceItems({
|
|
1134
|
+
baseUrl: marketplaceBaseUrl,
|
|
1135
|
+
type: route.type,
|
|
1136
|
+
query: {
|
|
1137
|
+
q: query.q,
|
|
1138
|
+
tag: query.tag,
|
|
1139
|
+
sort: query.sort,
|
|
1140
|
+
page: query.page,
|
|
1141
|
+
pageSize: query.pageSize
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
if (!result.ok) {
|
|
1145
|
+
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
1128
1146
|
}
|
|
1147
|
+
const knownSkillNames = collectKnownSkillNames(options);
|
|
1148
|
+
const filteredItems = result.data.items.map((item) => sanitizeMarketplaceItem(item)).filter((item) => isSupportedMarketplaceItem(item, knownSkillNames));
|
|
1149
|
+
const pageSize = Math.min(100, toPositiveInt(query.pageSize, 20));
|
|
1150
|
+
const requestedPage = toPositiveInt(query.page, 1);
|
|
1151
|
+
const totalPages = filteredItems.length === 0 ? 0 : Math.ceil(filteredItems.length / pageSize);
|
|
1152
|
+
const currentPage = totalPages === 0 ? 1 : Math.min(requestedPage, totalPages);
|
|
1153
|
+
return c.json(ok({
|
|
1154
|
+
total: filteredItems.length,
|
|
1155
|
+
page: currentPage,
|
|
1156
|
+
pageSize,
|
|
1157
|
+
totalPages,
|
|
1158
|
+
sort: result.data.sort,
|
|
1159
|
+
query: result.data.query,
|
|
1160
|
+
items: filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
|
1161
|
+
}));
|
|
1129
1162
|
});
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
items: filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
|
1147
|
-
}));
|
|
1148
|
-
});
|
|
1149
|
-
app.get("/api/marketplace/items/:slug", async (c) => {
|
|
1150
|
-
const slug = encodeURIComponent(c.req.param("slug"));
|
|
1151
|
-
const type = c.req.query("type");
|
|
1152
|
-
const result = await fetchMarketplaceData({
|
|
1153
|
-
baseUrl: marketplaceBaseUrl,
|
|
1154
|
-
path: `/api/v1/items/${slug}`,
|
|
1155
|
-
query: {
|
|
1156
|
-
type
|
|
1163
|
+
app.get(`/api/marketplace/${route.segment}/items/:slug`, async (c) => {
|
|
1164
|
+
const slug = encodeURIComponent(c.req.param("slug"));
|
|
1165
|
+
const result = await fetchMarketplaceData({
|
|
1166
|
+
baseUrl: marketplaceBaseUrl,
|
|
1167
|
+
path: `/api/v1/items/${slug}`,
|
|
1168
|
+
query: {
|
|
1169
|
+
type: route.type
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
if (!result.ok) {
|
|
1173
|
+
return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
1174
|
+
}
|
|
1175
|
+
const knownSkillNames = collectKnownSkillNames(options);
|
|
1176
|
+
const sanitized = sanitizeMarketplaceItem(result.data);
|
|
1177
|
+
if (!isSupportedMarketplaceItem(sanitized, knownSkillNames)) {
|
|
1178
|
+
return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
|
|
1157
1179
|
}
|
|
1180
|
+
return c.json(ok(sanitized));
|
|
1158
1181
|
});
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1182
|
+
app.post(`/api/marketplace/${route.segment}/install`, async (c) => {
|
|
1183
|
+
const body = await readJson(c.req.raw);
|
|
1184
|
+
if (!body.ok || !body.data || typeof body.data !== "object") {
|
|
1185
|
+
return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
1186
|
+
}
|
|
1187
|
+
if (body.data.type && body.data.type !== route.type) {
|
|
1188
|
+
return c.json(err("INVALID_BODY", "body.type does not match route type"), 400);
|
|
1189
|
+
}
|
|
1190
|
+
try {
|
|
1191
|
+
const payload = await installMarketplaceItem({
|
|
1192
|
+
options,
|
|
1193
|
+
body: {
|
|
1194
|
+
...body.data,
|
|
1195
|
+
type: route.type
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
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);
|
|
1203
|
+
}
|
|
1204
|
+
if (message.startsWith("NOT_AVAILABLE:")) {
|
|
1205
|
+
return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
|
|
1206
|
+
}
|
|
1207
|
+
return c.json(err("INSTALL_FAILED", message), 400);
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
app.post(`/api/marketplace/${route.segment}/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
|
+
if (body.data.type && body.data.type !== route.type) {
|
|
1216
|
+
return c.json(err("INVALID_BODY", "body.type does not match route type"), 400);
|
|
1217
|
+
}
|
|
1218
|
+
try {
|
|
1219
|
+
const payload = await manageMarketplaceItem({
|
|
1220
|
+
options,
|
|
1221
|
+
body: {
|
|
1222
|
+
...body.data,
|
|
1223
|
+
type: route.type
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
return c.json(ok(payload));
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
const message = String(error);
|
|
1229
|
+
if (message.startsWith("INVALID_BODY:")) {
|
|
1230
|
+
return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
|
|
1231
|
+
}
|
|
1232
|
+
if (message.startsWith("NOT_AVAILABLE:")) {
|
|
1233
|
+
return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
|
|
1234
|
+
}
|
|
1235
|
+
return c.json(err("MANAGE_FAILED", message), 400);
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1169
1239
|
app.get("/api/marketplace/recommendations", async (c) => {
|
|
1170
1240
|
const query = c.req.query();
|
|
1171
1241
|
const result = await fetchMarketplaceData({
|
|
@@ -1187,44 +1257,6 @@ function registerMarketplaceRoutes(app, options, marketplaceBaseUrl) {
|
|
|
1187
1257
|
items: filteredItems
|
|
1188
1258
|
}));
|
|
1189
1259
|
});
|
|
1190
|
-
app.post("/api/marketplace/install", async (c) => {
|
|
1191
|
-
const body = await readJson(c.req.raw);
|
|
1192
|
-
if (!body.ok || !body.data || typeof body.data !== "object") {
|
|
1193
|
-
return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
1194
|
-
}
|
|
1195
|
-
try {
|
|
1196
|
-
const payload = await installMarketplaceItem({ options, body: body.data });
|
|
1197
|
-
return c.json(ok(payload));
|
|
1198
|
-
} catch (error) {
|
|
1199
|
-
const message = String(error);
|
|
1200
|
-
if (message.startsWith("INVALID_BODY:")) {
|
|
1201
|
-
return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
|
|
1202
|
-
}
|
|
1203
|
-
if (message.startsWith("NOT_AVAILABLE:")) {
|
|
1204
|
-
return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
|
|
1205
|
-
}
|
|
1206
|
-
return c.json(err("INSTALL_FAILED", message), 400);
|
|
1207
|
-
}
|
|
1208
|
-
});
|
|
1209
|
-
app.post("/api/marketplace/manage", async (c) => {
|
|
1210
|
-
const body = await readJson(c.req.raw);
|
|
1211
|
-
if (!body.ok || !body.data || typeof body.data !== "object") {
|
|
1212
|
-
return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
1213
|
-
}
|
|
1214
|
-
try {
|
|
1215
|
-
const payload = await manageMarketplaceItem({ options, body: body.data });
|
|
1216
|
-
return c.json(ok(payload));
|
|
1217
|
-
} catch (error) {
|
|
1218
|
-
const message = String(error);
|
|
1219
|
-
if (message.startsWith("INVALID_BODY:")) {
|
|
1220
|
-
return c.json(err("INVALID_BODY", message.slice("INVALID_BODY:".length)), 400);
|
|
1221
|
-
}
|
|
1222
|
-
if (message.startsWith("NOT_AVAILABLE:")) {
|
|
1223
|
-
return c.json(err("NOT_AVAILABLE", message.slice("NOT_AVAILABLE:".length)), 503);
|
|
1224
|
-
}
|
|
1225
|
-
return c.json(err("MANAGE_FAILED", message), 400);
|
|
1226
|
-
}
|
|
1227
|
-
});
|
|
1228
1260
|
}
|
|
1229
1261
|
function createUiRouter(options) {
|
|
1230
1262
|
const app = new Hono();
|
|
@@ -1441,6 +1473,7 @@ var DEFAULT_CORS_ORIGINS = (origin) => {
|
|
|
1441
1473
|
};
|
|
1442
1474
|
function startUiServer(options) {
|
|
1443
1475
|
const app = new Hono2();
|
|
1476
|
+
app.use("/*", compress());
|
|
1444
1477
|
const origin = options.corsOrigins ?? DEFAULT_CORS_ORIGINS;
|
|
1445
1478
|
app.use("/api/*", cors({ origin }));
|
|
1446
1479
|
const clients = /* @__PURE__ */ new Set();
|