vibecodingmachine-cli 2026.2.20-438 → 2026.2.26-1739

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 (101) hide show
  1. package/bin/auth/auth-compliance.js +126 -0
  2. package/bin/cli-program.js +104 -0
  3. package/bin/cli-setup.js +52 -0
  4. package/bin/commands/agent-commands.js +310 -0
  5. package/bin/commands/auto-commands.js +70 -0
  6. package/bin/commands/command-aliases.js +118 -0
  7. package/bin/commands/repo-commands.js +39 -0
  8. package/bin/commands/rui-commands.js +152 -0
  9. package/bin/config/cli-config.js +394 -0
  10. package/bin/init/environment-setup.js +84 -0
  11. package/bin/update/update-checker.js +126 -0
  12. package/bin/vibecodingmachine-new.js +50 -0
  13. package/bin/vibecodingmachine.js +29 -663
  14. package/package.json +8 -2
  15. package/src/commands/agents/add.js +277 -0
  16. package/src/commands/agents/check.js +380 -0
  17. package/src/commands/agents/list.js +471 -0
  18. package/src/commands/agents/remove.js +351 -0
  19. package/src/commands/analyze-file-sizes.js +428 -0
  20. package/src/commands/auto-direct/code-processor.js +282 -0
  21. package/src/commands/auto-direct/file-scanner.js +266 -0
  22. package/src/commands/auto-direct/provider-config.js +178 -0
  23. package/src/commands/auto-direct/provider-manager.js +219 -0
  24. package/src/commands/auto-direct/requirement-manager.js +172 -0
  25. package/src/commands/auto-direct/status-display.js +91 -0
  26. package/src/commands/auto-direct/utils.js +106 -0
  27. package/src/commands/auto-direct.js +875 -488
  28. package/src/commands/auto-execution.js +342 -0
  29. package/src/commands/auto-provider-management.js +102 -0
  30. package/src/commands/auto-requirement-management.js +161 -0
  31. package/src/commands/auto-status-helpers.js +141 -0
  32. package/src/commands/auto.js +105 -5155
  33. package/src/commands/check-compliance.js +536 -0
  34. package/src/commands/continuous-scan.js +119 -0
  35. package/src/commands/ide.js +16 -4
  36. package/src/commands/refactor-file.js +486 -0
  37. package/src/commands/requirements.js +301 -2
  38. package/src/commands/timeout.js +290 -0
  39. package/src/trui/TruiInterface.js +108 -0
  40. package/src/trui/agents/AgentInterface.js +580 -0
  41. package/src/utils/antigravity-installer.js +60 -6
  42. package/src/utils/clarification-actions.js +290 -0
  43. package/src/utils/config.js +123 -2
  44. package/src/utils/first-run.js +5 -5
  45. package/src/utils/ide-handlers.js +212 -0
  46. package/src/utils/interactive/clarification-actions.js +348 -0
  47. package/src/utils/interactive/core-ui.js +265 -0
  48. package/src/utils/interactive/file-backup.js +237 -0
  49. package/src/utils/interactive/file-import-export.js +305 -0
  50. package/src/utils/interactive/file-operations.js +49 -0
  51. package/src/utils/interactive/file-validation.js +276 -0
  52. package/src/utils/interactive/interactive-prompts.js +480 -0
  53. package/src/utils/interactive/requirement-actions.js +127 -0
  54. package/src/utils/interactive/requirement-crud.js +356 -0
  55. package/src/utils/interactive/requirements-navigation.js +286 -0
  56. package/src/utils/interactive.js +390 -3459
  57. package/src/utils/provider-checker/agent-checker.js +250 -0
  58. package/src/utils/provider-checker/agent-runner.js +450 -0
  59. package/src/utils/provider-checker/cli-installer.js +123 -0
  60. package/src/utils/provider-checker/cli-utils.js +15 -0
  61. package/src/utils/provider-checker/format-utils.js +32 -0
  62. package/src/utils/provider-checker/ide-manager.js +72 -0
  63. package/src/utils/provider-checker/ide-utils.js +71 -0
  64. package/src/utils/provider-checker/node-detector.js +56 -0
  65. package/src/utils/provider-checker/node-utils.js +61 -0
  66. package/src/utils/provider-checker/process-spawn.js +22 -0
  67. package/src/utils/provider-checker/process-utils.js +37 -0
  68. package/src/utils/provider-checker/provider-validator.js +160 -0
  69. package/src/utils/provider-checker/quota-checker.js +54 -0
  70. package/src/utils/provider-checker/quota-detector.js +44 -0
  71. package/src/utils/provider-checker/requirements-manager.js +94 -0
  72. package/src/utils/provider-checker/test-requirements.js +95 -0
  73. package/src/utils/provider-checker/time-formatter.js +18 -0
  74. package/src/utils/provider-checker-new.js +14 -0
  75. package/src/utils/provider-checker.js +12 -407
  76. package/src/utils/provider-checkers/ide-manager.js +128 -0
  77. package/src/utils/provider-checkers/node-executable-finder.js +51 -0
  78. package/src/utils/provider-checkers/provider-checker-core.js +172 -0
  79. package/src/utils/provider-checkers/provider-checker-main.js +107 -0
  80. package/src/utils/provider-manager.js +60 -4
  81. package/src/utils/provider-registry.js +26 -3
  82. package/src/utils/provider-utils.js +173 -0
  83. package/src/utils/quota-detectors.js +212 -0
  84. package/src/utils/requirement-action-handlers.js +288 -0
  85. package/src/utils/requirement-actions/clarification-actions.js +229 -0
  86. package/src/utils/requirement-actions/confirmation-prompts.js +93 -0
  87. package/src/utils/requirement-actions/file-operations.js +92 -0
  88. package/src/utils/requirement-actions/helpers.js +40 -0
  89. package/src/utils/requirement-actions/requirement-operations.js +335 -0
  90. package/src/utils/requirement-actions.js +46 -856
  91. package/src/utils/requirement-file-operations.js +259 -0
  92. package/src/utils/requirement-helpers.js +128 -0
  93. package/src/utils/requirement-management.js +279 -0
  94. package/src/utils/requirement-navigation.js +146 -0
  95. package/src/utils/requirement-organization.js +271 -0
  96. package/src/utils/simple-trui.js +75 -1
  97. package/src/utils/trui-navigation.js +28 -2
  98. package/src/utils/trui-req-tree.js +196 -11
  99. package/src/utils/trui-specifications.js +31 -1
  100. package/src/utils/interactive-backup.js +0 -5664
  101. package/src/utils/trui-provider-manager.js +0 -182
@@ -0,0 +1,265 @@
1
+ const { t } = require('vibecodingmachine-core');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const chalk = require('chalk');
5
+
6
+ /**
7
+ * Translate workflow stage names
8
+ */
9
+ function translateStage(stage) {
10
+ const stageMap = {
11
+ 'PREPARE': 'workflow.stage.prepare',
12
+ 'REPRODUCE': 'workflow.stage.reproduce',
13
+ 'CREATE UNIT TEST': 'workflow.stage.create.unit.test',
14
+ 'ACT': 'workflow.stage.act',
15
+ 'CLEAN UP': 'workflow.stage.clean.up',
16
+ 'VERIFY': 'workflow.stage.verify',
17
+ 'RUN UNIT TESTS': 'workflow.stage.run.unit.tests',
18
+ 'DONE': 'workflow.stage.done'
19
+ };
20
+
21
+ const key = stageMap[stage];
22
+ return key ? t(key) : stage;
23
+ }
24
+
25
+ function normalizeProjectDirName(input) {
26
+ const s = String(input || '').trim().toLowerCase();
27
+ const replaced = s.replace(/\s+/g, '-');
28
+ const cleaned = replaced.replace(/[^a-z0-9._-]/g, '-');
29
+ const collapsed = cleaned.replace(/-+/g, '-').replace(/^-|-$/g, '');
30
+ return collapsed || 'my-project';
31
+ }
32
+
33
+ async function bootstrapProjectIfInHomeDir() {
34
+ let cwdResolved = path.resolve(process.cwd());
35
+ let homeResolved = path.resolve(os.homedir());
36
+
37
+ // Check if current directory is under home directory
38
+ if (cwdResolved.startsWith(homeResolved)) {
39
+ const relativePath = path.relative(homeResolved, cwdResolved);
40
+ const projectName = normalizeProjectDirName(relativePath);
41
+ const projectPath = path.join(homeResolved, 'vibe-coding-projects', projectName);
42
+
43
+ console.log(chalk.yellow(`\n⚠️ ${t('interactive.home.dir.warning')}`));
44
+ console.log(chalk.gray(` Current: ${cwdResolved}`));
45
+ console.log(chalk.gray(` Suggested: ${projectPath}`));
46
+
47
+ const { confirm } = require('inquirer');
48
+ const shouldCreate = await confirm({
49
+ message: t('interactive.create.project.dir'),
50
+ default: false
51
+ });
52
+
53
+ if (shouldCreate) {
54
+ const fs = require('fs-extra');
55
+ await fs.ensureDir(projectPath);
56
+ process.chdir(projectPath);
57
+ console.log(chalk.green(`✅ ${t('interactive.project.created')} ${projectPath}`));
58
+ return true;
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+
64
+ /**
65
+ * Format IDE name for display
66
+ * @param {string} ide - Internal IDE identifier
67
+ * @returns {string} Display name for IDE
68
+ */
69
+ function formatIDEName(ide) {
70
+ const ideNames = {
71
+ 'claude-code': 'Claude Code CLI',
72
+ 'aider': 'Aider CLI',
73
+ 'cline': 'Cline CLI',
74
+ 'continue': 'Continue CLI',
75
+ 'cursor': 'Cursor IDE',
76
+ 'windsurf': 'Windsurf IDE',
77
+ 'vscode': 'VS Code',
78
+ 'replit': 'Replit',
79
+ 'github-copilot': 'GitHub Copilot',
80
+ 'ollama': 'Ollama',
81
+ 'anthropic': 'Anthropic Claude',
82
+ 'groq': 'Groq',
83
+ 'openai': 'OpenAI GPT'
84
+ };
85
+ return ideNames[ide] || ide;
86
+ }
87
+
88
+ /**
89
+ * Get current AI provider name for IDEs that require it (like Cline and Continue)
90
+ * @param {string} ide - Internal IDE identifier
91
+ * @returns {string|null} Provider name or null if not applicable/configured
92
+ */
93
+ function getCurrentAIProvider(ide) {
94
+ // Aider, Cline, and Continue require AI provider configuration
95
+ if (ide !== 'aider' && ide !== 'cline' && ide !== 'continue') {
96
+ return null;
97
+ }
98
+
99
+ const { getAutoConfig } = require('vibecodingmachine-core');
100
+ const config = getAutoConfig();
101
+
102
+ switch (ide) {
103
+ case 'aider':
104
+ return config.aiderModel || 'gpt-4';
105
+ case 'cline':
106
+ return config.anthropicModel || 'claude-3-sonnet-20240229';
107
+ case 'continue':
108
+ return config.anthropicModel || 'claude-3-sonnet-20240229';
109
+ default:
110
+ return null;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Get unified agent name (IDE or LLM-based)
116
+ * @param {string} agentType - Agent type (e.g., 'cursor', 'ollama', 'anthropic')
117
+ * @returns {string} - Display name like "Cursor IDE Agent" or "Ollama (qwen2.5-coder:32b)"
118
+ */
119
+ function getAgentDisplayName(agentType) {
120
+ // IDE-based agents
121
+ if (agentType === 'cursor') return 'Cursor IDE Agent';
122
+ if (agentType === 'windsurf') return 'Windsurf IDE Agent';
123
+ if (agentType === 'vscode') return 'VS Code Agent';
124
+ if (agentType === 'claude-code') return 'Claude Code CLI';
125
+ if (agentType === 'aider') return 'Aider CLI';
126
+ if (agentType === 'cline') return 'Cline CLI';
127
+ if (agentType === 'continue') return 'Continue CLI';
128
+
129
+ // LLM-based agents
130
+ const { getAutoConfig } = require('vibecodingmachine-core');
131
+ const config = getAutoConfig();
132
+
133
+ if (agentType === 'ollama') {
134
+ const model = config.llmModel || config.aiderModel || 'llama3';
135
+ return `Ollama (${model})`;
136
+ }
137
+
138
+ if (agentType === 'anthropic') {
139
+ const model = config.anthropicModel || 'claude-3-sonnet-20240229';
140
+ return `Anthropic (${model})`;
141
+ }
142
+
143
+ if (agentType === 'groq') {
144
+ const model = config.groqModel || 'llama3-70b-8192';
145
+ return `Groq (${model})`;
146
+ }
147
+
148
+ if (agentType === 'openai') {
149
+ const model = config.openaiModel || 'gpt-4';
150
+ return `OpenAI (${model})`;
151
+ }
152
+
153
+ // Fallback to IDE formatting
154
+ return formatIDEName(agentType);
155
+ }
156
+
157
+ function formatPath(fullPath) {
158
+ const homeDir = os.homedir();
159
+ if (fullPath.startsWith(homeDir)) {
160
+ return fullPath.replace(homeDir, '~');
161
+ }
162
+ return fullPath;
163
+ }
164
+
165
+ async function countRequirements() {
166
+ try {
167
+ const { getProjectRequirementStats } = require('vibecodingmachine-core');
168
+ const repoPath = await getRepoPath();
169
+ const stats = await getProjectRequirementStats(repoPath);
170
+ return stats;
171
+ } catch (error) {
172
+ return { todo: 0, completed: 0, total: 0 };
173
+ }
174
+ }
175
+
176
+ async function getRepoPath() {
177
+ const { checkVibeCodingMachineExists } = require('vibecodingmachine-core');
178
+ const repoPath = process.cwd();
179
+
180
+ if (!await checkVibeCodingMachineExists(repoPath)) {
181
+ throw new Error('Not in a Vibe Coding Machine repository');
182
+ }
183
+
184
+ return repoPath;
185
+ }
186
+
187
+ async function getSyncStatus() {
188
+ try {
189
+ const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
190
+ const syncEngine = new SyncEngine();
191
+ const status = await syncEngine.getStatus();
192
+ return status;
193
+ } catch (error) {
194
+ return { status: 'unknown', message: 'Sync engine not available' };
195
+ }
196
+ }
197
+
198
+ async function getCurrentProgress() {
199
+ try {
200
+ const { getRequirementsPath } = require('vibecodingmachine-core');
201
+ const reqPath = await getRequirementsPath();
202
+ const fs = require('fs-extra');
203
+
204
+ if (!await fs.pathExists(reqPath)) {
205
+ return { stage: 'NOT_STARTED', progress: 0 };
206
+ }
207
+
208
+ const content = await fs.readFile(reqPath, 'utf8');
209
+ const stageMatch = content.match(/## 🚦 Current Status\s*\n\s*\n(.+)/);
210
+
211
+ if (stageMatch) {
212
+ const stage = stageMatch[1].trim();
213
+ const progress = getProgressPercentage(stage);
214
+ return { stage, progress };
215
+ }
216
+
217
+ return { stage: 'UNKNOWN', progress: 0 };
218
+ } catch (error) {
219
+ return { stage: 'ERROR', progress: 0 };
220
+ }
221
+ }
222
+
223
+ function getProgressPercentage(stage) {
224
+ const stageProgress = {
225
+ 'PREPARE': 10,
226
+ 'REPRODUCE': 20,
227
+ 'CREATE UNIT TEST': 30,
228
+ 'ACT': 50,
229
+ 'CLEAN UP': 80,
230
+ 'VERIFY': 90,
231
+ 'DONE': 100
232
+ };
233
+
234
+ return stageProgress[stage] || 0;
235
+ }
236
+
237
+ // Helper to show goodbye message
238
+ function showGoodbyeMessage() {
239
+ const hour = new Date().getHours();
240
+ const message = hour < 21
241
+ ? '\n👋 ' + t('interactive.goodbye') + '\n'
242
+ : '\n🌙 ' + t('interactive.goodbye.night') + '\n';
243
+ console.log(chalk.cyan(message));
244
+ }
245
+
246
+ // Helper to convert index to letter (0->a, 1->b, etc.)
247
+ function indexToLetter(index) {
248
+ return String.fromCharCode(97 + index); // 97 is 'a'
249
+ }
250
+
251
+ module.exports = {
252
+ translateStage,
253
+ normalizeProjectDirName,
254
+ bootstrapProjectIfInHomeDir,
255
+ formatIDEName,
256
+ getCurrentAIProvider,
257
+ getAgentDisplayName,
258
+ formatPath,
259
+ countRequirements,
260
+ getRepoPath,
261
+ getSyncStatus,
262
+ getCurrentProgress,
263
+ showGoodbyeMessage,
264
+ indexToLetter
265
+ };
@@ -0,0 +1,237 @@
1
+ /**
2
+ * File Backup Operations
3
+ *
4
+ * Handles backup creation, restoration, and management for requirements files
5
+ */
6
+
7
+ const fs = require('fs').promises;
8
+ const path = require('path');
9
+ const chalk = require('chalk');
10
+
11
+ /**
12
+ * Create a backup of the specified file
13
+ * @param {string} filePath - Path to the file to backup
14
+ * @param {Object} options - Backup options
15
+ * @returns {Promise<Object>} Backup result
16
+ */
17
+ async function createBackup(filePath, options = {}) {
18
+ try {
19
+ const backupDir = options.backupDir || path.join(path.dirname(filePath), '.backups');
20
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
21
+ const fileName = path.basename(filePath);
22
+ const backupFileName = `${fileName}.${timestamp}.backup`;
23
+ const backupPath = path.join(backupDir, backupFileName);
24
+
25
+ // Ensure backup directory exists
26
+ await fs.mkdir(backupDir, { recursive: true });
27
+
28
+ // Check if source file exists
29
+ const sourceExists = await fs.access(filePath).then(() => true).catch(() => false);
30
+ if (!sourceExists) {
31
+ return {
32
+ success: false,
33
+ error: `Source file not found: ${filePath}`
34
+ };
35
+ }
36
+
37
+ // Copy file to backup location
38
+ await fs.copyFile(filePath, backupPath);
39
+
40
+ return {
41
+ success: true,
42
+ backupPath,
43
+ timestamp: new Date().toISOString(),
44
+ originalPath: filePath
45
+ };
46
+ } catch (error) {
47
+ return {
48
+ success: false,
49
+ error: error.message
50
+ };
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Restore from a backup file
56
+ * @param {string} backupPath - Path to the backup file
57
+ * @param {string} targetPath - Path where to restore the file
58
+ * @returns {Promise<Object>} Restore result
59
+ */
60
+ async function restoreFromBackup(backupPath, targetPath) {
61
+ try {
62
+ // Check if backup file exists
63
+ const backupExists = await fs.access(backupPath).then(() => true).catch(() => false);
64
+ if (!backupExists) {
65
+ return {
66
+ success: false,
67
+ error: `Backup file not found: ${backupPath}`
68
+ };
69
+ }
70
+
71
+ // Create backup of current file before restoring
72
+ const currentBackup = await createBackup(targetPath, {
73
+ backupDir: path.join(path.dirname(targetPath), '.pre-restore-backups')
74
+ });
75
+
76
+ // Restore from backup
77
+ await fs.copyFile(backupPath, targetPath);
78
+
79
+ return {
80
+ success: true,
81
+ backupPath,
82
+ targetPath,
83
+ preRestoreBackup: currentBackup.success ? currentBackup.backupPath : null
84
+ };
85
+ } catch (error) {
86
+ return {
87
+ success: false,
88
+ error: error.message
89
+ };
90
+ }
91
+ }
92
+
93
+ /**
94
+ * List all available backups for a file
95
+ * @param {string} filePath - Path to the original file
96
+ * @param {Object} options - Listing options
97
+ * @returns {Promise<Object>} List of backups
98
+ */
99
+ async function listBackups(filePath, options = {}) {
100
+ try {
101
+ const backupDir = options.backupDir || path.join(path.dirname(filePath), '.backups');
102
+ const fileName = path.basename(filePath);
103
+
104
+ // Check if backup directory exists
105
+ const dirExists = await fs.access(backupDir).then(() => true).catch(() => false);
106
+ if (!dirExists) {
107
+ return {
108
+ success: true,
109
+ backups: []
110
+ };
111
+ }
112
+
113
+ // Read backup directory
114
+ const files = await fs.readdir(backupDir);
115
+ const backupFiles = files
116
+ .filter(file => file.startsWith(fileName) && file.endsWith('.backup'))
117
+ .map(file => {
118
+ const filePath = path.join(backupDir, file);
119
+ return {
120
+ fileName: file,
121
+ filePath,
122
+ timestamp: extractTimestampFromFileName(file),
123
+ size: null // Will be populated if detailed option is true
124
+ };
125
+ })
126
+ .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
127
+
128
+ // Get file sizes if detailed option is true
129
+ if (options.detailed) {
130
+ for (const backup of backupFiles) {
131
+ try {
132
+ const stats = await fs.stat(backup.filePath);
133
+ backup.size = stats.size;
134
+ } catch (error) {
135
+ backup.size = -1; // Error getting size
136
+ }
137
+ }
138
+ }
139
+
140
+ return {
141
+ success: true,
142
+ backups: backupFiles
143
+ };
144
+ } catch (error) {
145
+ return {
146
+ success: false,
147
+ error: error.message
148
+ };
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Clean up old backups
154
+ * @param {string} filePath - Path to the original file
155
+ * @param {Object} options - Cleanup options
156
+ * @returns {Promise<Object>} Cleanup result
157
+ */
158
+ async function cleanupOldBackups(filePath, options = {}) {
159
+ try {
160
+ const maxBackups = options.maxBackups || 10;
161
+ const olderThan = options.olderThan || null; // Date or days
162
+ const backupDir = options.backupDir || path.join(path.dirname(filePath), '.backups');
163
+
164
+ const listResult = await listBackups(filePath, { backupDir });
165
+ if (!listResult.success) {
166
+ return listResult;
167
+ }
168
+
169
+ const { backups } = listResult;
170
+ if (backups.length <= maxBackups) {
171
+ return {
172
+ success: true,
173
+ deletedBackups: [],
174
+ message: `No cleanup needed. ${backups.length} backups (limit: ${maxBackups})`
175
+ };
176
+ }
177
+
178
+ // Determine which backups to delete
179
+ const backupsToDelete = [];
180
+
181
+ if (olderThan) {
182
+ // Delete backups older than specified date
183
+ const cutoffDate = typeof olderThan === 'number'
184
+ ? new Date(Date.now() - olderThan * 24 * 60 * 60 * 1000) // days
185
+ : new Date(olderThan);
186
+
187
+ backupsToDelete.push(...backups.filter(backup =>
188
+ new Date(backup.timestamp) < cutoffDate
189
+ ));
190
+ } else {
191
+ // Keep only the most recent maxBackups
192
+ backupsToDelete.push(...backups.slice(maxBackups));
193
+ }
194
+
195
+ // Delete old backups
196
+ const deletedBackups = [];
197
+ for (const backup of backupsToDelete) {
198
+ try {
199
+ await fs.unlink(backup.filePath);
200
+ deletedBackups.push(backup);
201
+ } catch (error) {
202
+ console.warn(chalk.yellow(`Warning: Failed to delete backup ${backup.fileName}: ${error.message}`));
203
+ }
204
+ }
205
+
206
+ return {
207
+ success: true,
208
+ deletedBackups,
209
+ remainingBackups: backups.length - deletedBackups.length
210
+ };
211
+ } catch (error) {
212
+ return {
213
+ success: false,
214
+ error: error.message
215
+ };
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Extract timestamp from backup filename
221
+ * @param {string} fileName - Backup filename
222
+ * @returns {string} ISO timestamp
223
+ */
224
+ function extractTimestampFromFileName(fileName) {
225
+ const match = fileName.match(/\.(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)\.backup$/);
226
+ if (match) {
227
+ return match[1].replace(/-/g, ':').replace(/(\d{2}):(\d{2}):(\d{2})-(\d{3})/, '$1:$2:$3.$4');
228
+ }
229
+ return fileName;
230
+ }
231
+
232
+ module.exports = {
233
+ createBackup,
234
+ restoreFromBackup,
235
+ listBackups,
236
+ cleanupOldBackups
237
+ };