webspresso 0.0.95 → 0.0.97

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 (94) hide show
  1. package/.agents/ADMIN.md +225 -0
  2. package/.agents/AGENTS.md +72 -0
  3. package/.agents/ARCHITECTURE.md +119 -0
  4. package/.agents/AUTH.md +103 -0
  5. package/.agents/CLI.md +115 -0
  6. package/.agents/COMPRESSION.md +59 -0
  7. package/.agents/CONVENTIONS.md +46 -0
  8. package/.agents/EMAIL.md +141 -0
  9. package/.agents/ERRORS.md +201 -0
  10. package/.agents/MCP.md +103 -0
  11. package/.agents/ORM.md +230 -0
  12. package/.agents/PLUGINS.md +419 -0
  13. package/.agents/QUEUE.md +113 -0
  14. package/.agents/REALTIME.md +213 -0
  15. package/.agents/ROUTING.md +292 -0
  16. package/.agents/SERVICES.md +341 -0
  17. package/.agents/SHUTDOWN.md +67 -0
  18. package/.agents/STREAMING.md +73 -0
  19. package/.agents/WORKFLOW.md +27 -0
  20. package/.agents/internal/DOCUMENTATION_REVIEW.md +485 -0
  21. package/.agents/internal/TECHNICAL_REVIEW.md +428 -0
  22. package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +122 -0
  23. package/.agents/skills/webspresso-framework/SKILL.md +56 -0
  24. package/.agents/skills/webspresso-orm-and-database/SKILL.md +119 -0
  25. package/.agents/skills/webspresso-routing-and-api/SKILL.md +114 -0
  26. package/.agents/skills/webspresso-services/SKILL.md +101 -0
  27. package/.cursorrules +14 -0
  28. package/AGENTS.md +175 -0
  29. package/CLAUDE.md +54 -0
  30. package/README.md +18 -14
  31. package/bin/commands/admin-password.js +2 -1
  32. package/bin/commands/admin-setup.js +1 -1
  33. package/bin/commands/agents-init.js +209 -0
  34. package/bin/commands/api.js +114 -48
  35. package/bin/commands/audit-prune.js +1 -1
  36. package/bin/commands/doctor.js +35 -0
  37. package/bin/commands/favicon-generate.js +1 -1
  38. package/bin/commands/new.js +13 -2
  39. package/bin/commands/orm-map.js +5 -4
  40. package/bin/commands/page.js +75 -89
  41. package/bin/commands/polar-migrate.js +1 -1
  42. package/bin/commands/seed.js +1 -1
  43. package/bin/commands/service.js +77 -0
  44. package/bin/webspresso.js +2 -2
  45. package/core/auth/hash.js +10 -5
  46. package/core/compileSchema.js +1 -1
  47. package/core/errors/middleware.js +22 -2
  48. package/core/errors/normalize.js +29 -2
  49. package/{utils/schemaCache.js → core/schema-cache.js} +2 -3
  50. package/index.d.ts +306 -0
  51. package/index.js +35 -1
  52. package/package.json +36 -14
  53. package/plugins/admin-panel/app.js +10 -9
  54. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +22 -1
  55. package/plugins/admin-panel/core/api-extensions.js +4 -0
  56. package/plugins/basic-auth/index.js +3 -1
  57. package/plugins/content/client/inline-edit.js +12 -12
  58. package/plugins/cors.js +6 -7
  59. package/plugins/csrf/index.js +3 -2
  60. package/plugins/csv/engine.js +447 -0
  61. package/plugins/csv/index.js +94 -0
  62. package/plugins/csv/middleware.js +80 -0
  63. package/plugins/csv/services.js +138 -0
  64. package/plugins/file-manager/admin-component.js +613 -0
  65. package/plugins/file-manager/api-handlers.js +261 -0
  66. package/plugins/file-manager/core.js +601 -0
  67. package/plugins/file-manager/index.js +139 -0
  68. package/plugins/file-manager/security.js +217 -0
  69. package/plugins/index.js +45 -79
  70. package/plugins/mcp/transports/sse.js +4 -2
  71. package/plugins/site-analytics/admin-component.js +7 -8
  72. package/plugins/xlsx/engine.js +397 -0
  73. package/plugins/xlsx/index.js +91 -0
  74. package/plugins/xlsx/middleware.js +87 -0
  75. package/plugins/xlsx/services.js +118 -0
  76. package/src/api/api-loader.js +233 -0
  77. package/src/api/define-api.js +35 -0
  78. package/src/discovery/file-route-parser.js +201 -0
  79. package/src/discovery/scan-routes.js +265 -0
  80. package/src/file-router.js +9 -1
  81. package/src/modules/define-module.js +99 -0
  82. package/src/modules/module-discovery.js +117 -0
  83. package/src/pages/define-page.js +36 -0
  84. package/src/pages/page-loader.js +214 -0
  85. package/src/routing/mount-discovered-routes.js +78 -0
  86. package/src/routing/route-table.js +191 -0
  87. package/src/server.js +112 -15
  88. package/src/services/builtins/file-manager.js +178 -0
  89. package/src/services/discovery.js +6 -1
  90. package/src/services/index.js +6 -11
  91. package/bin/commands/skill.js +0 -187
  92. package/templates/skills/webspresso-usage/REFERENCE-framework.md +0 -289
  93. package/templates/skills/webspresso-usage/REFERENCE-kernel.md +0 -93
  94. package/templates/skills/webspresso-usage/SKILL.md +0 -48
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Built-in File Manager Services
3
+ * @module src/services/builtins/file-manager
4
+ */
5
+
6
+ const path = require('path');
7
+ const fs = require('fs/promises');
8
+ const { z } = require('zod');
9
+ const { ValidationError, NotFoundError } = require('../../../core/errors');
10
+ const {
11
+ listFiles,
12
+ createDirectory,
13
+ renameItem,
14
+ deleteItem,
15
+ moveItem,
16
+ saveUploadedFile,
17
+ } = require('../../../plugins/file-manager/core');
18
+ const { parseBase64 } = require('./media');
19
+
20
+ /**
21
+ * Create built-in file manager services map
22
+ * @param {Object} [options]
23
+ * @param {string} [options.baseDir]
24
+ * @param {string} [options.publicBasePath]
25
+ * @returns {Record<string, Object>}
26
+ */
27
+ function createFileManagerServices(options = {}) {
28
+ const defaultBaseDir = path.resolve(
29
+ options.baseDir || path.join(process.cwd(), 'public', 'uploads')
30
+ );
31
+ const defaultPublicBase = options.publicBasePath || '/uploads';
32
+
33
+ return {
34
+ 'fileManager.list': {
35
+ schema: z.object({
36
+ path: z.string().optional().default(''),
37
+ search: z.string().optional(),
38
+ type: z.string().optional(),
39
+ sort: z.enum(['name', 'size', 'mtime', 'ext']).optional().default('name'),
40
+ order: z.enum(['asc', 'desc']).optional().default('asc'),
41
+ page: z.number().int().positive().optional(),
42
+ perPage: z.number().int().positive().optional(),
43
+ baseDir: z.string().optional(),
44
+ publicBasePath: z.string().optional(),
45
+ }),
46
+ async handler(input) {
47
+ const baseDir = input.baseDir ? path.resolve(input.baseDir) : defaultBaseDir;
48
+ const publicBasePath = input.publicBasePath || defaultPublicBase;
49
+
50
+ return listFiles(baseDir, input.path, {
51
+ search: input.search,
52
+ type: input.type,
53
+ sort: input.sort,
54
+ order: input.order,
55
+ page: input.page,
56
+ perPage: input.perPage,
57
+ publicBasePath,
58
+ });
59
+ },
60
+ },
61
+
62
+ 'fileManager.mkdir': {
63
+ schema: z.object({
64
+ path: z.string().optional().default(''),
65
+ name: z.string().min(1, 'Directory name is required'),
66
+ baseDir: z.string().optional(),
67
+ }),
68
+ async handler(input) {
69
+ const baseDir = input.baseDir ? path.resolve(input.baseDir) : defaultBaseDir;
70
+ return createDirectory(baseDir, input.path, input.name);
71
+ },
72
+ },
73
+
74
+ 'fileManager.rename': {
75
+ schema: z.object({
76
+ path: z.string().min(1, 'Target path is required'),
77
+ newName: z.string().min(1, 'New name is required'),
78
+ baseDir: z.string().optional(),
79
+ }),
80
+ async handler(input) {
81
+ const baseDir = input.baseDir ? path.resolve(input.baseDir) : defaultBaseDir;
82
+ return renameItem(baseDir, input.path, input.newName);
83
+ },
84
+ },
85
+
86
+ 'fileManager.delete': {
87
+ schema: z.object({
88
+ path: z.string().min(1, 'Path is required'),
89
+ baseDir: z.string().optional(),
90
+ }),
91
+ async handler(input) {
92
+ const baseDir = input.baseDir ? path.resolve(input.baseDir) : defaultBaseDir;
93
+ return deleteItem(baseDir, input.path);
94
+ },
95
+ },
96
+
97
+ 'fileManager.move': {
98
+ schema: z.object({
99
+ source: z.string().min(1, 'Source path is required'),
100
+ destination: z.string().optional().default(''),
101
+ baseDir: z.string().optional(),
102
+ }),
103
+ async handler(input) {
104
+ const baseDir = input.baseDir ? path.resolve(input.baseDir) : defaultBaseDir;
105
+ return moveItem(baseDir, input.source, input.destination);
106
+ },
107
+ },
108
+
109
+ 'fileManager.upload': {
110
+ schema: z.object({
111
+ path: z.string().optional().default(''),
112
+ buffer: z.any().optional(),
113
+ base64: z.string().optional(),
114
+ filePath: z.string().optional(),
115
+ originalName: z.string().min(1).default('upload.bin'),
116
+ mimeType: z.string().optional(),
117
+ baseDir: z.string().optional(),
118
+ publicBasePath: z.string().optional(),
119
+ }),
120
+ async handler(input) {
121
+ let {
122
+ path: targetRelPath,
123
+ buffer,
124
+ base64,
125
+ filePath,
126
+ originalName,
127
+ mimeType,
128
+ baseDir: customBaseDir,
129
+ publicBasePath: customPublicBase,
130
+ } = input;
131
+ const baseDir = customBaseDir ? path.resolve(customBaseDir) : defaultBaseDir;
132
+ const publicBasePath = customPublicBase || defaultPublicBase;
133
+
134
+ if (buffer && !(buffer instanceof Buffer) && !(buffer instanceof Uint8Array)) {
135
+ if (typeof buffer === 'string') {
136
+ buffer = Buffer.from(buffer);
137
+ } else {
138
+ throw new ValidationError('Invalid buffer provided');
139
+ }
140
+ }
141
+
142
+ if (!buffer && base64) {
143
+ const parsed = parseBase64(base64);
144
+ buffer = parsed.buffer;
145
+ if (parsed.mimeType && (!mimeType || mimeType === 'application/octet-stream')) {
146
+ mimeType = parsed.mimeType;
147
+ }
148
+ } else if (!buffer && filePath) {
149
+ try {
150
+ buffer = await fs.readFile(path.resolve(filePath));
151
+ if (!originalName || originalName === 'upload.bin') {
152
+ originalName = path.basename(filePath);
153
+ }
154
+ } catch (err) {
155
+ throw new NotFoundError(`Source file not found: ${filePath}`, { cause: err });
156
+ }
157
+ }
158
+
159
+ if (!buffer || buffer.length === 0) {
160
+ throw new ValidationError('No file content provided');
161
+ }
162
+
163
+ return saveUploadedFile(
164
+ baseDir,
165
+ targetRelPath,
166
+ buffer,
167
+ originalName,
168
+ mimeType,
169
+ publicBasePath
170
+ );
171
+ },
172
+ },
173
+ };
174
+ }
175
+
176
+ module.exports = {
177
+ createFileManagerServices,
178
+ };
@@ -16,7 +16,12 @@ const VALID_EXTENSIONS = new Set(['.js', '.mjs', '.cjs']);
16
16
  * @returns {string}
17
17
  */
18
18
  function toCamelCase(str) {
19
- return str.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
19
+ if (!str) return '';
20
+ const parts = str.split(/[-_]+/);
21
+ return parts.map((part, index) => {
22
+ if (!part) return '';
23
+ return index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1);
24
+ }).join('');
20
25
  }
21
26
 
22
27
  /**
@@ -40,22 +40,17 @@ function createServiceRegistry(options = {}) {
40
40
  return registry;
41
41
  }
42
42
 
43
- const { createAuthServices } = require('./builtins/auth');
44
- const { createMailServices } = require('./builtins/mail');
45
- const { createMediaServices } = require('./builtins/media');
46
- const { createSystemServices } = require('./builtins/system');
47
- const { createExchangeServices } = require('./builtins/exchange');
48
-
49
43
  module.exports = {
50
44
  ServiceRegistry,
51
45
  createServiceRegistry,
52
46
  defineService,
53
47
  service: defineService, // alias
54
- createAuthServices,
55
- createMailServices,
56
- createMediaServices,
57
- createSystemServices,
58
- createExchangeServices,
48
+ get createAuthServices() { return require('./builtins/auth').createAuthServices; },
49
+ get createMailServices() { return require('./builtins/mail').createMailServices; },
50
+ get createMediaServices() { return require('./builtins/media').createMediaServices; },
51
+ get createFileManagerServices() { return require('./builtins/file-manager').createFileManagerServices; },
52
+ get createSystemServices() { return require('./builtins/system').createSystemServices; },
53
+ get createExchangeServices() { return require('./builtins/exchange').createExchangeServices; },
59
54
  memoize,
60
55
  parseTtlMs,
61
56
  stableCacheKey,
@@ -1,187 +0,0 @@
1
- /**
2
- * Skill Command — scaffold Agent Skill (SKILL.md) for Cursor / AI tools
3
- */
4
-
5
- const fs = require('fs');
6
- const path = require('path');
7
- const os = require('os');
8
- let _inquirer;
9
- const inquirer = {
10
- prompt: (...args) => {
11
- if (!_inquirer) _inquirer = require('inquirer');
12
- return _inquirer.prompt(...args);
13
- },
14
- };
15
-
16
- const NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$|^[a-z0-9]$/;
17
-
18
- function validateSkillName(name) {
19
- const t = String(name).trim().toLowerCase();
20
- if (!t) return 'Skill name is required';
21
- if (t.length > 64) return 'Skill name must be at most 64 characters';
22
- if (!NAME_PATTERN.test(t)) {
23
- return 'Use lowercase letters, numbers, and hyphens only (e.g. my-skill-name)';
24
- }
25
- return true;
26
- }
27
-
28
- function skillDir(base, skillName) {
29
- return path.join(base, '.agents', 'skills', skillName);
30
- }
31
-
32
-
33
- function defaultDescription(skillName) {
34
- const label = skillName.replace(/-/g, ' ');
35
- return `Guides the agent through tasks for ${label}. Use when the user works on ${label} or asks about related workflows.`;
36
- }
37
-
38
- /** Bundled presets: CLI flag → template directory (all *.md copied) */
39
- const PRESETS = {
40
- webspresso: {
41
- skillName: 'webspresso-usage',
42
- templateDir: path.join(__dirname, '../../templates/skills/webspresso-usage'),
43
- },
44
- };
45
-
46
- function buildSkillMarkdown(skillName, description) {
47
- const title = skillName
48
- .split('-')
49
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
50
- .join(' ');
51
- const descYaml = JSON.stringify(description.trim());
52
-
53
- return `---
54
- name: ${skillName}
55
- description: ${descYaml}
56
- ---
57
-
58
- # ${title}
59
-
60
- ## Instructions
61
-
62
- - Describe step-by-step what the agent should do.
63
- - Add constraints, file paths, and conventions for this project.
64
-
65
- ## When to use
66
-
67
- - List concrete user phrases or tasks that should trigger this skill.
68
-
69
- ## Examples
70
-
71
- \`\`\`
72
- Example prompt or command
73
- \`\`\`
74
-
75
- `;
76
-
77
- }
78
-
79
- function registerCommand(program) {
80
- program
81
- .command('skill [name]')
82
- .description('Create an Agent Skill folder with SKILL.md')
83
- .option('-g, --global', 'Write to ~/.agents/skills/ instead of ./.agents/skills/')
84
- .option('-d, --description <text>', 'Skill description (for YAML frontmatter)')
85
- .option('-f, --force', 'Overwrite existing SKILL.md')
86
- .option(
87
- '-p, --preset <name>',
88
- 'Install bundled skill: webspresso → agent reference (SKILL.md + REFERENCE-*.md in .agents/skills/webspresso-usage/)',
89
- )
90
-
91
- .action(async (nameArg, options) => {
92
- const presetKey = options.preset ? String(options.preset).trim().toLowerCase() : '';
93
-
94
- if (presetKey) {
95
- const preset = PRESETS[presetKey];
96
- if (!preset) {
97
- console.error(`❌ Unknown preset "${presetKey}". Available: ${Object.keys(PRESETS).join(', ')}`);
98
- process.exit(1);
99
- }
100
- const templateDir = preset.templateDir;
101
- if (!templateDir || !fs.existsSync(templateDir)) {
102
- console.error(`❌ Preset template directory missing: ${templateDir}`);
103
- process.exit(1);
104
- }
105
- const mdFiles = fs
106
- .readdirSync(templateDir)
107
- .filter((f) => f.endsWith('.md'));
108
- if (mdFiles.length === 0) {
109
- console.error(`❌ No .md files in preset: ${templateDir}`);
110
- process.exit(1);
111
- }
112
- const root = options.global ? os.homedir() : process.cwd();
113
- const dir = skillDir(root, preset.skillName);
114
- const skillFile = path.join(dir, 'SKILL.md');
115
-
116
- if (!options.force && fs.existsSync(skillFile)) {
117
- console.error(`❌ Already exists: ${skillFile}`);
118
- console.error(' Use --force to overwrite.');
119
- process.exit(1);
120
- }
121
-
122
- fs.mkdirSync(dir, { recursive: true });
123
- for (const file of mdFiles) {
124
- fs.copyFileSync(path.join(templateDir, file), path.join(dir, file));
125
- }
126
-
127
- console.log(`\n✅ Installed bundled skill "${presetKey}" →\n ${dir}/\n`);
128
- console.log(` Files: ${mdFiles.join(', ')}\n`);
129
- console.log(' Edit if needed, then restart Cursor or reload the window.\n');
130
- return;
131
- }
132
-
133
- let skillName = nameArg ? String(nameArg).trim().toLowerCase() : '';
134
-
135
- if (!skillName) {
136
- const { name } = await inquirer.prompt([
137
- {
138
- type: 'input',
139
- name: 'name',
140
- message: 'Skill name (kebab-case, e.g. review-api):',
141
- validate: validateSkillName,
142
- },
143
- ]);
144
- skillName = name.trim().toLowerCase();
145
- } else {
146
- const v = validateSkillName(skillName);
147
- if (v !== true) {
148
- console.error(`❌ ${v}`);
149
- process.exit(1);
150
- }
151
- skillName = skillName.trim().toLowerCase();
152
- }
153
-
154
- let description = options.description;
155
- if (!description) {
156
- const { desc } = await inquirer.prompt([
157
- {
158
- type: 'input',
159
- name: 'desc',
160
- message: 'Short description (what/when the agent should use this skill):',
161
- default: defaultDescription(skillName),
162
- },
163
- ]);
164
- description = desc || defaultDescription(skillName);
165
- }
166
-
167
- const root = options.global ? os.homedir() : process.cwd();
168
- const dir = skillDir(root, skillName);
169
- const skillFile = path.join(dir, 'SKILL.md');
170
-
171
- if (!options.force && fs.existsSync(skillFile)) {
172
- console.error(`❌ Already exists: ${skillFile}`);
173
- console.error(' Use --force to overwrite.');
174
- process.exit(1);
175
- }
176
-
177
- fs.mkdirSync(dir, { recursive: true });
178
-
179
- const body = buildSkillMarkdown(skillName, description);
180
- fs.writeFileSync(skillFile, body, 'utf8');
181
-
182
- console.log(`\n✅ Agent skill created:\n ${skillFile}\n`);
183
- console.log(' Edit SKILL.md, then restart Cursor or reload the window if needed.\n');
184
- });
185
- }
186
-
187
- module.exports = { registerCommand };