webspresso 0.0.95 → 0.0.96
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/.agents/ADMIN.md +225 -0
- package/.agents/AGENTS.md +72 -0
- package/.agents/ARCHITECTURE.md +119 -0
- package/.agents/AUTH.md +103 -0
- package/.agents/CLI.md +115 -0
- package/.agents/COMPRESSION.md +59 -0
- package/.agents/CONVENTIONS.md +46 -0
- package/.agents/EMAIL.md +141 -0
- package/.agents/ERRORS.md +201 -0
- package/.agents/MCP.md +103 -0
- package/.agents/ORM.md +230 -0
- package/.agents/PLUGINS.md +419 -0
- package/.agents/QUEUE.md +113 -0
- package/.agents/REALTIME.md +213 -0
- package/.agents/ROUTING.md +292 -0
- package/.agents/SERVICES.md +341 -0
- package/.agents/SHUTDOWN.md +67 -0
- package/.agents/STREAMING.md +73 -0
- package/.agents/WORKFLOW.md +27 -0
- package/.agents/internal/DOCUMENTATION_REVIEW.md +485 -0
- package/.agents/internal/TECHNICAL_REVIEW.md +428 -0
- package/.cursorrules +14 -0
- package/AGENTS.md +175 -0
- package/CLAUDE.md +54 -0
- package/README.md +18 -14
- package/bin/commands/agents-init.js +169 -0
- package/bin/commands/api.js +114 -48
- package/bin/commands/doctor.js +35 -0
- package/bin/commands/new.js +13 -2
- package/bin/commands/page.js +75 -89
- package/bin/commands/service.js +77 -0
- package/bin/webspresso.js +2 -2
- package/core/compileSchema.js +1 -1
- package/core/errors/middleware.js +22 -2
- package/core/errors/normalize.js +29 -2
- package/{utils/schemaCache.js → core/schema-cache.js} +2 -3
- package/index.d.ts +306 -0
- package/index.js +35 -1
- package/package.json +5 -3
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +22 -1
- package/plugins/csrf/index.js +3 -2
- package/plugins/csv/engine.js +447 -0
- package/plugins/csv/index.js +94 -0
- package/plugins/csv/middleware.js +80 -0
- package/plugins/csv/services.js +138 -0
- package/plugins/file-manager/admin-component.js +613 -0
- package/plugins/file-manager/api-handlers.js +261 -0
- package/plugins/file-manager/core.js +601 -0
- package/plugins/file-manager/index.js +139 -0
- package/plugins/file-manager/security.js +207 -0
- package/plugins/index.js +8 -0
- package/plugins/xlsx/engine.js +397 -0
- package/plugins/xlsx/index.js +91 -0
- package/plugins/xlsx/middleware.js +87 -0
- package/plugins/xlsx/services.js +118 -0
- package/src/api/api-loader.js +233 -0
- package/src/api/define-api.js +35 -0
- package/src/discovery/file-route-parser.js +201 -0
- package/src/discovery/scan-routes.js +265 -0
- package/src/file-router.js +6 -0
- package/src/modules/define-module.js +99 -0
- package/src/modules/module-discovery.js +117 -0
- package/src/pages/define-page.js +36 -0
- package/src/pages/page-loader.js +214 -0
- package/src/routing/mount-discovered-routes.js +78 -0
- package/src/routing/route-table.js +191 -0
- package/src/server.js +112 -15
- package/src/services/builtins/file-manager.js +178 -0
- package/src/services/index.js +2 -0
- package/bin/commands/skill.js +0 -187
- package/templates/skills/webspresso-usage/REFERENCE-framework.md +0 -289
- package/templates/skills/webspresso-usage/REFERENCE-kernel.md +0 -93
- package/templates/skills/webspresso-usage/SKILL.md +0 -48
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webspresso File Manager Plugin
|
|
3
|
+
* Filesystem-first media library, directory browser and asset picker for Admin Panel.
|
|
4
|
+
* @module plugins/file-manager
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { createFileManagerApiHandlers } = require('./api-handlers');
|
|
9
|
+
const { generateFileManagerComponent } = require('./admin-component');
|
|
10
|
+
const { createFileManagerServices } = require('../../src/services/builtins/file-manager');
|
|
11
|
+
const core = require('./core');
|
|
12
|
+
const security = require('./security');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} [options]
|
|
16
|
+
* @param {string} [options.baseDir='./public/uploads'] - Filesystem directory to manage
|
|
17
|
+
* @param {string} [options.publicBasePath='/uploads'] - URL prefix for stored files
|
|
18
|
+
* @param {string} [options.apiPrefix='/files'] - API subpath under Admin API
|
|
19
|
+
* @param {string} [options.pageTitle='Dosya Yöneticisi'] - Title of admin page
|
|
20
|
+
* @param {string} [options.menuLabel='Dosya Yöneticisi'] - Sidebar menu label
|
|
21
|
+
* @param {number} [options.menuOrder=50] - Sidebar menu ordering
|
|
22
|
+
* @param {number} [options.maxUploadBytes=10485760] - Max upload size in bytes (10MB default)
|
|
23
|
+
* @param {number|null} [options.maxTotalStorageBytes] - Storage quota limit in bytes (optional)
|
|
24
|
+
* @param {string[]|null} [options.allowedExtensions] - Whitelist of allowed extensions
|
|
25
|
+
* @param {string[]|null} [options.allowedMimeTypes] - Whitelist of allowed MIME types
|
|
26
|
+
* @param {Record<string, string[]>|null} [options.permissions] - RBAC permissions map
|
|
27
|
+
* @param {Object} [options.security] - Security configurations
|
|
28
|
+
* @param {boolean} [options.security.validateMagicBytes=true] - Validate file signatures
|
|
29
|
+
* @param {boolean} [options.security.sanitizeSvg=true] - Sanitize SVG XSS payloads
|
|
30
|
+
* @param {boolean} [options.includePage=true] - Whether to register admin menu item & page
|
|
31
|
+
*/
|
|
32
|
+
function fileManagerPlugin(options = {}) {
|
|
33
|
+
const {
|
|
34
|
+
baseDir = path.join(process.cwd(), 'public', 'uploads'),
|
|
35
|
+
publicBasePath = '/uploads',
|
|
36
|
+
apiPrefix = '/files',
|
|
37
|
+
pageTitle = 'Dosya Yöneticisi',
|
|
38
|
+
menuLabel = 'Dosya Yöneticisi',
|
|
39
|
+
menuOrder = 50,
|
|
40
|
+
maxUploadBytes = 10 * 1024 * 1024,
|
|
41
|
+
maxTotalStorageBytes = null,
|
|
42
|
+
allowedExtensions = null,
|
|
43
|
+
allowedMimeTypes = null,
|
|
44
|
+
permissions = null,
|
|
45
|
+
security: securityOpts = { validateMagicBytes: true, sanitizeSvg: true },
|
|
46
|
+
includePage = true,
|
|
47
|
+
} = options;
|
|
48
|
+
|
|
49
|
+
const resolvedBaseDir = path.resolve(baseDir);
|
|
50
|
+
|
|
51
|
+
const handlers = createFileManagerApiHandlers({
|
|
52
|
+
baseDir: resolvedBaseDir,
|
|
53
|
+
publicBasePath,
|
|
54
|
+
maxUploadBytes,
|
|
55
|
+
maxTotalStorageBytes,
|
|
56
|
+
allowedExtensions,
|
|
57
|
+
allowedMimeTypes,
|
|
58
|
+
permissions,
|
|
59
|
+
security: securityOpts,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
name: 'file-manager',
|
|
64
|
+
version: '1.0.0',
|
|
65
|
+
description: 'Filesystem-first media library, directory explorer & asset picker',
|
|
66
|
+
dependencies: { 'admin-panel': '*' },
|
|
67
|
+
|
|
68
|
+
api: {
|
|
69
|
+
...core,
|
|
70
|
+
security,
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
register(ctx) {
|
|
74
|
+
// Register built-in services in app service registry if available
|
|
75
|
+
if (ctx.app && ctx.app.serviceRegistry) {
|
|
76
|
+
const services = createFileManagerServices({
|
|
77
|
+
baseDir: resolvedBaseDir,
|
|
78
|
+
publicBasePath,
|
|
79
|
+
});
|
|
80
|
+
for (const [name, def] of Object.entries(services)) {
|
|
81
|
+
if (!ctx.app.serviceRegistry.has(name)) {
|
|
82
|
+
ctx.app.serviceRegistry.register(name, def);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
onRoutesReady(ctx) {
|
|
89
|
+
const adminApi = ctx.usePlugin('admin-panel');
|
|
90
|
+
if (!adminApi) {
|
|
91
|
+
console.warn('[file-manager] admin-panel plugin not found, skipping Admin UI registration');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
adminApi.registerModule({
|
|
96
|
+
id: 'files',
|
|
97
|
+
pages: includePage
|
|
98
|
+
? [{
|
|
99
|
+
id: 'files',
|
|
100
|
+
title: pageTitle,
|
|
101
|
+
path: '/files',
|
|
102
|
+
icon: 'folder',
|
|
103
|
+
component: generateFileManagerComponent({
|
|
104
|
+
apiPrefix,
|
|
105
|
+
publicBasePath,
|
|
106
|
+
}),
|
|
107
|
+
}]
|
|
108
|
+
: [],
|
|
109
|
+
|
|
110
|
+
menu: includePage
|
|
111
|
+
? [{
|
|
112
|
+
id: 'files',
|
|
113
|
+
label: menuLabel,
|
|
114
|
+
path: '/files',
|
|
115
|
+
icon: 'folder',
|
|
116
|
+
order: menuOrder,
|
|
117
|
+
}]
|
|
118
|
+
: [],
|
|
119
|
+
|
|
120
|
+
api: {
|
|
121
|
+
prefix: apiPrefix,
|
|
122
|
+
routes: [
|
|
123
|
+
{ method: 'get', path: '', handler: handlers.list },
|
|
124
|
+
{ method: 'post', path: '/mkdir', handler: handlers.mkdir },
|
|
125
|
+
{ method: 'post', path: '/rename', handler: handlers.rename },
|
|
126
|
+
{ method: 'post', path: '/move', handler: handlers.move },
|
|
127
|
+
{ method: 'post', path: '/delete', handler: handlers.delete },
|
|
128
|
+
{ method: 'post', path: '/upload', handler: handlers.upload },
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = fileManagerPlugin;
|
|
137
|
+
module.exports.fileManagerPlugin = fileManagerPlugin;
|
|
138
|
+
module.exports.core = core;
|
|
139
|
+
module.exports.security = security;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Manager Security Subsystem — Magic byte verification, dangerous extension blocking & SVG sanitization.
|
|
3
|
+
* Zero external dependencies, ultra-high performance O(1) buffer checks.
|
|
4
|
+
* @module plugins/file-manager/security
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { SecurityError, ValidationError } = require('../../core/errors');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Universal list of dangerous/executable extensions (lowercase without dot)
|
|
11
|
+
*/
|
|
12
|
+
const DANGEROUS_EXTENSIONS = new Set([
|
|
13
|
+
// Scripts & Executables
|
|
14
|
+
'php', 'phtml', 'php3', 'php4', 'php5', 'php7', 'php8', 'phps', 'phar',
|
|
15
|
+
'exe', 'dll', 'so', 'dylib', 'bin', 'elf',
|
|
16
|
+
'sh', 'bash', 'zsh', 'csh', 'ksh', 'fish', 'bat', 'cmd', 'ps1', 'psm1',
|
|
17
|
+
'vbs', 'vbe', 'wsf', 'wsh', 'scr', 'cpl', 'msc', 'hta',
|
|
18
|
+
'jar', 'war', 'ear', 'class',
|
|
19
|
+
'jsp', 'jspx', 'jsw', 'jsv', 'jspf',
|
|
20
|
+
'asp', 'aspx', 'axd', 'asx', 'ashx', 'asmx',
|
|
21
|
+
'cgi', 'pl', 'pm', 'py', 'pyc', 'pyo', 'rb',
|
|
22
|
+
// System & Config files
|
|
23
|
+
'htaccess', 'htpasswd', 'ini', 'env', 'config', 'conf', 'passwd', 'shadow',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Known magic numbers table for high-speed signature verification.
|
|
28
|
+
* Only validates when extension matches known binary format.
|
|
29
|
+
*/
|
|
30
|
+
const MAGIC_NUMBERS = {
|
|
31
|
+
// JPEG / JPG
|
|
32
|
+
jpg: (buf) => buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff,
|
|
33
|
+
jpeg: (buf) => buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff,
|
|
34
|
+
|
|
35
|
+
// PNG
|
|
36
|
+
png: (buf) =>
|
|
37
|
+
buf.length >= 8 &&
|
|
38
|
+
buf[0] === 0x89 &&
|
|
39
|
+
buf[1] === 0x50 &&
|
|
40
|
+
buf[2] === 0x4e &&
|
|
41
|
+
buf[3] === 0x47 &&
|
|
42
|
+
buf[4] === 0x0d &&
|
|
43
|
+
buf[5] === 0x0a &&
|
|
44
|
+
buf[6] === 0x1a &&
|
|
45
|
+
buf[7] === 0x0a,
|
|
46
|
+
|
|
47
|
+
// GIF
|
|
48
|
+
gif: (buf) =>
|
|
49
|
+
buf.length >= 6 &&
|
|
50
|
+
buf[0] === 0x47 &&
|
|
51
|
+
buf[1] === 0x49 &&
|
|
52
|
+
buf[2] === 0x46 &&
|
|
53
|
+
buf[3] === 0x38 &&
|
|
54
|
+
(buf[4] === 0x37 || buf[4] === 0x39) &&
|
|
55
|
+
buf[5] === 0x61,
|
|
56
|
+
|
|
57
|
+
// WebP: RIFF at 0..3 and WEBP at 8..11
|
|
58
|
+
webp: (buf) =>
|
|
59
|
+
buf.length >= 12 &&
|
|
60
|
+
buf[0] === 0x52 &&
|
|
61
|
+
buf[1] === 0x49 &&
|
|
62
|
+
buf[2] === 0x46 &&
|
|
63
|
+
buf[3] === 0x46 &&
|
|
64
|
+
buf[8] === 0x57 &&
|
|
65
|
+
buf[9] === 0x45 &&
|
|
66
|
+
buf[10] === 0x42 &&
|
|
67
|
+
buf[11] === 0x50,
|
|
68
|
+
|
|
69
|
+
// PDF: %PDF-
|
|
70
|
+
pdf: (buf) =>
|
|
71
|
+
buf.length >= 5 &&
|
|
72
|
+
buf[0] === 0x25 &&
|
|
73
|
+
buf[1] === 0x50 &&
|
|
74
|
+
buf[2] === 0x44 &&
|
|
75
|
+
buf[3] === 0x46 &&
|
|
76
|
+
buf[4] === 0x2d,
|
|
77
|
+
|
|
78
|
+
// ZIP (and DOCX/XLSX/PPTX)
|
|
79
|
+
zip: (buf) =>
|
|
80
|
+
buf.length >= 4 &&
|
|
81
|
+
buf[0] === 0x50 &&
|
|
82
|
+
buf[1] === 0x4b &&
|
|
83
|
+
(buf[2] === 0x03 || buf[2] === 0x05 || buf[2] === 0x07) &&
|
|
84
|
+
(buf[3] === 0x04 || buf[3] === 0x06 || buf[3] === 0x08),
|
|
85
|
+
docx: (buf) => MAGIC_NUMBERS.zip(buf),
|
|
86
|
+
xlsx: (buf) => MAGIC_NUMBERS.zip(buf),
|
|
87
|
+
pptx: (buf) => MAGIC_NUMBERS.zip(buf),
|
|
88
|
+
|
|
89
|
+
// MP3: ID3 or frame sync 0xFFE / 0xFFF
|
|
90
|
+
mp3: (buf) =>
|
|
91
|
+
(buf.length >= 3 && buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) ||
|
|
92
|
+
(buf.length >= 2 && buf[0] === 0xff && (buf[1] & 0xe0) === 0xe0),
|
|
93
|
+
|
|
94
|
+
// MP4: ....ftyp at bytes 4..7
|
|
95
|
+
mp4: (buf) =>
|
|
96
|
+
buf.length >= 8 &&
|
|
97
|
+
buf[4] === 0x66 &&
|
|
98
|
+
buf[5] === 0x74 &&
|
|
99
|
+
buf[6] === 0x79 &&
|
|
100
|
+
buf[7] === 0x70,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate that a filename contains no dangerous extensions or double-extension tricks.
|
|
105
|
+
* @param {string} filename
|
|
106
|
+
* @returns {string} Clean lowercase extension
|
|
107
|
+
*/
|
|
108
|
+
function validateSafeExtension(filename) {
|
|
109
|
+
if (!filename || typeof filename !== 'string') {
|
|
110
|
+
throw new ValidationError('Filename is required');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const cleanName = filename.toLowerCase().trim();
|
|
114
|
+
const parts = cleanName.split('.').filter(Boolean);
|
|
115
|
+
|
|
116
|
+
if (parts.length === 0) {
|
|
117
|
+
throw new ValidationError('Filename must have a valid extension');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check all dotted segments against dangerous extensions to block double extensions (e.g. payload.php.jpg)
|
|
121
|
+
for (let i = 1; i < parts.length; i++) {
|
|
122
|
+
const segment = parts[i];
|
|
123
|
+
if (DANGEROUS_EXTENSIONS.has(segment)) {
|
|
124
|
+
throw new SecurityError(
|
|
125
|
+
`Dangerous extension or double-extension detected: .${segment}`,
|
|
126
|
+
400
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const finalExt = parts[parts.length - 1];
|
|
132
|
+
if (DANGEROUS_EXTENSIONS.has(finalExt)) {
|
|
133
|
+
throw new SecurityError(`File type .${finalExt} is forbidden for upload`, 403);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return finalExt;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Verify that the buffer's magic bytes match the claimed extension.
|
|
141
|
+
* O(1) time complexity, minimal memory overhead.
|
|
142
|
+
* @param {Buffer} buffer
|
|
143
|
+
* @param {string} ext
|
|
144
|
+
* @returns {boolean}
|
|
145
|
+
*/
|
|
146
|
+
function validateMagicBytes(buffer, ext) {
|
|
147
|
+
if (!buffer || !Buffer.isBuffer(buffer)) {
|
|
148
|
+
throw new ValidationError('Valid buffer is required for magic byte check');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const cleanExt = (ext || '').replace(/^\./, '').toLowerCase();
|
|
152
|
+
const validator = MAGIC_NUMBERS[cleanExt];
|
|
153
|
+
|
|
154
|
+
if (validator) {
|
|
155
|
+
const isValid = validator(buffer);
|
|
156
|
+
if (!isValid) {
|
|
157
|
+
throw new SecurityError(
|
|
158
|
+
`File signature mismatch: file content does not match expected .${cleanExt} format`,
|
|
159
|
+
400
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Sanitize SVG content against Stored XSS attacks.
|
|
169
|
+
* Strips <script>, <foreignObject>, <iframe>, <embed>, <object> and on* event handlers.
|
|
170
|
+
* @param {Buffer|string} content
|
|
171
|
+
* @returns {Buffer} Sanitized SVG buffer
|
|
172
|
+
*/
|
|
173
|
+
function sanitizeSvgContent(content) {
|
|
174
|
+
const str = Buffer.isBuffer(content) ? content.toString('utf8') : String(content);
|
|
175
|
+
|
|
176
|
+
// Quick check: if no dangerous keywords, return original buffer directly for maximum speed
|
|
177
|
+
if (!/<(script|foreignobject|iframe|embed|object|meta|link|style)/i.test(str) &&
|
|
178
|
+
!/\s+on[a-z]+\s*=/i.test(str) &&
|
|
179
|
+
!/javascript:/i.test(str) &&
|
|
180
|
+
!/data:text\/html/i.test(str)) {
|
|
181
|
+
return Buffer.isBuffer(content) ? content : Buffer.from(str, 'utf8');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let sanitized = str
|
|
185
|
+
// Remove script tags and content
|
|
186
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script\s*>/gi, '')
|
|
187
|
+
// Remove dangerous containers
|
|
188
|
+
.replace(/<foreignObject\b[^<]*(?:(?!<\/foreignObject>)<[^<]*)*<\/foreignObject\s*>/gi, '')
|
|
189
|
+
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe\s*>/gi, '')
|
|
190
|
+
.replace(/<embed\b[^>]*>/gi, '')
|
|
191
|
+
.replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object\s*>/gi, '')
|
|
192
|
+
// Remove inline event handlers (onload, onclick, onerror, onmouseover, etc.)
|
|
193
|
+
.replace(/\s+on[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '')
|
|
194
|
+
// Remove javascript: and data:text/html URIs in href and xlink:href
|
|
195
|
+
.replace(/(?:href|xlink:href)\s*=\s*(?:"\s*javascript:[^"]*"|'\s*javascript:[^']*')/gi, 'href=""')
|
|
196
|
+
.replace(/(?:href|xlink:href)\s*=\s*(?:"\s*data:text\/html[^"]*"|'\s*data:text\/html[^']*')/gi, 'href=""');
|
|
197
|
+
|
|
198
|
+
return Buffer.from(sanitized, 'utf8');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
DANGEROUS_EXTENSIONS,
|
|
203
|
+
MAGIC_NUMBERS,
|
|
204
|
+
validateSafeExtension,
|
|
205
|
+
validateMagicBytes,
|
|
206
|
+
sanitizeSvgContent,
|
|
207
|
+
};
|
package/plugins/index.js
CHANGED
|
@@ -41,6 +41,9 @@ const {
|
|
|
41
41
|
} = require('./realtime');
|
|
42
42
|
const { mcpPlugin, createMcpServer, McpServer } = require('./mcp');
|
|
43
43
|
const polarPlugin = require('./polar');
|
|
44
|
+
const fileManagerPlugin = require('./file-manager');
|
|
45
|
+
const { xlsxPlugin, xlsx } = require('./xlsx');
|
|
46
|
+
const { csvPlugin, csv } = require('./csv');
|
|
44
47
|
|
|
45
48
|
module.exports = {
|
|
46
49
|
sitemapPlugin,
|
|
@@ -83,6 +86,11 @@ module.exports = {
|
|
|
83
86
|
createMcpServer,
|
|
84
87
|
McpServer,
|
|
85
88
|
polarPlugin,
|
|
89
|
+
fileManagerPlugin,
|
|
90
|
+
xlsxPlugin,
|
|
91
|
+
xlsx,
|
|
92
|
+
csvPlugin,
|
|
93
|
+
csv,
|
|
86
94
|
};
|
|
87
95
|
|
|
88
96
|
|