@intranefr/superbackend 1.6.5 → 1.6.7

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 (53) hide show
  1. package/.env.example +4 -0
  2. package/README.md +18 -0
  3. package/package.json +6 -1
  4. package/public/js/admin-superdemos.js +396 -0
  5. package/public/sdk/superdemos.iife.js +614 -0
  6. package/public/superdemos-qa.html +324 -0
  7. package/sdk/superdemos/browser/src/index.js +719 -0
  8. package/src/cli/agent-chat.js +369 -0
  9. package/src/cli/agent-list.js +42 -0
  10. package/src/controllers/adminAgentsChat.controller.js +172 -0
  11. package/src/controllers/adminSuperDemos.controller.js +382 -0
  12. package/src/controllers/superDemosPublic.controller.js +126 -0
  13. package/src/middleware.js +108 -19
  14. package/src/models/BlogAutomationLock.js +4 -4
  15. package/src/models/BlogPost.js +16 -16
  16. package/src/models/CacheEntry.js +17 -6
  17. package/src/models/JsonConfig.js +2 -4
  18. package/src/models/RateLimitMetricBucket.js +10 -5
  19. package/src/models/SuperDemo.js +38 -0
  20. package/src/models/SuperDemoProject.js +32 -0
  21. package/src/models/SuperDemoStep.js +27 -0
  22. package/src/routes/adminAgents.routes.js +10 -0
  23. package/src/routes/adminMarkdowns.routes.js +3 -0
  24. package/src/routes/adminSuperDemos.routes.js +31 -0
  25. package/src/routes/superDemos.routes.js +9 -0
  26. package/src/services/auditLogger.js +75 -37
  27. package/src/services/email.service.js +18 -3
  28. package/src/services/llm.service.js +1 -0
  29. package/src/services/plugins.service.js +50 -16
  30. package/src/services/superDemosAuthoringSessions.service.js +132 -0
  31. package/src/services/superDemosWs.service.js +164 -0
  32. package/src/services/terminalsWs.service.js +35 -3
  33. package/src/utils/rbac/rightsRegistry.js +2 -0
  34. package/views/admin-agents.ejs +261 -11
  35. package/views/admin-dashboard.ejs +78 -8
  36. package/views/admin-superdemos.ejs +335 -0
  37. package/views/admin-terminals.ejs +462 -34
  38. package/views/partials/admin/agents-chat.ejs +80 -0
  39. package/views/partials/dashboard/nav-items.ejs +1 -0
  40. package/views/partials/dashboard/tab-bar.ejs +6 -0
  41. package/cookies.txt +0 -6
  42. package/cookies1.txt +0 -6
  43. package/cookies2.txt +0 -6
  44. package/cookies3.txt +0 -6
  45. package/cookies4.txt +0 -5
  46. package/cookies_old.txt +0 -5
  47. package/cookies_old_test.txt +0 -6
  48. package/cookies_super.txt +0 -5
  49. package/cookies_super_test.txt +0 -6
  50. package/cookies_test.txt +0 -6
  51. package/test-access.js +0 -63
  52. package/test-iframe-fix.html +0 -63
  53. package/test-iframe.html +0 -14
package/.env.example CHANGED
@@ -65,3 +65,7 @@ MAX_FILE_SIZE_HARD_CAP=10485760
65
65
  # Set to 'false' to disable console manager initialization
66
66
  # When disabled, console methods are not overridden and no console entries are tracked
67
67
  # CONSOLE_MANAGER_ENABLED=true
68
+
69
+ # Admin Dashboard
70
+ # Maximum number of tabs allowed in the admin dashboard (default: 5)
71
+ ADMIN_MAX_TABS=5
package/README.md CHANGED
@@ -26,6 +26,8 @@ Node.js middleware that gives your project backend superpowers. Handles authenti
26
26
  - **Workflows System**: Node-based automation with LLM integration, conditionals, and HTTP calls
27
27
  - **LLM UI Integration**: AI-powered UI components and conversational interfaces
28
28
  - **Admin Scripts & Terminals**: Operational tooling for script execution and terminal management
29
+ - **AI Agent System**: Configurable AI agents with tool integration and multi-interface support
30
+ - **CLI Tools**: Terminal-based agent interaction and management commands
29
31
  - **Migration System**: Database migration and data transfer between environments
30
32
  - **Upload Namespaces**: Advanced file organization with customizable storage rules
31
33
  - **UI Components**: Project-scoped reusable UI widgets with browser SDK integration
@@ -85,6 +87,22 @@ const backend = new SuperBackend({
85
87
 
86
88
  ---
87
89
 
90
+ ## CLI Tools
91
+
92
+ SuperBackend includes command-line tools for AI agent interaction:
93
+
94
+ ```bash
95
+ # List available agents
96
+ npx @intranefr/superbackend agent-list
97
+
98
+ # Start interactive chat with an agent
99
+ npx @intranefr/superbackend agent-chat
100
+ ```
101
+
102
+ See [CLI-README.md](CLI-README.md) for detailed CLI usage.
103
+
104
+ ---
105
+
88
106
  ## Documentation
89
107
 
90
108
  See the `docs/features/` directory for detailed guides:
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@intranefr/superbackend",
3
- "version": "1.6.5",
3
+ "version": "1.6.7",
4
4
  "description": "Node.js middleware that gives your project backend superpowers",
5
5
  "main": "index.js",
6
+ "bin": {
7
+ "agent-chat": "src/cli/agent-chat.js",
8
+ "agent-list": "src/cli/agent-list.js"
9
+ },
6
10
  "scripts": {
7
11
  "start": "node server.js",
8
12
  "dev": "nodemon --verbose --ignore uploads --ignore stdout.log --ignore '*.log' server.js",
@@ -10,6 +14,7 @@
10
14
  "minio:envs": "node -e \"console.log(['S3_ENDPOINT=http://localhost:9000','S3_REGION=us-east-1','S3_ACCESS_KEY_ID=minioadmin','S3_SECRET_ACCESS_KEY=minioadmin','S3_BUCKET=saasbackend','S3_FORCE_PATH_STYLE=true'].join('\\n'))\"",
11
15
  "build:sdk:error-tracking:browser": "esbuild sdk/error-tracking/browser/src/embed.js --bundle --format=iife --global-name=saasbackendErrorTrackingEmbed --outfile=sdk/error-tracking/browser/dist/embed.iife.js",
12
16
  "build:sdk:ui-components:browser": "esbuild sdk/ui-components/browser/src/index.js --bundle --format=iife --outfile=public/sdk/ui-components.iife.js",
17
+ "build:sdk:superdemos:browser": "esbuild sdk/superdemos/browser/src/index.js --bundle --format=iife --global-name=SuperDemos --outfile=public/sdk/superdemos.iife.js",
13
18
  "test": "jest",
14
19
  "test:watch": "jest --watch",
15
20
  "test:coverage": "jest --coverage"
@@ -0,0 +1,396 @@
1
+ (() => {
2
+ const { createApp, ref, computed, onBeforeUnmount } = Vue;
3
+
4
+ function withToast(fn, showToast) {
5
+ return async (...args) => {
6
+ try {
7
+ return await fn(...args);
8
+ } catch (e) {
9
+ showToast(e.message, 'error');
10
+ return null;
11
+ }
12
+ };
13
+ }
14
+
15
+ createApp({
16
+ setup() {
17
+ const cfg = window.__adminSuperDemosConfig || {};
18
+ const baseUrl = String(cfg.baseUrl || '');
19
+ const API_BASE = window.location.origin + baseUrl;
20
+
21
+ const origin = computed(() => window.location.origin);
22
+ const qaPageUrl = computed(() => `${window.location.origin}${baseUrl}/superdemos-qa.html`);
23
+
24
+ const toast = ref({ show: false, message: '', type: 'success' });
25
+ let toastTimer = null;
26
+ const showToast = (message, type) => {
27
+ toast.value = { show: true, message: String(message || ''), type: type || 'success' };
28
+ if (toastTimer) clearTimeout(toastTimer);
29
+ toastTimer = setTimeout(() => {
30
+ toast.value.show = false;
31
+ }, 2500);
32
+ };
33
+
34
+ const api = async (path, options) => {
35
+ const res = await fetch(baseUrl + path, {
36
+ method: (options && options.method) || 'GET',
37
+ headers: { 'Content-Type': 'application/json' },
38
+ body: options && options.body ? JSON.stringify(options.body) : undefined,
39
+ });
40
+ const text = await res.text();
41
+ let data = null;
42
+ try {
43
+ data = text ? JSON.parse(text) : null;
44
+ } catch {
45
+ data = null;
46
+ }
47
+ if (!res.ok) throw new Error((data && data.error) || ('Request failed: ' + res.status));
48
+ return data;
49
+ };
50
+
51
+ const projects = ref([]);
52
+ const selectedProject = ref(null);
53
+ const demos = ref([]);
54
+ const selectedDemo = ref(null);
55
+ const steps = ref([]);
56
+
57
+ const lastGeneratedKey = ref('');
58
+
59
+ const newProject = ref({ name: '', projectId: '', isPublic: true });
60
+ const newDemo = ref({ name: '', startUrlPattern: '' });
61
+ const projectStylePreset = ref('default');
62
+ const projectStyleOverrides = ref('');
63
+
64
+ const lastSelection = ref(null);
65
+
66
+ const authoring = ref({
67
+ targetUrl: '',
68
+ sessionId: '',
69
+ token: '',
70
+ connectUrl: '',
71
+ wsStatus: 'disconnected',
72
+ });
73
+
74
+ let ws = null;
75
+
76
+ function closeWs() {
77
+ try {
78
+ if (ws) ws.close();
79
+ } catch {}
80
+ ws = null;
81
+ authoring.value.wsStatus = 'disconnected';
82
+ }
83
+
84
+ function connectWs(sessionId, token) {
85
+ closeWs();
86
+
87
+ const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
88
+ const wsUrl = `${proto}//${window.location.host}${baseUrl}/api/superdemos/ws?sessionId=${encodeURIComponent(
89
+ sessionId,
90
+ )}&role=admin&token=${encodeURIComponent(token)}`;
91
+
92
+ ws = new WebSocket(wsUrl);
93
+ authoring.value.wsStatus = 'connecting';
94
+
95
+ ws.addEventListener('open', () => {
96
+ authoring.value.wsStatus = 'connected';
97
+ });
98
+
99
+ ws.addEventListener('close', () => {
100
+ authoring.value.wsStatus = 'disconnected';
101
+ });
102
+
103
+ ws.addEventListener('error', () => {
104
+ authoring.value.wsStatus = 'error';
105
+ });
106
+
107
+ ws.addEventListener('message', (evt) => {
108
+ let msg;
109
+ try {
110
+ msg = JSON.parse(String(evt.data || ''));
111
+ } catch {
112
+ return;
113
+ }
114
+
115
+ if (msg.type === 'select' && msg.element) {
116
+ lastSelection.value = msg.element;
117
+ }
118
+
119
+ if (msg.type === 'hover' && msg.element && !lastSelection.value) {
120
+ // helpful for first feedback
121
+ lastSelection.value = msg.element;
122
+ }
123
+ });
124
+ }
125
+
126
+ const refreshProjects = async () => {
127
+ const data = await api('/api/admin/superdemos/projects');
128
+ const items = Array.isArray(data && data.items) ? data.items : [];
129
+ projects.value = items
130
+ .filter(Boolean)
131
+ .map((p) => ({
132
+ ...p,
133
+ projectId: String((p && p.projectId) || ''),
134
+ name: String((p && p.name) || ''),
135
+ isPublic: Boolean(p && p.isPublic),
136
+ }))
137
+ .filter((p) => p.projectId);
138
+ };
139
+
140
+ const refreshDemos = async (projectId) => {
141
+ const data = await api('/api/admin/superdemos/projects/' + encodeURIComponent(projectId) + '/demos');
142
+ const items = Array.isArray(data && data.items) ? data.items : [];
143
+ demos.value = items
144
+ .filter(Boolean)
145
+ .map((d) => ({
146
+ ...d,
147
+ demoId: String((d && d.demoId) || ''),
148
+ name: String((d && d.name) || ''),
149
+ status: String((d && d.status) || 'draft'),
150
+ publishedVersion: Number((d && d.publishedVersion) || 0),
151
+ }))
152
+ .filter((d) => d.demoId);
153
+ };
154
+
155
+ const refreshSteps = async (demoId) => {
156
+ const data = await api('/api/admin/superdemos/demos/' + encodeURIComponent(demoId) + '/steps');
157
+ const items = (data && data.items) || [];
158
+ steps.value = items.map((s) => ({
159
+ selector: s.selector,
160
+ selectorHints: s.selectorHints || null,
161
+ message: s.message,
162
+ placement: s.placement || 'auto',
163
+ waitFor: s.waitFor || null,
164
+ advance: s.advance || { type: 'manualNext' },
165
+ }));
166
+ };
167
+
168
+ const refreshAll = withToast(async () => {
169
+ await refreshProjects();
170
+ if (selectedProject.value) await refreshDemos(selectedProject.value.projectId);
171
+ if (selectedDemo.value) await refreshSteps(selectedDemo.value.demoId);
172
+ showToast('Refreshed', 'success');
173
+ }, showToast);
174
+
175
+ const saveProjectStyleSettings = withToast(async () => {
176
+ if (!selectedProject.value) return;
177
+ const data = await api('/api/admin/superdemos/projects/' + encodeURIComponent(selectedProject.value.projectId), {
178
+ method: 'PUT',
179
+ body: {
180
+ stylePreset: projectStylePreset.value || 'default',
181
+ styleOverrides: projectStyleOverrides.value || '',
182
+ },
183
+ });
184
+ selectedProject.value = { ...data.item };
185
+ showToast('Project style settings saved', 'success');
186
+ }, showToast);
187
+
188
+ const copyText = async (text, label) => {
189
+ const v = String(text || '');
190
+ if (!v) {
191
+ showToast('Nothing to copy', 'error');
192
+ return;
193
+ }
194
+ try {
195
+ await navigator.clipboard.writeText(v);
196
+ showToast(`${label || 'Value'} copied`, 'success');
197
+ } catch {
198
+ showToast('Clipboard copy failed', 'error');
199
+ }
200
+ };
201
+
202
+ const createProject = withToast(async () => {
203
+ lastGeneratedKey.value = '';
204
+ const data = await api('/api/admin/superdemos/projects', {
205
+ method: 'POST',
206
+ body: {
207
+ name: newProject.value.name,
208
+ projectId: newProject.value.projectId || undefined,
209
+ isPublic: Boolean(newProject.value.isPublic),
210
+ },
211
+ });
212
+ if (data && data.apiKey) lastGeneratedKey.value = data.apiKey;
213
+ newProject.value = { name: '', projectId: '', isPublic: true };
214
+ await refreshProjects();
215
+ showToast('Project created', 'success');
216
+ }, showToast);
217
+
218
+ const selectProject = withToast(async (p) => {
219
+ selectedProject.value = { ...p };
220
+ projectStylePreset.value = String(p.stylePreset || 'default');
221
+ projectStyleOverrides.value = String(p.styleOverrides || '');
222
+ selectedDemo.value = null;
223
+ demos.value = [];
224
+ steps.value = [];
225
+ closeWs();
226
+ authoring.value = { targetUrl: '', sessionId: '', token: '', connectUrl: '', wsStatus: 'disconnected' };
227
+ lastSelection.value = null;
228
+ await refreshDemos(p.projectId);
229
+ }, showToast);
230
+
231
+ const createDemo = withToast(async () => {
232
+ if (!selectedProject.value) return;
233
+ const data = await api(
234
+ '/api/admin/superdemos/projects/' + encodeURIComponent(selectedProject.value.projectId) + '/demos',
235
+ {
236
+ method: 'POST',
237
+ body: {
238
+ name: newDemo.value.name,
239
+ startUrlPattern: newDemo.value.startUrlPattern || null,
240
+ },
241
+ },
242
+ );
243
+ newDemo.value = { name: '', startUrlPattern: '' };
244
+ await refreshDemos(selectedProject.value.projectId);
245
+ showToast('Demo created', 'success');
246
+ return data;
247
+ }, showToast);
248
+
249
+ const selectDemo = withToast(async (d) => {
250
+ selectedDemo.value = { ...d };
251
+ steps.value = [];
252
+ closeWs();
253
+ authoring.value = { targetUrl: '', sessionId: '', token: '', connectUrl: '', wsStatus: 'disconnected' };
254
+ lastSelection.value = null;
255
+ await refreshSteps(d.demoId);
256
+ }, showToast);
257
+
258
+ const publishDemo = withToast(async () => {
259
+ if (!selectedDemo.value) return;
260
+ const data = await api('/api/admin/superdemos/demos/' + encodeURIComponent(selectedDemo.value.demoId) + '/publish', {
261
+ method: 'POST',
262
+ });
263
+ await refreshDemos(selectedProject.value.projectId);
264
+ selectedDemo.value = data.item;
265
+ showToast('Published', 'success');
266
+ }, showToast);
267
+
268
+ const saveSteps = withToast(async () => {
269
+ if (!selectedDemo.value) return;
270
+ await api('/api/admin/superdemos/demos/' + encodeURIComponent(selectedDemo.value.demoId) + '/steps', {
271
+ method: 'PUT',
272
+ body: {
273
+ steps: steps.value.map((s) => ({
274
+ selector: s.selector,
275
+ selectorHints: s.selectorHints || null,
276
+ message: s.message,
277
+ placement: s.placement || 'auto',
278
+ waitFor: s.waitFor || null,
279
+ advance: s.advance || { type: 'manualNext' },
280
+ })),
281
+ },
282
+ });
283
+ showToast('Steps saved', 'success');
284
+ }, showToast);
285
+
286
+ const removeStep = (idx) => {
287
+ steps.value = steps.value.filter((_, i) => i !== idx);
288
+ };
289
+
290
+ const moveStep = (idx, delta) => {
291
+ const next = idx + delta;
292
+ if (next < 0 || next >= steps.value.length) return;
293
+ const copy = [...steps.value];
294
+ const [item] = copy.splice(idx, 1);
295
+ copy.splice(next, 0, item);
296
+ steps.value = copy;
297
+ };
298
+
299
+ const addStepFromSelection = () => {
300
+ if (!lastSelection.value) {
301
+ showToast('No selection from SDK yet', 'error');
302
+ return;
303
+ }
304
+ steps.value = [
305
+ ...steps.value,
306
+ {
307
+ selector: String(lastSelection.value.selector || ''),
308
+ selectorHints: lastSelection.value.hints || null,
309
+ message: '...',
310
+ placement: 'auto',
311
+ waitFor: null,
312
+ advance: { type: 'manualNext' },
313
+ },
314
+ ];
315
+ };
316
+
317
+ const startAuthoring = withToast(async () => {
318
+ if (!selectedDemo.value) return;
319
+ const data = await api('/api/admin/superdemos/authoring-sessions', {
320
+ method: 'POST',
321
+ body: {
322
+ demoId: selectedDemo.value.demoId,
323
+ targetUrl: authoring.value.targetUrl,
324
+ },
325
+ });
326
+
327
+ authoring.value.sessionId = data.sessionId;
328
+ authoring.value.token = data.token;
329
+ authoring.value.connectUrl = data.connectUrl;
330
+
331
+ connectWs(data.sessionId, data.token);
332
+ showToast('Authoring session created', 'success');
333
+ }, showToast);
334
+
335
+ const setQaTargetUrl = () => {
336
+ authoring.value.targetUrl = qaPageUrl.value;
337
+ showToast('Target URL set to QA page', 'success');
338
+ };
339
+
340
+ const previewStep = (step) => {
341
+ if (!ws || ws.readyState !== ws.OPEN) {
342
+ showToast('WS not connected', 'error');
343
+ return;
344
+ }
345
+ const s = step || {};
346
+ ws.send(
347
+ JSON.stringify({
348
+ type: 'preview_bubble',
349
+ selector: s.selector,
350
+ message: s.message,
351
+ placement: s.placement || 'auto',
352
+ }),
353
+ );
354
+ };
355
+
356
+ refreshAll().catch(() => {});
357
+
358
+ onBeforeUnmount(() => {
359
+ closeWs();
360
+ });
361
+
362
+ return {
363
+ origin,
364
+ qaPageUrl,
365
+ toast,
366
+ projects,
367
+ selectedProject,
368
+ demos,
369
+ selectedDemo,
370
+ steps,
371
+ newProject,
372
+ newDemo,
373
+ projectStylePreset,
374
+ projectStyleOverrides,
375
+ createProject,
376
+ refreshAll,
377
+ selectProject,
378
+ createDemo,
379
+ selectDemo,
380
+ publishDemo,
381
+ saveSteps,
382
+ removeStep,
383
+ moveStep,
384
+ authoring,
385
+ startAuthoring,
386
+ setQaTargetUrl,
387
+ saveProjectStyleSettings,
388
+ copyText,
389
+ lastGeneratedKey,
390
+ lastSelection,
391
+ addStepFromSelection,
392
+ previewStep,
393
+ };
394
+ },
395
+ }).mount('#app');
396
+ })();