shell-mirror 1.0.0

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/README.md ADDED
@@ -0,0 +1,272 @@
1
+ # Shell Mirror
2
+
3
+ A simple application to securely mirror your local computer's shell to a web browser on any device. It uses Google Authentication to ensure only you can access your shell session.
4
+
5
+ ## 🎯 Project Goal
6
+
7
+ The primary goal is to allow a user to see their personal shell session (initially for macOS) running on their own computer, reflected in a web page accessible from their mobile phone or tablet. Access must be securely restricted to the user, leveraging a standard and robust authentication provider.
8
+
9
+ Key objectives:
10
+ - **Secure Mirroring**: Provide a real-time, read/write mirror of a user's shell.
11
+ - **Robust Authentication**: Use Google OAuth 2.0 to ensure that only the owner can access the terminal session.
12
+ - **Web-Based Access**: The web interface should allow a user to log in and connect to their running shell server.
13
+ - **Simplicity**: The application should be a simple, standalone server that is easy to set up and run on a personal computer.
14
+
15
+ ---
16
+
17
+ ## πŸ—ΊοΈ Development Plan
18
+
19
+ To achieve this vision, the project will be pivoted from its original, more complex architecture to a streamlined solution.
20
+
21
+ ### High-Level Plan
22
+
23
+ 1. **Simplify the Architecture:**
24
+ * **Remove Docker:** Eliminate all Docker-related files (`Dockerfile`, `docker-compose.yml`, etc.) and scripts. The application will run directly on the host machine.
25
+ * **Remove Connection Broker:** The `broker.js` service is overly complex for the new goal and will be removed.
26
+
27
+ 2. **Implement Robust Authentication:**
28
+ * **Replace Custom Auth:** Swap the current manual token system (`auth.js`) with a standard, secure **Google OAuth 2.0** login flow using Passport.js.
29
+
30
+ 3. **Refine the User Experience:**
31
+ * **Login Page:** The web interface (`index.html`) will first present a "Login with Google" button.
32
+ * **Secure Shell Access:** The shell view will only be loaded and connected *after* a successful login.
33
+
34
+ ---
35
+
36
+ ### Detailed Implementation Steps
37
+
38
+ * **Phase 1: Cleanup & Refactoring**
39
+ 1. Delete `Dockerfile`, `docker-compose.yml`, `docker-compose.override.yml`, and `.dockerignore`.
40
+ 2. Delete `broker.js`.
41
+ 3. Uninstall unused dependencies and remove Docker-related commands from `package.json`.
42
+ 4. Update `server.js` to remove all logic related to the broker and the old authentication system.
43
+
44
+ * **Phase 2: Google Authentication**
45
+ 1. Add new dependencies: `passport`, `passport-google-oauth20`, and `express-session`.
46
+ 2. Create a new `auth.js` to configure Passport for the Google login flow.
47
+ 3. Add the necessary login (`/auth/google`) and callback (`/auth/google/callback`) routes to `server.js`.
48
+ 4. Secure the WebSocket connection by ensuring the user is authenticated via their session.
49
+
50
+ * **Phase 3: Frontend UI**
51
+ 1. Modify `public/index.html` to conditionally show a "Login" button or the terminal.
52
+ 2. Add client-side logic to check the user's authentication status and initiate the WebSocket connection only when logged in.
53
+
54
+ * **Phase 4: Documentation**
55
+ 1. Update this `README.md` to reflect the new, simplified project goals, the plan outlined above, and provide clear instructions on how to set up and run the application, including how to generate Google OAuth credentials.
56
+
57
+ ---
58
+
59
+ ## πŸš€ Getting Started
60
+
61
+ ### 1. Prerequisites
62
+
63
+ - **Node.js**: Make sure you have Node.js (version 18 or higher) installed on your Mac.
64
+ - **Google Account**: You will need a Google account to generate authentication credentials.
65
+
66
+ ### 2. Configure Google OAuth 2.0 Credentials
67
+
68
+ Before you can run the application, you need to get a **Client ID** and **Client Secret** from the Google Cloud Console.
69
+
70
+ 1. **Go to the Google Cloud Console**: [https://console.cloud.google.com/](https://console.cloud.google.com/)
71
+
72
+ 2. **Create a new project** (or select an existing one).
73
+
74
+ 3. **Enable the "Google People API"**: In the navigation menu, go to **APIs & Services > Library**, search for "Google People API", and click **Enable**.
75
+
76
+ 4. **Configure the OAuth Consent Screen**:
77
+ * Go to **APIs & Services > OAuth consent screen**.
78
+ * Choose **External** and click **Create**.
79
+ * Fill in the required fields (app name, user support email, developer contact).
80
+ * On the "Scopes" page, you can leave it blank.
81
+ * On the "Test users" page, add the Google account email you intend to log in with.
82
+
83
+ 5. **Create Credentials**:
84
+ * Go to **APIs & Services > Credentials**.
85
+ * Click **+ CREATE CREDENTIALS** and select **OAuth client ID**.
86
+ * For **Application type**, select **Web application**.
87
+
88
+ **For Production (www.igori.eu):**
89
+ * Under **Authorized JavaScript origins**, add `https://www.igori.eu`.
90
+ * Under **Authorized redirect URIs**, add `https://www.igori.eu/auth/google/callback`.
91
+
92
+ **For Local Development:**
93
+ * Under **Authorized JavaScript origins**, add `http://localhost:3000`.
94
+ * Under **Authorized redirect URIs**, add `http://localhost:3000/auth/google/callback`.
95
+
96
+ *Note: You can add both production and development URLs to the same OAuth client.*
97
+
98
+ * Click **Create**. You will be shown your **Client ID** and **Client Secret**. Copy these.
99
+
100
+ ### 3. Configure the Application
101
+
102
+ 1. **Create a `.env` file** in the root of the project by copying the example:
103
+ ```bash
104
+ cp .env.example .env
105
+ ```
106
+
107
+ 2. **Edit the `.env` file** and add the credentials you just created:
108
+
109
+ **For Production (www.igori.eu):**
110
+ ```
111
+ BASE_URL=https://www.igori.eu
112
+ PORT=3000
113
+ HOST=0.0.0.0
114
+ GOOGLE_CLIENT_ID=YOUR_GOOGLE_CLIENT_ID
115
+ GOOGLE_CLIENT_SECRET=YOUR_GOOGLE_CLIENT_SECRET
116
+ SESSION_SECRET=a-very-strong-and-long-random-string
117
+ ```
118
+
119
+ **For Local Development:**
120
+ ```
121
+ BASE_URL=http://localhost:3000
122
+ PORT=3000
123
+ HOST=0.0.0.0
124
+ GOOGLE_CLIENT_ID=YOUR_GOOGLE_CLIENT_ID
125
+ GOOGLE_CLIENT_SECRET=YOUR_GOOGLE_CLIENT_SECRET
126
+ SESSION_SECRET=a-very-strong-and-long-random-string
127
+ ```
128
+
129
+ *Replace the placeholder values with your actual credentials. Use a long, random string for the `SESSION_SECRET`.*
130
+
131
+ ### 4. Install & Run
132
+
133
+ 1. **Install dependencies**:
134
+ ```bash
135
+ npm install
136
+ ```
137
+
138
+ 2. **Start the server**:
139
+ ```bash
140
+ npm start
141
+ ```
142
+
143
+ ### 5. Access Your Terminal
144
+
145
+ **For Production:**
146
+ - Open your web browser and go to `https://www.igori.eu`
147
+ - You will be prompted to log in with your Google account
148
+ - Once authenticated, you will see your shell mirrored in the browser
149
+
150
+ **For Local Development:**
151
+ - Open your web browser and go to `http://localhost:3000`
152
+ - Or access from another device on the same network: `http://<your-macs-ip-address>:3000`
153
+ - You will be prompted to log in with your Google account
154
+ - Once authenticated, you will see your shell mirrored in the browser
155
+
156
+ ### 6. Deployment Notes
157
+
158
+ **For Production Deployment:**
159
+ - Ensure your server has SSL/TLS configured (required for HTTPS)
160
+ - Set up proper firewall rules and security measures
161
+ - Consider using a process manager like PM2 for production
162
+ - Make sure the `BASE_URL` environment variable matches your actual domain
163
+
164
+ **Environment Variables:**
165
+ The application will validate that all required environment variables are set on startup and show helpful error messages if any are missing.
166
+
167
+ ### 7. Production Deployment
168
+
169
+ **Automated Deployment to www.igori.eu:**
170
+
171
+ The project includes an automated deployment script that uploads the application to the production server.
172
+
173
+ ```bash
174
+ npm run deploy
175
+ ```
176
+
177
+ **Deployment Process:**
178
+ 1. **Validation**: Checks that all required files exist and syntax is valid
179
+ 2. **Upload**: Securely uploads all application files via FTP
180
+ 3. **Verification**: Confirms all files were uploaded successfully
181
+ 4. **Instructions**: Displays post-deployment setup steps
182
+
183
+ **Post-Deployment Setup:**
184
+ After running the deployment script, you'll need to SSH into your server and:
185
+
186
+ 1. Install dependencies: `npm install`
187
+ 2. Configure environment: `cp .env.example .env` and edit with your Google OAuth credentials
188
+ 3. Start the server: `npm start` or `pm2 start server.js --name terminal-mirror`
189
+ 4. Set up reverse proxy (nginx) if needed for HTTPS
190
+
191
+ **Security Notes:**
192
+ - Ensure `.env` file has proper permissions (600)
193
+ - Use a strong `SESSION_SECRET` (generate with: `openssl rand -hex 32`)
194
+ - Keep Google OAuth credentials secure
195
+ - Set up proper firewall rules
196
+
197
+ ---
198
+
199
+ ## πŸ› οΈ Development Roadmap
200
+
201
+ ### Current Status βœ…
202
+ The application now has a solid foundation with:
203
+ - Google OAuth 2.0 authentication implemented
204
+ - Environment-based configuration for production/development
205
+ - Basic shell mirroring via WebSocket
206
+ - Secure session management
207
+
208
+ ### Next Development Phases
209
+
210
+ #### Phase 1: Core Functionality Enhancement
211
+ **Priority: High**
212
+ - [ ] **WebSocket Authentication**: Secure WebSocket connections by validating user sessions
213
+ - [ ] **Shell Session Management**: Implement proper shell session isolation per user
214
+ - [ ] **Connection Resilience**: Add automatic reconnection logic for dropped WebSocket connections
215
+ - [ ] **Error Handling**: Improve error handling for authentication failures and connection issues
216
+
217
+ #### Phase 2: User Experience Improvements
218
+ **Priority: Medium**
219
+ - [ ] **Frontend UI Enhancement**:
220
+ - Modernize the terminal interface (consider using xterm.js for better terminal emulation)
221
+ - Add login/logout UI states
222
+ - Implement loading states and connection indicators
223
+ - [ ] **Shell Features**:
224
+ - Shell resizing support
225
+ - Copy/paste functionality
226
+ - Shell themes and customization
227
+ - [ ] **Mobile Optimization**: Ensure the interface works well on mobile devices
228
+
229
+ #### Phase 3: Security & Production Readiness
230
+ **Priority: High for Production**
231
+ - [ ] **Rate Limiting**: Add rate limiting for authentication and WebSocket connections
232
+ - [ ] **Logging & Monitoring**: Implement structured logging and health check endpoints
233
+ - [ ] **Session Security**: Add session timeout and secure session storage
234
+ - [ ] **HTTPS/WSS**: Ensure all connections use secure protocols in production
235
+ - [ ] **Environment Isolation**: Add development/staging/production environment configurations
236
+
237
+ #### Phase 4: Advanced Features
238
+ **Priority: Low (Future Enhancements)**
239
+ - [ ] **Multi-Shell Support**: Allow multiple shell sessions per user
240
+ - [ ] **File Upload/Download**: Add secure file transfer capabilities
241
+ - [ ] **Shell Recording**: Save and replay shell sessions
242
+ - [ ] **Collaboration Features**: Allow shared shell sessions (with proper security)
243
+ - [ ] **Resource Monitoring**: Display system resource usage in the interface
244
+
245
+ ### Development Guidelines
246
+
247
+ #### Code Structure
248
+ - Keep `server.js` focused on server setup and routing
249
+ - Use `auth.js` for all authentication logic
250
+ - Create separate modules for shell management and WebSocket handling
251
+ - Follow Node.js best practices for async/await and error handling
252
+
253
+ #### Testing Strategy
254
+ - Add unit tests for authentication logic
255
+ - Implement integration tests for WebSocket functionality
256
+ - Test OAuth flow in both development and production environments
257
+ - Add automated security testing
258
+
259
+ #### Deployment Considerations
260
+ - Use PM2 or similar process manager for production
261
+ - Implement proper logging with log rotation
262
+ - Set up SSL/TLS certificates for HTTPS
263
+ - Configure reverse proxy (nginx) if needed
264
+ - Monitor application health and performance
265
+
266
+ ### Contributing
267
+ When working on this project:
268
+ 1. Create feature branches for each development phase
269
+ 2. Test thoroughly in local environment before production
270
+ 3. Update this roadmap as features are completed
271
+ 4. Document any new environment variables in `.env.example`
272
+ 5. Keep security as the top priority for all changes
package/auth.js ADDED
@@ -0,0 +1,22 @@
1
+ const passport = require('passport');
2
+ const GoogleStrategy = require('passport-google-oauth20').Strategy;
3
+
4
+ passport.use(new GoogleStrategy({
5
+ clientID: process.env.GOOGLE_CLIENT_ID,
6
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
7
+ callbackURL: `${process.env.BASE_URL}/auth/google/callback`
8
+ },
9
+ function(accessToken, refreshToken, profile, cb) {
10
+ // Here, you would typically find or create a user in your database.
11
+ // For this simple application, we'll just pass the profile through.
12
+ return cb(null, profile);
13
+ }
14
+ ));
15
+
16
+ passport.serializeUser(function(user, cb) {
17
+ cb(null, user);
18
+ });
19
+
20
+ passport.deserializeUser(function(obj, cb) {
21
+ cb(null, obj);
22
+ });
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const package = require('../package.json');
7
+
8
+ // Import command modules
9
+ const setupCommand = require('../lib/setup-wizard');
10
+ const serverManager = require('../lib/server-manager');
11
+ const configManager = require('../lib/config-manager');
12
+
13
+ const program = new Command();
14
+
15
+ program
16
+ .name('shell-mirror')
17
+ .description('Access your Mac shell from any device securely')
18
+ .version(package.version);
19
+
20
+ // Setup command - Interactive OAuth configuration wizard
21
+ program
22
+ .command('setup')
23
+ .description('Configure Google OAuth credentials and initialize Shell Mirror')
24
+ .option('-f, --force', 'Overwrite existing configuration')
25
+ .action(async (options) => {
26
+ try {
27
+ await setupCommand.run(options);
28
+ } catch (error) {
29
+ console.error('Setup failed:', error.message);
30
+ process.exit(1);
31
+ }
32
+ });
33
+
34
+ // Start command - Launch the terminal server
35
+ program
36
+ .command('start')
37
+ .description('Start the Shell Mirror server')
38
+ .option('-p, --port <port>', 'Port to run the server on', '3000')
39
+ .option('-h, --host <host>', 'Host to bind the server to', '0.0.0.0')
40
+ .option('-d, --daemon', 'Run as background daemon')
41
+ .action(async (options) => {
42
+ try {
43
+ await serverManager.start(options);
44
+ } catch (error) {
45
+ console.error('Failed to start server:', error.message);
46
+ process.exit(1);
47
+ }
48
+ });
49
+
50
+ // Stop command - Stop the terminal server
51
+ program
52
+ .command('stop')
53
+ .description('Stop the Shell Mirror server')
54
+ .action(async () => {
55
+ try {
56
+ await serverManager.stop();
57
+ } catch (error) {
58
+ console.error('Failed to stop server:', error.message);
59
+ process.exit(1);
60
+ }
61
+ });
62
+
63
+ // Status command - Check server status
64
+ program
65
+ .command('status')
66
+ .description('Check Shell Mirror server status')
67
+ .action(async () => {
68
+ try {
69
+ await serverManager.status();
70
+ } catch (error) {
71
+ console.error('Failed to check status:', error.message);
72
+ process.exit(1);
73
+ }
74
+ });
75
+
76
+ // Config command - Manage configuration
77
+ program
78
+ .command('config')
79
+ .description('Manage Shell Mirror configuration')
80
+ .option('--show', 'Show current configuration')
81
+ .option('--reset', 'Reset configuration to defaults')
82
+ .option('--set <key=value>', 'Set a configuration value')
83
+ .action(async (options) => {
84
+ try {
85
+ await configManager.manage(options);
86
+ } catch (error) {
87
+ console.error('Config management failed:', error.message);
88
+ process.exit(1);
89
+ }
90
+ });
91
+
92
+ // Health command - Run health checks
93
+ program
94
+ .command('health')
95
+ .description('Run health checks and diagnostics')
96
+ .action(async () => {
97
+ try {
98
+ const healthChecker = require('../lib/health-checker');
99
+ await healthChecker.run();
100
+ } catch (error) {
101
+ console.error('Health check failed:', error.message);
102
+ process.exit(1);
103
+ }
104
+ });
105
+
106
+ // Install command - Post-install setup
107
+ program
108
+ .command('install')
109
+ .description('Post-installation setup and verification')
110
+ .action(async () => {
111
+ try {
112
+ console.log('πŸŽ‰ Shell Mirror installed successfully!');
113
+ console.log('');
114
+ console.log('Next steps:');
115
+ console.log('1. Run: shell-mirror setup');
116
+ console.log('2. Run: shell-mirror start');
117
+ console.log('3. Open your browser and visit the URL shown');
118
+ console.log('');
119
+ console.log('For help: shell-mirror --help');
120
+ } catch (error) {
121
+ console.error('Installation verification failed:', error.message);
122
+ process.exit(1);
123
+ }
124
+ });
125
+
126
+ // Parse command line arguments
127
+ program.parse();
@@ -0,0 +1,203 @@
1
+ const fs = require('fs').promises;
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ class ConfigManager {
6
+ constructor() {
7
+ this.configDir = path.join(os.homedir(), '.terminal-mirror');
8
+ this.configFile = path.join(this.configDir, 'config.json');
9
+ this.envFile = path.join(process.cwd(), '.env');
10
+ }
11
+
12
+ async manage(options = {}) {
13
+ if (options.show) {
14
+ await this.showConfig();
15
+ } else if (options.reset) {
16
+ await this.resetConfig();
17
+ } else if (options.set) {
18
+ await this.setConfig(options.set);
19
+ } else {
20
+ console.log('Configuration Management');
21
+ console.log('─'.repeat(30));
22
+ console.log('');
23
+ console.log('Usage:');
24
+ console.log(' terminal-mirror config --show Show current configuration');
25
+ console.log(' terminal-mirror config --reset Reset to defaults');
26
+ console.log(' terminal-mirror config --set key=value Set a value');
27
+ console.log('');
28
+ console.log('Examples:');
29
+ console.log(' terminal-mirror config --set port=8080');
30
+ console.log(' terminal-mirror config --set baseUrl=https://example.com');
31
+ }
32
+ }
33
+
34
+ async showConfig() {
35
+ console.log('πŸ“‹ Current Configuration');
36
+ console.log('─'.repeat(30));
37
+ console.log('');
38
+
39
+ // Load environment
40
+ require('dotenv').config({ path: this.envFile });
41
+
42
+ // Show main configuration
43
+ console.log('Environment Variables:');
44
+ const envVars = [
45
+ 'BASE_URL',
46
+ 'PORT',
47
+ 'HOST',
48
+ 'NODE_ENV',
49
+ 'GOOGLE_CLIENT_ID'
50
+ ];
51
+
52
+ envVars.forEach(key => {
53
+ const value = process.env[key];
54
+ if (key === 'GOOGLE_CLIENT_ID' && value) {
55
+ // Mask client ID for security
56
+ const masked = value.substring(0, 10) + '...' + value.substring(value.length - 10);
57
+ console.log(` ${key}: ${masked}`);
58
+ } else {
59
+ console.log(` ${key}: ${value || 'Not set'}`);
60
+ }
61
+ });
62
+
63
+ console.log('');
64
+ console.log('Secret Variables:');
65
+ console.log(' GOOGLE_CLIENT_SECRET: ' + (process.env.GOOGLE_CLIENT_SECRET ? '[SET]' : '[NOT SET]'));
66
+ console.log(' SESSION_SECRET: ' + (process.env.SESSION_SECRET ? '[SET]' : '[NOT SET]'));
67
+
68
+ // Show user config if exists
69
+ try {
70
+ const userConfig = JSON.parse(await fs.readFile(this.configFile, 'utf8'));
71
+ console.log('');
72
+ console.log('User Configuration:');
73
+ console.log(` Setup Date: ${userConfig.setupDate || 'Unknown'}`);
74
+ console.log(` Environment: ${userConfig.environment || 'Unknown'}`);
75
+ } catch (error) {
76
+ console.log('');
77
+ console.log('User Configuration: Not found');
78
+ }
79
+
80
+ // Show file locations
81
+ console.log('');
82
+ console.log('Configuration Files:');
83
+ console.log(` Environment: ${this.envFile}`);
84
+ console.log(` User Config: ${this.configFile}`);
85
+ }
86
+
87
+ async resetConfig() {
88
+ console.log('πŸ—‘οΈ Resetting Configuration');
89
+ console.log('');
90
+
91
+ const { confirm } = require('inquirer').prompt([{
92
+ type: 'confirm',
93
+ name: 'confirm',
94
+ message: 'This will delete all configuration. Continue?',
95
+ default: false
96
+ }]);
97
+
98
+ if (!(await confirm).confirm) {
99
+ console.log('Reset cancelled');
100
+ return;
101
+ }
102
+
103
+ try {
104
+ // Remove user config
105
+ try {
106
+ await fs.unlink(this.configFile);
107
+ console.log('βœ… User configuration removed');
108
+ } catch (error) {
109
+ console.log('⚠️ User configuration not found');
110
+ }
111
+
112
+ // Remove .env file
113
+ try {
114
+ await fs.unlink(this.envFile);
115
+ console.log('βœ… Environment file removed');
116
+ } catch (error) {
117
+ console.log('⚠️ Environment file not found');
118
+ }
119
+
120
+ // Remove PID and log files
121
+ const pidFile = path.join(this.configDir, 'server.pid');
122
+ const logFile = path.join(this.configDir, 'server.log');
123
+
124
+ try {
125
+ await fs.unlink(pidFile);
126
+ } catch (error) {
127
+ // Ignore
128
+ }
129
+
130
+ try {
131
+ await fs.unlink(logFile);
132
+ } catch (error) {
133
+ // Ignore
134
+ }
135
+
136
+ console.log('');
137
+ console.log('βœ… Configuration reset complete');
138
+ console.log('Run "terminal-mirror setup" to reconfigure');
139
+
140
+ } catch (error) {
141
+ console.error('❌ Reset failed:', error.message);
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ async setConfig(keyValue) {
147
+ const [key, value] = keyValue.split('=');
148
+
149
+ if (!key || !value) {
150
+ console.error('❌ Invalid format. Use: key=value');
151
+ return;
152
+ }
153
+
154
+ console.log(`πŸ”§ Setting ${key} = ${value}`);
155
+
156
+ try {
157
+ // Read current .env file
158
+ let envContent = '';
159
+ try {
160
+ envContent = await fs.readFile(this.envFile, 'utf8');
161
+ } catch (error) {
162
+ console.log('⚠️ .env file not found, creating new one');
163
+ }
164
+
165
+ // Update or add the key
166
+ const lines = envContent.split('\n');
167
+ let found = false;
168
+ const updatedLines = lines.map(line => {
169
+ if (line.startsWith(`${key}=`)) {
170
+ found = true;
171
+ return `${key}=${value}`;
172
+ }
173
+ return line;
174
+ });
175
+
176
+ if (!found) {
177
+ updatedLines.push(`${key}=${value}`);
178
+ }
179
+
180
+ // Write back to file
181
+ await fs.writeFile(this.envFile, updatedLines.join('\n'));
182
+ console.log('βœ… Configuration updated');
183
+
184
+ // Update user config if applicable
185
+ if (['port', 'baseUrl'].includes(key)) {
186
+ try {
187
+ const userConfig = JSON.parse(await fs.readFile(this.configFile, 'utf8'));
188
+ userConfig[key] = value;
189
+ userConfig.lastModified = new Date().toISOString();
190
+ await fs.writeFile(this.configFile, JSON.stringify(userConfig, null, 2));
191
+ } catch (error) {
192
+ // User config doesn't exist, that's ok
193
+ }
194
+ }
195
+
196
+ } catch (error) {
197
+ console.error('❌ Failed to update configuration:', error.message);
198
+ throw error;
199
+ }
200
+ }
201
+ }
202
+
203
+ module.exports = new ConfigManager();