teleton 0.5.2 → 0.6.0
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/README.md +239 -101
- package/dist/chunk-2QUJLHCZ.js +362 -0
- package/dist/chunk-4IPJ25HE.js +2839 -0
- package/dist/{chunk-WOXBZOQX.js → chunk-6L6KGATM.js} +1026 -3071
- package/dist/{chunk-WUTMT6DW.js → chunk-ADCMUNYU.js} +65 -522
- package/dist/chunk-D5I7GBV7.js +322 -0
- package/dist/chunk-ECSCVEQQ.js +139 -0
- package/dist/chunk-GDCODBNO.js +72 -0
- package/dist/{chunk-O4R7V5Y2.js → chunk-RO62LO6Z.js} +11 -1
- package/dist/cli/index.js +344 -22
- package/dist/index.js +7 -4
- package/dist/{memory-Y5J7CXAR.js → memory-TVDOGQXS.js} +14 -10
- package/dist/{migrate-UEQCDWL2.js → migrate-QIEMPOMT.js} +5 -2
- package/dist/{server-BQY7CM2N.js → server-RSWVCVY3.js} +805 -26
- package/dist/{task-dependency-resolver-TRPILAHM.js → task-dependency-resolver-72DLY2HV.js} +1 -1
- package/dist/{task-executor-N7XNVK5N.js → task-executor-VXB6DAV2.js} +1 -1
- package/dist/tool-index-DKI2ZNOU.js +245 -0
- package/dist/web/assets/index-BNhrx9S1.js +67 -0
- package/dist/web/assets/index-CqrrRLOh.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +16 -14
- package/dist/chunk-5WWR4CU3.js +0 -124
- package/dist/web/assets/index-CDMbujHf.css +0 -1
- package/dist/web/assets/index-DDX8oQ2z.js +0 -67
|
@@ -1,27 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONFIGURABLE_KEYS,
|
|
3
|
+
deleteNestedValue,
|
|
4
|
+
getNestedValue,
|
|
5
|
+
readRawConfig,
|
|
6
|
+
setNestedValue,
|
|
7
|
+
writeRawConfig
|
|
8
|
+
} from "./chunk-2QUJLHCZ.js";
|
|
1
9
|
import {
|
|
2
10
|
WorkspaceSecurityError,
|
|
11
|
+
adaptPlugin,
|
|
12
|
+
deletePluginSecret,
|
|
13
|
+
ensurePluginDeps,
|
|
14
|
+
listPluginSecretKeys,
|
|
3
15
|
validateDirectory,
|
|
4
16
|
validatePath,
|
|
5
17
|
validateReadPath,
|
|
6
|
-
validateWritePath
|
|
7
|
-
|
|
8
|
-
|
|
18
|
+
validateWritePath,
|
|
19
|
+
writePluginSecret
|
|
20
|
+
} from "./chunk-4IPJ25HE.js";
|
|
21
|
+
import "./chunk-ECSCVEQQ.js";
|
|
22
|
+
import "./chunk-GDCODBNO.js";
|
|
23
|
+
import "./chunk-4DU3C27M.js";
|
|
24
|
+
import "./chunk-RO62LO6Z.js";
|
|
9
25
|
import {
|
|
26
|
+
WORKSPACE_PATHS,
|
|
10
27
|
WORKSPACE_ROOT
|
|
11
28
|
} from "./chunk-EYWNOHMJ.js";
|
|
12
29
|
import {
|
|
13
30
|
getTaskStore
|
|
14
31
|
} from "./chunk-NUGDTPE4.js";
|
|
32
|
+
import "./chunk-QUAPFI2N.js";
|
|
15
33
|
import "./chunk-QGM4M3NI.js";
|
|
16
34
|
|
|
17
35
|
// src/webui/server.ts
|
|
18
|
-
import { Hono as
|
|
36
|
+
import { Hono as Hono12 } from "hono";
|
|
19
37
|
import { serve } from "@hono/node-server";
|
|
20
38
|
import { cors } from "hono/cors";
|
|
21
39
|
import { bodyLimit } from "hono/body-limit";
|
|
22
40
|
import { setCookie, getCookie, deleteCookie } from "hono/cookie";
|
|
23
|
-
import { existsSync as
|
|
24
|
-
import { join as
|
|
41
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
42
|
+
import { join as join4, dirname, resolve as resolve2, relative as relative2 } from "path";
|
|
25
43
|
import { fileURLToPath } from "url";
|
|
26
44
|
|
|
27
45
|
// src/webui/middleware/auth.ts
|
|
@@ -174,6 +192,55 @@ function createToolsRoutes(deps) {
|
|
|
174
192
|
return c.json(response, 500);
|
|
175
193
|
}
|
|
176
194
|
});
|
|
195
|
+
app.get("/rag", (c) => {
|
|
196
|
+
try {
|
|
197
|
+
const config = deps.agent.getConfig();
|
|
198
|
+
const toolIndex = deps.toolRegistry.getToolIndex();
|
|
199
|
+
const response = {
|
|
200
|
+
success: true,
|
|
201
|
+
data: {
|
|
202
|
+
enabled: config.tool_rag.enabled,
|
|
203
|
+
indexed: toolIndex?.isIndexed ?? false,
|
|
204
|
+
topK: config.tool_rag.top_k,
|
|
205
|
+
totalTools: deps.toolRegistry.count,
|
|
206
|
+
alwaysInclude: config.tool_rag.always_include,
|
|
207
|
+
skipUnlimitedProviders: config.tool_rag.skip_unlimited_providers
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
return c.json(response);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return c.json({ success: false, error: String(error) }, 500);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
app.put("/rag", async (c) => {
|
|
216
|
+
try {
|
|
217
|
+
const config = deps.agent.getConfig();
|
|
218
|
+
const body = await c.req.json();
|
|
219
|
+
const { enabled, topK } = body;
|
|
220
|
+
if (enabled !== void 0) {
|
|
221
|
+
config.tool_rag.enabled = enabled;
|
|
222
|
+
}
|
|
223
|
+
if (topK !== void 0) {
|
|
224
|
+
if (topK < 5 || topK > 200) {
|
|
225
|
+
return c.json({ success: false, error: "topK must be between 5 and 200" }, 400);
|
|
226
|
+
}
|
|
227
|
+
config.tool_rag.top_k = topK;
|
|
228
|
+
}
|
|
229
|
+
const toolIndex = deps.toolRegistry.getToolIndex();
|
|
230
|
+
const response = {
|
|
231
|
+
success: true,
|
|
232
|
+
data: {
|
|
233
|
+
enabled: config.tool_rag.enabled,
|
|
234
|
+
indexed: toolIndex?.isIndexed ?? false,
|
|
235
|
+
topK: config.tool_rag.top_k,
|
|
236
|
+
totalTools: deps.toolRegistry.count
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
return c.json(response);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return c.json({ success: false, error: String(error) }, 500);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
177
244
|
app.put("/:name", async (c) => {
|
|
178
245
|
try {
|
|
179
246
|
const toolName = c.req.param("name");
|
|
@@ -327,9 +394,9 @@ function createLogsRoutes(_deps) {
|
|
|
327
394
|
}),
|
|
328
395
|
event: "log"
|
|
329
396
|
});
|
|
330
|
-
await new Promise((
|
|
331
|
-
if (aborted) return
|
|
332
|
-
stream.onAbort(() =>
|
|
397
|
+
await new Promise((resolve3) => {
|
|
398
|
+
if (aborted) return resolve3();
|
|
399
|
+
stream.onAbort(() => resolve3());
|
|
333
400
|
});
|
|
334
401
|
if (cleanup) cleanup();
|
|
335
402
|
});
|
|
@@ -551,8 +618,124 @@ function createPluginsRoutes(deps) {
|
|
|
551
618
|
return app;
|
|
552
619
|
}
|
|
553
620
|
|
|
554
|
-
// src/webui/routes/
|
|
621
|
+
// src/webui/routes/mcp.ts
|
|
555
622
|
import { Hono as Hono7 } from "hono";
|
|
623
|
+
var SAFE_PACKAGE_RE = /^[@a-zA-Z0-9._\/-]+$/;
|
|
624
|
+
var SAFE_ARG_RE = /^[a-zA-Z0-9._\/:=@-]+$/;
|
|
625
|
+
function createMcpRoutes(deps) {
|
|
626
|
+
const app = new Hono7();
|
|
627
|
+
app.get("/", (c) => {
|
|
628
|
+
const response = {
|
|
629
|
+
success: true,
|
|
630
|
+
data: deps.mcpServers
|
|
631
|
+
};
|
|
632
|
+
return c.json(response);
|
|
633
|
+
});
|
|
634
|
+
app.post("/", async (c) => {
|
|
635
|
+
try {
|
|
636
|
+
const body = await c.req.json();
|
|
637
|
+
if (!body.package && !body.url) {
|
|
638
|
+
return c.json(
|
|
639
|
+
{ success: false, error: "Either 'package' or 'url' is required" },
|
|
640
|
+
400
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (body.package && !SAFE_PACKAGE_RE.test(body.package)) {
|
|
644
|
+
return c.json(
|
|
645
|
+
{
|
|
646
|
+
success: false,
|
|
647
|
+
error: "Invalid package name \u2014 only alphanumeric, @, /, ., - allowed"
|
|
648
|
+
},
|
|
649
|
+
400
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
if (body.args) {
|
|
653
|
+
for (const arg of body.args) {
|
|
654
|
+
if (!SAFE_ARG_RE.test(arg)) {
|
|
655
|
+
return c.json(
|
|
656
|
+
{
|
|
657
|
+
success: false,
|
|
658
|
+
error: `Invalid argument "${arg}" \u2014 only alphanumeric, ., /, :, =, @, - allowed`
|
|
659
|
+
},
|
|
660
|
+
400
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
const raw = readRawConfig(deps.configPath);
|
|
666
|
+
if (!raw.mcp || typeof raw.mcp !== "object") raw.mcp = { servers: {} };
|
|
667
|
+
const mcp = raw.mcp;
|
|
668
|
+
if (!mcp.servers || typeof mcp.servers !== "object") mcp.servers = {};
|
|
669
|
+
const servers = mcp.servers;
|
|
670
|
+
const serverName = body.name || deriveServerName(body.package || body.url || "unknown");
|
|
671
|
+
if (servers[serverName]) {
|
|
672
|
+
return c.json(
|
|
673
|
+
{ success: false, error: `Server "${serverName}" already exists` },
|
|
674
|
+
409
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
const entry = {};
|
|
678
|
+
if (body.url) {
|
|
679
|
+
entry.url = body.url;
|
|
680
|
+
} else {
|
|
681
|
+
entry.command = "npx";
|
|
682
|
+
entry.args = ["-y", body.package, ...body.args || []];
|
|
683
|
+
}
|
|
684
|
+
if (body.scope && body.scope !== "always") entry.scope = body.scope;
|
|
685
|
+
if (body.env && Object.keys(body.env).length > 0) entry.env = body.env;
|
|
686
|
+
servers[serverName] = entry;
|
|
687
|
+
writeRawConfig(raw, deps.configPath);
|
|
688
|
+
return c.json({
|
|
689
|
+
success: true,
|
|
690
|
+
data: { name: serverName, message: "Server added. Restart teleton to connect." }
|
|
691
|
+
});
|
|
692
|
+
} catch (error) {
|
|
693
|
+
return c.json(
|
|
694
|
+
{
|
|
695
|
+
success: false,
|
|
696
|
+
error: error instanceof Error ? error.message : String(error)
|
|
697
|
+
},
|
|
698
|
+
500
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
app.delete("/:name", (c) => {
|
|
703
|
+
try {
|
|
704
|
+
const name = c.req.param("name");
|
|
705
|
+
const raw = readRawConfig(deps.configPath);
|
|
706
|
+
const mcp = raw.mcp || {};
|
|
707
|
+
const servers = mcp.servers || {};
|
|
708
|
+
if (!servers[name]) {
|
|
709
|
+
return c.json(
|
|
710
|
+
{ success: false, error: `Server "${name}" not found` },
|
|
711
|
+
404
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
delete servers[name];
|
|
715
|
+
writeRawConfig(raw, deps.configPath);
|
|
716
|
+
return c.json({
|
|
717
|
+
success: true,
|
|
718
|
+
data: { name, message: "Server removed. Restart teleton to apply." }
|
|
719
|
+
});
|
|
720
|
+
} catch (error) {
|
|
721
|
+
return c.json(
|
|
722
|
+
{
|
|
723
|
+
success: false,
|
|
724
|
+
error: error instanceof Error ? error.message : String(error)
|
|
725
|
+
},
|
|
726
|
+
500
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
return app;
|
|
731
|
+
}
|
|
732
|
+
function deriveServerName(pkg) {
|
|
733
|
+
const unscoped = pkg.includes("/") ? pkg.split("/").pop() : pkg;
|
|
734
|
+
return unscoped.replace(/^server-/, "").replace(/^mcp-server-/, "").replace(/^mcp-/, "");
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// src/webui/routes/workspace.ts
|
|
738
|
+
import { Hono as Hono8 } from "hono";
|
|
556
739
|
import {
|
|
557
740
|
readFileSync as readFileSync2,
|
|
558
741
|
writeFileSync as writeFileSync2,
|
|
@@ -614,7 +797,7 @@ function listDir(absPath, recursive) {
|
|
|
614
797
|
return entries;
|
|
615
798
|
}
|
|
616
799
|
function createWorkspaceRoutes(_deps) {
|
|
617
|
-
const app = new
|
|
800
|
+
const app = new Hono8();
|
|
618
801
|
app.get("/", (c) => {
|
|
619
802
|
try {
|
|
620
803
|
const subpath = c.req.query("path") || "";
|
|
@@ -778,10 +961,10 @@ function createWorkspaceRoutes(_deps) {
|
|
|
778
961
|
}
|
|
779
962
|
|
|
780
963
|
// src/webui/routes/tasks.ts
|
|
781
|
-
import { Hono as
|
|
964
|
+
import { Hono as Hono9 } from "hono";
|
|
782
965
|
var VALID_STATUSES = ["pending", "in_progress", "done", "failed", "cancelled"];
|
|
783
966
|
function createTasksRoutes(deps) {
|
|
784
|
-
const app = new
|
|
967
|
+
const app = new Hono9();
|
|
785
968
|
function store() {
|
|
786
969
|
return getTaskStore(deps.memory.db);
|
|
787
970
|
}
|
|
@@ -889,23 +1072,616 @@ function createTasksRoutes(deps) {
|
|
|
889
1072
|
return app;
|
|
890
1073
|
}
|
|
891
1074
|
|
|
1075
|
+
// src/webui/routes/config.ts
|
|
1076
|
+
import { Hono as Hono10 } from "hono";
|
|
1077
|
+
function createConfigRoutes(deps) {
|
|
1078
|
+
const app = new Hono10();
|
|
1079
|
+
app.get("/", (c) => {
|
|
1080
|
+
try {
|
|
1081
|
+
const raw = readRawConfig(deps.configPath);
|
|
1082
|
+
const data = Object.entries(CONFIGURABLE_KEYS).map(([key, meta]) => {
|
|
1083
|
+
const value = getNestedValue(raw, key);
|
|
1084
|
+
const isSet = value != null && value !== "";
|
|
1085
|
+
return {
|
|
1086
|
+
key,
|
|
1087
|
+
set: isSet,
|
|
1088
|
+
value: isSet ? meta.mask(String(value)) : null,
|
|
1089
|
+
sensitive: meta.sensitive,
|
|
1090
|
+
type: meta.type,
|
|
1091
|
+
category: meta.category,
|
|
1092
|
+
description: meta.description,
|
|
1093
|
+
...meta.options ? { options: meta.options } : {}
|
|
1094
|
+
};
|
|
1095
|
+
});
|
|
1096
|
+
const response = { success: true, data };
|
|
1097
|
+
return c.json(response);
|
|
1098
|
+
} catch (err) {
|
|
1099
|
+
return c.json(
|
|
1100
|
+
{ success: false, error: err instanceof Error ? err.message : String(err) },
|
|
1101
|
+
500
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
app.put("/:key", async (c) => {
|
|
1106
|
+
const key = c.req.param("key");
|
|
1107
|
+
const meta = CONFIGURABLE_KEYS[key];
|
|
1108
|
+
if (!meta) {
|
|
1109
|
+
const allowed = Object.keys(CONFIGURABLE_KEYS).join(", ");
|
|
1110
|
+
return c.json(
|
|
1111
|
+
{
|
|
1112
|
+
success: false,
|
|
1113
|
+
error: `Key "${key}" is not configurable. Allowed: ${allowed}`
|
|
1114
|
+
},
|
|
1115
|
+
400
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
let body;
|
|
1119
|
+
try {
|
|
1120
|
+
body = await c.req.json();
|
|
1121
|
+
} catch {
|
|
1122
|
+
return c.json({ success: false, error: "Invalid JSON body" }, 400);
|
|
1123
|
+
}
|
|
1124
|
+
const value = body.value;
|
|
1125
|
+
if (value == null || typeof value !== "string") {
|
|
1126
|
+
return c.json(
|
|
1127
|
+
{ success: false, error: "Missing or invalid 'value' field" },
|
|
1128
|
+
400
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
const validationErr = meta.validate(value);
|
|
1132
|
+
if (validationErr) {
|
|
1133
|
+
return c.json(
|
|
1134
|
+
{ success: false, error: `Invalid value for ${key}: ${validationErr}` },
|
|
1135
|
+
400
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
try {
|
|
1139
|
+
const parsed = meta.parse(value);
|
|
1140
|
+
const raw = readRawConfig(deps.configPath);
|
|
1141
|
+
setNestedValue(raw, key, parsed);
|
|
1142
|
+
writeRawConfig(raw, deps.configPath);
|
|
1143
|
+
const runtimeConfig = deps.agent.getConfig();
|
|
1144
|
+
setNestedValue(runtimeConfig, key, parsed);
|
|
1145
|
+
const result = {
|
|
1146
|
+
key,
|
|
1147
|
+
set: true,
|
|
1148
|
+
value: meta.mask(value),
|
|
1149
|
+
sensitive: meta.sensitive,
|
|
1150
|
+
type: meta.type,
|
|
1151
|
+
category: meta.category,
|
|
1152
|
+
description: meta.description,
|
|
1153
|
+
...meta.options ? { options: meta.options } : {}
|
|
1154
|
+
};
|
|
1155
|
+
return c.json({ success: true, data: result });
|
|
1156
|
+
} catch (err) {
|
|
1157
|
+
return c.json(
|
|
1158
|
+
{ success: false, error: err instanceof Error ? err.message : String(err) },
|
|
1159
|
+
500
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
app.delete("/:key", (c) => {
|
|
1164
|
+
const key = c.req.param("key");
|
|
1165
|
+
const meta = CONFIGURABLE_KEYS[key];
|
|
1166
|
+
if (!meta) {
|
|
1167
|
+
const allowed = Object.keys(CONFIGURABLE_KEYS).join(", ");
|
|
1168
|
+
return c.json(
|
|
1169
|
+
{
|
|
1170
|
+
success: false,
|
|
1171
|
+
error: `Key "${key}" is not configurable. Allowed: ${allowed}`
|
|
1172
|
+
},
|
|
1173
|
+
400
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
try {
|
|
1177
|
+
const raw = readRawConfig(deps.configPath);
|
|
1178
|
+
deleteNestedValue(raw, key);
|
|
1179
|
+
writeRawConfig(raw, deps.configPath);
|
|
1180
|
+
const runtimeConfig = deps.agent.getConfig();
|
|
1181
|
+
deleteNestedValue(runtimeConfig, key);
|
|
1182
|
+
const result = {
|
|
1183
|
+
key,
|
|
1184
|
+
set: false,
|
|
1185
|
+
value: null,
|
|
1186
|
+
sensitive: meta.sensitive,
|
|
1187
|
+
type: meta.type,
|
|
1188
|
+
category: meta.category,
|
|
1189
|
+
description: meta.description,
|
|
1190
|
+
...meta.options ? { options: meta.options } : {}
|
|
1191
|
+
};
|
|
1192
|
+
return c.json({ success: true, data: result });
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
return c.json(
|
|
1195
|
+
{ success: false, error: err instanceof Error ? err.message : String(err) },
|
|
1196
|
+
500
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
return app;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/webui/routes/marketplace.ts
|
|
1204
|
+
import { Hono as Hono11 } from "hono";
|
|
1205
|
+
|
|
1206
|
+
// src/webui/services/marketplace.ts
|
|
1207
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "fs";
|
|
1208
|
+
import { join as join3, resolve } from "path";
|
|
1209
|
+
import { pathToFileURL } from "url";
|
|
1210
|
+
var REGISTRY_URL = "https://raw.githubusercontent.com/TONresistor/teleton-plugins/main/registry.json";
|
|
1211
|
+
var PLUGIN_BASE_URL = "https://raw.githubusercontent.com/TONresistor/teleton-plugins/main";
|
|
1212
|
+
var GITHUB_API_BASE = "https://api.github.com/repos/TONresistor/teleton-plugins/contents";
|
|
1213
|
+
var CACHE_TTL = 5 * 60 * 1e3;
|
|
1214
|
+
var PLUGINS_DIR = WORKSPACE_PATHS.PLUGINS_DIR;
|
|
1215
|
+
var VALID_ID = /^[a-z0-9][a-z0-9-]*$/;
|
|
1216
|
+
var MarketplaceService = class {
|
|
1217
|
+
deps;
|
|
1218
|
+
cache = null;
|
|
1219
|
+
fetchPromise = null;
|
|
1220
|
+
manifestCache = /* @__PURE__ */ new Map();
|
|
1221
|
+
installing = /* @__PURE__ */ new Set();
|
|
1222
|
+
constructor(deps) {
|
|
1223
|
+
this.deps = deps;
|
|
1224
|
+
}
|
|
1225
|
+
// ── Registry ────────────────────────────────────────────────────────
|
|
1226
|
+
async getRegistry(forceRefresh = false) {
|
|
1227
|
+
if (!forceRefresh && this.cache && Date.now() - this.cache.fetchedAt < CACHE_TTL) {
|
|
1228
|
+
return this.cache.entries;
|
|
1229
|
+
}
|
|
1230
|
+
if (this.fetchPromise) return this.fetchPromise;
|
|
1231
|
+
this.fetchPromise = this.fetchRegistry();
|
|
1232
|
+
try {
|
|
1233
|
+
const entries = await this.fetchPromise;
|
|
1234
|
+
this.cache = { entries, fetchedAt: Date.now() };
|
|
1235
|
+
return entries;
|
|
1236
|
+
} catch (err) {
|
|
1237
|
+
if (this.cache) {
|
|
1238
|
+
console.warn("[marketplace] Registry fetch failed, using stale cache:", err);
|
|
1239
|
+
return this.cache.entries;
|
|
1240
|
+
}
|
|
1241
|
+
throw err;
|
|
1242
|
+
} finally {
|
|
1243
|
+
this.fetchPromise = null;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
async fetchRegistry() {
|
|
1247
|
+
const res = await fetch(REGISTRY_URL);
|
|
1248
|
+
if (!res.ok) throw new Error(`Registry fetch failed: ${res.status} ${res.statusText}`);
|
|
1249
|
+
const data = await res.json();
|
|
1250
|
+
const plugins = Array.isArray(data) ? data : data?.plugins;
|
|
1251
|
+
if (!Array.isArray(plugins)) throw new Error("Registry has no plugins array");
|
|
1252
|
+
const VALID_PATH = /^[a-zA-Z0-9][a-zA-Z0-9._\/-]*$/;
|
|
1253
|
+
for (const entry of plugins) {
|
|
1254
|
+
if (!entry.id || !entry.name || !entry.path) {
|
|
1255
|
+
throw new Error(`Invalid registry entry: missing required fields (id=${entry.id ?? "?"})`);
|
|
1256
|
+
}
|
|
1257
|
+
if (!VALID_PATH.test(entry.path) || entry.path.includes("..")) {
|
|
1258
|
+
throw new Error(`Invalid registry path for "${entry.id}": "${entry.path}"`);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
return plugins;
|
|
1262
|
+
}
|
|
1263
|
+
// ── Remote manifest ─────────────────────────────────────────────────
|
|
1264
|
+
async fetchRemoteManifest(entry) {
|
|
1265
|
+
const cached = this.manifestCache.get(entry.id);
|
|
1266
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
|
|
1267
|
+
return cached.data;
|
|
1268
|
+
}
|
|
1269
|
+
const url = `${PLUGIN_BASE_URL}/${entry.path}/manifest.json`;
|
|
1270
|
+
const res = await fetch(url);
|
|
1271
|
+
if (!res.ok) {
|
|
1272
|
+
return {
|
|
1273
|
+
name: entry.name,
|
|
1274
|
+
version: "0.0.0",
|
|
1275
|
+
description: entry.description,
|
|
1276
|
+
author: entry.author
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
const raw = await res.json();
|
|
1280
|
+
const data = {
|
|
1281
|
+
...raw,
|
|
1282
|
+
author: normalizeAuthor(raw.author)
|
|
1283
|
+
};
|
|
1284
|
+
this.manifestCache.set(entry.id, { data, fetchedAt: Date.now() });
|
|
1285
|
+
return data;
|
|
1286
|
+
}
|
|
1287
|
+
// ── List plugins (combined view) ────────────────────────────────────
|
|
1288
|
+
async listPlugins(forceRefresh = false) {
|
|
1289
|
+
const registry = await this.getRegistry(forceRefresh);
|
|
1290
|
+
const results = [];
|
|
1291
|
+
const manifests = await Promise.allSettled(
|
|
1292
|
+
registry.map((entry) => this.fetchRemoteManifest(entry))
|
|
1293
|
+
);
|
|
1294
|
+
for (let i = 0; i < registry.length; i++) {
|
|
1295
|
+
const entry = registry[i];
|
|
1296
|
+
const manifestResult = manifests[i];
|
|
1297
|
+
const manifest = manifestResult.status === "fulfilled" ? manifestResult.value : {
|
|
1298
|
+
name: entry.name,
|
|
1299
|
+
version: "0.0.0",
|
|
1300
|
+
description: entry.description,
|
|
1301
|
+
author: entry.author
|
|
1302
|
+
};
|
|
1303
|
+
const installed = this.deps.modules.find((m) => m.name === entry.id || m.name === entry.name);
|
|
1304
|
+
const installedVersion = installed?.version ?? null;
|
|
1305
|
+
const remoteVersion = manifest.version || "0.0.0";
|
|
1306
|
+
let status = "available";
|
|
1307
|
+
if (installedVersion) {
|
|
1308
|
+
status = installedVersion !== remoteVersion ? "updatable" : "installed";
|
|
1309
|
+
}
|
|
1310
|
+
let toolCount = manifest.tools?.length ?? 0;
|
|
1311
|
+
let tools = manifest.tools ?? [];
|
|
1312
|
+
if (installed) {
|
|
1313
|
+
const moduleTools = this.deps.toolRegistry.getModuleTools(installed.name);
|
|
1314
|
+
const allToolDefs = this.deps.toolRegistry.getAll();
|
|
1315
|
+
const toolMap = new Map(allToolDefs.map((t) => [t.name, t]));
|
|
1316
|
+
tools = moduleTools.map((mt) => ({
|
|
1317
|
+
name: mt.name,
|
|
1318
|
+
description: toolMap.get(mt.name)?.description ?? ""
|
|
1319
|
+
}));
|
|
1320
|
+
toolCount = tools.length;
|
|
1321
|
+
}
|
|
1322
|
+
results.push({
|
|
1323
|
+
id: entry.id,
|
|
1324
|
+
name: entry.name,
|
|
1325
|
+
description: manifest.description || entry.description,
|
|
1326
|
+
author: manifest.author || entry.author,
|
|
1327
|
+
tags: entry.tags,
|
|
1328
|
+
remoteVersion,
|
|
1329
|
+
installedVersion,
|
|
1330
|
+
status,
|
|
1331
|
+
toolCount,
|
|
1332
|
+
tools,
|
|
1333
|
+
secrets: manifest.secrets
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
return results;
|
|
1337
|
+
}
|
|
1338
|
+
// ── Install ─────────────────────────────────────────────────────────
|
|
1339
|
+
async installPlugin(pluginId) {
|
|
1340
|
+
this.validateId(pluginId);
|
|
1341
|
+
if (this.installing.has(pluginId)) {
|
|
1342
|
+
throw new ConflictError(`Plugin "${pluginId}" is already being installed`);
|
|
1343
|
+
}
|
|
1344
|
+
const existing = this.findModuleByPluginId(pluginId);
|
|
1345
|
+
if (existing) {
|
|
1346
|
+
throw new ConflictError(`Plugin "${pluginId}" is already installed`);
|
|
1347
|
+
}
|
|
1348
|
+
this.installing.add(pluginId);
|
|
1349
|
+
const pluginDir = join3(PLUGINS_DIR, pluginId);
|
|
1350
|
+
try {
|
|
1351
|
+
const registry = await this.getRegistry();
|
|
1352
|
+
const entry = registry.find((e) => e.id === pluginId);
|
|
1353
|
+
if (!entry) throw new Error(`Plugin "${pluginId}" not found in registry`);
|
|
1354
|
+
const manifest = await this.fetchRemoteManifest(entry);
|
|
1355
|
+
mkdirSync2(pluginDir, { recursive: true });
|
|
1356
|
+
await this.downloadDir(entry.path, pluginDir);
|
|
1357
|
+
await ensurePluginDeps(pluginDir, pluginId);
|
|
1358
|
+
const indexPath = join3(pluginDir, "index.js");
|
|
1359
|
+
const moduleUrl = pathToFileURL(indexPath).href + `?t=${Date.now()}`;
|
|
1360
|
+
const mod = await import(moduleUrl);
|
|
1361
|
+
const adapted = adaptPlugin(
|
|
1362
|
+
mod,
|
|
1363
|
+
pluginId,
|
|
1364
|
+
this.deps.config,
|
|
1365
|
+
this.deps.loadedModuleNames,
|
|
1366
|
+
this.deps.sdkDeps
|
|
1367
|
+
);
|
|
1368
|
+
adapted.migrate?.(this.deps.pluginContext.db);
|
|
1369
|
+
const tools = adapted.tools(this.deps.config);
|
|
1370
|
+
const toolCount = this.deps.toolRegistry.registerPluginTools(adapted.name, tools);
|
|
1371
|
+
await adapted.start?.(this.deps.pluginContext);
|
|
1372
|
+
this.deps.modules.push(adapted);
|
|
1373
|
+
this.deps.rewireHooks();
|
|
1374
|
+
return {
|
|
1375
|
+
name: adapted.name,
|
|
1376
|
+
version: adapted.version,
|
|
1377
|
+
toolCount
|
|
1378
|
+
};
|
|
1379
|
+
} catch (err) {
|
|
1380
|
+
if (existsSync2(pluginDir)) {
|
|
1381
|
+
try {
|
|
1382
|
+
rmSync2(pluginDir, { recursive: true, force: true });
|
|
1383
|
+
} catch (cleanupErr) {
|
|
1384
|
+
console.error(`[marketplace] Failed to cleanup ${pluginDir}:`, cleanupErr);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
throw err;
|
|
1388
|
+
} finally {
|
|
1389
|
+
this.installing.delete(pluginId);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
// ── Uninstall ───────────────────────────────────────────────────────
|
|
1393
|
+
async uninstallPlugin(pluginId) {
|
|
1394
|
+
this.validateId(pluginId);
|
|
1395
|
+
if (this.installing.has(pluginId)) {
|
|
1396
|
+
throw new ConflictError(`Plugin "${pluginId}" has an operation in progress`);
|
|
1397
|
+
}
|
|
1398
|
+
const mod = this.findModuleByPluginId(pluginId);
|
|
1399
|
+
if (!mod) {
|
|
1400
|
+
throw new Error(`Plugin "${pluginId}" is not installed`);
|
|
1401
|
+
}
|
|
1402
|
+
const moduleName = mod.name;
|
|
1403
|
+
const idx = this.deps.modules.indexOf(mod);
|
|
1404
|
+
this.installing.add(pluginId);
|
|
1405
|
+
try {
|
|
1406
|
+
await mod.stop?.();
|
|
1407
|
+
this.deps.toolRegistry.removePluginTools(moduleName);
|
|
1408
|
+
if (idx >= 0) this.deps.modules.splice(idx, 1);
|
|
1409
|
+
this.deps.rewireHooks();
|
|
1410
|
+
const pluginDir = join3(PLUGINS_DIR, pluginId);
|
|
1411
|
+
if (existsSync2(pluginDir)) {
|
|
1412
|
+
rmSync2(pluginDir, { recursive: true, force: true });
|
|
1413
|
+
}
|
|
1414
|
+
return { message: `Plugin "${pluginId}" uninstalled successfully` };
|
|
1415
|
+
} finally {
|
|
1416
|
+
this.installing.delete(pluginId);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
// ── Update ──────────────────────────────────────────────────────────
|
|
1420
|
+
async updatePlugin(pluginId) {
|
|
1421
|
+
await this.uninstallPlugin(pluginId);
|
|
1422
|
+
return this.installPlugin(pluginId);
|
|
1423
|
+
}
|
|
1424
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
1425
|
+
/**
|
|
1426
|
+
* Resolve a registry plugin ID to the actual loaded module.
|
|
1427
|
+
* Handles name mismatch: registry id "fragment" → module name "Fragment Marketplace".
|
|
1428
|
+
*/
|
|
1429
|
+
findModuleByPluginId(pluginId) {
|
|
1430
|
+
let mod = this.deps.modules.find((m) => m.name === pluginId);
|
|
1431
|
+
if (mod) return mod;
|
|
1432
|
+
const entry = this.cache?.entries.find((e) => e.id === pluginId);
|
|
1433
|
+
if (entry) {
|
|
1434
|
+
mod = this.deps.modules.find((m) => m.name === entry.name);
|
|
1435
|
+
}
|
|
1436
|
+
return mod ?? null;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Recursively download a GitHub directory to a local path.
|
|
1440
|
+
* Uses the GitHub Contents API to list files, then fetches each via raw.githubusercontent.
|
|
1441
|
+
*/
|
|
1442
|
+
async downloadDir(remotePath, localDir, depth = 0) {
|
|
1443
|
+
if (depth > 5) throw new Error("Plugin directory too deeply nested");
|
|
1444
|
+
const res = await fetch(`${GITHUB_API_BASE}/${remotePath}`);
|
|
1445
|
+
if (!res.ok) throw new Error(`Failed to list directory "${remotePath}": ${res.status}`);
|
|
1446
|
+
const entries = await res.json();
|
|
1447
|
+
for (const item of entries) {
|
|
1448
|
+
if (!item.name || /[/\\]/.test(item.name) || item.name === ".." || item.name === ".") {
|
|
1449
|
+
throw new Error(`Invalid entry name in plugin directory: "${item.name}"`);
|
|
1450
|
+
}
|
|
1451
|
+
const target = resolve(localDir, item.name);
|
|
1452
|
+
if (!target.startsWith(resolve(PLUGINS_DIR))) {
|
|
1453
|
+
throw new Error(`Path escape detected: ${target}`);
|
|
1454
|
+
}
|
|
1455
|
+
if (item.type === "dir") {
|
|
1456
|
+
mkdirSync2(target, { recursive: true });
|
|
1457
|
+
await this.downloadDir(item.path, target, depth + 1);
|
|
1458
|
+
} else if (item.type === "file" && item.download_url) {
|
|
1459
|
+
const url = new URL(item.download_url);
|
|
1460
|
+
if (!url.hostname.endsWith("githubusercontent.com") && !url.hostname.endsWith("github.com")) {
|
|
1461
|
+
throw new Error(`Untrusted download host: ${url.hostname}`);
|
|
1462
|
+
}
|
|
1463
|
+
const fileRes = await fetch(item.download_url);
|
|
1464
|
+
if (!fileRes.ok) throw new Error(`Failed to download ${item.name}: ${fileRes.status}`);
|
|
1465
|
+
const content = await fileRes.text();
|
|
1466
|
+
writeFileSync3(target, content, "utf-8");
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
validateId(id) {
|
|
1471
|
+
if (!VALID_ID.test(id)) {
|
|
1472
|
+
throw new Error(`Invalid plugin ID: "${id}"`);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
function normalizeAuthor(author) {
|
|
1477
|
+
if (typeof author === "string") return author;
|
|
1478
|
+
if (author && typeof author === "object" && "name" in author) {
|
|
1479
|
+
return String(author.name);
|
|
1480
|
+
}
|
|
1481
|
+
return "unknown";
|
|
1482
|
+
}
|
|
1483
|
+
var ConflictError = class extends Error {
|
|
1484
|
+
constructor(message) {
|
|
1485
|
+
super(message);
|
|
1486
|
+
this.name = "ConflictError";
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
|
|
1490
|
+
// src/webui/routes/marketplace.ts
|
|
1491
|
+
var VALID_ID2 = /^[a-z0-9][a-z0-9-]*$/;
|
|
1492
|
+
var VALID_KEY = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
1493
|
+
function createMarketplaceRoutes(deps) {
|
|
1494
|
+
const app = new Hono11();
|
|
1495
|
+
let service = null;
|
|
1496
|
+
const getService = () => {
|
|
1497
|
+
if (!deps.marketplace) return null;
|
|
1498
|
+
service ??= new MarketplaceService({ ...deps.marketplace, toolRegistry: deps.toolRegistry });
|
|
1499
|
+
return service;
|
|
1500
|
+
};
|
|
1501
|
+
app.get("/", async (c) => {
|
|
1502
|
+
const svc = getService();
|
|
1503
|
+
if (!svc) {
|
|
1504
|
+
return c.json({ success: false, error: "Marketplace not configured" }, 501);
|
|
1505
|
+
}
|
|
1506
|
+
try {
|
|
1507
|
+
const refresh = c.req.query("refresh") === "true";
|
|
1508
|
+
const plugins = await svc.listPlugins(refresh);
|
|
1509
|
+
return c.json({ success: true, data: plugins });
|
|
1510
|
+
} catch (err) {
|
|
1511
|
+
return c.json(
|
|
1512
|
+
{ success: false, error: err instanceof Error ? err.message : String(err) },
|
|
1513
|
+
500
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
app.post("/install", async (c) => {
|
|
1518
|
+
const svc = getService();
|
|
1519
|
+
if (!svc) {
|
|
1520
|
+
return c.json({ success: false, error: "Marketplace not configured" }, 501);
|
|
1521
|
+
}
|
|
1522
|
+
try {
|
|
1523
|
+
const body = await c.req.json();
|
|
1524
|
+
if (!body.id) {
|
|
1525
|
+
return c.json({ success: false, error: "Missing plugin id" }, 400);
|
|
1526
|
+
}
|
|
1527
|
+
const result = await svc.installPlugin(body.id);
|
|
1528
|
+
deps.plugins.length = 0;
|
|
1529
|
+
deps.plugins.push(
|
|
1530
|
+
...deps.marketplace.modules.filter((m) => deps.toolRegistry.isPluginModule(m.name)).map((m) => ({ name: m.name, version: m.version ?? "0.0.0" }))
|
|
1531
|
+
);
|
|
1532
|
+
return c.json({ success: true, data: result });
|
|
1533
|
+
} catch (err) {
|
|
1534
|
+
const status = err instanceof ConflictError ? 409 : 500;
|
|
1535
|
+
return c.json(
|
|
1536
|
+
{ success: false, error: err instanceof Error ? err.message : String(err) },
|
|
1537
|
+
status
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
app.post("/uninstall", async (c) => {
|
|
1542
|
+
const svc = getService();
|
|
1543
|
+
if (!svc) {
|
|
1544
|
+
return c.json({ success: false, error: "Marketplace not configured" }, 501);
|
|
1545
|
+
}
|
|
1546
|
+
try {
|
|
1547
|
+
const body = await c.req.json();
|
|
1548
|
+
if (!body.id) {
|
|
1549
|
+
return c.json({ success: false, error: "Missing plugin id" }, 400);
|
|
1550
|
+
}
|
|
1551
|
+
const result = await svc.uninstallPlugin(body.id);
|
|
1552
|
+
deps.plugins.length = 0;
|
|
1553
|
+
deps.plugins.push(
|
|
1554
|
+
...deps.marketplace.modules.filter((m) => deps.toolRegistry.isPluginModule(m.name)).map((m) => ({ name: m.name, version: m.version ?? "0.0.0" }))
|
|
1555
|
+
);
|
|
1556
|
+
return c.json({ success: true, data: result });
|
|
1557
|
+
} catch (err) {
|
|
1558
|
+
const status = err instanceof ConflictError ? 409 : 500;
|
|
1559
|
+
return c.json(
|
|
1560
|
+
{ success: false, error: err instanceof Error ? err.message : String(err) },
|
|
1561
|
+
status
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
app.post("/update", async (c) => {
|
|
1566
|
+
const svc = getService();
|
|
1567
|
+
if (!svc) {
|
|
1568
|
+
return c.json({ success: false, error: "Marketplace not configured" }, 501);
|
|
1569
|
+
}
|
|
1570
|
+
try {
|
|
1571
|
+
const body = await c.req.json();
|
|
1572
|
+
if (!body.id) {
|
|
1573
|
+
return c.json({ success: false, error: "Missing plugin id" }, 400);
|
|
1574
|
+
}
|
|
1575
|
+
const result = await svc.updatePlugin(body.id);
|
|
1576
|
+
deps.plugins.length = 0;
|
|
1577
|
+
deps.plugins.push(
|
|
1578
|
+
...deps.marketplace.modules.filter((m) => deps.toolRegistry.isPluginModule(m.name)).map((m) => ({ name: m.name, version: m.version ?? "0.0.0" }))
|
|
1579
|
+
);
|
|
1580
|
+
return c.json({ success: true, data: result });
|
|
1581
|
+
} catch (err) {
|
|
1582
|
+
const status = err instanceof ConflictError ? 409 : 500;
|
|
1583
|
+
return c.json(
|
|
1584
|
+
{ success: false, error: err instanceof Error ? err.message : String(err) },
|
|
1585
|
+
status
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
app.get("/secrets/:pluginId", async (c) => {
|
|
1590
|
+
const svc = getService();
|
|
1591
|
+
if (!svc) {
|
|
1592
|
+
return c.json({ success: false, error: "Marketplace not configured" }, 501);
|
|
1593
|
+
}
|
|
1594
|
+
const pluginId = c.req.param("pluginId");
|
|
1595
|
+
if (!VALID_ID2.test(pluginId)) {
|
|
1596
|
+
return c.json({ success: false, error: "Invalid plugin ID" }, 400);
|
|
1597
|
+
}
|
|
1598
|
+
try {
|
|
1599
|
+
const plugins = await svc.listPlugins();
|
|
1600
|
+
const plugin = plugins.find((p) => p.id === pluginId);
|
|
1601
|
+
const declared = plugin?.secrets ?? {};
|
|
1602
|
+
const configured = listPluginSecretKeys(pluginId);
|
|
1603
|
+
return c.json({ success: true, data: { declared, configured } });
|
|
1604
|
+
} catch (err) {
|
|
1605
|
+
return c.json(
|
|
1606
|
+
{ success: false, error: err instanceof Error ? err.message : String(err) },
|
|
1607
|
+
500
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
app.put("/secrets/:pluginId/:key", async (c) => {
|
|
1612
|
+
const pluginId = c.req.param("pluginId");
|
|
1613
|
+
const key = c.req.param("key");
|
|
1614
|
+
if (!VALID_ID2.test(pluginId)) {
|
|
1615
|
+
return c.json({ success: false, error: "Invalid plugin ID" }, 400);
|
|
1616
|
+
}
|
|
1617
|
+
if (!key || !VALID_KEY.test(key)) {
|
|
1618
|
+
return c.json(
|
|
1619
|
+
{ success: false, error: "Invalid key name \u2014 use letters, digits, underscores" },
|
|
1620
|
+
400
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
try {
|
|
1624
|
+
const body = await c.req.json();
|
|
1625
|
+
if (typeof body.value !== "string" || !body.value) {
|
|
1626
|
+
return c.json({ success: false, error: "Missing or invalid value" }, 400);
|
|
1627
|
+
}
|
|
1628
|
+
writePluginSecret(pluginId, key, body.value);
|
|
1629
|
+
return c.json({
|
|
1630
|
+
success: true,
|
|
1631
|
+
data: { key, set: true }
|
|
1632
|
+
});
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
return c.json(
|
|
1635
|
+
{ success: false, error: err instanceof Error ? err.message : String(err) },
|
|
1636
|
+
500
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
app.delete("/secrets/:pluginId/:key", async (c) => {
|
|
1641
|
+
const pluginId = c.req.param("pluginId");
|
|
1642
|
+
const key = c.req.param("key");
|
|
1643
|
+
if (!VALID_ID2.test(pluginId)) {
|
|
1644
|
+
return c.json({ success: false, error: "Invalid plugin ID" }, 400);
|
|
1645
|
+
}
|
|
1646
|
+
if (!key || !VALID_KEY.test(key)) {
|
|
1647
|
+
return c.json(
|
|
1648
|
+
{ success: false, error: "Invalid key name \u2014 use letters, digits, underscores" },
|
|
1649
|
+
400
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
try {
|
|
1653
|
+
deletePluginSecret(pluginId, key);
|
|
1654
|
+
return c.json({
|
|
1655
|
+
success: true,
|
|
1656
|
+
data: { key, set: false }
|
|
1657
|
+
});
|
|
1658
|
+
} catch (err) {
|
|
1659
|
+
return c.json(
|
|
1660
|
+
{ success: false, error: err instanceof Error ? err.message : String(err) },
|
|
1661
|
+
500
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
});
|
|
1665
|
+
return app;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
892
1668
|
// src/webui/server.ts
|
|
893
1669
|
function findWebDist() {
|
|
894
1670
|
const candidates = [
|
|
895
|
-
|
|
1671
|
+
resolve2("dist/web"),
|
|
896
1672
|
// npm start / teleton start (from project root)
|
|
897
|
-
|
|
1673
|
+
resolve2("web")
|
|
898
1674
|
// fallback
|
|
899
1675
|
];
|
|
900
1676
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
901
1677
|
candidates.push(
|
|
902
|
-
|
|
1678
|
+
resolve2(__dirname, "web"),
|
|
903
1679
|
// dist/web when __dirname = dist/
|
|
904
|
-
|
|
1680
|
+
resolve2(__dirname, "../dist/web")
|
|
905
1681
|
// when running with tsx from src/
|
|
906
1682
|
);
|
|
907
1683
|
for (const candidate of candidates) {
|
|
908
|
-
if (
|
|
1684
|
+
if (existsSync3(join4(candidate, "index.html"))) {
|
|
909
1685
|
return candidate;
|
|
910
1686
|
}
|
|
911
1687
|
}
|
|
@@ -918,7 +1694,7 @@ var WebUIServer = class {
|
|
|
918
1694
|
authToken;
|
|
919
1695
|
constructor(deps) {
|
|
920
1696
|
this.deps = deps;
|
|
921
|
-
this.app = new
|
|
1697
|
+
this.app = new Hono12();
|
|
922
1698
|
this.authToken = deps.config.auth_token || generateToken();
|
|
923
1699
|
this.setupMiddleware();
|
|
924
1700
|
this.setupRoutes();
|
|
@@ -1023,11 +1799,14 @@ var WebUIServer = class {
|
|
|
1023
1799
|
this.app.route("/api/memory", createMemoryRoutes(this.deps));
|
|
1024
1800
|
this.app.route("/api/soul", createSoulRoutes(this.deps));
|
|
1025
1801
|
this.app.route("/api/plugins", createPluginsRoutes(this.deps));
|
|
1802
|
+
this.app.route("/api/mcp", createMcpRoutes(this.deps));
|
|
1026
1803
|
this.app.route("/api/workspace", createWorkspaceRoutes(this.deps));
|
|
1027
1804
|
this.app.route("/api/tasks", createTasksRoutes(this.deps));
|
|
1805
|
+
this.app.route("/api/config", createConfigRoutes(this.deps));
|
|
1806
|
+
this.app.route("/api/marketplace", createMarketplaceRoutes(this.deps));
|
|
1028
1807
|
const webDist = findWebDist();
|
|
1029
1808
|
if (webDist) {
|
|
1030
|
-
const indexHtml = readFileSync3(
|
|
1809
|
+
const indexHtml = readFileSync3(join4(webDist, "index.html"), "utf-8");
|
|
1031
1810
|
const mimeTypes = {
|
|
1032
1811
|
js: "application/javascript",
|
|
1033
1812
|
css: "text/css",
|
|
@@ -1041,9 +1820,9 @@ var WebUIServer = class {
|
|
|
1041
1820
|
woff: "font/woff"
|
|
1042
1821
|
};
|
|
1043
1822
|
this.app.get("*", (c) => {
|
|
1044
|
-
const filePath =
|
|
1823
|
+
const filePath = resolve2(join4(webDist, c.req.path));
|
|
1045
1824
|
const rel = relative2(webDist, filePath);
|
|
1046
|
-
if (rel.startsWith("..") ||
|
|
1825
|
+
if (rel.startsWith("..") || resolve2(filePath) !== filePath) {
|
|
1047
1826
|
return c.html(indexHtml);
|
|
1048
1827
|
}
|
|
1049
1828
|
try {
|
|
@@ -1073,7 +1852,7 @@ var WebUIServer = class {
|
|
|
1073
1852
|
});
|
|
1074
1853
|
}
|
|
1075
1854
|
async start() {
|
|
1076
|
-
return new Promise((
|
|
1855
|
+
return new Promise((resolve3, reject) => {
|
|
1077
1856
|
try {
|
|
1078
1857
|
logInterceptor.install();
|
|
1079
1858
|
this.server = serve(
|
|
@@ -1091,7 +1870,7 @@ var WebUIServer = class {
|
|
|
1091
1870
|
` Token: ${maskToken(this.authToken)} (use Bearer header for API access)
|
|
1092
1871
|
`
|
|
1093
1872
|
);
|
|
1094
|
-
|
|
1873
|
+
resolve3();
|
|
1095
1874
|
}
|
|
1096
1875
|
);
|
|
1097
1876
|
} catch (error) {
|
|
@@ -1102,11 +1881,11 @@ var WebUIServer = class {
|
|
|
1102
1881
|
}
|
|
1103
1882
|
async stop() {
|
|
1104
1883
|
if (this.server) {
|
|
1105
|
-
return new Promise((
|
|
1884
|
+
return new Promise((resolve3) => {
|
|
1106
1885
|
this.server.close(() => {
|
|
1107
1886
|
logInterceptor.uninstall();
|
|
1108
1887
|
console.log("\u{1F310} WebUI server stopped");
|
|
1109
|
-
|
|
1888
|
+
resolve3();
|
|
1110
1889
|
});
|
|
1111
1890
|
});
|
|
1112
1891
|
}
|