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.
Files changed (125) hide show
  1. package/cli/adapter-schema.js +1 -6
  2. package/cli/config.js +95 -0
  3. package/cli/executor.js +90 -11
  4. package/cli/server-command.js +395 -0
  5. package/cli/supercli.js +82 -1
  6. package/docs/adapters.md +159 -0
  7. package/docs/changelog-2026-04.html +15 -0
  8. package/docs/index.html +314 -98
  9. package/docs/meta-plugins.json +24424 -1247
  10. package/docs/plugins-examples.md +8 -8
  11. package/docs/plugins-how-to.md +0 -13
  12. package/docs/plugins.html +165 -61
  13. package/docs/server.md +50 -0
  14. package/graphify-out/GRAPH_REPORT.md +141 -136
  15. package/graphify-out/cache/1821936911d6eb3130499b6b45b8e6d760d6c05328131e115ac9825a5e3061cd.json +1 -0
  16. package/graphify-out/cache/20b0e1261b41286d639e7b039c8143682001981f607e26eb2a492a06f12c233a.json +1 -0
  17. package/graphify-out/cache/21dc011c08a0813ac33c0520c9cf131d8922e6007df7d9b51df740974fee5fb3.json +1 -0
  18. package/graphify-out/cache/22d3d43c24fdff310ab8445bd442fbcf4714872c6950f15b90777e5a8358dcd9.json +1 -0
  19. package/graphify-out/cache/2b8a3fbc23afe7bbc1a5ecb60d4074f39afdf6cb42120922176c280f4cc4ab4b.json +1 -0
  20. package/graphify-out/cache/37418b8b152d9c436d75361969c39bc74a76260ab7209ceabf455f80452f1654.json +1 -0
  21. package/graphify-out/cache/3b9aae57e4326f5862f8153c13b7d809a34c422996993e7b491c5ea3a66b5e04.json +1 -0
  22. package/graphify-out/cache/448f8e0c5e77cfb15fa625ed40ee0dc1622d9814d24905fe4c12c4d175131dc9.json +1 -0
  23. package/graphify-out/cache/47dd42009ceac28b7a7fec5586e565e7bc850c80be96b8329e1ed7c49efbe34f.json +1 -0
  24. package/graphify-out/cache/492712949d38efbe312c303fe717e5c93e1ecc8888e1ceb855750afc48caffdb.json +1 -0
  25. package/graphify-out/cache/494fd6af80b6a1bb7a2a5641c6ad11e2fbe2a67b5a2b3ff57fd04bb49ccad1c4.json +1 -0
  26. package/graphify-out/cache/4b31b0de656deff083119780df64d17205c45e72233987e1c495f2121c4923fd.json +1 -0
  27. package/graphify-out/cache/5a16fa8cdd687a39661321adb39186ce8cf924a79ef936e42b365380b5e3543e.json +1 -0
  28. package/graphify-out/cache/644fcb129e595cfdb4e3a9757ed9658230347f5ed0652be45a894528a2b6585b.json +1 -0
  29. package/graphify-out/cache/6b8762db2c5f51408cf49d915f3e44bcc25c7ebb32abc635307fdf2a672b370c.json +1 -0
  30. package/graphify-out/cache/6db88050060880e56dd26ec58f29e3b56a409ace48e31bc75f54cab7dc74e409.json +1 -0
  31. package/graphify-out/cache/7932d1b6a9751b2cb2f4dc6b4e5f9fd7dcc2cee82d7866ec3d655482010533c0.json +1 -0
  32. package/graphify-out/cache/8140a76ff5b70dfdd0d020aeffff27f87af39da5b237c35baa938fcc4b288ba5.json +1 -0
  33. package/graphify-out/cache/8876638878758733a412bd4ca4cbd22863322689fbba9f037d92abfa877ea3ab.json +1 -0
  34. package/graphify-out/cache/90445359c448f8135207ddcd378c8391b9274460064cf998f1ea2ad6f2173703.json +1 -0
  35. package/graphify-out/cache/b01f675bb3942d2e8f359d11c38d52f28487544f54cdef518c60b7d8dfd2b383.json +1 -0
  36. package/graphify-out/cache/bc353b40b348d54c730d8498af675f57998b14ae796b4f952cdc200147008f13.json +1 -0
  37. package/graphify-out/cache/c38b2e784b1c441121b422db0c79b4911fb525d2a3cfad10d3fd561a07900bd5.json +1 -0
  38. package/graphify-out/cache/ce9df9bb2d36ecf310a53fb34526168bdf25773edf0826c1f55accebfa3c012d.json +1 -0
  39. package/graphify-out/cache/ceaeb0f99851d313b29cb1440240db1cf835de425b9b0c1e58fa3035355c2b2b.json +1 -0
  40. package/graphify-out/cache/d8f3e78931a41cd0f367fdab1d11b634bbffb2c90280c75a80a1a53a6b2d6bc7.json +1 -0
  41. package/graphify-out/cache/f574e4ce3bcb1250ca88410519fb4fd26c5b6cd786039410880fbd6e606ab23f.json +1 -0
  42. package/graphify-out/cache/faa65636d5f779b8b1c790dde11cc6470b48125bf42a12a77651827fd1653fd7.json +1 -0
  43. package/graphify-out/graph.json +4173 -2115
  44. package/ideal-plugins.csv +124 -0
  45. package/package.json +3 -2
  46. package/plugin-scores.csv +999 -0
  47. package/plugins/beads/plugin.json +2 -2
  48. package/plugins/biome/install-guidance.json +11 -0
  49. package/plugins/biome/meta.json +13 -0
  50. package/plugins/biome/plugin.json +83 -0
  51. package/plugins/biome/skills/quickstart/SKILL.md +25 -0
  52. package/plugins/bore/install-guidance.json +11 -0
  53. package/plugins/bore/meta.json +11 -0
  54. package/plugins/bore/plugin.json +65 -0
  55. package/plugins/bore/skills/quickstart/SKILL.md +25 -0
  56. package/plugins/ccf-deadlines/install-guidance.json +11 -0
  57. package/plugins/ccf-deadlines/meta.json +11 -0
  58. package/plugins/ccf-deadlines/plugin.json +28 -0
  59. package/plugins/ccf-deadlines/skills/quickstart/SKILL.md +25 -0
  60. package/plugins/code2prompt/install-guidance.json +11 -0
  61. package/plugins/code2prompt/meta.json +12 -0
  62. package/plugins/code2prompt/plugin.json +46 -0
  63. package/plugins/code2prompt/skills/quickstart/SKILL.md +25 -0
  64. package/plugins/commiat/install-guidance.json +10 -0
  65. package/plugins/commiat/plugin.json +2 -2
  66. package/plugins/dua-cli/install-guidance.json +11 -0
  67. package/plugins/dua-cli/meta.json +11 -0
  68. package/plugins/dua-cli/plugin.json +57 -0
  69. package/plugins/dua-cli/skills/quickstart/SKILL.md +25 -0
  70. package/plugins/forever/install-guidance.json +11 -0
  71. package/plugins/forever/meta.json +11 -0
  72. package/plugins/forever/plugin.json +75 -0
  73. package/plugins/forever/skills/quickstart/SKILL.md +25 -0
  74. package/plugins/gitmoji-cli/install-guidance.json +11 -0
  75. package/plugins/gitmoji-cli/meta.json +11 -0
  76. package/plugins/gitmoji-cli/plugin.json +49 -0
  77. package/plugins/gitmoji-cli/skills/quickstart/SKILL.md +25 -0
  78. package/plugins/google-maps-scraper/plugin.json +1 -1
  79. package/plugins/gwc/install-guidance.json +10 -0
  80. package/plugins/gwc/plugin.json +2 -2
  81. package/plugins/quarto/install-guidance.json +11 -0
  82. package/plugins/quarto/meta.json +11 -0
  83. package/plugins/quarto/plugin.json +61 -0
  84. package/plugins/quarto/skills/quickstart/SKILL.md +25 -0
  85. package/plugins/readme-md-generator/install-guidance.json +11 -0
  86. package/plugins/readme-md-generator/meta.json +11 -0
  87. package/plugins/readme-md-generator/plugin.json +28 -0
  88. package/plugins/readme-md-generator/skills/quickstart/SKILL.md +25 -0
  89. package/plugins/twarc/install-guidance.json +11 -0
  90. package/plugins/twarc/meta.json +13 -0
  91. package/plugins/twarc/plugin.json +165 -0
  92. package/plugins/twarc/skills/quickstart/SKILL.md +61 -0
  93. package/plugins/vale/install-guidance.json +11 -0
  94. package/plugins/vale/meta.json +12 -0
  95. package/plugins/vale/plugin.json +50 -0
  96. package/plugins/vale/skills/quickstart/SKILL.md +25 -0
  97. package/plugins/volta/install-guidance.json +11 -0
  98. package/plugins/volta/meta.json +12 -0
  99. package/plugins/volta/plugin.json +75 -0
  100. package/plugins/volta/skills/quickstart/SKILL.md +25 -0
  101. package/plugins/wgcf/install-guidance.json +11 -0
  102. package/plugins/wgcf/meta.json +12 -0
  103. package/plugins/wgcf/plugin.json +45 -0
  104. package/plugins/wgcf/skills/quickstart/SKILL.md +25 -0
  105. package/scripts/analyze-plugins.js +130 -0
  106. package/scripts/enrich-meta-plugins.js +67 -0
  107. package/server/app.js +23 -1
  108. package/server/routes/adapters.js +356 -0
  109. package/server/routes/dashboard.js +113 -0
  110. package/server/routes/jobs.js +26 -0
  111. package/server/services/adaptersService.js +284 -0
  112. package/server/services/pluginsService.js +4 -0
  113. package/server/views/adapter-edit.ejs +226 -0
  114. package/server/views/adapter-packages.ejs +191 -0
  115. package/server/views/adapters.ejs +112 -0
  116. package/server/views/command-edit.ejs +48 -21
  117. package/server/views/commands.ejs +25 -22
  118. package/server/views/dashboard.ejs +196 -0
  119. package/server/views/layout.ejs +94 -14
  120. package/server/views/mcp.ejs +38 -35
  121. package/server/views/partials/head.ejs +88 -12
  122. package/server/views/plugins.ejs +9 -0
  123. package/server/views/specs.ejs +33 -30
  124. package/cli/adapters/builtin.js +0 -43
  125. package/index.html +0 -384
@@ -0,0 +1,191 @@
1
+ <%- include("partials/head", { title: `${adapter.name} - Packages` }) %>
2
+
3
+ <div class="max-w-2xl">
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">Adapter Packages</p>
7
+ <h1 class="text-2xl editorial-heading text-[#111111]"><%= adapter.name %></h1>
8
+ </div>
9
+ <div class="flex gap-2">
10
+ <a href="/api/adapters/<%= adapter.name %>/edit" class="btn">Edit Adapter</a>
11
+ <a href="/api/adapters" class="btn">Back</a>
12
+ </div>
13
+ </div>
14
+
15
+ <div id="packages-manager">
16
+ <div v-if="message.text" :class="['alert mb-4', message.type === 'error' ? 'alert-error' : 'alert-success']">
17
+ <span>{{ message.text }}</span>
18
+ </div>
19
+
20
+ <div class="bento-card p-6 mb-6">
21
+ <h3 class="text-sm font-medium text-[#111111] mb-4">Add Package</h3>
22
+ <div class="flex gap-2">
23
+ <input v-model="newPackage" class="input input-bordered input-sm flex-1"
24
+ placeholder="package-name@version (e.g., pg@8.11.0)"
25
+ @keyup.enter="addPackage"
26
+ :disabled="adding">
27
+ <button @click="addPackage" class="btn btn-primary btn-sm" :disabled="!newPackage.trim() || adding">
28
+ <span v-if="adding" class="loading loading-spinner loading-sm"></span>
29
+ <span v-else>Add</span>
30
+ </button>
31
+ </div>
32
+ <p class="text-xs text-[#787774] mt-2">
33
+ Enter package name with optional version. Examples: <code>pg</code>, <code>pg@8.11.0</code>, <code>redis@latest</code>
34
+ </p>
35
+ </div>
36
+
37
+ <div class="bento-card overflow-hidden">
38
+ <div class="px-6 py-4 border-b border-[#EAEAEA] bg-[#F7F6F3] flex justify-between items-center">
39
+ <h3 class="text-sm font-medium text-[#111111]">Installed Packages ({{ packages.length }})</h3>
40
+ <button v-if="packages.length > 0" @click="prunePackages" class="btn btn-xs border-red-200 text-red-600 hover:bg-red-50" :disabled="pruning">
41
+ <span v-if="pruning" class="loading loading-spinner loading-sm"></span>
42
+ <span v-else>Prune All</span>
43
+ </button>
44
+ </div>
45
+
46
+ <table class="w-full">
47
+ <thead>
48
+ <tr class="border-b border-[#EAEAEA] bg-[#F7F6F3]">
49
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Package</th>
50
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Version</th>
51
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Actions</th>
52
+ </tr>
53
+ </thead>
54
+ <tbody>
55
+ <tr v-if="packages.length === 0">
56
+ <td colspan="3" class="px-6 py-12 text-center text-[#787774]">
57
+ No packages installed. Add packages above.
58
+ </td>
59
+ </tr>
60
+ <tr v-for="pkg in packages" :key="pkg" class="border-b border-[#EAEAEA] hover:bg-[#F7F6F3] transition-colors">
61
+ <td class="px-6 py-4 font-medium text-[#111111]">{{ parsePackageName(pkg).name }}</td>
62
+ <td class="px-6 py-4 text-sm text-[#2F3437]">{{ parsePackageName(pkg).version }}</td>
63
+ <td class="px-6 py-4">
64
+ <button @click="removePackage(pkg)" class="btn text-xs border-red-200 text-red-600 hover:bg-red-50 hover:border-red-300" :disabled="removing === pkg">
65
+ <span v-if="removing === pkg" class="loading loading-spinner loading-sm"></span>
66
+ <span v-else>Remove</span>
67
+ </button>
68
+ </td>
69
+ </tr>
70
+ </tbody>
71
+ </table>
72
+ </div>
73
+
74
+ <div class="mt-6 p-4 bg-blue-50 border border-blue-200 rounded">
75
+ <h4 class="text-sm font-medium text-blue-800 mb-2">Auto-Install</h4>
76
+ <p class="text-xs text-blue-700">
77
+ Packages are automatically installed when the adapter runs.
78
+ For CLI-context adapters, packages are installed on the user's machine when they sync.
79
+ </p>
80
+ </div>
81
+ </div>
82
+ </div>
83
+
84
+ <script>
85
+ const adapterName = '<%= adapter.name %>'
86
+ const initialPackages = <%- JSON.stringify(packages) %>
87
+
88
+ Vue.createApp({
89
+ data() {
90
+ return {
91
+ packages: initialPackages,
92
+ newPackage: '',
93
+ message: { text: '', type: '' },
94
+ adding: false,
95
+ removing: null,
96
+ pruning: false
97
+ }
98
+ },
99
+ methods: {
100
+ parsePackageName(pkg) {
101
+ if (pkg.includes('@') && !pkg.startsWith('@')) {
102
+ const parts = pkg.split('@')
103
+ return { name: parts[0], version: parts[1] }
104
+ }
105
+ return { name: pkg, version: 'latest' }
106
+ },
107
+
108
+ async addPackage() {
109
+ const pkg = this.newPackage.trim()
110
+ if (!pkg) return
111
+
112
+ this.message.text = ''
113
+ this.adding = true
114
+
115
+ try {
116
+ const res = await fetch(`/api/adapters/${adapterName}/packages`, {
117
+ method: 'POST',
118
+ headers: { 'Content-Type': 'application/json' },
119
+ body: JSON.stringify({ package: pkg })
120
+ })
121
+
122
+ const data = await res.json()
123
+
124
+ if (!res.ok) {
125
+ throw new Error(data.error || 'Failed to add package')
126
+ }
127
+
128
+ this.packages = data.packages
129
+ this.newPackage = ''
130
+ this.message = { text: `Package '${pkg}' added`, type: 'success' }
131
+ } catch (err) {
132
+ this.message = { text: err.message, type: 'error' }
133
+ } finally {
134
+ this.adding = false
135
+ }
136
+ },
137
+
138
+ async removePackage(pkg) {
139
+ if (!confirm(`Remove package '${this.parsePackageName(pkg).name}'?`)) return
140
+
141
+ this.message.text = ''
142
+ this.removing = pkg
143
+
144
+ try {
145
+ const name = this.parsePackageName(pkg).name
146
+ const res = await fetch(`/api/adapters/${adapterName}/packages/${name}`, {
147
+ method: 'DELETE'
148
+ })
149
+
150
+ const data = await res.json()
151
+
152
+ if (!res.ok) {
153
+ throw new Error(data.error || 'Failed to remove package')
154
+ }
155
+
156
+ this.packages = data.packages
157
+ this.message = { text: `Package '${name}' removed`, type: 'success' }
158
+ } catch (err) {
159
+ this.message = { text: err.message, type: 'error' }
160
+ } finally {
161
+ this.removing = null
162
+ }
163
+ },
164
+
165
+ async prunePackages() {
166
+ if (!confirm('Remove all packages and uninstall them from disk?')) return
167
+
168
+ this.message.text = ''
169
+ this.pruning = true
170
+
171
+ try {
172
+ const res = await fetch(`/api/adapters/${adapterName}/packages`, {
173
+ method: 'POST',
174
+ headers: { 'Content-Type': 'application/json' },
175
+ body: JSON.stringify({ packages: [] })
176
+ })
177
+
178
+ const data = await res.json()
179
+ this.packages = []
180
+ this.message = { text: 'All packages removed', type: 'success' }
181
+ } catch (err) {
182
+ this.message = { text: err.message, type: 'error' }
183
+ } finally {
184
+ this.pruning = false
185
+ }
186
+ }
187
+ }
188
+ }).mount('#packages-manager')
189
+ </script>
190
+
191
+ <%- include("partials/foot") %>
@@ -0,0 +1,112 @@
1
+ <%- include("partials/head", { title: "Adapters" }) %>
2
+
3
+ <div class="flex justify-between items-center mb-8">
4
+ <div>
5
+ <p class="text-xs uppercase tracking-[0.2em] text-[#787774] mb-1">Management</p>
6
+ <h1 class="text-2xl editorial-heading text-[#111111]">Custom Adapters</h1>
7
+ <p class="text-sm text-[#787774] mt-1">Create and manage JavaScript adapters for custom integrations</p>
8
+ </div>
9
+ <a href="/api/adapters/new" class="btn btn-primary">+ New Adapter</a>
10
+ </div>
11
+
12
+ <div class="bento-card overflow-hidden">
13
+ <table class="w-full">
14
+ <thead>
15
+ <tr class="border-b border-[#EAEAEA] bg-[#F7F6F3]">
16
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Name</th>
17
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Context</th>
18
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Packages</th>
19
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Updated</th>
20
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Actions</th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ <% if (adapters.length === 0) { %>
25
+ <tr><td colspan="5" class="px-6 py-12 text-center text-[#787774]">No adapters yet. Create one to get started.</td></tr>
26
+ <% } %>
27
+ <% adapters.forEach(a => { %>
28
+ <tr class="border-b border-[#EAEAEA] hover:bg-[#F7F6F3] transition-colors">
29
+ <td class="px-6 py-4">
30
+ <div class="flex flex-col">
31
+ <span class="font-medium text-[#111111]"><%= a.name %></span>
32
+ <% if (a.description) { %>
33
+ <span class="text-xs text-[#787774] mt-1"><%= a.description %></span>
34
+ <% } %>
35
+ </div>
36
+ </td>
37
+ <td class="px-6 py-4">
38
+ <span class="badge <%= a.execution_context === 'cli' ? 'badge-warning' : 'badge-success' %>">
39
+ <%= a.execution_context %>
40
+ </span>
41
+ </td>
42
+ <td class="px-6 py-4">
43
+ <span class="mono-text text-sm text-[#2F3437]"><%= a.dependencies.length %> deps</span>
44
+ </td>
45
+ <td class="px-6 py-4 text-sm text-[#787774]">
46
+ <%= new Date(a.updated_at).toLocaleDateString() %>
47
+ </td>
48
+ <td class="px-6 py-4 flex gap-2">
49
+ <a href="/api/adapters/<%= a.name %>/edit" class="btn text-xs">Edit</a>
50
+ <a href="/api/adapters/<%= a.name %>/packages" class="btn text-xs">Packages</a>
51
+ <% if (a.execution_context === 'server') { %>
52
+ <button onclick="testAdapter('<%= a.name %>')" class="btn text-xs">Test</button>
53
+ <% } %>
54
+ <button onclick="deleteAdapter('<%= a.name %>')" class="btn text-xs border-red-200 text-red-600 hover:bg-red-50 hover:border-red-300">Delete</button>
55
+ </td>
56
+ </tr>
57
+ <% }) %>
58
+ </tbody>
59
+ </table>
60
+ </div>
61
+
62
+ <div id="test-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
63
+ <div class="bg-white rounded-lg p-6 max-w-lg w-full mx-4 shadow-xl">
64
+ <h3 class="text-lg font-medium text-[#111111] mb-4">Test Adapter</h3>
65
+ <div id="test-result" class="mb-4"></div>
66
+ <button onclick="closeTestModal()" class="btn btn-primary">Close</button>
67
+ </div>
68
+ </div>
69
+
70
+ <script>
71
+ async function deleteAdapter(name) {
72
+ if (!confirm(`Delete adapter '${name}'?`)) return
73
+ await fetch('/api/adapters/' + name, { method: 'DELETE' })
74
+ location.reload()
75
+ }
76
+
77
+ async function testAdapter(name) {
78
+ const modal = document.getElementById('test-modal')
79
+ const result = document.getElementById('test-result')
80
+ modal.classList.remove('hidden')
81
+ result.innerHTML = '<p class="text-[#787774]">Running test...</p>'
82
+
83
+ try {
84
+ const res = await fetch(`/api/adapters/${name}/test`, { method: 'POST' })
85
+ const data = await res.json()
86
+
87
+ if (data.ok) {
88
+ result.innerHTML = `
89
+ <div class="space-y-2">
90
+ <p class="text-green-600 font-medium">✓ Test passed (${data.duration_ms}ms)</p>
91
+ <pre class="bg-[#F7F6F3] p-3 rounded text-xs overflow-auto">${JSON.stringify(data.result, null, 2)}</pre>
92
+ </div>
93
+ `
94
+ } else {
95
+ result.innerHTML = `
96
+ <div class="space-y-2">
97
+ <p class="text-red-600 font-medium">✗ Test failed (${data.duration_ms}ms)</p>
98
+ <p class="text-sm text-red-500">${data.error}</p>
99
+ </div>
100
+ `
101
+ }
102
+ } catch (err) {
103
+ result.innerHTML = `<p class="text-red-600">Error: ${err.message}</p>`
104
+ }
105
+ }
106
+
107
+ function closeTestModal() {
108
+ document.getElementById('test-modal').classList.add('hidden')
109
+ }
110
+ </script>
111
+
112
+ <%- include("partials/foot") %>
@@ -37,14 +37,22 @@
37
37
  <div class="form-control">
38
38
  <label class="label"><span class="label-text">Adapter</span></label>
39
39
  <select v-model="form.adapter" @change="onAdapterChange" class="select select-bordered select-sm">
40
- <option value="http">http</option>
41
- <option value="openapi">openapi</option>
42
- <option value="mcp">mcp</option>
43
- <option value="shell">shell</option>
44
- <option value="process">process</option>
45
- <option value="builtin">builtin</option>
46
- <option value="custom">custom</option>
40
+ <optgroup label="Built-in">
41
+ <option value="http">http</option>
42
+ <option value="openapi">openapi</option>
43
+ <option value="mcp">mcp</option>
44
+ <option value="shell">shell</option>
45
+ <option value="process">process</option>
46
+ </optgroup>
47
+ <optgroup v-if="customAdapters.length > 0" label="Custom">
48
+ <option v-for="adapter in customAdapters" :key="adapter.name" :value="adapter.name">
49
+ {{ adapter.name }} ({{ adapter.execution_context }})
50
+ </option>
51
+ </optgroup>
47
52
  </select>
53
+ <span v-if="customAdapters.length === 0" class="text-xs text-[#787774] mt-1">
54
+ <a href="/api/adapters" class="text-blue-600 hover:underline">Create custom adapters</a> to see them here
55
+ </span>
48
56
  </div>
49
57
 
50
58
  <div class="rounded border border-base-300 p-3 bg-base-100">
@@ -148,16 +156,9 @@
148
156
  timeout_ms: 5000
149
157
  }
150
158
  },
151
- builtin: {
152
- required: "adapterConfig.builtin",
153
- help: "Runs internal built-in behavior.",
154
- template: {
155
- builtin: "noop"
156
- }
157
- },
158
159
  custom: {
159
160
  required: "Depends on your custom adapter",
160
- help: "Use when loading adapters/<name>.js from the CLI workspace.",
161
+ help: "Custom adapters are loaded dynamically from the server. Create them in the Adapters section.",
161
162
  template: {}
162
163
  }
163
164
  };
@@ -177,19 +178,45 @@
177
178
  adapterConfigStr: editData && editData.adapterConfig ? JSON.stringify(editData.adapterConfig, null, 2) : '{}',
178
179
  message: { type: '', text: '' },
179
180
  validationResult: null,
181
+ customAdapters: [],
180
182
  }
181
183
  },
182
- computed: {
183
- adapterGuide() {
184
- return ADAPTER_GUIDES[this.form.adapter] || ADAPTER_GUIDES.http
185
- }
186
- },
187
- mounted() {
184
+ async mounted() {
185
+ await this.loadCustomAdapters()
188
186
  if (!this.editId && this.adapterConfigStr.trim() === '{}') {
189
187
  this.loadTemplate(false)
190
188
  }
191
189
  },
190
+ computed: {
191
+ adapterGuide() {
192
+ // Check if it's a built-in adapter
193
+ if (ADAPTER_GUIDES[this.form.adapter]) {
194
+ return ADAPTER_GUIDES[this.form.adapter]
195
+ }
196
+ // Check if it's a custom adapter
197
+ const customAdapter = this.customAdapters.find(a => a.name === this.form.adapter)
198
+ if (customAdapter) {
199
+ return {
200
+ required: "adapterConfig (custom per adapter)",
201
+ help: `Custom adapter: ${customAdapter.description || 'No description'} (${customAdapter.execution_context} context)`,
202
+ template: {}
203
+ }
204
+ }
205
+ return ADAPTER_GUIDES.http
206
+ }
207
+ },
192
208
  methods: {
209
+ async loadCustomAdapters() {
210
+ try {
211
+ const res = await fetch('/api/adapters', { headers: { 'Accept': 'application/json' } })
212
+ if (res.ok) {
213
+ const data = await res.json()
214
+ this.customAdapters = data.adapters || []
215
+ }
216
+ } catch (err) {
217
+ console.error('Failed to load custom adapters:', err)
218
+ }
219
+ },
193
220
  setMessage(type, text) {
194
221
  this.message = { type, text }
195
222
  },
@@ -1,36 +1,39 @@
1
1
  <%- include("partials/head", { title: "Commands" }) %>
2
2
 
3
- <div class="flex justify-between items-center mb-6">
4
- <h1 class="text-2xl font-bold">Commands</h1>
5
- <a href="/api/commands/new" class="btn btn-primary btn-sm">+ Create Command</a>
3
+ <div class="flex justify-between items-center mb-8">
4
+ <div>
5
+ <p class="text-xs uppercase tracking-[0.2em] text-[#787774] mb-1">Management</p>
6
+ <h1 class="text-2xl editorial-heading text-[#111111]">Commands</h1>
7
+ </div>
8
+ <a href="/api/commands/new" class="btn btn-primary">+ Create Command</a>
6
9
  </div>
7
10
 
8
- <div class="overflow-x-auto">
9
- <table class="table table-zebra w-full">
11
+ <div class="bento-card overflow-hidden">
12
+ <table class="w-full">
10
13
  <thead>
11
- <tr>
12
- <th>Namespace</th>
13
- <th>Resource</th>
14
- <th>Action</th>
15
- <th>Adapter</th>
16
- <th>Description</th>
17
- <th>Actions</th>
14
+ <tr class="border-b border-[#EAEAEA] bg-[#F7F6F3]">
15
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Namespace</th>
16
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Resource</th>
17
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Action</th>
18
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Adapter</th>
19
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Description</th>
20
+ <th class="px-6 py-3 text-left text-xs font-semibold text-[#787774] uppercase tracking-wider">Actions</th>
18
21
  </tr>
19
22
  </thead>
20
23
  <tbody>
21
24
  <% if (commands.length === 0) { %>
22
- <tr><td colspan="6" class="text-center opacity-60">No commands yet. Create one to get started.</td></tr>
25
+ <tr><td colspan="6" class="px-6 py-12 text-center text-[#787774]">No commands yet. Create one to get started.</td></tr>
23
26
  <% } %>
24
27
  <% commands.forEach(c => { %>
25
- <tr>
26
- <td><span class="badge badge-outline"><%= c.namespace %></span></td>
27
- <td><%= c.resource %></td>
28
- <td><code><%= c.action %></code></td>
29
- <td><span class="badge badge-ghost badge-sm"><%= c.adapter %></span></td>
30
- <td class="max-w-xs truncate opacity-70"><%= c.description || '-' %></td>
31
- <td class="flex gap-1">
32
- <a href="/api/commands/<%= c._id %>/edit" class="btn btn-xs btn-outline">Edit</a>
33
- <button onclick="deleteCommand('<%= c._id %>')" class="btn btn-xs btn-error btn-outline">Delete</button>
28
+ <tr class="border-b border-[#EAEAEA] hover:bg-[#F7F6F3] transition-colors">
29
+ <td class="px-6 py-4"><span class="badge badge-outline"><%= c.namespace %></span></td>
30
+ <td class="px-6 py-4 text-[#111111]"><%= c.resource %></td>
31
+ <td class="px-6 py-4"><code class="mono-text text-sm text-[#1F6C9F]"><%= c.action %></code></td>
32
+ <td class="px-6 py-4"><span class="badge"><%= c.adapter %></span></td>
33
+ <td class="px-6 py-4 max-w-xs truncate text-[#2F3437]"><%= c.description || '-' %></td>
34
+ <td class="px-6 py-4 flex gap-2">
35
+ <a href="/api/commands/<%= c._id %>/edit" class="btn text-xs">Edit</a>
36
+ <button onclick="deleteCommand('<%= c._id %>')" class="btn text-xs border-red-200 text-red-600 hover:bg-red-50 hover:border-red-300">Delete</button>
34
37
  </td>
35
38
  </tr>
36
39
  <% }) %>
@@ -0,0 +1,196 @@
1
+ <%- include("partials/head", { title: "Dashboard" }) %>
2
+
3
+ <div class="mb-8">
4
+ <p class="text-xs uppercase tracking-[0.2em] text-[#787774] mb-1">Overview</p>
5
+ <h1 class="text-2xl editorial-heading text-[#111111]">Dashboard</h1>
6
+ </div>
7
+
8
+ <!-- Resource Summary Cards -->
9
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
10
+ <a href="/api/commands" class="bento-card p-5 block no-underline">
11
+ <p class="text-xs uppercase tracking-widest text-[#787774] mb-2">Commands</p>
12
+ <p class="text-3xl font-semibold text-[#111111]"><%= counts.commands %></p>
13
+ </a>
14
+ <a href="/api/mcp" class="bento-card p-5 block no-underline">
15
+ <p class="text-xs uppercase tracking-widest text-[#787774] mb-2">MCP Servers</p>
16
+ <p class="text-3xl font-semibold text-[#111111]"><%= counts.mcpServers %></p>
17
+ </a>
18
+ <a href="/api/specs" class="bento-card p-5 block no-underline">
19
+ <p class="text-xs uppercase tracking-widest text-[#787774] mb-2">OpenAPI Specs</p>
20
+ <p class="text-3xl font-semibold text-[#111111]"><%= counts.specs %></p>
21
+ </a>
22
+ <a href="/api/plugins" class="bento-card p-5 block no-underline">
23
+ <p class="text-xs uppercase tracking-widest text-[#787774] mb-2">Plugins</p>
24
+ <p class="text-3xl font-semibold text-[#111111]"><%= counts.plugins %></p>
25
+ <p class="text-xs text-[#787774] mt-1"><%= counts.pluginsEnabled %> enabled</p>
26
+ </a>
27
+ </div>
28
+
29
+ <!-- Job Metrics Row -->
30
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
31
+ <div class="bento-card p-5">
32
+ <p class="text-xs uppercase tracking-widest text-[#787774] mb-2">Total Jobs</p>
33
+ <p class="text-3xl font-semibold text-[#111111]"><%= jobs.total %></p>
34
+ </div>
35
+ <div class="bento-card p-5">
36
+ <p class="text-xs uppercase tracking-widest text-[#787774] mb-2">Success Rate</p>
37
+ <p class="text-3xl font-semibold text-[#346538]"><%= jobs.successRate %>%</p>
38
+ </div>
39
+ <div class="bento-card p-5">
40
+ <p class="text-xs uppercase tracking-widest text-[#787774] mb-2">Failure Rate</p>
41
+ <p class="text-3xl font-semibold <%= jobs.failed > 0 ? 'text-red-600' : 'text-[#111111]' %>"><%= jobs.failureRate %>%</p>
42
+ </div>
43
+ <div class="bento-card p-5">
44
+ <p class="text-xs uppercase tracking-widest text-[#787774] mb-2">Avg Duration</p>
45
+ <p class="text-3xl font-semibold text-[#111111]"><%= jobs.avgDuration %><span class="text-base text-[#787774] ml-1">ms</span></p>
46
+ </div>
47
+ </div>
48
+
49
+ <!-- Charts Row -->
50
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
51
+ <!-- 7-day Trend Chart -->
52
+ <div class="bento-card p-5 md:col-span-2">
53
+ <p class="text-xs uppercase tracking-widest text-[#787774] mb-4">Job Activity (Last 7 Days)</p>
54
+ <canvas id="trendChart" height="120"></canvas>
55
+ </div>
56
+ <!-- Status Pie Chart -->
57
+ <div class="bento-card p-5">
58
+ <p class="text-xs uppercase tracking-widest text-[#787774] mb-4">Job Status</p>
59
+ <% if (jobs.total > 0) { %>
60
+ <canvas id="statusChart" height="160"></canvas>
61
+ <% } else { %>
62
+ <div class="flex items-center justify-center h-40 text-[#787774] text-sm">No jobs yet</div>
63
+ <% } %>
64
+ </div>
65
+ </div>
66
+
67
+ <!-- Top Commands + Recent Jobs -->
68
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
69
+ <!-- Top Commands -->
70
+ <div class="bento-card overflow-hidden">
71
+ <div class="px-6 py-4 border-b border-[#EAEAEA] bg-[#F7F6F3]">
72
+ <p class="text-xs uppercase tracking-widest text-[#787774]">Top Commands</p>
73
+ </div>
74
+ <% if (topCommands.length === 0) { %>
75
+ <div class="px-6 py-10 text-center text-[#787774] text-sm">No job data yet</div>
76
+ <% } else { %>
77
+ <div class="divide-y divide-[#EAEAEA]">
78
+ <% topCommands.forEach((cmd, i) => { %>
79
+ <div class="px-6 py-3 flex items-center justify-between">
80
+ <div class="flex items-center gap-3">
81
+ <span class="text-xs text-[#787774] w-4"><%= i + 1 %></span>
82
+ <code class="mono-text text-sm text-[#1F6C9F]"><%= cmd.command %></code>
83
+ </div>
84
+ <div class="flex items-center gap-4 text-xs text-[#787774]">
85
+ <span><%= cmd.count %> runs</span>
86
+ <span><%= cmd.avg_ms %>ms avg</span>
87
+ </div>
88
+ </div>
89
+ <% }) %>
90
+ </div>
91
+ <% } %>
92
+ </div>
93
+
94
+ <!-- Recent Jobs -->
95
+ <div class="bento-card overflow-hidden">
96
+ <div class="px-6 py-4 border-b border-[#EAEAEA] bg-[#F7F6F3] flex items-center justify-between">
97
+ <p class="text-xs uppercase tracking-widest text-[#787774]">Recent Jobs</p>
98
+ <a href="/api/jobs" class="text-xs text-[#787774] hover:text-[#111111] transition-colors">View all →</a>
99
+ </div>
100
+ <% if (recentJobs.length === 0) { %>
101
+ <div class="px-6 py-10 text-center text-[#787774] text-sm">No jobs yet</div>
102
+ <% } else { %>
103
+ <div class="divide-y divide-[#EAEAEA]">
104
+ <% recentJobs.forEach(job => { %>
105
+ <div class="px-6 py-3 flex items-center justify-between">
106
+ <div class="flex items-center gap-3">
107
+ <span class="w-2 h-2 rounded-full flex-shrink-0
108
+ <%= job.status === 'success' ? 'bg-[#346538]' : job.status === 'failed' ? 'bg-red-500' : 'bg-[#787774]' %>">
109
+ </span>
110
+ <code class="mono-text text-xs text-[#1F6C9F] truncate max-w-[160px]"><%= job.command || '—' %></code>
111
+ </div>
112
+ <div class="flex items-center gap-3 text-xs text-[#787774] flex-shrink-0">
113
+ <% if (job.duration_ms) { %><span><%= job.duration_ms %>ms</span><% } %>
114
+ <span><%= new Date(job.timestamp).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) %></span>
115
+ </div>
116
+ </div>
117
+ <% }) %>
118
+ </div>
119
+ <% } %>
120
+ </div>
121
+ </div>
122
+
123
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.2/dist/chart.umd.min.js"></script>
124
+ <script>
125
+ const trend = <%- JSON.stringify(trend) %>;
126
+ const jobs = <%- JSON.stringify(jobs) %>;
127
+
128
+ Chart.defaults.font.family = "'Geist', system-ui, sans-serif";
129
+ Chart.defaults.color = '#787774';
130
+
131
+ // Trend line chart
132
+ const trendCtx = document.getElementById('trendChart');
133
+ if (trendCtx) {
134
+ new Chart(trendCtx, {
135
+ type: 'bar',
136
+ data: {
137
+ labels: trend.map(d => d.label),
138
+ datasets: [
139
+ {
140
+ label: 'Success',
141
+ data: trend.map(d => d.success),
142
+ backgroundColor: '#EDF3EC',
143
+ borderColor: '#346538',
144
+ borderWidth: 1,
145
+ borderRadius: 4,
146
+ },
147
+ {
148
+ label: 'Failed',
149
+ data: trend.map(d => d.failed),
150
+ backgroundColor: '#FEE2E2',
151
+ borderColor: '#DC2626',
152
+ borderWidth: 1,
153
+ borderRadius: 4,
154
+ }
155
+ ]
156
+ },
157
+ options: {
158
+ responsive: true,
159
+ plugins: {
160
+ legend: { position: 'bottom', labels: { boxWidth: 12, padding: 16, font: { size: 11 } } }
161
+ },
162
+ scales: {
163
+ x: { grid: { display: false }, border: { display: false } },
164
+ y: { grid: { color: '#EAEAEA' }, border: { display: false }, beginAtZero: true, ticks: { stepSize: 1 } }
165
+ }
166
+ }
167
+ });
168
+ }
169
+
170
+ // Status pie chart
171
+ const statusCtx = document.getElementById('statusChart');
172
+ if (statusCtx && jobs.total > 0) {
173
+ const unknown = jobs.total - jobs.success - jobs.failed;
174
+ new Chart(statusCtx, {
175
+ type: 'doughnut',
176
+ data: {
177
+ labels: ['Success', 'Failed', 'Unknown'],
178
+ datasets: [{
179
+ data: [jobs.success, jobs.failed, unknown],
180
+ backgroundColor: ['#EDF3EC', '#FEE2E2', '#F7F6F3'],
181
+ borderColor: ['#346538', '#DC2626', '#EAEAEA'],
182
+ borderWidth: 1,
183
+ }]
184
+ },
185
+ options: {
186
+ responsive: true,
187
+ cutout: '65%',
188
+ plugins: {
189
+ legend: { position: 'bottom', labels: { boxWidth: 12, padding: 12, font: { size: 11 } } }
190
+ }
191
+ }
192
+ });
193
+ }
194
+ </script>
195
+
196
+ <%- include("partials/foot") %>