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 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**: NT$168/month — unlimited access
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
- sendToRelay({ type: 'output', data, seq: session.bufferSeq, id });
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) createSession();
279
- sendToRelay({ type: 'sessions', list: getSessionList() });
280
- const first = sessions.values().next().value;
281
- 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
+ }
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 = createSession();
358
+ const session = useStandbyOrCreate();
307
359
  sendToRelay({ type: 'sessions', list: getSessionList() });
308
360
  sendToRelay({ type: 'attached', id: session.id });
309
361
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostterm",
3
- "version": "1.0.3",
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"