shell-mirror 1.0.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/bin/shell-mirror +11 -1
- package/lib/auto-start.js +500 -0
- package/package.json +1 -1
- package/public/index.html +3 -3
- package/server.js +2 -2
package/bin/shell-mirror
CHANGED
|
@@ -15,7 +15,17 @@ const program = new Command();
|
|
|
15
15
|
program
|
|
16
16
|
.name('shell-mirror')
|
|
17
17
|
.description('Access your Mac shell from any device securely')
|
|
18
|
-
.version(package.version)
|
|
18
|
+
.version(package.version)
|
|
19
|
+
.action(async () => {
|
|
20
|
+
// Default action: auto-start with authentication
|
|
21
|
+
try {
|
|
22
|
+
const autoStart = require('../lib/auto-start');
|
|
23
|
+
await autoStart.run();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('Failed to start Shell Mirror:', error.message);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
19
29
|
|
|
20
30
|
// Setup command - Interactive OAuth configuration wizard
|
|
21
31
|
program
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const querystring = require('querystring');
|
|
8
|
+
|
|
9
|
+
class AutoStart {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.configDir = path.join(os.homedir(), '.shell-mirror');
|
|
12
|
+
this.authFile = path.join(this.configDir, 'auth.json');
|
|
13
|
+
this.configFile = path.join(this.configDir, 'config.json');
|
|
14
|
+
this.envFile = path.join(process.cwd(), '.env');
|
|
15
|
+
|
|
16
|
+
// Shell Mirror OAuth App credentials (production)
|
|
17
|
+
this.oauthConfig = {
|
|
18
|
+
clientId: '804759223392-i5nv5csn1o6siqr760c99l2a9k4sammp.apps.googleusercontent.com',
|
|
19
|
+
clientSecret: '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',
|
|
20
|
+
redirectUri: 'http://localhost:3000/auth/google/callback'
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async run() {
|
|
25
|
+
console.log('đ Starting Shell Mirror...');
|
|
26
|
+
console.log('');
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Ensure config directory exists
|
|
30
|
+
await fs.mkdir(this.configDir, { recursive: true });
|
|
31
|
+
|
|
32
|
+
// Check authentication status
|
|
33
|
+
const authInfo = await this.checkAuth();
|
|
34
|
+
|
|
35
|
+
if (!authInfo) {
|
|
36
|
+
console.log('đ Not logged in. Opening browser for Google authentication...');
|
|
37
|
+
await this.initiateLogin();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Start the server
|
|
42
|
+
await this.startServer(authInfo);
|
|
43
|
+
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('â Failed to start Shell Mirror:', error.message);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async checkAuth() {
|
|
51
|
+
try {
|
|
52
|
+
const authData = await fs.readFile(this.authFile, 'utf8');
|
|
53
|
+
const auth = JSON.parse(authData);
|
|
54
|
+
|
|
55
|
+
// Check if token is still valid (basic check)
|
|
56
|
+
if (auth.accessToken && auth.email && auth.expiresAt) {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
if (now < auth.expiresAt) {
|
|
59
|
+
return auth;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async initiateLogin() {
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log('đ Opening browser for Google authentication...');
|
|
72
|
+
console.log('');
|
|
73
|
+
|
|
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
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
buildAuthUrl() {
|
|
96
|
+
const params = new URLSearchParams({
|
|
97
|
+
client_id: this.oauthConfig.clientId,
|
|
98
|
+
redirect_uri: this.oauthConfig.redirectUri,
|
|
99
|
+
response_type: 'code',
|
|
100
|
+
scope: 'openid email profile',
|
|
101
|
+
access_type: 'offline',
|
|
102
|
+
prompt: 'consent'
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async createOAuthCallbackServer() {
|
|
109
|
+
const express = require('express');
|
|
110
|
+
const app = express();
|
|
111
|
+
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
let serverInstance;
|
|
114
|
+
|
|
115
|
+
app.get('/auth/google/callback', async (req, res) => {
|
|
116
|
+
try {
|
|
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
|
+
|
|
124
|
+
if (code) {
|
|
125
|
+
console.log('đ Received authorization code, exchanging for tokens...');
|
|
126
|
+
|
|
127
|
+
// Exchange code for tokens
|
|
128
|
+
const tokens = await this.exchangeCodeForTokens(code);
|
|
129
|
+
await this.saveAuth(tokens);
|
|
130
|
+
|
|
131
|
+
res.send(`
|
|
132
|
+
<html>
|
|
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>
|
|
156
|
+
</body>
|
|
157
|
+
</html>
|
|
158
|
+
`);
|
|
159
|
+
|
|
160
|
+
// Close the temporary server and restart with authenticated state
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
console.log(`â
Login successful! Welcome ${tokens.email}`);
|
|
163
|
+
serverInstance.close(() => {
|
|
164
|
+
this.startAuthenticatedServer(tokens);
|
|
165
|
+
});
|
|
166
|
+
}, 1000);
|
|
167
|
+
|
|
168
|
+
} else {
|
|
169
|
+
throw new Error('No authorization code received');
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error('â OAuth callback error:', error.message);
|
|
173
|
+
res.send(`
|
|
174
|
+
<html>
|
|
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>
|
|
190
|
+
</body>
|
|
191
|
+
</html>
|
|
192
|
+
`);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
serverInstance = app.listen(3000, (err) => {
|
|
197
|
+
if (err) {
|
|
198
|
+
reject(err);
|
|
199
|
+
} else {
|
|
200
|
+
resolve(serverInstance);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async createProductionConfig() {
|
|
207
|
+
// Create production environment file with real OAuth credentials
|
|
208
|
+
const envContent = `# Shell Mirror Configuration
|
|
209
|
+
# Auto-generated on ${new Date().toISOString()}
|
|
210
|
+
|
|
211
|
+
BASE_URL=http://localhost:3000
|
|
212
|
+
PORT=3000
|
|
213
|
+
HOST=0.0.0.0
|
|
214
|
+
GOOGLE_CLIENT_ID=${this.oauthConfig.clientId}
|
|
215
|
+
GOOGLE_CLIENT_SECRET=${this.oauthConfig.clientSecret}
|
|
216
|
+
SESSION_SECRET=${crypto.randomBytes(32).toString('hex')}
|
|
217
|
+
NODE_ENV=development
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
await fs.writeFile(this.envFile, envContent);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async startServerForLogin() {
|
|
224
|
+
console.log('đ Starting Shell Mirror server...');
|
|
225
|
+
console.log('');
|
|
226
|
+
|
|
227
|
+
// Start the server in background
|
|
228
|
+
const serverProcess = spawn('node', ['server.js'], {
|
|
229
|
+
stdio: 'pipe',
|
|
230
|
+
cwd: path.dirname(this.envFile)
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Wait a moment for server to start
|
|
234
|
+
setTimeout(() => {
|
|
235
|
+
this.displayLoginInstructions();
|
|
236
|
+
this.openBrowser('http://localhost:3000');
|
|
237
|
+
}, 2000);
|
|
238
|
+
|
|
239
|
+
// Handle Ctrl+C gracefully
|
|
240
|
+
process.on('SIGINT', () => {
|
|
241
|
+
console.log('');
|
|
242
|
+
console.log('đ Stopping Shell Mirror...');
|
|
243
|
+
serverProcess.kill('SIGINT');
|
|
244
|
+
process.exit(0);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Monitor server
|
|
248
|
+
serverProcess.on('close', (code) => {
|
|
249
|
+
if (code !== 0) {
|
|
250
|
+
console.error(`â Server exited with code ${code}`);
|
|
251
|
+
process.exit(code);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
displayLoginInstructions() {
|
|
257
|
+
console.log('â
Shell Mirror server is running');
|
|
258
|
+
console.log('đ Local server: http://localhost:3000');
|
|
259
|
+
console.log('đą Access from phone: https://shellmirror.app');
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log('đ First-time setup:');
|
|
262
|
+
console.log(' 1. A browser window should open automatically');
|
|
263
|
+
console.log(' 2. Click "Login with Google" to authenticate');
|
|
264
|
+
console.log(' 3. After login, your Mac terminal will be accessible from your phone');
|
|
265
|
+
console.log('');
|
|
266
|
+
console.log('âšī¸ To use from your phone:');
|
|
267
|
+
console.log(' 1. Open https://shellmirror.app on your phone');
|
|
268
|
+
console.log(' 2. Login with the same Google account');
|
|
269
|
+
console.log(' 3. Access your Mac terminal instantly');
|
|
270
|
+
console.log('');
|
|
271
|
+
console.log('Press Ctrl+C to stop');
|
|
272
|
+
console.log('');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async exchangeCodeForTokens(code) {
|
|
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
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async saveAuth(authData) {
|
|
368
|
+
await fs.writeFile(this.authFile, JSON.stringify(authData, null, 2));
|
|
369
|
+
console.log('đ Authentication saved');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async startServer(authInfo) {
|
|
373
|
+
// Create .env file with configuration
|
|
374
|
+
await this.createEnvFile(authInfo);
|
|
375
|
+
|
|
376
|
+
// Start the Express server
|
|
377
|
+
console.log('đ Starting server...');
|
|
378
|
+
console.log('');
|
|
379
|
+
|
|
380
|
+
// Start the main server process
|
|
381
|
+
const serverProcess = spawn('node', ['server.js'], {
|
|
382
|
+
stdio: 'inherit',
|
|
383
|
+
cwd: path.dirname(this.envFile)
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Display status information
|
|
387
|
+
this.displayStatus(authInfo);
|
|
388
|
+
|
|
389
|
+
// Handle Ctrl+C gracefully
|
|
390
|
+
process.on('SIGINT', () => {
|
|
391
|
+
console.log('');
|
|
392
|
+
console.log('đ Stopping Shell Mirror...');
|
|
393
|
+
serverProcess.kill('SIGINT');
|
|
394
|
+
process.exit(0);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Wait for server process
|
|
398
|
+
serverProcess.on('close', (code) => {
|
|
399
|
+
if (code !== 0) {
|
|
400
|
+
console.error(`â Server exited with code ${code}`);
|
|
401
|
+
process.exit(code);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async createEnvFile(authInfo) {
|
|
407
|
+
const envContent = `# Shell Mirror Configuration
|
|
408
|
+
# Auto-generated on ${new Date().toISOString()}
|
|
409
|
+
|
|
410
|
+
BASE_URL=http://localhost:3000
|
|
411
|
+
PORT=3000
|
|
412
|
+
HOST=0.0.0.0
|
|
413
|
+
GOOGLE_CLIENT_ID=${this.oauthConfig.clientId}
|
|
414
|
+
GOOGLE_CLIENT_SECRET=${this.oauthConfig.clientSecret}
|
|
415
|
+
SESSION_SECRET=${crypto.randomBytes(32).toString('hex')}
|
|
416
|
+
NODE_ENV=development
|
|
417
|
+
|
|
418
|
+
# User authentication
|
|
419
|
+
USER_EMAIL=${authInfo.email}
|
|
420
|
+
USER_NAME=${authInfo.name}
|
|
421
|
+
ACCESS_TOKEN=${authInfo.accessToken}
|
|
422
|
+
`;
|
|
423
|
+
|
|
424
|
+
await fs.writeFile(this.envFile, envContent);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
displayStatus(authInfo) {
|
|
428
|
+
console.log('â
Shell Mirror is running');
|
|
429
|
+
console.log(`đ¤ Logged in as: ${authInfo.email}`);
|
|
430
|
+
console.log('đ Local server: http://localhost:3000');
|
|
431
|
+
console.log('đą Access from phone: https://shellmirror.app');
|
|
432
|
+
console.log('');
|
|
433
|
+
console.log('âšī¸ To use from your phone:');
|
|
434
|
+
console.log(' 1. Open https://shellmirror.app on your phone');
|
|
435
|
+
console.log(` 2. Login with the same Google account (${authInfo.email})`);
|
|
436
|
+
console.log(' 3. Access your Mac terminal instantly');
|
|
437
|
+
console.log('');
|
|
438
|
+
console.log('Press Ctrl+C to stop');
|
|
439
|
+
console.log('');
|
|
440
|
+
}
|
|
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
|
+
|
|
475
|
+
async openBrowser(url) {
|
|
476
|
+
const platform = os.platform();
|
|
477
|
+
let command;
|
|
478
|
+
|
|
479
|
+
switch (platform) {
|
|
480
|
+
case 'darwin': // macOS
|
|
481
|
+
command = 'open';
|
|
482
|
+
break;
|
|
483
|
+
case 'win32': // Windows
|
|
484
|
+
command = 'start';
|
|
485
|
+
break;
|
|
486
|
+
default: // Linux and others
|
|
487
|
+
command = 'xdg-open';
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
spawn(command, [url], { detached: true, stdio: 'ignore' });
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.log('â ī¸ Could not open browser automatically.');
|
|
495
|
+
console.log(' Please visit: ' + url);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
module.exports = new AutoStart();
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -692,9 +692,9 @@
|
|
|
692
692
|
<p><strong>Next steps:</strong></p>
|
|
693
693
|
<ol style="text-align: left; margin: 20px 0;">
|
|
694
694
|
<li>Install Shell Mirror using the command above</li>
|
|
695
|
-
<li>Run <code>shell-mirror
|
|
696
|
-
<li>
|
|
697
|
-
<li>
|
|
695
|
+
<li>Run <code>shell-mirror</code> (it will auto-start and open your browser)</li>
|
|
696
|
+
<li>Login with Google when prompted</li>
|
|
697
|
+
<li>Access your terminal from your phone at shellmirror.app</li>
|
|
698
698
|
</ol>
|
|
699
699
|
|
|
700
700
|
<button class="btn-primary" onclick="closeInstallModal()" style="margin-top: 20px;">Got it!</button>
|
package/server.js
CHANGED
|
@@ -96,11 +96,11 @@ if (missingEnvVars.length > 0) {
|
|
|
96
96
|
missingEnvVars.forEach(varName => {
|
|
97
97
|
console.error(` - ${varName}`);
|
|
98
98
|
});
|
|
99
|
-
console.error('\nPlease
|
|
100
|
-
console.error('See README.md for instructions on getting Google OAuth credentials.');
|
|
99
|
+
console.error('\nPlease run "shell-mirror" to set up authentication.');
|
|
101
100
|
process.exit(1);
|
|
102
101
|
}
|
|
103
102
|
|
|
103
|
+
|
|
104
104
|
// 4. WebSocket Connection Handler with Authentication
|
|
105
105
|
wss.on('connection', function connection(ws, request) {
|
|
106
106
|
console.log('New WebSocket connection attempt');
|