ghostterm 1.0.3 → 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/README.md +15 -1
- package/bin/ghostterm.js +59 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,10 +84,24 @@ Take a screenshot of your phone screen and send it to your PC terminal. Upload f
|
|
|
84
84
|
| No VPN needed | ✅ | N/A |
|
|
85
85
|
| Google auto-pairing | ✅ | - |
|
|
86
86
|
|
|
87
|
+
## Controls
|
|
88
|
+
|
|
89
|
+
**Top bar**: 4 ghost cells (tap to switch terminals, `+` to create new) · `🏠` pixel office view · `A` font size · `▼` hide controls
|
|
90
|
+
|
|
91
|
+
**Quick keys**: `y` / `n` (approve/deny) · `S+Tab` (shift-tab) · `/ cmd` (slash commands) · `Tab` · `←Bksp`
|
|
92
|
+
|
|
93
|
+
**Left column**: `⏻ claude` (launch menu: new / resume / continue / dangerous mode) · `Stop` (Ctrl+C) · `Close` (kill session)
|
|
94
|
+
|
|
95
|
+
**Center**: D-pad (↑↓←→) + `Enter`
|
|
96
|
+
|
|
97
|
+
**Right column**: `Line↵` (newline) · `⬇` (scroll to bottom) · `Space` · `▲▼` (page up/down) · `Copy` (select mode)
|
|
98
|
+
|
|
99
|
+
**Bottom**: Text input + `Send` · `📷 Shot` (screenshot to PC) · `📁 File` (upload to PC)
|
|
100
|
+
|
|
87
101
|
## Pricing
|
|
88
102
|
|
|
89
103
|
- **Free**: 1 hour/day, full features
|
|
90
|
-
- **Pro**:
|
|
104
|
+
- **Pro**: $5/month or $12/quarter — unlimited access
|
|
91
105
|
|
|
92
106
|
## How It Works
|
|
93
107
|
|
package/bin/ghostterm.js
CHANGED
|
@@ -171,17 +171,33 @@ function createSession() {
|
|
|
171
171
|
env: (() => { const e = { ...process.env, TERM: 'xterm-256color' }; delete e.CLAUDECODE; delete e.CLAUDE_CODE; return e; })(),
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
const session = { id, term, outputBuffer: '', bufferSeq: 0, exited: false };
|
|
174
|
+
const session = { id, term, outputBuffer: '', bufferSeq: 0, exited: false, pendingData: '', flushTimer: null };
|
|
175
175
|
|
|
176
176
|
console.log(` Terminal ${id} spawned (PID: ${term.pid})`);
|
|
177
177
|
|
|
178
|
+
function flushOutput() {
|
|
179
|
+
if (session.pendingData) {
|
|
180
|
+
sendToRelay({ type: 'output', data: session.pendingData, seq: session.bufferSeq, id });
|
|
181
|
+
session.pendingData = '';
|
|
182
|
+
}
|
|
183
|
+
session.flushTimer = null;
|
|
184
|
+
}
|
|
185
|
+
|
|
178
186
|
term.onData((data) => {
|
|
179
187
|
session.outputBuffer += data;
|
|
180
188
|
session.bufferSeq += data.length;
|
|
181
189
|
if (session.outputBuffer.length > OUTPUT_BUFFER_MAX) {
|
|
182
190
|
session.outputBuffer = session.outputBuffer.slice(-OUTPUT_BUFFER_MAX);
|
|
183
191
|
}
|
|
184
|
-
|
|
192
|
+
session.pendingData += data;
|
|
193
|
+
// Adaptive: small output (keystroke echo) → flush immediately
|
|
194
|
+
// Large output (bulk) → batch for 8ms
|
|
195
|
+
if (session.pendingData.length < 64) {
|
|
196
|
+
if (session.flushTimer) { clearTimeout(session.flushTimer); session.flushTimer = null; }
|
|
197
|
+
flushOutput();
|
|
198
|
+
} else if (!session.flushTimer) {
|
|
199
|
+
session.flushTimer = setTimeout(flushOutput, 8);
|
|
200
|
+
}
|
|
185
201
|
});
|
|
186
202
|
|
|
187
203
|
term.onExit(({ exitCode }) => {
|
|
@@ -200,6 +216,34 @@ function getSessionList() {
|
|
|
200
216
|
return Array.from(sessions.values()).map(s => ({ id: s.id, exited: s.exited }));
|
|
201
217
|
}
|
|
202
218
|
|
|
219
|
+
// ==================== Standby Session (pre-spawn for instant open) ====================
|
|
220
|
+
let standbySession = null;
|
|
221
|
+
|
|
222
|
+
function prepareStandby() {
|
|
223
|
+
// Only prepare if under max and no standby exists
|
|
224
|
+
if (standbySession || sessions.size >= MAX_SESSIONS) return;
|
|
225
|
+
standbySession = createSession();
|
|
226
|
+
// Remove from active sessions — it's hidden until needed
|
|
227
|
+
sessions.delete(standbySession.id);
|
|
228
|
+
console.log(` Standby terminal ${standbySession.id} ready`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function useStandbyOrCreate() {
|
|
232
|
+
let s;
|
|
233
|
+
if (standbySession && !standbySession.exited) {
|
|
234
|
+
s = standbySession;
|
|
235
|
+
sessions.set(s.id, s);
|
|
236
|
+
standbySession = null;
|
|
237
|
+
console.log(` Using standby terminal ${s.id}`);
|
|
238
|
+
} else {
|
|
239
|
+
standbySession = null;
|
|
240
|
+
s = createSession();
|
|
241
|
+
}
|
|
242
|
+
// Prepare next standby after a short delay
|
|
243
|
+
setTimeout(() => prepareStandby(), 1000);
|
|
244
|
+
return s;
|
|
245
|
+
}
|
|
246
|
+
|
|
203
247
|
// ==================== Relay Connection ====================
|
|
204
248
|
let relayWs = null;
|
|
205
249
|
let pairCode = null;
|
|
@@ -259,6 +303,8 @@ function handleRelayMessage(msg) {
|
|
|
259
303
|
case 'auth_ok':
|
|
260
304
|
console.log(` Authenticated as: ${msg.email}`);
|
|
261
305
|
console.log(' Phone will auto-connect with same Google account');
|
|
306
|
+
// Pre-spawn a standby terminal so first open is instant
|
|
307
|
+
prepareStandby();
|
|
262
308
|
break;
|
|
263
309
|
|
|
264
310
|
case 'auth_error':
|
|
@@ -275,10 +321,16 @@ function handleRelayMessage(msg) {
|
|
|
275
321
|
|
|
276
322
|
case 'mobile_connected':
|
|
277
323
|
console.log(' Mobile connected!');
|
|
278
|
-
if (sessions.size === 0)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
324
|
+
if (sessions.size === 0) {
|
|
325
|
+
// Use standby session if available, otherwise create new
|
|
326
|
+
const s = useStandbyOrCreate();
|
|
327
|
+
sendToRelay({ type: 'sessions', list: getSessionList() });
|
|
328
|
+
sendToRelay({ type: 'attached', id: s.id });
|
|
329
|
+
} else {
|
|
330
|
+
sendToRelay({ type: 'sessions', list: getSessionList() });
|
|
331
|
+
const first = sessions.values().next().value;
|
|
332
|
+
if (first) sendToRelay({ type: 'attached', id: first.id });
|
|
333
|
+
}
|
|
282
334
|
break;
|
|
283
335
|
|
|
284
336
|
case 'mobile_disconnected':
|
|
@@ -303,7 +355,7 @@ function handleRelayMessage(msg) {
|
|
|
303
355
|
|
|
304
356
|
case 'create-session':
|
|
305
357
|
if (sessions.size < MAX_SESSIONS) {
|
|
306
|
-
const session =
|
|
358
|
+
const session = useStandbyOrCreate();
|
|
307
359
|
sendToRelay({ type: 'sessions', list: getSessionList() });
|
|
308
360
|
sendToRelay({ type: 'attached', id: session.id });
|
|
309
361
|
} else {
|