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,284 @@
|
|
|
1
|
+
const { getStorage } = require("../storage/adapter")
|
|
2
|
+
const { bumpVersion } = require("./configService")
|
|
3
|
+
|
|
4
|
+
const ADAPTER_PREFIX = "adapter:"
|
|
5
|
+
const ADAPTER_PACKAGES_PREFIX = "adapter_packages:"
|
|
6
|
+
|
|
7
|
+
function invalid(message) {
|
|
8
|
+
return Object.assign(new Error(message), {
|
|
9
|
+
code: 85,
|
|
10
|
+
type: "invalid_argument",
|
|
11
|
+
recoverable: false,
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function adapterKey(name) {
|
|
16
|
+
return `${ADAPTER_PREFIX}${name}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function adapterPackagesKey(name) {
|
|
20
|
+
return `${ADAPTER_PACKAGES_PREFIX}${name}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function sanitizeAdapter(adapter) {
|
|
24
|
+
if (!adapter || typeof adapter !== "object") {
|
|
25
|
+
throw invalid("adapter must be an object")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const name = String(adapter.name || "").trim()
|
|
29
|
+
if (!name) throw invalid("adapter.name is required")
|
|
30
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
|
|
31
|
+
throw invalid("adapter.name must start with letter and contain only letters, numbers, underscores, hyphens")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const description = String(adapter.description || "")
|
|
35
|
+
const source = String(adapter.source || "").trim()
|
|
36
|
+
if (!source) throw invalid("adapter.source is required")
|
|
37
|
+
|
|
38
|
+
const executionContext = ["server", "cli"].includes(adapter.execution_context)
|
|
39
|
+
? adapter.execution_context
|
|
40
|
+
: "server"
|
|
41
|
+
|
|
42
|
+
const dependencies = Array.isArray(adapter.dependencies)
|
|
43
|
+
? adapter.dependencies.filter(d => typeof d === "string")
|
|
44
|
+
: []
|
|
45
|
+
|
|
46
|
+
const timeoutMs = Number(adapter.timeout_ms) || 30000
|
|
47
|
+
const memoryLimitMb = Number(adapter.memory_limit_mb) || 128
|
|
48
|
+
const allowNetwork = adapter.allow_network === true
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
name,
|
|
52
|
+
description,
|
|
53
|
+
execution_context: executionContext,
|
|
54
|
+
source,
|
|
55
|
+
dependencies,
|
|
56
|
+
timeout_ms: timeoutMs,
|
|
57
|
+
memory_limit_mb: memoryLimitMb,
|
|
58
|
+
allow_network: allowNetwork,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function listAdapters() {
|
|
63
|
+
const storage = getStorage()
|
|
64
|
+
const keys = await storage.listKeys(ADAPTER_PREFIX)
|
|
65
|
+
const records = await Promise.all(keys.map(k => storage.get(k)))
|
|
66
|
+
return records
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
.sort((a, b) => String(a.name || "").localeCompare(String(b.name || "")))
|
|
69
|
+
.map(toPublicAdapter)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function getAdapter(name) {
|
|
73
|
+
const storage = getStorage()
|
|
74
|
+
const record = await storage.get(adapterKey(name))
|
|
75
|
+
if (!record) return null
|
|
76
|
+
return toPublicAdapter(record)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function getAdapterSource(name) {
|
|
80
|
+
const storage = getStorage()
|
|
81
|
+
const record = await storage.get(adapterKey(name))
|
|
82
|
+
if (!record) return null
|
|
83
|
+
return record.source || null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toPublicAdapter(record) {
|
|
87
|
+
return {
|
|
88
|
+
name: record.name,
|
|
89
|
+
description: record.description || "",
|
|
90
|
+
execution_context: record.execution_context || "server",
|
|
91
|
+
dependencies: record.dependencies || [],
|
|
92
|
+
timeout_ms: record.timeout_ms || 30000,
|
|
93
|
+
memory_limit_mb: record.memory_limit_mb || 128,
|
|
94
|
+
allow_network: record.allow_network === true,
|
|
95
|
+
created_at: record.created_at,
|
|
96
|
+
updated_at: record.updated_at,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function createAdapter(payload) {
|
|
101
|
+
const sanitized = sanitizeAdapter(payload)
|
|
102
|
+
const now = new Date().toISOString()
|
|
103
|
+
|
|
104
|
+
const record = {
|
|
105
|
+
...sanitized,
|
|
106
|
+
created_at: now,
|
|
107
|
+
updated_at: now,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const storage = getStorage()
|
|
111
|
+
await storage.set(adapterKey(sanitized.name), record)
|
|
112
|
+
await bumpVersion()
|
|
113
|
+
return toPublicAdapter(record)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function updateAdapter(name, payload) {
|
|
117
|
+
const storage = getStorage()
|
|
118
|
+
const key = adapterKey(name)
|
|
119
|
+
const current = await storage.get(key)
|
|
120
|
+
|
|
121
|
+
if (!current) {
|
|
122
|
+
throw Object.assign(new Error(`Adapter '${name}' not found`), {
|
|
123
|
+
code: 92,
|
|
124
|
+
type: "resource_not_found",
|
|
125
|
+
recoverable: false,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const sanitized = sanitizeAdapter({ ...payload, name })
|
|
130
|
+
const now = new Date().toISOString()
|
|
131
|
+
|
|
132
|
+
const record = {
|
|
133
|
+
...sanitized,
|
|
134
|
+
created_at: current.created_at || now,
|
|
135
|
+
updated_at: now,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await storage.set(key, record)
|
|
139
|
+
await bumpVersion()
|
|
140
|
+
return toPublicAdapter(record)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function deleteAdapter(name) {
|
|
144
|
+
const storage = getStorage()
|
|
145
|
+
const key = adapterKey(name)
|
|
146
|
+
const current = await storage.get(key)
|
|
147
|
+
if (!current) return false
|
|
148
|
+
|
|
149
|
+
await storage.delete(key)
|
|
150
|
+
await storage.delete(adapterPackagesKey(name))
|
|
151
|
+
await bumpVersion()
|
|
152
|
+
return true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function getAdapterPackages(name) {
|
|
156
|
+
const storage = getStorage()
|
|
157
|
+
const packages = await storage.get(adapterPackagesKey(name))
|
|
158
|
+
return packages || []
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function addAdapterPackage(name, packageName, version = "latest") {
|
|
162
|
+
const { execSync } = require("child_process")
|
|
163
|
+
const path = require("path")
|
|
164
|
+
const fs = require("fs")
|
|
165
|
+
|
|
166
|
+
const storage = getStorage()
|
|
167
|
+
const key = adapterPackagesKey(name)
|
|
168
|
+
const current = await storage.get(key) || []
|
|
169
|
+
|
|
170
|
+
const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`
|
|
171
|
+
|
|
172
|
+
if (!current.includes(packageSpec)) {
|
|
173
|
+
// Create adapter directory if it doesn't exist
|
|
174
|
+
const adapterDir = path.join(process.cwd(), "supercli_storage", "adapters", name)
|
|
175
|
+
fs.mkdirSync(adapterDir, { recursive: true })
|
|
176
|
+
|
|
177
|
+
// Initialize package.json if it doesn't exist
|
|
178
|
+
const packageJsonPath = path.join(adapterDir, "package.json")
|
|
179
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
180
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify({ name, version: "1.0.0" }, null, 2))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
current.push(packageSpec)
|
|
184
|
+
await storage.set(key, current)
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
// Install only in adapter-specific directory
|
|
188
|
+
execSync(`npm install ${packageSpec}`, {
|
|
189
|
+
cwd: adapterDir,
|
|
190
|
+
stdio: "inherit",
|
|
191
|
+
})
|
|
192
|
+
} catch (err) {
|
|
193
|
+
// Rollback if install fails
|
|
194
|
+
const filtered = current.filter(p => p !== packageSpec)
|
|
195
|
+
await storage.set(key, filtered)
|
|
196
|
+
throw Object.assign(new Error(`Failed to install package ${packageSpec}`), {
|
|
197
|
+
code: 105,
|
|
198
|
+
type: "integration_error",
|
|
199
|
+
recoverable: false
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return current
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function removeAdapterPackage(name, packageName) {
|
|
208
|
+
const { execSync } = require("child_process")
|
|
209
|
+
const path = require("path")
|
|
210
|
+
const fs = require("fs")
|
|
211
|
+
|
|
212
|
+
const storage = getStorage()
|
|
213
|
+
const key = adapterPackagesKey(name)
|
|
214
|
+
const current = await storage.get(key) || []
|
|
215
|
+
|
|
216
|
+
const filtered = current.filter(p => !p.startsWith(`${packageName}@`) && p !== packageName)
|
|
217
|
+
|
|
218
|
+
if (filtered.length !== current.length) {
|
|
219
|
+
await storage.set(key, filtered)
|
|
220
|
+
|
|
221
|
+
// Uninstall from adapter-specific directory only
|
|
222
|
+
const adapterDir = path.join(process.cwd(), "supercli_storage", "adapters", name)
|
|
223
|
+
if (fs.existsSync(adapterDir)) {
|
|
224
|
+
try {
|
|
225
|
+
execSync(`npm uninstall ${packageName}`, {
|
|
226
|
+
cwd: adapterDir,
|
|
227
|
+
stdio: "inherit",
|
|
228
|
+
})
|
|
229
|
+
} catch (err) {
|
|
230
|
+
// Don't fail if uninstall fails, just log
|
|
231
|
+
console.error(`Failed to uninstall ${packageName} from adapter directory:`, err.message)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return filtered
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function setAdapterPackages(name, packages) {
|
|
240
|
+
const { execSync } = require("child_process")
|
|
241
|
+
const path = require("path")
|
|
242
|
+
const fs = require("fs")
|
|
243
|
+
|
|
244
|
+
const storage = getStorage()
|
|
245
|
+
const key = adapterPackagesKey(name)
|
|
246
|
+
|
|
247
|
+
// Get current packages to determine which ones to uninstall
|
|
248
|
+
const current = await storage.get(key) || []
|
|
249
|
+
const newPackages = Array.isArray(packages) ? packages : []
|
|
250
|
+
|
|
251
|
+
// Find packages that are being removed
|
|
252
|
+
const packagesToRemove = current.filter(p => !newPackages.includes(p))
|
|
253
|
+
|
|
254
|
+
// Uninstall removed packages from adapter directory
|
|
255
|
+
const adapterDir = path.join(process.cwd(), "supercli_storage", "adapters", name)
|
|
256
|
+
if (packagesToRemove.length > 0 && fs.existsSync(adapterDir)) {
|
|
257
|
+
for (const pkg of packagesToRemove) {
|
|
258
|
+
const packageName = pkg.split('@')[0]
|
|
259
|
+
try {
|
|
260
|
+
execSync(`npm uninstall ${packageName}`, {
|
|
261
|
+
cwd: adapterDir,
|
|
262
|
+
stdio: "inherit",
|
|
263
|
+
})
|
|
264
|
+
} catch (err) {
|
|
265
|
+
console.error(`Failed to uninstall ${packageName} during prune:`, err.message)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
await storage.set(key, newPackages)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = {
|
|
274
|
+
listAdapters,
|
|
275
|
+
getAdapter,
|
|
276
|
+
getAdapterSource,
|
|
277
|
+
createAdapter,
|
|
278
|
+
updateAdapter,
|
|
279
|
+
deleteAdapter,
|
|
280
|
+
getAdapterPackages,
|
|
281
|
+
addAdapterPackage,
|
|
282
|
+
removeAdapterPackage,
|
|
283
|
+
setAdapterPackages,
|
|
284
|
+
}
|
|
@@ -17,6 +17,7 @@ function defaultSettings() {
|
|
|
17
17
|
return {
|
|
18
18
|
max_zip_mb: 10,
|
|
19
19
|
default_hooks_policy: "deny",
|
|
20
|
+
admin_mode_enabled: false,
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -115,6 +116,9 @@ async function updateSettings(patch = {}) {
|
|
|
115
116
|
if (patch.default_hooks_policy !== undefined) {
|
|
116
117
|
next.default_hooks_policy = sanitizeHooksPolicy(patch.default_hooks_policy, next.default_hooks_policy)
|
|
117
118
|
}
|
|
119
|
+
if (patch.admin_mode_enabled !== undefined) {
|
|
120
|
+
next.admin_mode_enabled = patch.admin_mode_enabled === true || patch.admin_mode_enabled === "true"
|
|
121
|
+
}
|
|
118
122
|
const storage = getStorage()
|
|
119
123
|
await storage.set(SETTINGS_KEY, next)
|
|
120
124
|
return next
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<%- include("partials/head", { title: adapter ? "Edit Adapter" : "New Adapter" }) %>
|
|
2
|
+
|
|
3
|
+
<div class="max-w-4xl">
|
|
4
|
+
<div class="flex justify-between items-center mb-6">
|
|
5
|
+
<div>
|
|
6
|
+
<p class="text-xs uppercase tracking-[0.2em] text-[#787774] mb-1">Management</p>
|
|
7
|
+
<h1 class="text-2xl editorial-heading text-[#111111]"><%= adapter ? "Edit Adapter" : "New Adapter" %></h1>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="flex gap-2">
|
|
10
|
+
<% if (adapter) { %>
|
|
11
|
+
<a href="/api/adapters/<%= adapter.name %>/packages" class="btn">Packages</a>
|
|
12
|
+
<% } %>
|
|
13
|
+
<a href="/api/adapters" class="btn">Back</a>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div id="adapter-editor">
|
|
18
|
+
<div v-if="message.text" :class="['alert mb-4', message.type === 'error' ? 'alert-error' : 'alert-success']">
|
|
19
|
+
<span>{{ message.text }}</span>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<form @submit.prevent="save" class="flex flex-col gap-4">
|
|
23
|
+
<div class="form-control">
|
|
24
|
+
<label class="label"><span class="label-text">Name</span></label>
|
|
25
|
+
<input v-model="form.name" class="input input-bordered input-sm" required
|
|
26
|
+
placeholder="e.g. postgres" :disabled="!!editName"
|
|
27
|
+
pattern="[a-zA-Z][a-zA-Z0-9_-]*"
|
|
28
|
+
title="Must start with letter, contain only letters, numbers, underscores, hyphens">
|
|
29
|
+
<span class="text-xs text-[#787774] mt-1">Unique identifier for this adapter</span>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="form-control">
|
|
33
|
+
<label class="label"><span class="label-text">Description</span></label>
|
|
34
|
+
<input v-model="form.description" class="input input-bordered input-sm"
|
|
35
|
+
placeholder="What does this adapter do?">
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="form-control">
|
|
39
|
+
<label class="label"><span class="label-text">Execution Context</span></label>
|
|
40
|
+
<div class="flex gap-4 mt-2">
|
|
41
|
+
<label class="flex items-center gap-2 cursor-pointer">
|
|
42
|
+
<input type="radio" v-model="form.execution_context" value="server" class="radio">
|
|
43
|
+
<span>Server (runs on server, CLI receives result)</span>
|
|
44
|
+
</label>
|
|
45
|
+
<label class="flex items-center gap-2 cursor-pointer">
|
|
46
|
+
<input type="radio" v-model="form.execution_context" value="cli" class="radio">
|
|
47
|
+
<span>CLI (syncs to CLI, runs locally)</span>
|
|
48
|
+
</label>
|
|
49
|
+
</div>
|
|
50
|
+
<span class="text-xs text-[#787774] mt-1">
|
|
51
|
+
{{ form.execution_context === 'server' ?
|
|
52
|
+
'Adapter runs on the server. Use for accessing server-side resources like internal databases.' :
|
|
53
|
+
'Adapter code is synced to CLI and runs locally. Use for accessing user machine resources.' }}
|
|
54
|
+
</span>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="grid grid-cols-3 gap-4">
|
|
58
|
+
<div class="form-control">
|
|
59
|
+
<label class="label"><span class="label-text">Timeout (ms)</span></label>
|
|
60
|
+
<input v-model.number="form.timeout_ms" type="number" class="input input-bordered input-sm"
|
|
61
|
+
min="1000" max="300000" step="1000">
|
|
62
|
+
</div>
|
|
63
|
+
<div class="form-control">
|
|
64
|
+
<label class="label"><span class="label-text">Memory Limit (MB)</span></label>
|
|
65
|
+
<input v-model.number="form.memory_limit_mb" type="number" class="input input-bordered input-sm"
|
|
66
|
+
min="16" max="512" step="16">
|
|
67
|
+
</div>
|
|
68
|
+
<div class="form-control">
|
|
69
|
+
<label class="label"><span class="label-text">Allow Network</span></label>
|
|
70
|
+
<input v-model="form.allow_network" type="checkbox" class="checkbox">
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="form-control">
|
|
75
|
+
<label class="label flex justify-between">
|
|
76
|
+
<span>Source Code</span>
|
|
77
|
+
<span class="text-xs text-[#787774]">Must export an 'execute' function</span>
|
|
78
|
+
</label>
|
|
79
|
+
<div ref="editor" class="border border-[#EAEAEA] rounded" style="height: 400px;"></div>
|
|
80
|
+
<span class="text-xs text-[#787774] mt-1">
|
|
81
|
+
The adapter must export an 'execute' function: <code>function execute(cmd, flags, context)</code>
|
|
82
|
+
</span>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="flex gap-2 mt-4">
|
|
86
|
+
<button type="submit" class="btn btn-primary">Save</button>
|
|
87
|
+
<button type="button" @click="testAdapter" class="btn" :disabled="form.execution_context === 'cli'">
|
|
88
|
+
{{ form.execution_context === 'cli' ? 'Test (CLI only)' : 'Test' }}
|
|
89
|
+
</button>
|
|
90
|
+
<a href="/api/adapters" class="btn">Cancel</a>
|
|
91
|
+
</div>
|
|
92
|
+
</form>
|
|
93
|
+
|
|
94
|
+
<div v-if="testResult" class="mt-6 p-4 rounded bg-[#F7F6F3]">
|
|
95
|
+
<h3 class="text-sm font-medium mb-2">Test Result</h3>
|
|
96
|
+
<div v-if="testResult.ok" class="text-green-600 text-sm">
|
|
97
|
+
✓ Success ({{ testResult.duration_ms }}ms)
|
|
98
|
+
</div>
|
|
99
|
+
<div v-else class="text-red-600 text-sm">
|
|
100
|
+
✗ Failed ({{ testResult.duration_ms }}ms): {{ testResult.error }}
|
|
101
|
+
</div>
|
|
102
|
+
<pre v-if="testResult.result" class="mt-2 p-2 bg-white rounded text-xs overflow-auto">{{ JSON.stringify(testResult.result, null, 2) }}</pre>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css">
|
|
108
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/eclipse.min.css">
|
|
109
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
|
|
110
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/javascript/javascript.min.js"></script>
|
|
111
|
+
|
|
112
|
+
<script>
|
|
113
|
+
const defaultSource = `function execute(cmd, flags, context) {
|
|
114
|
+
// cmd: { namespace, resource, action, adapter, adapterConfig, args }
|
|
115
|
+
// flags: { key: value } - command line arguments
|
|
116
|
+
// context: { config, ... } - execution context
|
|
117
|
+
|
|
118
|
+
// Example: return data
|
|
119
|
+
return {
|
|
120
|
+
data: {
|
|
121
|
+
message: "Hello from custom adapter",
|
|
122
|
+
flags: flags
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = { execute }
|
|
128
|
+
`
|
|
129
|
+
|
|
130
|
+
const editData = <%- JSON.stringify(adapter || null) %>
|
|
131
|
+
const editName = '<%= adapter ? adapter.name : "" %>'
|
|
132
|
+
|
|
133
|
+
Vue.createApp({
|
|
134
|
+
data() {
|
|
135
|
+
return {
|
|
136
|
+
editName,
|
|
137
|
+
form: {
|
|
138
|
+
name: editData?.name || '',
|
|
139
|
+
description: editData?.description || '',
|
|
140
|
+
execution_context: editData?.execution_context || 'server',
|
|
141
|
+
timeout_ms: editData?.timeout_ms || 30000,
|
|
142
|
+
memory_limit_mb: editData?.memory_limit_mb || 128,
|
|
143
|
+
allow_network: editData?.allow_network || false,
|
|
144
|
+
},
|
|
145
|
+
source: editData?.source || defaultSource,
|
|
146
|
+
message: { text: '', type: '' },
|
|
147
|
+
testResult: null,
|
|
148
|
+
editor: null,
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
mounted() {
|
|
152
|
+
this.editor = CodeMirror(this.$refs.editor, {
|
|
153
|
+
value: this.source,
|
|
154
|
+
mode: 'javascript',
|
|
155
|
+
theme: 'eclipse',
|
|
156
|
+
lineNumbers: true,
|
|
157
|
+
indentUnit: 2,
|
|
158
|
+
tabSize: 2,
|
|
159
|
+
lineWrapping: true,
|
|
160
|
+
viewportMargin: Infinity,
|
|
161
|
+
})
|
|
162
|
+
this.editor.on('change', () => {
|
|
163
|
+
this.source = this.editor.getValue()
|
|
164
|
+
})
|
|
165
|
+
},
|
|
166
|
+
methods: {
|
|
167
|
+
async save() {
|
|
168
|
+
this.message.text = ''
|
|
169
|
+
this.testResult = null
|
|
170
|
+
|
|
171
|
+
const payload = {
|
|
172
|
+
...this.form,
|
|
173
|
+
source: this.source,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const url = this.editName ? `/api/adapters/${this.editName}` : '/api/adapters'
|
|
178
|
+
const method = this.editName ? 'PUT' : 'POST'
|
|
179
|
+
|
|
180
|
+
const res = await fetch(url, {
|
|
181
|
+
method,
|
|
182
|
+
headers: { 'Content-Type': 'application/json' },
|
|
183
|
+
body: JSON.stringify(payload)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const data = await res.json()
|
|
187
|
+
|
|
188
|
+
if (!res.ok) {
|
|
189
|
+
throw new Error(data.error || 'Save failed')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.message = { text: 'Adapter saved successfully', type: 'success' }
|
|
193
|
+
|
|
194
|
+
if (!this.editName) {
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
window.location.href = `/api/adapters/${data.adapter.name}/edit`
|
|
197
|
+
}, 500)
|
|
198
|
+
}
|
|
199
|
+
} catch (err) {
|
|
200
|
+
this.message = { text: err.message, type: 'error' }
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
async testAdapter() {
|
|
205
|
+
if (this.form.execution_context === 'cli') return
|
|
206
|
+
|
|
207
|
+
this.testResult = null
|
|
208
|
+
this.message.text = ''
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const res = await fetch(`/api/adapters/${this.editName}/test`, {
|
|
212
|
+
method: 'POST',
|
|
213
|
+
headers: { 'Content-Type': 'application/json' },
|
|
214
|
+
body: JSON.stringify({ flags: { test: true } })
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
this.testResult = await res.json()
|
|
218
|
+
} catch (err) {
|
|
219
|
+
this.testResult = { ok: false, error: err.message, duration_ms: 0 }
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}).mount('#adapter-editor')
|
|
224
|
+
</script>
|
|
225
|
+
|
|
226
|
+
<%- include("partials/foot") %>
|