remotosh 1.0.1 → 1.1.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/remoto.js +100 -20
- package/package.json +3 -2
package/bin/remoto.js
CHANGED
|
@@ -11,6 +11,57 @@ const WS_SERVER_URL = process.env.REMOTO_WS_URL || 'wss://remoto-ws.fly.dev';
|
|
|
11
11
|
const WEB_APP_URL = process.env.REMOTO_WEB_URL || 'https://remoto.sh';
|
|
12
12
|
const API_KEY = process.env.REMOTO_API_KEY;
|
|
13
13
|
|
|
14
|
+
// Sensitive environment variable patterns to filter out
|
|
15
|
+
const SENSITIVE_ENV_PATTERNS = [
|
|
16
|
+
/^AWS_/i,
|
|
17
|
+
/^AZURE_/i,
|
|
18
|
+
/^GCP_/i,
|
|
19
|
+
/^GOOGLE_/i,
|
|
20
|
+
/^GITHUB_TOKEN$/i,
|
|
21
|
+
/^GH_TOKEN$/i,
|
|
22
|
+
/^GITLAB_/i,
|
|
23
|
+
/^NPM_TOKEN$/i,
|
|
24
|
+
/^NUGET_/i,
|
|
25
|
+
/^DOCKER_/i,
|
|
26
|
+
/^KUBERNETES_/i,
|
|
27
|
+
/^K8S_/i,
|
|
28
|
+
/SECRET/i,
|
|
29
|
+
/PASSWORD/i,
|
|
30
|
+
/PRIVATE_KEY/i,
|
|
31
|
+
/API_KEY/i,
|
|
32
|
+
/AUTH_TOKEN/i,
|
|
33
|
+
/ACCESS_TOKEN/i,
|
|
34
|
+
/REFRESH_TOKEN/i,
|
|
35
|
+
/DATABASE_URL/i,
|
|
36
|
+
/DB_PASSWORD/i,
|
|
37
|
+
/POSTGRES_/i,
|
|
38
|
+
/MYSQL_/i,
|
|
39
|
+
/MONGO_/i,
|
|
40
|
+
/REDIS_/i,
|
|
41
|
+
/STRIPE_/i,
|
|
42
|
+
/TWILIO_/i,
|
|
43
|
+
/SENDGRID_/i,
|
|
44
|
+
/MAILGUN_/i,
|
|
45
|
+
/SUPABASE_/i,
|
|
46
|
+
/FIREBASE_/i,
|
|
47
|
+
/OPENAI_/i,
|
|
48
|
+
/ANTHROPIC_/i,
|
|
49
|
+
/SLACK_/i,
|
|
50
|
+
/DISCORD_/i,
|
|
51
|
+
/^REMOTO_API_KEY$/i, // Our own API key
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// Create sanitized environment for PTY
|
|
55
|
+
function getSanitizedEnv() {
|
|
56
|
+
const env = { ...process.env };
|
|
57
|
+
for (const key of Object.keys(env)) {
|
|
58
|
+
if (SENSITIVE_ENV_PATTERNS.some(pattern => pattern.test(key))) {
|
|
59
|
+
delete env[key];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return env;
|
|
63
|
+
}
|
|
64
|
+
|
|
14
65
|
// Detect shell
|
|
15
66
|
const shell = process.env.SHELL || (os.platform() === 'win32' ? 'powershell.exe' : 'zsh');
|
|
16
67
|
|
|
@@ -21,18 +72,33 @@ let rows = process.stdout.rows || 24;
|
|
|
21
72
|
console.clear();
|
|
22
73
|
console.log(chalk.bold.white('\n remoto'));
|
|
23
74
|
console.log(chalk.dim(' control your terminal from your phone\n'));
|
|
75
|
+
|
|
76
|
+
// API key is required
|
|
77
|
+
if (!API_KEY) {
|
|
78
|
+
console.log(chalk.yellow(' authentication required\n'));
|
|
79
|
+
console.log(chalk.white(' to use remoto, you need an API key:\n'));
|
|
80
|
+
console.log(chalk.dim(' 1. create a free account at ') + chalk.cyan('https://remoto.sh/signup'));
|
|
81
|
+
console.log(chalk.dim(' 2. go to ') + chalk.cyan('https://remoto.sh/dashboard/api-keys'));
|
|
82
|
+
console.log(chalk.dim(' 3. create an API key and set it as an environment variable:\n'));
|
|
83
|
+
console.log(chalk.green(' export REMOTO_API_KEY="your_key_here"'));
|
|
84
|
+
console.log(chalk.dim('\n free plan includes:'));
|
|
85
|
+
console.log(chalk.dim(' • 2 concurrent sessions'));
|
|
86
|
+
console.log(chalk.dim(' • 1 hour session duration'));
|
|
87
|
+
console.log(chalk.dim(' • unlimited sessions per month'));
|
|
88
|
+
console.log(chalk.dim('\n pro plans with higher limits coming soon!\n'));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
24
92
|
console.log(chalk.dim(' connecting...'));
|
|
25
93
|
|
|
26
|
-
// Connect to WebSocket server
|
|
27
|
-
const wsUrl = API_KEY
|
|
28
|
-
? `${WS_SERVER_URL}/cli/?apiKey=${encodeURIComponent(API_KEY)}`
|
|
29
|
-
: `${WS_SERVER_URL}/cli/`;
|
|
94
|
+
// Connect to WebSocket server
|
|
95
|
+
const wsUrl = `${WS_SERVER_URL}/cli/?apiKey=${encodeURIComponent(API_KEY)}`;
|
|
30
96
|
const ws = new WebSocket(wsUrl);
|
|
31
97
|
|
|
32
98
|
let ptyProcess = null;
|
|
33
99
|
let sessionId = null;
|
|
34
100
|
let sessionToken = null;
|
|
35
|
-
let
|
|
101
|
+
let maxDuration = null;
|
|
36
102
|
let outputBuffer = '';
|
|
37
103
|
let flushTimeout = null;
|
|
38
104
|
|
|
@@ -53,7 +119,25 @@ ws.on('message', (data) => {
|
|
|
53
119
|
});
|
|
54
120
|
|
|
55
121
|
ws.on('close', (code, reason) => {
|
|
56
|
-
|
|
122
|
+
const reasonStr = reason?.toString() || '';
|
|
123
|
+
|
|
124
|
+
if (code === 4001) {
|
|
125
|
+
console.log(chalk.red('\n error: API key required'));
|
|
126
|
+
console.log(chalk.dim(' set REMOTO_API_KEY environment variable'));
|
|
127
|
+
} else if (code === 4002) {
|
|
128
|
+
console.log(chalk.red('\n error: invalid API key'));
|
|
129
|
+
console.log(chalk.dim(' check your API key at https://remoto.sh/dashboard/api-keys'));
|
|
130
|
+
} else if (code === 4004) {
|
|
131
|
+
console.log(chalk.yellow('\n session limit reached'));
|
|
132
|
+
console.log(chalk.dim(' free plan allows 2 concurrent sessions'));
|
|
133
|
+
console.log(chalk.dim(' close an existing session and try again'));
|
|
134
|
+
console.log(chalk.dim('\n pro plans with higher limits coming soon!'));
|
|
135
|
+
} else if (reasonStr.includes('expired')) {
|
|
136
|
+
console.log(chalk.yellow('\n session expired (1 hour limit)'));
|
|
137
|
+
console.log(chalk.dim(' start a new session with: npx remotosh'));
|
|
138
|
+
} else {
|
|
139
|
+
console.log(chalk.dim(`\n disconnected (${code})`));
|
|
140
|
+
}
|
|
57
141
|
cleanup();
|
|
58
142
|
});
|
|
59
143
|
|
|
@@ -70,7 +154,7 @@ function handleServerMessage(message) {
|
|
|
70
154
|
case 'session_created':
|
|
71
155
|
sessionId = message.sessionId;
|
|
72
156
|
sessionToken = message.sessionToken;
|
|
73
|
-
|
|
157
|
+
maxDuration = message.maxDuration;
|
|
74
158
|
showQRCode();
|
|
75
159
|
startPTY();
|
|
76
160
|
break;
|
|
@@ -91,14 +175,14 @@ function handleServerMessage(message) {
|
|
|
91
175
|
}
|
|
92
176
|
break;
|
|
93
177
|
|
|
178
|
+
case 'session_expiring':
|
|
179
|
+
console.log(chalk.yellow(`\n ⚠ session expiring in ${message.minutesRemaining} minutes\n`));
|
|
180
|
+
break;
|
|
181
|
+
|
|
94
182
|
case 'input':
|
|
95
183
|
// Input from phone
|
|
96
|
-
console.log(chalk.dim(` [debug] input handler, ptyProcess exists: ${!!ptyProcess}`));
|
|
97
184
|
if (ptyProcess) {
|
|
98
|
-
console.log(chalk.dim(` [debug] writing to pty: "${message.data}"`));
|
|
99
185
|
ptyProcess.write(message.data);
|
|
100
|
-
} else {
|
|
101
|
-
console.log(chalk.red(' [debug] ptyProcess is null!'));
|
|
102
186
|
}
|
|
103
187
|
break;
|
|
104
188
|
|
|
@@ -116,6 +200,7 @@ function handleServerMessage(message) {
|
|
|
116
200
|
|
|
117
201
|
function showQRCode() {
|
|
118
202
|
const connectionUrl = `${WEB_APP_URL}/session/${sessionId}?token=${sessionToken}`;
|
|
203
|
+
const durationMinutes = maxDuration ? Math.floor(maxDuration / 60000) : 60;
|
|
119
204
|
|
|
120
205
|
console.clear();
|
|
121
206
|
console.log(chalk.bold.white('\n remoto'));
|
|
@@ -127,21 +212,23 @@ function showQRCode() {
|
|
|
127
212
|
console.log(indentedQr);
|
|
128
213
|
console.log(chalk.dim(`\n ${connectionUrl}\n`));
|
|
129
214
|
console.log(chalk.dim(' scan the qr code or open the link on your phone'));
|
|
215
|
+
console.log(chalk.dim(` session expires in ${durationMinutes} minutes`));
|
|
130
216
|
console.log(chalk.dim(' waiting for connection...\n'));
|
|
131
217
|
console.log(chalk.dim('─'.repeat(Math.min(cols, 60))));
|
|
132
218
|
});
|
|
133
219
|
}
|
|
134
220
|
|
|
135
221
|
function startPTY() {
|
|
136
|
-
// Initialize PTY
|
|
222
|
+
// Initialize PTY with sanitized environment (removes secrets)
|
|
137
223
|
console.log(chalk.dim(` [debug] spawning shell: ${shell}`));
|
|
138
224
|
try {
|
|
225
|
+
const sanitizedEnv = getSanitizedEnv();
|
|
139
226
|
ptyProcess = pty.spawn(shell, [], {
|
|
140
227
|
name: 'xterm-256color',
|
|
141
228
|
cols,
|
|
142
229
|
rows,
|
|
143
230
|
cwd: process.cwd(),
|
|
144
|
-
env:
|
|
231
|
+
env: sanitizedEnv,
|
|
145
232
|
});
|
|
146
233
|
console.log(chalk.dim(` [debug] pty spawned successfully, pid: ${ptyProcess.pid}`));
|
|
147
234
|
} catch (err) {
|
|
@@ -174,13 +261,6 @@ function startPTY() {
|
|
|
174
261
|
ptyProcess.onExit(({ exitCode }) => {
|
|
175
262
|
console.log(chalk.dim(`\n session ended`));
|
|
176
263
|
|
|
177
|
-
// Show account nudge for anonymous users
|
|
178
|
-
if (isAnonymous) {
|
|
179
|
-
console.log(chalk.dim('\n ─────────────────────────────────────────'));
|
|
180
|
-
console.log(chalk.white('\n create an account to save session history'));
|
|
181
|
-
console.log(chalk.dim(` ${WEB_APP_URL}/signup\n`));
|
|
182
|
-
}
|
|
183
|
-
|
|
184
264
|
if (ws.readyState === WebSocket.OPEN) {
|
|
185
265
|
ws.send(JSON.stringify({ type: 'exit', code: exitCode }));
|
|
186
266
|
ws.close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remotosh",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Control your terminal from your phone",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "node bin/remoto.js",
|
|
11
|
-
"build": "echo 'No build step needed'"
|
|
11
|
+
"build": "echo 'No build step needed'",
|
|
12
|
+
"postinstall": "chmod +x ./node_modules/node-pty/prebuilds/darwin-*/spawn-helper 2>/dev/null || true"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
14
15
|
"node-pty": "^1.0.0",
|