superacli 1.1.20 → 1.1.21
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/cli/adapter-schema.js +1 -6
- package/cli/config.js +95 -0
- package/cli/executor.js +90 -11
- package/cli/server-command.js +395 -0
- package/cli/supercli.js +82 -1
- package/docs/adapters.md +159 -0
- package/docs/changelog-2026-04.html +15 -0
- package/docs/index.html +314 -98
- package/docs/meta-plugins.json +24424 -1247
- package/docs/plugins-examples.md +8 -8
- package/docs/plugins-how-to.md +0 -13
- package/docs/plugins.html +165 -61
- package/docs/server.md +50 -0
- package/graphify-out/GRAPH_REPORT.md +141 -136
- package/graphify-out/cache/1821936911d6eb3130499b6b45b8e6d760d6c05328131e115ac9825a5e3061cd.json +1 -0
- package/graphify-out/cache/20b0e1261b41286d639e7b039c8143682001981f607e26eb2a492a06f12c233a.json +1 -0
- package/graphify-out/cache/21dc011c08a0813ac33c0520c9cf131d8922e6007df7d9b51df740974fee5fb3.json +1 -0
- package/graphify-out/cache/22d3d43c24fdff310ab8445bd442fbcf4714872c6950f15b90777e5a8358dcd9.json +1 -0
- package/graphify-out/cache/2b8a3fbc23afe7bbc1a5ecb60d4074f39afdf6cb42120922176c280f4cc4ab4b.json +1 -0
- package/graphify-out/cache/37418b8b152d9c436d75361969c39bc74a76260ab7209ceabf455f80452f1654.json +1 -0
- package/graphify-out/cache/3b9aae57e4326f5862f8153c13b7d809a34c422996993e7b491c5ea3a66b5e04.json +1 -0
- package/graphify-out/cache/448f8e0c5e77cfb15fa625ed40ee0dc1622d9814d24905fe4c12c4d175131dc9.json +1 -0
- package/graphify-out/cache/47dd42009ceac28b7a7fec5586e565e7bc850c80be96b8329e1ed7c49efbe34f.json +1 -0
- package/graphify-out/cache/492712949d38efbe312c303fe717e5c93e1ecc8888e1ceb855750afc48caffdb.json +1 -0
- package/graphify-out/cache/494fd6af80b6a1bb7a2a5641c6ad11e2fbe2a67b5a2b3ff57fd04bb49ccad1c4.json +1 -0
- package/graphify-out/cache/4b31b0de656deff083119780df64d17205c45e72233987e1c495f2121c4923fd.json +1 -0
- package/graphify-out/cache/5a16fa8cdd687a39661321adb39186ce8cf924a79ef936e42b365380b5e3543e.json +1 -0
- package/graphify-out/cache/644fcb129e595cfdb4e3a9757ed9658230347f5ed0652be45a894528a2b6585b.json +1 -0
- package/graphify-out/cache/6b8762db2c5f51408cf49d915f3e44bcc25c7ebb32abc635307fdf2a672b370c.json +1 -0
- package/graphify-out/cache/6db88050060880e56dd26ec58f29e3b56a409ace48e31bc75f54cab7dc74e409.json +1 -0
- package/graphify-out/cache/7932d1b6a9751b2cb2f4dc6b4e5f9fd7dcc2cee82d7866ec3d655482010533c0.json +1 -0
- package/graphify-out/cache/8140a76ff5b70dfdd0d020aeffff27f87af39da5b237c35baa938fcc4b288ba5.json +1 -0
- package/graphify-out/cache/8876638878758733a412bd4ca4cbd22863322689fbba9f037d92abfa877ea3ab.json +1 -0
- package/graphify-out/cache/90445359c448f8135207ddcd378c8391b9274460064cf998f1ea2ad6f2173703.json +1 -0
- package/graphify-out/cache/b01f675bb3942d2e8f359d11c38d52f28487544f54cdef518c60b7d8dfd2b383.json +1 -0
- package/graphify-out/cache/bc353b40b348d54c730d8498af675f57998b14ae796b4f952cdc200147008f13.json +1 -0
- package/graphify-out/cache/c38b2e784b1c441121b422db0c79b4911fb525d2a3cfad10d3fd561a07900bd5.json +1 -0
- package/graphify-out/cache/ce9df9bb2d36ecf310a53fb34526168bdf25773edf0826c1f55accebfa3c012d.json +1 -0
- package/graphify-out/cache/ceaeb0f99851d313b29cb1440240db1cf835de425b9b0c1e58fa3035355c2b2b.json +1 -0
- package/graphify-out/cache/d8f3e78931a41cd0f367fdab1d11b634bbffb2c90280c75a80a1a53a6b2d6bc7.json +1 -0
- package/graphify-out/cache/f574e4ce3bcb1250ca88410519fb4fd26c5b6cd786039410880fbd6e606ab23f.json +1 -0
- package/graphify-out/cache/faa65636d5f779b8b1c790dde11cc6470b48125bf42a12a77651827fd1653fd7.json +1 -0
- package/graphify-out/graph.json +4173 -2115
- package/ideal-plugins.csv +124 -0
- package/package.json +3 -2
- package/plugin-scores.csv +999 -0
- package/plugins/beads/plugin.json +2 -2
- package/plugins/biome/install-guidance.json +11 -0
- package/plugins/biome/meta.json +13 -0
- package/plugins/biome/plugin.json +83 -0
- package/plugins/biome/skills/quickstart/SKILL.md +25 -0
- package/plugins/bore/install-guidance.json +11 -0
- package/plugins/bore/meta.json +11 -0
- package/plugins/bore/plugin.json +65 -0
- package/plugins/bore/skills/quickstart/SKILL.md +25 -0
- package/plugins/ccf-deadlines/install-guidance.json +11 -0
- package/plugins/ccf-deadlines/meta.json +11 -0
- package/plugins/ccf-deadlines/plugin.json +28 -0
- package/plugins/ccf-deadlines/skills/quickstart/SKILL.md +25 -0
- package/plugins/code2prompt/install-guidance.json +11 -0
- package/plugins/code2prompt/meta.json +12 -0
- package/plugins/code2prompt/plugin.json +46 -0
- package/plugins/code2prompt/skills/quickstart/SKILL.md +25 -0
- package/plugins/commiat/install-guidance.json +10 -0
- package/plugins/commiat/plugin.json +2 -2
- package/plugins/dua-cli/install-guidance.json +11 -0
- package/plugins/dua-cli/meta.json +11 -0
- package/plugins/dua-cli/plugin.json +57 -0
- package/plugins/dua-cli/skills/quickstart/SKILL.md +25 -0
- package/plugins/forever/install-guidance.json +11 -0
- package/plugins/forever/meta.json +11 -0
- package/plugins/forever/plugin.json +75 -0
- package/plugins/forever/skills/quickstart/SKILL.md +25 -0
- package/plugins/gitmoji-cli/install-guidance.json +11 -0
- package/plugins/gitmoji-cli/meta.json +11 -0
- package/plugins/gitmoji-cli/plugin.json +49 -0
- package/plugins/gitmoji-cli/skills/quickstart/SKILL.md +25 -0
- package/plugins/google-maps-scraper/plugin.json +1 -1
- package/plugins/gwc/install-guidance.json +10 -0
- package/plugins/gwc/plugin.json +2 -2
- package/plugins/quarto/install-guidance.json +11 -0
- package/plugins/quarto/meta.json +11 -0
- package/plugins/quarto/plugin.json +61 -0
- package/plugins/quarto/skills/quickstart/SKILL.md +25 -0
- package/plugins/readme-md-generator/install-guidance.json +11 -0
- package/plugins/readme-md-generator/meta.json +11 -0
- package/plugins/readme-md-generator/plugin.json +28 -0
- package/plugins/readme-md-generator/skills/quickstart/SKILL.md +25 -0
- package/plugins/twarc/install-guidance.json +11 -0
- package/plugins/twarc/meta.json +13 -0
- package/plugins/twarc/plugin.json +165 -0
- package/plugins/twarc/skills/quickstart/SKILL.md +61 -0
- package/plugins/vale/install-guidance.json +11 -0
- package/plugins/vale/meta.json +12 -0
- package/plugins/vale/plugin.json +50 -0
- package/plugins/vale/skills/quickstart/SKILL.md +25 -0
- package/plugins/volta/install-guidance.json +11 -0
- package/plugins/volta/meta.json +12 -0
- package/plugins/volta/plugin.json +75 -0
- package/plugins/volta/skills/quickstart/SKILL.md +25 -0
- package/plugins/wgcf/install-guidance.json +11 -0
- package/plugins/wgcf/meta.json +12 -0
- package/plugins/wgcf/plugin.json +45 -0
- package/plugins/wgcf/skills/quickstart/SKILL.md +25 -0
- package/scripts/analyze-plugins.js +130 -0
- package/scripts/enrich-meta-plugins.js +67 -0
- package/server/app.js +23 -1
- package/server/routes/adapters.js +356 -0
- package/server/routes/dashboard.js +113 -0
- package/server/routes/jobs.js +26 -0
- package/server/services/adaptersService.js +284 -0
- package/server/services/pluginsService.js +4 -0
- package/server/views/adapter-edit.ejs +226 -0
- package/server/views/adapter-packages.ejs +191 -0
- package/server/views/adapters.ejs +112 -0
- package/server/views/command-edit.ejs +48 -21
- package/server/views/commands.ejs +25 -22
- package/server/views/dashboard.ejs +196 -0
- package/server/views/layout.ejs +94 -14
- package/server/views/mcp.ejs +38 -35
- package/server/views/partials/head.ejs +88 -12
- package/server/views/plugins.ejs +9 -0
- package/server/views/specs.ejs +33 -30
- package/cli/adapters/builtin.js +0 -43
- package/index.html +0 -384
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
const { Router } = require("express")
|
|
2
|
+
const { NodeVM } = require("vm2")
|
|
3
|
+
const path = require("path")
|
|
4
|
+
const adaptersService = require("../services/adaptersService")
|
|
5
|
+
|
|
6
|
+
const router = Router()
|
|
7
|
+
|
|
8
|
+
function invalid(message) {
|
|
9
|
+
return Object.assign(new Error(message), {
|
|
10
|
+
code: 85,
|
|
11
|
+
type: "invalid_argument",
|
|
12
|
+
recoverable: false,
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// UI: List adapters page
|
|
17
|
+
router.get("/", async (req, res, next) => {
|
|
18
|
+
try {
|
|
19
|
+
const adapters = await adaptersService.listAdapters()
|
|
20
|
+
if (req.headers.accept?.includes("application/json")) {
|
|
21
|
+
return res.json({ adapters })
|
|
22
|
+
}
|
|
23
|
+
res.render("adapters", { adapters })
|
|
24
|
+
} catch (err) {
|
|
25
|
+
next(err)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// UI: New adapter page
|
|
30
|
+
router.get("/new", (req, res) => {
|
|
31
|
+
res.render("adapter-edit", { adapter: null })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// UI: Edit adapter page
|
|
35
|
+
router.get("/:name/edit", async (req, res, next) => {
|
|
36
|
+
try {
|
|
37
|
+
const { name } = req.params
|
|
38
|
+
const adapter = await adaptersService.getAdapter(name)
|
|
39
|
+
if (!adapter) {
|
|
40
|
+
return res.status(404).render("error", { message: `Adapter '${name}' not found` })
|
|
41
|
+
}
|
|
42
|
+
const source = await adaptersService.getAdapterSource(name)
|
|
43
|
+
res.render("adapter-edit", { adapter: { ...adapter, source } })
|
|
44
|
+
} catch (err) {
|
|
45
|
+
next(err)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// UI: Adapter packages page
|
|
50
|
+
router.get("/:name/packages", async (req, res, next) => {
|
|
51
|
+
try {
|
|
52
|
+
const { name } = req.params
|
|
53
|
+
const adapter = await adaptersService.getAdapter(name)
|
|
54
|
+
if (!adapter) {
|
|
55
|
+
return res.status(404).render("error", { message: `Adapter '${name}' not found` })
|
|
56
|
+
}
|
|
57
|
+
const packages = await adaptersService.getAdapterPackages(name)
|
|
58
|
+
res.render("adapter-packages", { adapter, packages })
|
|
59
|
+
} catch (err) {
|
|
60
|
+
next(err)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Get single adapter
|
|
65
|
+
router.get("/:name", async (req, res, next) => {
|
|
66
|
+
try {
|
|
67
|
+
const { name } = req.params
|
|
68
|
+
const adapter = await adaptersService.getAdapter(name)
|
|
69
|
+
if (!adapter) {
|
|
70
|
+
return res.status(404).json({ error: `Adapter '${name}' not found` })
|
|
71
|
+
}
|
|
72
|
+
res.json({ adapter })
|
|
73
|
+
} catch (err) {
|
|
74
|
+
next(err)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Get adapter source code
|
|
79
|
+
router.get("/:name/source", async (req, res, next) => {
|
|
80
|
+
try {
|
|
81
|
+
const { name } = req.params
|
|
82
|
+
const source = await adaptersService.getAdapterSource(name)
|
|
83
|
+
if (!source) {
|
|
84
|
+
return res.status(404).json({ error: `Adapter '${name}' not found` })
|
|
85
|
+
}
|
|
86
|
+
res.json({ source })
|
|
87
|
+
} catch (err) {
|
|
88
|
+
next(err)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Create adapter
|
|
93
|
+
router.post("/", async (req, res, next) => {
|
|
94
|
+
try {
|
|
95
|
+
const { name, description, execution_context, source, dependencies, timeout_ms, memory_limit_mb, allow_network } = req.body
|
|
96
|
+
|
|
97
|
+
if (!name || !source) {
|
|
98
|
+
throw invalid("name and source are required")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const adapter = await adaptersService.createAdapter({
|
|
102
|
+
name,
|
|
103
|
+
description,
|
|
104
|
+
execution_context,
|
|
105
|
+
source,
|
|
106
|
+
dependencies,
|
|
107
|
+
timeout_ms,
|
|
108
|
+
memory_limit_mb,
|
|
109
|
+
allow_network,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
res.status(201).json({ adapter })
|
|
113
|
+
} catch (err) {
|
|
114
|
+
next(err)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Update adapter
|
|
119
|
+
router.put("/:name", async (req, res, next) => {
|
|
120
|
+
try {
|
|
121
|
+
const { name } = req.params
|
|
122
|
+
const { description, execution_context, source, dependencies, timeout_ms, memory_limit_mb, allow_network } = req.body
|
|
123
|
+
|
|
124
|
+
const adapter = await adaptersService.updateAdapter(name, {
|
|
125
|
+
description,
|
|
126
|
+
execution_context,
|
|
127
|
+
source,
|
|
128
|
+
dependencies,
|
|
129
|
+
timeout_ms,
|
|
130
|
+
memory_limit_mb,
|
|
131
|
+
allow_network,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
res.json({ adapter })
|
|
135
|
+
} catch (err) {
|
|
136
|
+
next(err)
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Delete adapter
|
|
141
|
+
router.delete("/:name", async (req, res, next) => {
|
|
142
|
+
try {
|
|
143
|
+
const { name } = req.params
|
|
144
|
+
const deleted = await adaptersService.deleteAdapter(name)
|
|
145
|
+
if (!deleted) {
|
|
146
|
+
return res.status(404).json({ error: `Adapter '${name}' not found` })
|
|
147
|
+
}
|
|
148
|
+
res.json({ ok: true, message: `Adapter '${name}' deleted` })
|
|
149
|
+
} catch (err) {
|
|
150
|
+
next(err)
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Get adapter packages
|
|
155
|
+
router.get("/:name/packages", async (req, res, next) => {
|
|
156
|
+
try {
|
|
157
|
+
const { name } = req.params
|
|
158
|
+
const packages = await adaptersService.getAdapterPackages(name)
|
|
159
|
+
res.json({ packages })
|
|
160
|
+
} catch (err) {
|
|
161
|
+
next(err)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Add package to adapter
|
|
166
|
+
router.post("/:name/packages", async (req, res, next) => {
|
|
167
|
+
try {
|
|
168
|
+
const { name } = req.params
|
|
169
|
+
const { package: packageName, version, packages } = req.body
|
|
170
|
+
|
|
171
|
+
// Handle prune all (set packages array)
|
|
172
|
+
if (packages !== undefined) {
|
|
173
|
+
const result = await adaptersService.setAdapterPackages(name, packages)
|
|
174
|
+
res.json({ packages: result })
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Handle add single package
|
|
179
|
+
if (!packageName) {
|
|
180
|
+
throw invalid("package is required")
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const result = await adaptersService.addAdapterPackage(name, packageName, version)
|
|
184
|
+
res.json({ packages: result })
|
|
185
|
+
} catch (err) {
|
|
186
|
+
next(err)
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// Remove package from adapter
|
|
191
|
+
router.delete("/:name/packages/:package", async (req, res, next) => {
|
|
192
|
+
try {
|
|
193
|
+
const { name, package: packageName } = req.params
|
|
194
|
+
const packages = await adaptersService.removeAdapterPackage(name, packageName)
|
|
195
|
+
res.json({ packages })
|
|
196
|
+
} catch (err) {
|
|
197
|
+
next(err)
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// Test run adapter (server context only)
|
|
202
|
+
router.post("/:name/test", async (req, res, next) => {
|
|
203
|
+
try {
|
|
204
|
+
const { name } = req.params
|
|
205
|
+
const { flags = {}, context = {} } = req.body
|
|
206
|
+
|
|
207
|
+
const adapter = await adaptersService.getAdapter(name)
|
|
208
|
+
if (!adapter) {
|
|
209
|
+
return res.status(404).json({ error: `Adapter '${name}' not found` })
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (adapter.execution_context === "cli") {
|
|
213
|
+
return res.status(400).json({
|
|
214
|
+
error: "Cannot test CLI-context adapter on server. Test locally using the CLI."
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const source = await adaptersService.getAdapterSource(name)
|
|
219
|
+
if (!source) {
|
|
220
|
+
return res.status(404).json({ error: `Adapter '${name}' source not found` })
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const startTime = Date.now()
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const vm = new NodeVM({
|
|
227
|
+
timeout: adapter.timeout_ms,
|
|
228
|
+
sandbox: {
|
|
229
|
+
console: {
|
|
230
|
+
log: (...args) => {},
|
|
231
|
+
error: (...args) => {},
|
|
232
|
+
warn: (...args) => {},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
require: {
|
|
236
|
+
external: adapter.allow_network,
|
|
237
|
+
root: process.cwd(),
|
|
238
|
+
},
|
|
239
|
+
memoryLimit: adapter.memory_limit_mb * 1024 * 1024,
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const script = `
|
|
243
|
+
${source}
|
|
244
|
+
module.exports = { execute }
|
|
245
|
+
`
|
|
246
|
+
|
|
247
|
+
const fn = vm.run(script, "adapter.js")
|
|
248
|
+
|
|
249
|
+
if (typeof fn.execute !== "function") {
|
|
250
|
+
throw new Error("Adapter must export an 'execute' function")
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const mockCmd = {
|
|
254
|
+
namespace: "test",
|
|
255
|
+
resource: "test",
|
|
256
|
+
action: "test",
|
|
257
|
+
adapter: name,
|
|
258
|
+
adapterConfig: {},
|
|
259
|
+
args: [],
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const result = await fn.execute(mockCmd, flags, context)
|
|
263
|
+
|
|
264
|
+
const duration = Date.now() - startTime
|
|
265
|
+
|
|
266
|
+
res.json({
|
|
267
|
+
ok: true,
|
|
268
|
+
result,
|
|
269
|
+
duration_ms: duration,
|
|
270
|
+
message: "Adapter executed successfully",
|
|
271
|
+
})
|
|
272
|
+
} catch (execErr) {
|
|
273
|
+
const duration = Date.now() - startTime
|
|
274
|
+
res.json({
|
|
275
|
+
ok: false,
|
|
276
|
+
error: execErr.message,
|
|
277
|
+
duration_ms: duration,
|
|
278
|
+
message: "Adapter execution failed",
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
} catch (err) {
|
|
282
|
+
next(err)
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
// Execute adapter on server (for CLI delegation)
|
|
287
|
+
router.post("/execute", async (req, res, next) => {
|
|
288
|
+
try {
|
|
289
|
+
const { cmd, flags = {}, context = {} } = req.body
|
|
290
|
+
|
|
291
|
+
if (!cmd || !cmd.adapter) {
|
|
292
|
+
throw invalid("cmd.adapter is required")
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const adapter = await adaptersService.getAdapter(cmd.adapter)
|
|
296
|
+
if (!adapter) {
|
|
297
|
+
return res.status(404).json({ error: `Adapter '${cmd.adapter}' not found` })
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (adapter.execution_context === "cli") {
|
|
301
|
+
return res.status(400).json({
|
|
302
|
+
error: "CLI-context adapters must be executed locally"
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const source = await adaptersService.getAdapterSource(cmd.adapter)
|
|
307
|
+
if (!source) {
|
|
308
|
+
return res.status(404).json({ error: `Adapter '${cmd.adapter}' source not found` })
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const adapterDir = path.join(process.cwd(), "supercli_storage", "adapters", cmd.adapter)
|
|
312
|
+
const adapterNodeModules = path.join(adapterDir, "node_modules")
|
|
313
|
+
|
|
314
|
+
const vm = new NodeVM({
|
|
315
|
+
timeout: adapter.timeout_ms,
|
|
316
|
+
sandbox: {
|
|
317
|
+
console: {
|
|
318
|
+
log: (...args) => {},
|
|
319
|
+
error: (...args) => {},
|
|
320
|
+
warn: (...args) => {},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
require: {
|
|
324
|
+
external: true,
|
|
325
|
+
root: [adapterDir, adapterNodeModules],
|
|
326
|
+
builtin: ["*"],
|
|
327
|
+
resolve: (moduleName) => {
|
|
328
|
+
try {
|
|
329
|
+
return require.resolve(moduleName, { paths: [adapterNodeModules] })
|
|
330
|
+
} catch {
|
|
331
|
+
return require.resolve(moduleName)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
memoryLimit: adapter.memory_limit_mb * 1024 * 1024,
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
const script = `
|
|
339
|
+
${source}
|
|
340
|
+
module.exports = { execute }
|
|
341
|
+
`
|
|
342
|
+
|
|
343
|
+
const fn = vm.run(script, "adapter.js")
|
|
344
|
+
|
|
345
|
+
if (typeof fn.execute !== "function") {
|
|
346
|
+
throw invalid("Adapter must export an 'execute' function")
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const result = await fn.execute(cmd, flags, context)
|
|
350
|
+
res.json(result)
|
|
351
|
+
} catch (err) {
|
|
352
|
+
next(err)
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
module.exports = router
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { Router } = require("express")
|
|
2
|
+
const { getStorage } = require("../storage/adapter")
|
|
3
|
+
const { listServerPlugins } = require("../services/pluginsService")
|
|
4
|
+
|
|
5
|
+
const router = Router()
|
|
6
|
+
|
|
7
|
+
async function getDashboardData() {
|
|
8
|
+
const storage = getStorage()
|
|
9
|
+
|
|
10
|
+
const [commandKeys, mcpKeys, specKeys, jobKeys] = await Promise.all([
|
|
11
|
+
storage.listKeys("command:"),
|
|
12
|
+
storage.listKeys("mcp:"),
|
|
13
|
+
storage.listKeys("spec:"),
|
|
14
|
+
storage.listKeys("job:"),
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
const [commands, mcpServers, specs, allJobs] = await Promise.all([
|
|
18
|
+
Promise.all(commandKeys.map(k => storage.get(k))),
|
|
19
|
+
Promise.all(mcpKeys.map(k => storage.get(k))),
|
|
20
|
+
Promise.all(specKeys.map(k => storage.get(k))),
|
|
21
|
+
Promise.all(jobKeys.map(k => storage.get(k))),
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
let plugins = []
|
|
25
|
+
try {
|
|
26
|
+
plugins = await listServerPlugins()
|
|
27
|
+
} catch (_) {}
|
|
28
|
+
|
|
29
|
+
const validJobs = allJobs.filter(Boolean)
|
|
30
|
+
const totalJobs = validJobs.length
|
|
31
|
+
const successJobs = validJobs.filter(j => j.status === "success").length
|
|
32
|
+
const failedJobs = validJobs.filter(j => j.status === "failed").length
|
|
33
|
+
const successRate = totalJobs > 0 ? ((successJobs / totalJobs) * 100).toFixed(1) : "0.0"
|
|
34
|
+
const failureRate = totalJobs > 0 ? ((failedJobs / totalJobs) * 100).toFixed(1) : "0.0"
|
|
35
|
+
|
|
36
|
+
const avgDuration =
|
|
37
|
+
totalJobs > 0
|
|
38
|
+
? Math.round(validJobs.reduce((sum, j) => sum + (j.duration_ms || 0), 0) / totalJobs)
|
|
39
|
+
: 0
|
|
40
|
+
|
|
41
|
+
const commandStats = {}
|
|
42
|
+
for (const job of validJobs) {
|
|
43
|
+
if (!job.command) continue
|
|
44
|
+
if (!commandStats[job.command]) commandStats[job.command] = { count: 0, sum_ms: 0 }
|
|
45
|
+
commandStats[job.command].count++
|
|
46
|
+
commandStats[job.command].sum_ms += job.duration_ms || 0
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const topCommands = Object.entries(commandStats)
|
|
50
|
+
.map(([cmd, stats]) => ({
|
|
51
|
+
command: cmd,
|
|
52
|
+
count: stats.count,
|
|
53
|
+
avg_ms: Math.round(stats.sum_ms / stats.count) || 0,
|
|
54
|
+
}))
|
|
55
|
+
.sort((a, b) => b.count - a.count)
|
|
56
|
+
.slice(0, 5)
|
|
57
|
+
|
|
58
|
+
const recentJobs = validJobs
|
|
59
|
+
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
|
|
60
|
+
.slice(0, 10)
|
|
61
|
+
|
|
62
|
+
// 7-day job trend
|
|
63
|
+
const now = Date.now()
|
|
64
|
+
const dayMs = 86400000
|
|
65
|
+
const trend = []
|
|
66
|
+
for (let i = 6; i >= 0; i--) {
|
|
67
|
+
const dayStart = now - i * dayMs
|
|
68
|
+
const dayEnd = dayStart + dayMs
|
|
69
|
+
const label = new Date(dayStart).toLocaleDateString("en-US", { weekday: "short" })
|
|
70
|
+
const dayJobs = validJobs.filter(j => {
|
|
71
|
+
const t = new Date(j.timestamp).getTime()
|
|
72
|
+
return t >= dayStart && t < dayEnd
|
|
73
|
+
})
|
|
74
|
+
trend.push({
|
|
75
|
+
label,
|
|
76
|
+
total: dayJobs.length,
|
|
77
|
+
success: dayJobs.filter(j => j.status === "success").length,
|
|
78
|
+
failed: dayJobs.filter(j => j.status === "failed").length,
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
counts: {
|
|
84
|
+
commands: commands.filter(Boolean).length,
|
|
85
|
+
mcpServers: mcpServers.filter(Boolean).length,
|
|
86
|
+
specs: specs.filter(Boolean).length,
|
|
87
|
+
plugins: plugins.length,
|
|
88
|
+
pluginsEnabled: plugins.filter(p => p.enabled !== false).length,
|
|
89
|
+
},
|
|
90
|
+
jobs: {
|
|
91
|
+
total: totalJobs,
|
|
92
|
+
success: successJobs,
|
|
93
|
+
failed: failedJobs,
|
|
94
|
+
successRate,
|
|
95
|
+
failureRate,
|
|
96
|
+
avgDuration,
|
|
97
|
+
},
|
|
98
|
+
topCommands,
|
|
99
|
+
recentJobs,
|
|
100
|
+
trend,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
router.get("/", async (req, res) => {
|
|
105
|
+
try {
|
|
106
|
+
const data = await getDashboardData()
|
|
107
|
+
res.render("dashboard", data)
|
|
108
|
+
} catch (err) {
|
|
109
|
+
res.status(500).send(err.message)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
module.exports = router
|
package/server/routes/jobs.js
CHANGED
|
@@ -119,4 +119,30 @@ router.get("/:id", async (req, res) => {
|
|
|
119
119
|
}
|
|
120
120
|
})
|
|
121
121
|
|
|
122
|
+
// DELETE /api/jobs — prune old jobs (admin mode)
|
|
123
|
+
router.delete("/", async (req, res) => {
|
|
124
|
+
try {
|
|
125
|
+
const storage = getStorage()
|
|
126
|
+
const olderThanDays = parseInt(req.query.older_than) || 7
|
|
127
|
+
const cutoff = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000)
|
|
128
|
+
|
|
129
|
+
const keys = await storage.listKeys("job:")
|
|
130
|
+
const jobs = await Promise.all(keys.map(k => storage.get(k)))
|
|
131
|
+
|
|
132
|
+
let pruned = 0
|
|
133
|
+
for (const job of jobs) {
|
|
134
|
+
if (!job || !job.timestamp) continue
|
|
135
|
+
const jobDate = new Date(job.timestamp)
|
|
136
|
+
if (jobDate < cutoff) {
|
|
137
|
+
await storage.delete(job._id)
|
|
138
|
+
pruned++
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
res.json({ ok: true, pruned, older_than_days: olderThanDays })
|
|
143
|
+
} catch (err) {
|
|
144
|
+
res.status(500).json({ error: err.message })
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
|
|
122
148
|
module.exports = router
|