shell-mirror 1.1.0 → 1.2.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/lib/auto-start.js CHANGED
@@ -3,6 +3,8 @@ const path = require('path');
3
3
  const os = require('os');
4
4
  const crypto = require('crypto');
5
5
  const { spawn } = require('child_process');
6
+ const https = require('https');
7
+ const querystring = require('querystring');
6
8
 
7
9
  class AutoStart {
8
10
  constructor() {
@@ -11,10 +13,10 @@ class AutoStart {
11
13
  this.configFile = path.join(this.configDir, 'config.json');
12
14
  this.envFile = path.join(process.cwd(), '.env');
13
15
 
14
- // Shell Mirror OAuth App credentials (shared for all users)
16
+ // Shell Mirror OAuth App credentials (production)
15
17
  this.oauthConfig = {
16
- clientId: 'your-shell-mirror-oauth-client-id', // TODO: Replace with actual
17
- clientSecret: 'your-shell-mirror-oauth-client-secret', // TODO: Replace with actual
18
+ clientId: '804759223392-i5nv5csn1o6siqr760c99l2a9k4sammp.apps.googleusercontent.com',
19
+ clientSecret: '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',
18
20
  redirectUri: 'http://localhost:3000/auth/google/callback'
19
21
  };
20
22
  }
@@ -66,13 +68,28 @@ class AutoStart {
66
68
 
67
69
  async initiateLogin() {
68
70
  console.log('');
69
- console.log('📱 Setting up Google authentication...');
71
+ console.log('🔐 Opening browser for Google authentication...');
70
72
  console.log('');
71
73
 
72
- // For now, we'll use a simplified setup that creates minimal config
73
- // and directs user to use the web interface
74
- await this.createMinimalConfig();
75
- await this.startServerForLogin();
74
+ try {
75
+ // Start OAuth callback server
76
+ console.log('🔄 Starting OAuth callback server...');
77
+ const server = await this.createOAuthCallbackServer();
78
+
79
+ // Open browser for OAuth
80
+ const authUrl = this.buildAuthUrl();
81
+ console.log('🌐 Opening browser for login...');
82
+ await this.openBrowser(authUrl);
83
+
84
+ console.log('');
85
+ console.log('👤 Please complete the Google login in your browser');
86
+ console.log(' (If browser didn\'t open, visit: ' + authUrl + ')');
87
+ console.log('');
88
+
89
+ } catch (error) {
90
+ console.error('❌ Failed to start OAuth flow:', error.message);
91
+ process.exit(1);
92
+ }
76
93
  }
77
94
 
78
95
  buildAuthUrl() {
@@ -88,69 +105,114 @@ class AutoStart {
88
105
  return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
89
106
  }
90
107
 
91
- async createTempOAuthServer() {
92
- // This would create a temporary Express server to handle OAuth callback
93
- // For now, we'll use a simplified approach
108
+ async createOAuthCallbackServer() {
94
109
  const express = require('express');
95
110
  const app = express();
96
111
 
97
112
  return new Promise((resolve, reject) => {
113
+ let serverInstance;
114
+
98
115
  app.get('/auth/google/callback', async (req, res) => {
99
116
  try {
100
117
  const code = req.query.code;
118
+ const error = req.query.error;
119
+
120
+ if (error) {
121
+ throw new Error(`OAuth error: ${error}`);
122
+ }
123
+
101
124
  if (code) {
125
+ console.log('📝 Received authorization code, exchanging for tokens...');
126
+
102
127
  // Exchange code for tokens
103
128
  const tokens = await this.exchangeCodeForTokens(code);
104
129
  await this.saveAuth(tokens);
105
130
 
106
131
  res.send(`
107
132
  <html>
108
- <body style="font-family: Arial; text-align: center; padding: 50px;">
109
- <h2>✅ Login Successful!</h2>
110
- <p>You can close this window and return to your terminal.</p>
111
- <script>window.close();</script>
133
+ <head>
134
+ <title>Shell Mirror - Login Successful</title>
135
+ <style>
136
+ body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
137
+ .container { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 500px; margin: 0 auto; }
138
+ h2 { color: #4CAF50; margin-bottom: 20px; }
139
+ p { color: #666; margin-bottom: 15px; }
140
+ .email { color: #333; font-weight: bold; }
141
+ </style>
142
+ </head>
143
+ <body>
144
+ <div class="container">
145
+ <h2>✅ Login Successful!</h2>
146
+ <p>Welcome, <span class="email">${tokens.email}</span></p>
147
+ <p>Shell Mirror is now starting with your account.</p>
148
+ <p>You can close this window and return to your terminal.</p>
149
+ <p><small>Or visit <strong>https://shellmirror.app</strong> on your phone to access your terminal.</small></p>
150
+ </div>
151
+ <script>
152
+ setTimeout(() => {
153
+ window.close();
154
+ }, 3000);
155
+ </script>
112
156
  </body>
113
157
  </html>
114
158
  `);
115
159
 
116
- // Restart the main process
160
+ // Close the temporary server and restart with authenticated state
117
161
  setTimeout(() => {
118
- console.log('✅ Login successful! Starting Shell Mirror...');
119
- this.run();
162
+ console.log(`✅ Login successful! Welcome ${tokens.email}`);
163
+ serverInstance.close(() => {
164
+ this.startAuthenticatedServer(tokens);
165
+ });
120
166
  }, 1000);
121
167
 
122
168
  } else {
123
169
  throw new Error('No authorization code received');
124
170
  }
125
171
  } catch (error) {
172
+ console.error('❌ OAuth callback error:', error.message);
126
173
  res.send(`
127
174
  <html>
128
- <body style="font-family: Arial; text-align: center; padding: 50px;">
129
- <h2>❌ Login Failed</h2>
130
- <p>Error: ${error.message}</p>
131
- <p>Please try running 'shell-mirror' again.</p>
175
+ <head>
176
+ <title>Shell Mirror - Login Failed</title>
177
+ <style>
178
+ body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; }
179
+ .container { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 500px; margin: 0 auto; }
180
+ h2 { color: #f44336; margin-bottom: 20px; }
181
+ p { color: #666; margin-bottom: 15px; }
182
+ </style>
183
+ </head>
184
+ <body>
185
+ <div class="container">
186
+ <h2>❌ Login Failed</h2>
187
+ <p>Error: ${error.message}</p>
188
+ <p>Please try running 'shell-mirror' again.</p>
189
+ </div>
132
190
  </body>
133
191
  </html>
134
192
  `);
135
193
  }
136
194
  });
137
195
 
138
- const server = app.listen(3000, () => {
139
- resolve(server);
196
+ serverInstance = app.listen(3000, (err) => {
197
+ if (err) {
198
+ reject(err);
199
+ } else {
200
+ resolve(serverInstance);
201
+ }
140
202
  });
141
203
  });
142
204
  }
143
205
 
144
- async createMinimalConfig() {
145
- // Create a basic environment file that allows the server to start
206
+ async createProductionConfig() {
207
+ // Create production environment file with real OAuth credentials
146
208
  const envContent = `# Shell Mirror Configuration
147
209
  # Auto-generated on ${new Date().toISOString()}
148
210
 
149
211
  BASE_URL=http://localhost:3000
150
212
  PORT=3000
151
213
  HOST=0.0.0.0
152
- GOOGLE_CLIENT_ID=placeholder-will-be-set-after-login
153
- GOOGLE_CLIENT_SECRET=placeholder-will-be-set-after-login
214
+ GOOGLE_CLIENT_ID=${this.oauthConfig.clientId}
215
+ GOOGLE_CLIENT_SECRET=${this.oauthConfig.clientSecret}
154
216
  SESSION_SECRET=${crypto.randomBytes(32).toString('hex')}
155
217
  NODE_ENV=development
156
218
  `;
@@ -211,15 +273,95 @@ NODE_ENV=development
211
273
  }
212
274
 
213
275
  async exchangeCodeForTokens(code) {
214
- // This would exchange the OAuth code for access tokens
215
- // For now, return mock data (this needs real OAuth implementation)
216
- return {
217
- accessToken: 'mock-access-token',
218
- refreshToken: 'mock-refresh-token',
219
- email: 'user@example.com',
220
- name: 'User Name',
221
- expiresAt: Date.now() + (3600 * 1000) // 1 hour
222
- };
276
+ return new Promise((resolve, reject) => {
277
+ const postData = querystring.stringify({
278
+ code: code,
279
+ client_id: this.oauthConfig.clientId,
280
+ client_secret: this.oauthConfig.clientSecret,
281
+ redirect_uri: this.oauthConfig.redirectUri,
282
+ grant_type: 'authorization_code'
283
+ });
284
+
285
+ const options = {
286
+ hostname: 'oauth2.googleapis.com',
287
+ port: 443,
288
+ path: '/token',
289
+ method: 'POST',
290
+ headers: {
291
+ 'Content-Type': 'application/x-www-form-urlencoded',
292
+ 'Content-Length': Buffer.byteLength(postData)
293
+ }
294
+ };
295
+
296
+ const req = https.request(options, (res) => {
297
+ let data = '';
298
+ res.on('data', (chunk) => {
299
+ data += chunk;
300
+ });
301
+ res.on('end', async () => {
302
+ try {
303
+ const tokens = JSON.parse(data);
304
+ if (tokens.access_token) {
305
+ // Get user info
306
+ const userInfo = await this.getUserInfo(tokens.access_token);
307
+ resolve({
308
+ accessToken: tokens.access_token,
309
+ refreshToken: tokens.refresh_token,
310
+ email: userInfo.email,
311
+ name: userInfo.name,
312
+ expiresAt: Date.now() + (tokens.expires_in * 1000)
313
+ });
314
+ } else {
315
+ reject(new Error('Failed to get access token: ' + data));
316
+ }
317
+ } catch (error) {
318
+ reject(error);
319
+ }
320
+ });
321
+ });
322
+
323
+ req.on('error', (error) => {
324
+ reject(error);
325
+ });
326
+
327
+ req.write(postData);
328
+ req.end();
329
+ });
330
+ }
331
+
332
+ async getUserInfo(accessToken) {
333
+ return new Promise((resolve, reject) => {
334
+ const options = {
335
+ hostname: 'www.googleapis.com',
336
+ port: 443,
337
+ path: '/oauth2/v2/userinfo',
338
+ method: 'GET',
339
+ headers: {
340
+ 'Authorization': `Bearer ${accessToken}`
341
+ }
342
+ };
343
+
344
+ const req = https.request(options, (res) => {
345
+ let data = '';
346
+ res.on('data', (chunk) => {
347
+ data += chunk;
348
+ });
349
+ res.on('end', () => {
350
+ try {
351
+ const userInfo = JSON.parse(data);
352
+ resolve(userInfo);
353
+ } catch (error) {
354
+ reject(error);
355
+ }
356
+ });
357
+ });
358
+
359
+ req.on('error', (error) => {
360
+ reject(error);
361
+ });
362
+
363
+ req.end();
364
+ });
223
365
  }
224
366
 
225
367
  async saveAuth(authData) {
@@ -297,6 +439,39 @@ ACCESS_TOKEN=${authInfo.accessToken}
297
439
  console.log('');
298
440
  }
299
441
 
442
+ async startAuthenticatedServer(authInfo) {
443
+ console.log('');
444
+ console.log('🚀 Starting authenticated Shell Mirror server...');
445
+
446
+ // Create .env file with authentication
447
+ await this.createEnvFile(authInfo);
448
+
449
+ // Start the main server process
450
+ const serverProcess = spawn('node', ['server.js'], {
451
+ stdio: 'inherit',
452
+ cwd: path.dirname(this.envFile)
453
+ });
454
+
455
+ // Display status information
456
+ this.displayStatus(authInfo);
457
+
458
+ // Handle Ctrl+C gracefully
459
+ process.on('SIGINT', () => {
460
+ console.log('');
461
+ console.log('🛑 Stopping Shell Mirror...');
462
+ serverProcess.kill('SIGINT');
463
+ process.exit(0);
464
+ });
465
+
466
+ // Wait for server process
467
+ serverProcess.on('close', (code) => {
468
+ if (code !== 0) {
469
+ console.error(`❌ Server exited with code ${code}`);
470
+ process.exit(code);
471
+ }
472
+ });
473
+ }
474
+
300
475
  async openBrowser(url) {
301
476
  const platform = os.platform();
302
477
  let command;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Access your Mac shell from any device securely. Perfect for mobile coding with Claude Code CLI, Gemini CLI, and any shell tool.",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -100,15 +100,6 @@ if (missingEnvVars.length > 0) {
100
100
  process.exit(1);
101
101
  }
102
102
 
103
- // Check if OAuth credentials are placeholders (first-time setup)
104
- const isFirstTimeSetup = process.env.GOOGLE_CLIENT_ID === 'placeholder-will-be-set-after-login';
105
-
106
- if (isFirstTimeSetup) {
107
- console.log('â„šī¸ First-time setup detected. OAuth authentication is not yet configured.');
108
- console.log(' The server will start, but Google login will not work until configured.');
109
- console.log(' Please visit the web interface to complete setup.');
110
- console.log('');
111
- }
112
103
 
113
104
  // 4. WebSocket Connection Handler with Authentication
114
105
  wss.on('connection', function connection(ws, request) {