ghostterm 1.0.4 → 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 CHANGED
@@ -84,6 +84,20 @@ 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
package/bin/ghostterm.js CHANGED
@@ -175,20 +175,28 @@ function createSession() {
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
- // Batch output: accumulate for 12ms then flush once
185
192
  session.pendingData += data;
186
- if (!session.flushTimer) {
187
- session.flushTimer = setTimeout(() => {
188
- sendToRelay({ type: 'output', data: session.pendingData, seq: session.bufferSeq, id });
189
- session.pendingData = '';
190
- session.flushTimer = null;
191
- }, 12);
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);
192
200
  }
193
201
  });
194
202
 
@@ -208,6 +216,34 @@ function getSessionList() {
208
216
  return Array.from(sessions.values()).map(s => ({ id: s.id, exited: s.exited }));
209
217
  }
210
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
+
211
247
  // ==================== Relay Connection ====================
212
248
  let relayWs = null;
213
249
  let pairCode = null;
@@ -267,6 +303,8 @@ function handleRelayMessage(msg) {
267
303
  case 'auth_ok':
268
304
  console.log(` Authenticated as: ${msg.email}`);
269
305
  console.log(' Phone will auto-connect with same Google account');
306
+ // Pre-spawn a standby terminal so first open is instant
307
+ prepareStandby();
270
308
  break;
271
309
 
272
310
  case 'auth_error':
@@ -283,10 +321,16 @@ function handleRelayMessage(msg) {
283
321
 
284
322
  case 'mobile_connected':
285
323
  console.log(' Mobile connected!');
286
- if (sessions.size === 0) createSession();
287
- sendToRelay({ type: 'sessions', list: getSessionList() });
288
- const first = sessions.values().next().value;
289
- if (first) sendToRelay({ type: 'attached', id: first.id });
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
+ }
290
334
  break;
291
335
 
292
336
  case 'mobile_disconnected':
@@ -311,7 +355,7 @@ function handleRelayMessage(msg) {
311
355
 
312
356
  case 'create-session':
313
357
  if (sessions.size < MAX_SESSIONS) {
314
- const session = createSession();
358
+ const session = useStandbyOrCreate();
315
359
  sendToRelay({ type: 'sessions', list: getSessionList() });
316
360
  sendToRelay({ type: 'attached', id: session.id });
317
361
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostterm",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "Mobile terminal for Claude Code — control your PC from your phone",
5
5
  "bin": {
6
6
  "ghostterm": "bin/ghostterm.js"