@dollhousemcp/mcp-server 1.7.3 → 1.7.4

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 (51) hide show
  1. package/dist/config/ConfigWizard.d.ts +78 -0
  2. package/dist/config/ConfigWizard.d.ts.map +1 -0
  3. package/dist/config/ConfigWizard.js +370 -0
  4. package/dist/config/ConfigWizardCheck.d.ts +47 -0
  5. package/dist/config/ConfigWizardCheck.d.ts.map +1 -0
  6. package/dist/config/ConfigWizardCheck.js +208 -0
  7. package/dist/config/ConfigWizardDisplay.d.ts +64 -0
  8. package/dist/config/ConfigWizardDisplay.d.ts.map +1 -0
  9. package/dist/config/ConfigWizardDisplay.js +150 -0
  10. package/dist/config/WizardFirstResponse.d.ts +25 -0
  11. package/dist/config/WizardFirstResponse.d.ts.map +1 -0
  12. package/dist/config/WizardFirstResponse.js +118 -0
  13. package/dist/config/portfolioConfig.d.ts +40 -0
  14. package/dist/config/portfolioConfig.d.ts.map +1 -0
  15. package/dist/config/portfolioConfig.js +58 -0
  16. package/dist/config/wizardTemplates.d.ts +84 -0
  17. package/dist/config/wizardTemplates.d.ts.map +1 -0
  18. package/dist/config/wizardTemplates.js +195 -0
  19. package/dist/elements/BaseElement.d.ts +15 -0
  20. package/dist/elements/BaseElement.d.ts.map +1 -1
  21. package/dist/elements/BaseElement.js +38 -5
  22. package/dist/generated/version.d.ts +2 -2
  23. package/dist/generated/version.js +3 -3
  24. package/dist/handlers/PortfolioPullHandler.d.ts +69 -0
  25. package/dist/handlers/PortfolioPullHandler.d.ts.map +1 -0
  26. package/dist/handlers/PortfolioPullHandler.js +340 -0
  27. package/dist/scripts/scripts/run-config-wizard.js +57 -0
  28. package/dist/scripts/src/config/ConfigManager.js +799 -0
  29. package/dist/scripts/src/config/ConfigWizard.js +368 -0
  30. package/dist/scripts/src/errors/SecurityError.js +47 -0
  31. package/dist/scripts/src/security/constants.js +28 -0
  32. package/dist/scripts/src/security/contentValidator.js +415 -0
  33. package/dist/scripts/src/security/errors.js +32 -0
  34. package/dist/scripts/src/security/regexValidator.js +217 -0
  35. package/dist/scripts/src/security/secureYamlParser.js +272 -0
  36. package/dist/scripts/src/security/securityMonitor.js +111 -0
  37. package/dist/scripts/src/security/validators/unicodeValidator.js +315 -0
  38. package/dist/scripts/src/utils/logger.js +288 -0
  39. package/dist/sync/PortfolioDownloader.d.ts +27 -0
  40. package/dist/sync/PortfolioDownloader.d.ts.map +1 -0
  41. package/dist/sync/PortfolioDownloader.js +120 -0
  42. package/dist/sync/PortfolioSyncComparer.d.ts +50 -0
  43. package/dist/sync/PortfolioSyncComparer.d.ts.map +1 -0
  44. package/dist/sync/PortfolioSyncComparer.js +158 -0
  45. package/dist/tools/getWelcomeMessage.d.ts +41 -0
  46. package/dist/tools/getWelcomeMessage.d.ts.map +1 -0
  47. package/dist/tools/getWelcomeMessage.js +109 -0
  48. package/dist/utils/TemplateRenderer.d.ts +63 -0
  49. package/dist/utils/TemplateRenderer.d.ts.map +1 -0
  50. package/dist/utils/TemplateRenderer.js +154 -0
  51. package/package.json +1 -1
@@ -0,0 +1,368 @@
1
+ "use strict";
2
+ /**
3
+ * ConfigWizard - Interactive configuration wizard for new DollhouseMCP installations
4
+ *
5
+ * Features:
6
+ * - Guided walkthrough of all configuration options
7
+ * - Educational explanations for each setting
8
+ * - Tracking of completion/dismissal status
9
+ * - Non-intrusive first-run experience
10
+ * - Re-runnable via MCP tool
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.ConfigWizard = void 0;
14
+ const readline = require("readline");
15
+ const logger_js_1 = require("../utils/logger.js");
16
+ const chalk_1 = require("chalk");
17
+ const WIZARD_VERSION = '1.0.0';
18
+ class ConfigWizard {
19
+ constructor(configManager) {
20
+ this.configManager = configManager;
21
+ // Check if we're in an interactive terminal
22
+ this.isInteractive = process.stdin.isTTY && process.stdout.isTTY;
23
+ // Only create readline interface if interactive
24
+ if (this.isInteractive) {
25
+ this.rl = readline.createInterface({
26
+ input: process.stdin,
27
+ output: process.stdout,
28
+ terminal: true
29
+ });
30
+ }
31
+ else {
32
+ // No interface in non-interactive mode
33
+ this.rl = null;
34
+ }
35
+ }
36
+ /**
37
+ * Check if the wizard should run
38
+ */
39
+ async shouldRunWizard() {
40
+ // Don't run in non-interactive environments (CI, automated scripts)
41
+ if (!this.isInteractive) {
42
+ return false;
43
+ }
44
+ const config = this.configManager.getConfig();
45
+ return !config.wizard?.completed && !config.wizard?.dismissed;
46
+ }
47
+ /**
48
+ * Show initial prompt to user
49
+ */
50
+ async promptInitial() {
51
+ console.log('\n' + chalk_1.default.cyan('═'.repeat(60)));
52
+ console.log(chalk_1.default.cyan.bold(' Welcome to DollhouseMCP! 🎭'));
53
+ console.log(chalk_1.default.cyan('═'.repeat(60)) + '\n');
54
+ console.log(chalk_1.default.yellow('This appears to be your first time using DollhouseMCP.'));
55
+ console.log('Would you like a guided walkthrough of the configuration options?\n');
56
+ console.log(chalk_1.default.green(' [1]') + ' Yes, guide me through the configuration');
57
+ console.log(chalk_1.default.yellow(' [2]') + ' Skip for now (ask again next time)');
58
+ console.log(chalk_1.default.gray(' [3]') + ' Don\'t show this again\n');
59
+ const choice = await this.prompt('Choice [1/2/3]: ', '2');
60
+ switch (choice) {
61
+ case '1':
62
+ return 'yes';
63
+ case '3':
64
+ return 'never';
65
+ default:
66
+ return 'skip';
67
+ }
68
+ }
69
+ /**
70
+ * Run the full configuration wizard
71
+ */
72
+ async runWizard() {
73
+ console.log('\n' + chalk_1.default.cyan('Starting Configuration Wizard...'));
74
+ console.log(chalk_1.default.gray('You can press Ctrl+C at any time to exit\n'));
75
+ try {
76
+ // Track skipped sections
77
+ const skippedSections = [];
78
+ // Section 1: User Identity
79
+ const skipIdentity = await this.runUserIdentitySection();
80
+ if (skipIdentity)
81
+ skippedSections.push('user');
82
+ // Section 2: GitHub Integration
83
+ const skipGitHub = await this.runGitHubSection();
84
+ if (skipGitHub)
85
+ skippedSections.push('github');
86
+ // Section 3: Sync Settings
87
+ const skipSync = await this.runSyncSection();
88
+ if (skipSync)
89
+ skippedSections.push('sync');
90
+ // Section 4: Collection Settings
91
+ const skipCollection = await this.runCollectionSection();
92
+ if (skipCollection)
93
+ skippedSections.push('collection');
94
+ // Section 5: Display Settings
95
+ const skipDisplay = await this.runDisplaySection();
96
+ if (skipDisplay)
97
+ skippedSections.push('display');
98
+ // Show summary and confirm
99
+ await this.showSummary();
100
+ const save = await this.promptYesNo('Save this configuration?', true);
101
+ if (save) {
102
+ await this.markCompleted(skippedSections);
103
+ console.log(chalk_1.default.green('\n✅ Configuration saved successfully!'));
104
+ console.log(chalk_1.default.gray('You can re-run this wizard anytime with the "run_config_wizard" tool.\n'));
105
+ }
106
+ else {
107
+ console.log(chalk_1.default.yellow('\n⚠️ Configuration not saved. You can run the wizard again later.\n'));
108
+ }
109
+ }
110
+ catch (error) {
111
+ if (error instanceof Error) {
112
+ if (error.message.includes('canceled')) {
113
+ console.log(chalk_1.default.yellow('\n⚠️ Wizard canceled. You can run it again later.\n'));
114
+ }
115
+ else if (error.message.includes('EACCES') || error.message.includes('permission')) {
116
+ logger_js_1.logger.error('Permission denied while saving configuration', { error });
117
+ console.log(chalk_1.default.red('\n❌ Permission denied: Unable to save configuration.'));
118
+ console.log(chalk_1.default.yellow(' Please check file permissions for ~/.dollhouse/config.yml\n'));
119
+ }
120
+ else if (error.message.includes('ENOSPC')) {
121
+ logger_js_1.logger.error('No space left on device', { error });
122
+ console.log(chalk_1.default.red('\n❌ Disk full: Unable to save configuration.'));
123
+ console.log(chalk_1.default.yellow(' Please free up some disk space and try again.\n'));
124
+ }
125
+ else if (error.message.includes('readline')) {
126
+ logger_js_1.logger.error('Terminal input error', { error });
127
+ console.log(chalk_1.default.red('\n❌ Terminal error: Unable to read input.'));
128
+ console.log(chalk_1.default.yellow(' Please ensure you\'re running in an interactive terminal.\n'));
129
+ }
130
+ else {
131
+ logger_js_1.logger.error('Unexpected error in configuration wizard', { error });
132
+ console.log(chalk_1.default.red('\n❌ An unexpected error occurred.'));
133
+ console.log(chalk_1.default.gray(` Error: ${error.message}\n`));
134
+ }
135
+ }
136
+ else {
137
+ logger_js_1.logger.error('Unknown error in configuration wizard', { error });
138
+ console.log(chalk_1.default.red('\n❌ An unknown error occurred. Please try again later.\n'));
139
+ }
140
+ }
141
+ }
142
+ /**
143
+ * User Identity Section
144
+ */
145
+ async runUserIdentitySection() {
146
+ console.log('\n' + chalk_1.default.cyan.bold('=== User Identity Configuration ==='));
147
+ console.log('Let\'s set up your identity for persona attribution.\n');
148
+ const config = this.configManager.getConfig();
149
+ // Username
150
+ console.log(chalk_1.default.white('Username (for persona credits):'));
151
+ console.log(chalk_1.default.gray(` Current: ${config.user.username || 'not set'}`));
152
+ console.log(chalk_1.default.gray(' Recommended: Your GitHub username'));
153
+ const username = await this.prompt(' > ', config.user.username || '');
154
+ if (username) {
155
+ await this.configManager.updateSetting('user.username', username);
156
+ }
157
+ // Email with validation
158
+ console.log(chalk_1.default.white('\nEmail (for Git commits and GitHub attribution):'));
159
+ console.log(chalk_1.default.gray(' Used for: Git author info, GitHub commits, and element attribution'));
160
+ console.log(chalk_1.default.gray(` Current: ${config.user.email || 'not set'}`));
161
+ console.log(chalk_1.default.gray(' Recommended: Your GitHub email address'));
162
+ let email = await this.prompt(' > ', config.user.email || '');
163
+ // Validate email if provided
164
+ while (email && !this.isValidEmail(email)) {
165
+ console.log(chalk_1.default.yellow(' ⚠️ Invalid email format. Please enter a valid email or leave empty to skip.'));
166
+ email = await this.prompt(' > ', '');
167
+ }
168
+ if (email) {
169
+ await this.configManager.updateSetting('user.email', email);
170
+ }
171
+ // Display Name
172
+ console.log(chalk_1.default.white('\nDisplay Name (how you appear in the community):'));
173
+ console.log(chalk_1.default.gray(` Current: ${config.user.display_name || 'not set'}`));
174
+ const displayName = await this.prompt(' > ', config.user.display_name || '');
175
+ if (displayName) {
176
+ await this.configManager.updateSetting('user.display_name', displayName);
177
+ }
178
+ return false; // Section not skipped
179
+ }
180
+ /**
181
+ * GitHub Integration Section
182
+ */
183
+ async runGitHubSection() {
184
+ console.log('\n' + chalk_1.default.cyan.bold('=== GitHub Integration ==='));
185
+ console.log('DollhouseMCP can sync your personas with GitHub for backup and sharing.\n');
186
+ const setupGitHub = await this.promptYesNo('Would you like to set up GitHub integration?', false);
187
+ if (!setupGitHub) {
188
+ console.log(chalk_1.default.gray(' Skipping GitHub setup. You can configure this later.\n'));
189
+ return true; // Section skipped
190
+ }
191
+ const config = this.configManager.getConfig();
192
+ // Repository name
193
+ console.log(chalk_1.default.white('\nGitHub Portfolio Repository:'));
194
+ console.log(chalk_1.default.gray(' This will store your personal collection'));
195
+ console.log(chalk_1.default.gray(' Format: username/repository-name'));
196
+ console.log(chalk_1.default.gray(` Default: ${config.github.portfolio.repository_name}`));
197
+ const repoName = await this.prompt(' > ', config.github.portfolio.repository_name);
198
+ if (repoName) {
199
+ await this.configManager.updateSetting('github.portfolio.repository_name', repoName);
200
+ }
201
+ // Auto-create repository
202
+ const autoCreate = await this.promptYesNo('\nAuto-create repository if it doesn\'t exist?', true);
203
+ await this.configManager.updateSetting('github.portfolio.auto_create', autoCreate);
204
+ // OAuth authentication
205
+ const useOAuth = await this.promptYesNo('\nUse OAuth for authentication? (More secure than tokens)', true);
206
+ await this.configManager.updateSetting('github.auth.use_oauth', useOAuth);
207
+ return false; // Section not skipped
208
+ }
209
+ /**
210
+ * Sync Settings Section
211
+ */
212
+ async runSyncSection() {
213
+ console.log('\n' + chalk_1.default.cyan.bold('=== Sync Settings ==='));
214
+ console.log('Control how your personas sync with GitHub.\n');
215
+ const enableSync = await this.promptYesNo('Enable sync features? (You can always enable later)', false);
216
+ await this.configManager.updateSetting('sync.enabled', enableSync);
217
+ if (!enableSync) {
218
+ console.log(chalk_1.default.gray(' Sync disabled. You can enable it later in settings.\n'));
219
+ return true; // Section skipped (partially)
220
+ }
221
+ // Individual sync settings
222
+ console.log(chalk_1.default.white('\nIndividual Sync Settings:'));
223
+ const requireConfirm = await this.promptYesNo(' Require confirmation before sync?', true);
224
+ await this.configManager.updateSetting('sync.individual.require_confirmation', requireConfirm);
225
+ const showDiff = await this.promptYesNo(' Show diff before syncing?', true);
226
+ await this.configManager.updateSetting('sync.individual.show_diff_before_sync', showDiff);
227
+ const trackVersions = await this.promptYesNo(' Keep version history?', true);
228
+ await this.configManager.updateSetting('sync.individual.track_versions', trackVersions);
229
+ if (trackVersions) {
230
+ const historyCount = await this.prompt(' How many versions to keep? [10]: ', '10');
231
+ await this.configManager.updateSetting('sync.individual.keep_history', parseInt(historyCount) || 10);
232
+ }
233
+ // Privacy settings
234
+ console.log(chalk_1.default.white('\nPrivacy Settings:'));
235
+ const scanSecrets = await this.promptYesNo(' Scan for secrets before upload?', true);
236
+ await this.configManager.updateSetting('sync.privacy.scan_for_secrets', scanSecrets);
237
+ const scanPII = await this.promptYesNo(' Scan for PII (personal info)?', true);
238
+ await this.configManager.updateSetting('sync.privacy.scan_for_pii', scanPII);
239
+ const warnSensitive = await this.promptYesNo(' Warn on sensitive content?', true);
240
+ await this.configManager.updateSetting('sync.privacy.warn_on_sensitive', warnSensitive);
241
+ return false; // Section not skipped
242
+ }
243
+ /**
244
+ * Collection Settings Section
245
+ */
246
+ async runCollectionSection() {
247
+ console.log('\n' + chalk_1.default.cyan.bold('=== Collection Settings ==='));
248
+ console.log('The DollhouseMCP Collection is our community marketplace.\n');
249
+ const autoSubmit = await this.promptYesNo('Auto-submit to collection when uploading?', false);
250
+ await this.configManager.updateSetting('collection.auto_submit', autoSubmit);
251
+ const requireReview = await this.promptYesNo('Require review before submission?', true);
252
+ await this.configManager.updateSetting('collection.require_review', requireReview);
253
+ const addAttribution = await this.promptYesNo('Add attribution to your personas? (Adds your username)', true);
254
+ await this.configManager.updateSetting('collection.add_attribution', addAttribution);
255
+ return false; // Section not skipped
256
+ }
257
+ /**
258
+ * Display Settings Section
259
+ */
260
+ async runDisplaySection() {
261
+ console.log('\n' + chalk_1.default.cyan.bold('=== Display Settings ==='));
262
+ console.log('How DollhouseMCP appears in your terminal.\n');
263
+ const showIndicators = await this.promptYesNo('Show persona indicators in responses?', true);
264
+ await this.configManager.updateSetting('display.persona_indicators.enabled', showIndicators);
265
+ if (showIndicators) {
266
+ console.log(chalk_1.default.white('\nIndicator Style:'));
267
+ console.log(' [1] Full - Complete information');
268
+ console.log(' [2] Minimal - Just the name');
269
+ console.log(' [3] Compact - Name and version');
270
+ console.log(' [4] Custom - Define your own format');
271
+ const styleChoice = await this.prompt(' Choice [1-4]: ', '2');
272
+ const styles = ['full', 'minimal', 'compact', 'custom'];
273
+ const style = styles[parseInt(styleChoice) - 1] || 'minimal';
274
+ await this.configManager.updateSetting('display.persona_indicators.style', style);
275
+ const includeEmoji = await this.promptYesNo('\n Include emoji in indicators?', true);
276
+ await this.configManager.updateSetting('display.persona_indicators.include_emoji', includeEmoji);
277
+ }
278
+ const verboseLogging = await this.promptYesNo('\nEnable verbose logging?', false);
279
+ await this.configManager.updateSetting('display.verbose_logging', verboseLogging);
280
+ const showProgress = await this.promptYesNo('Show progress indicators?', true);
281
+ await this.configManager.updateSetting('display.show_progress', showProgress);
282
+ return false; // Section not skipped
283
+ }
284
+ /**
285
+ * Show configuration summary
286
+ */
287
+ async showSummary() {
288
+ console.log('\n' + chalk_1.default.cyan.bold('=== Configuration Summary ==='));
289
+ console.log('Here\'s what we\'ll set up:\n');
290
+ const config = this.configManager.getConfig();
291
+ // User
292
+ if (config.user.username) {
293
+ console.log(chalk_1.default.green('✓') + ` Username: ${config.user.username}`);
294
+ }
295
+ if (config.user.email) {
296
+ console.log(chalk_1.default.green('✓') + ` Email: ${config.user.email}`);
297
+ }
298
+ // GitHub
299
+ if (config.github.portfolio.repository_name !== 'dollhouse-portfolio') {
300
+ console.log(chalk_1.default.green('✓') + ` GitHub: ${config.github.portfolio.repository_name}`);
301
+ }
302
+ // Sync
303
+ console.log(chalk_1.default.green('✓') + ` Sync: ${config.sync.enabled ? 'Enabled' : 'Disabled'}`);
304
+ // Collection
305
+ console.log(chalk_1.default.green('✓') + ` Collection: ${config.collection.auto_submit ? 'Auto-submit' : 'Manual submission'}`);
306
+ // Display
307
+ if (config.display.persona_indicators.enabled) {
308
+ console.log(chalk_1.default.green('✓') + ` Display: ${config.display.persona_indicators.style} indicators` +
309
+ (config.display.persona_indicators.include_emoji ? ' with emoji' : ''));
310
+ }
311
+ }
312
+ /**
313
+ * Mark wizard as completed
314
+ */
315
+ async markCompleted(skippedSections = []) {
316
+ await this.configManager.updateSetting('wizard.completed', true);
317
+ await this.configManager.updateSetting('wizard.completedAt', new Date().toISOString());
318
+ await this.configManager.updateSetting('wizard.version', WIZARD_VERSION);
319
+ if (skippedSections.length > 0) {
320
+ await this.configManager.updateSetting('wizard.skippedSections', skippedSections);
321
+ }
322
+ }
323
+ /**
324
+ * Mark wizard as dismissed
325
+ */
326
+ async markDismissed() {
327
+ await this.configManager.updateSetting('wizard.dismissed', true);
328
+ }
329
+ /**
330
+ * Helper: Prompt for user input
331
+ */
332
+ prompt(question, defaultValue = '') {
333
+ return new Promise((resolve) => {
334
+ if (!this.isInteractive || !this.rl) {
335
+ resolve(defaultValue);
336
+ return;
337
+ }
338
+ this.rl.question(question, (answer) => {
339
+ resolve(answer || defaultValue);
340
+ });
341
+ });
342
+ }
343
+ /**
344
+ * Helper: Prompt for yes/no
345
+ */
346
+ async promptYesNo(question, defaultYes = true) {
347
+ const hint = defaultYes ? '[Y/n]' : '[y/N]';
348
+ const answer = await this.prompt(`${question} ${hint}: `, defaultYes ? 'y' : 'n');
349
+ return answer.toLowerCase().startsWith('y');
350
+ }
351
+ /**
352
+ * Helper: Validate email format
353
+ */
354
+ isValidEmail(email) {
355
+ // Basic email regex - not perfect but good enough for wizard validation
356
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
357
+ return emailRegex.test(email);
358
+ }
359
+ /**
360
+ * Cleanup readline interface
361
+ */
362
+ close() {
363
+ if (this.isInteractive && this.rl) {
364
+ this.rl.close();
365
+ }
366
+ }
367
+ }
368
+ exports.ConfigWizard = ConfigWizard;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /**
3
+ * Security-specific error class for DollhouseMCP
4
+ *
5
+ * Used to indicate security violations, validation failures,
6
+ * and other security-related issues.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SecurityError = void 0;
10
+ class SecurityError extends Error {
11
+ constructor(message, code = 'SECURITY_VIOLATION', severity = 'high', details) {
12
+ super(message);
13
+ this.name = 'SecurityError';
14
+ this.code = code;
15
+ this.severity = severity;
16
+ this.details = details;
17
+ // Maintains proper stack trace for where our error was thrown
18
+ if (Error.captureStackTrace) {
19
+ Error.captureStackTrace(this, SecurityError);
20
+ }
21
+ }
22
+ /**
23
+ * Creates a SecurityError for content validation failures
24
+ */
25
+ static contentValidation(message, patterns) {
26
+ return new SecurityError(message, 'CONTENT_VALIDATION_FAILED', 'high', { detectedPatterns: patterns });
27
+ }
28
+ /**
29
+ * Creates a SecurityError for YAML injection attempts
30
+ */
31
+ static yamlInjection(message) {
32
+ return new SecurityError(message, 'YAML_INJECTION_DETECTED', 'critical');
33
+ }
34
+ /**
35
+ * Creates a SecurityError for path traversal attempts
36
+ */
37
+ static pathTraversal(message, path) {
38
+ return new SecurityError(message, 'PATH_TRAVERSAL_DETECTED', 'high', { attemptedPath: path });
39
+ }
40
+ /**
41
+ * Creates a SecurityError for token validation failures
42
+ */
43
+ static tokenValidation(message) {
44
+ return new SecurityError(message, 'TOKEN_VALIDATION_FAILED', 'high');
45
+ }
46
+ }
47
+ exports.SecurityError = SecurityError;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * Security-related constants and limits
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.VALIDATION_PATTERNS = exports.SECURITY_LIMITS = void 0;
7
+ // Security and performance limits
8
+ exports.SECURITY_LIMITS = {
9
+ MAX_PERSONA_SIZE_BYTES: 1024 * 1024 * 2, // 2MB max persona file size
10
+ MAX_FILENAME_LENGTH: 255, // Max filename length
11
+ MAX_PATH_DEPTH: 10, // Max directory depth for paths
12
+ MAX_CONTENT_LENGTH: 500000, // Max persona content length (500KB)
13
+ MAX_YAML_LENGTH: 64 * 1024, // Max YAML frontmatter length (64KB)
14
+ MAX_METADATA_FIELD_LENGTH: 1024, // Max individual metadata field length (1KB)
15
+ MAX_FILE_SIZE: 1024 * 1024 * 2, // Max file size (2MB)
16
+ RATE_LIMIT_REQUESTS: 100, // Max requests per window
17
+ RATE_LIMIT_WINDOW_MS: 60 * 1000, // 1 minute window
18
+ CACHE_TTL_MS: 5 * 60 * 1000, // 5 minute cache TTL
19
+ MAX_SEARCH_RESULTS: 50 // Max search results to return
20
+ };
21
+ // Input validation patterns
22
+ exports.VALIDATION_PATTERNS = {
23
+ SAFE_FILENAME: /^[a-zA-Z0-9][a-zA-Z0-9\-_.]{0,250}[a-zA-Z0-9]$/,
24
+ SAFE_PATH: /^[a-zA-Z0-9:/\-_.~]{1,500}$/,
25
+ SAFE_USERNAME: /^[a-zA-Z0-9][a-zA-Z0-9\-_.]{0,30}[a-zA-Z0-9]$/,
26
+ SAFE_CATEGORY: /^[a-zA-Z][a-zA-Z0-9\-_]{0,20}$/,
27
+ SAFE_EMAIL: /^[^\s@]{1,64}@[^\s@]{1,253}\.[^\s@]{1,63}$/ // RFC 5321 compliant limits
28
+ };