postforme-terminal 1.0.0 → 1.0.2

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.
Files changed (2) hide show
  1. package/dist/index.js +143 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,9 +3,31 @@ import { WebSocketServer, WebSocket } from 'ws';
3
3
  import * as pty from 'node-pty';
4
4
  import { homedir } from 'os';
5
5
  import { createServer } from 'http';
6
+ import { spawn, execSync } from 'child_process';
7
+ import { createConnection } from 'net';
8
+ import { existsSync } from 'fs';
9
+ import { resolve } from 'path';
6
10
  const PORT = parseInt(process.env.TERMINAL_PORT || '5101', 10);
11
+ const STUDIO_PORT = parseInt(process.env.REMOTION_STUDIO_PORT || '3100', 10);
7
12
  const CWD = process.cwd();
8
13
  const MAX_BUFFER = 100_000;
14
+ // --- Locate render project ---
15
+ function findRenderProject() {
16
+ const candidates = [
17
+ process.env.POSTFORME_RENDER_PATH,
18
+ resolve(CWD, 'postforme-render'),
19
+ resolve(CWD, '../postforme-render'),
20
+ resolve(CWD, '../../postforme-render'),
21
+ resolve(homedir(), 'postforme-render'),
22
+ ].filter(Boolean);
23
+ for (const p of candidates) {
24
+ if (existsSync(resolve(p, 'package.json')) && existsSync(resolve(p, 'src'))) {
25
+ return p;
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ const RENDER_PROJECT = findRenderProject();
9
31
  const state = {
10
32
  process: null,
11
33
  buffer: '',
@@ -28,13 +50,20 @@ function spawnClaude() {
28
50
  const shell = process.env.SHELL || '/bin/zsh';
29
51
  console.log(`[postforme] Starting Claude Code in ${CWD}`);
30
52
  try {
53
+ // Build a clean env: strip CLAUDECODE so Claude Code doesn't think it's nested
54
+ const cleanEnv = {};
55
+ for (const [k, v] of Object.entries(process.env)) {
56
+ if (v !== undefined && k !== 'CLAUDECODE' && !k.startsWith('CLAUDE_')) {
57
+ cleanEnv[k] = v;
58
+ }
59
+ }
31
60
  const proc = pty.spawn(shell, [], {
32
61
  name: 'xterm-256color',
33
62
  cols: 120,
34
63
  rows: 30,
35
64
  cwd: CWD,
36
65
  env: {
37
- ...process.env,
66
+ ...cleanEnv,
38
67
  TERM: 'xterm-256color',
39
68
  HOME: homedir(),
40
69
  PATH: `${homedir()}/.local/bin:/opt/homebrew/bin:${process.env.PATH || ''}`,
@@ -77,9 +106,79 @@ function stopClaude() {
77
106
  state.buffer = '';
78
107
  broadcast({ type: 'status', running: false });
79
108
  }
80
- // --- HTTP handler (health check + CORS) ---
81
- function handleHttp(_req, res) {
82
- const origin = _req.headers.origin || '';
109
+ // --- Remotion Studio Management ---
110
+ let studioProcess = null;
111
+ async function checkPort(port) {
112
+ return new Promise((ok) => {
113
+ const socket = createConnection(port, 'localhost');
114
+ socket.setTimeout(1500);
115
+ socket.on('connect', () => { socket.destroy(); ok(true); });
116
+ socket.on('error', () => ok(false));
117
+ socket.on('timeout', () => { socket.destroy(); ok(false); });
118
+ });
119
+ }
120
+ async function isStudioRunning() {
121
+ if (studioProcess && studioProcess.exitCode === null)
122
+ return true;
123
+ // Also check if something external is listening on the studio port
124
+ return checkPort(STUDIO_PORT);
125
+ }
126
+ function startStudio(res) {
127
+ if (!RENDER_PROJECT) {
128
+ res.writeHead(404, { 'Content-Type': 'application/json' });
129
+ res.end(JSON.stringify({ ok: false, error: 'Render project not found. Set POSTFORME_RENDER_PATH or place postforme-render next to your project.' }));
130
+ return;
131
+ }
132
+ if (studioProcess && studioProcess.exitCode === null) {
133
+ res.writeHead(200, { 'Content-Type': 'application/json' });
134
+ res.end(JSON.stringify({ ok: true, message: 'Remotion Studio is already running' }));
135
+ return;
136
+ }
137
+ console.log(`[postforme] Starting Remotion Studio on port ${STUDIO_PORT}...`);
138
+ console.log(`[postforme] render project: ${RENDER_PROJECT}`);
139
+ studioProcess = spawn('npx', ['remotion', 'studio', '--port', String(STUDIO_PORT)], {
140
+ cwd: RENDER_PROJECT,
141
+ stdio: 'pipe',
142
+ env: { ...process.env, FORCE_COLOR: '0' },
143
+ detached: false,
144
+ });
145
+ studioProcess.stdout?.on('data', (data) => {
146
+ const line = data.toString().trim();
147
+ if (line)
148
+ console.log(`[remotion-studio] ${line}`);
149
+ });
150
+ studioProcess.stderr?.on('data', (data) => {
151
+ const line = data.toString().trim();
152
+ if (line)
153
+ console.log(`[remotion-studio] ${line}`);
154
+ });
155
+ studioProcess.on('exit', (code) => {
156
+ console.log(`[postforme] Remotion Studio exited with code ${code}`);
157
+ studioProcess = null;
158
+ });
159
+ res.writeHead(200, { 'Content-Type': 'application/json' });
160
+ res.end(JSON.stringify({ ok: true, message: 'Remotion Studio starting', port: STUDIO_PORT }));
161
+ }
162
+ function stopStudio(res) {
163
+ if (!studioProcess || studioProcess.exitCode !== null) {
164
+ try {
165
+ execSync(`lsof -ti:${STUDIO_PORT} | xargs kill -9 2>/dev/null || true`);
166
+ }
167
+ catch { /* ignore */ }
168
+ studioProcess = null;
169
+ res.writeHead(200, { 'Content-Type': 'application/json' });
170
+ res.end(JSON.stringify({ ok: true, message: 'Remotion Studio stopped' }));
171
+ return;
172
+ }
173
+ console.log('[postforme] Stopping Remotion Studio...');
174
+ studioProcess.kill('SIGTERM');
175
+ studioProcess = null;
176
+ res.writeHead(200, { 'Content-Type': 'application/json' });
177
+ res.end(JSON.stringify({ ok: true, message: 'Remotion Studio stopped' }));
178
+ }
179
+ // --- HTTP handler ---
180
+ async function handleHttp(req, res) {
181
+ const origin = req.headers.origin || '';
83
182
  const allowed = [
84
183
  'http://localhost:5100',
85
184
  'http://localhost:3000',
@@ -88,16 +187,41 @@ function handleHttp(_req, res) {
88
187
  if (allowed.includes(origin)) {
89
188
  res.setHeader('Access-Control-Allow-Origin', origin);
90
189
  }
91
- res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
92
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
93
- if (_req.method === 'OPTIONS') {
190
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
191
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
192
+ if (req.method === 'OPTIONS') {
94
193
  res.writeHead(204);
95
194
  res.end();
96
195
  return;
97
196
  }
98
- if (_req.url === '/status') {
197
+ // GET /status — full service status (matches LocalServicesGuard expectations)
198
+ if (req.method === 'GET' && req.url === '/status') {
199
+ const studioRunning = await isStudioRunning();
200
+ // Clean up dead studio process reference
201
+ if (studioProcess && studioProcess.exitCode !== null) {
202
+ studioProcess = null;
203
+ }
99
204
  res.writeHead(200, { 'Content-Type': 'application/json' });
100
- res.end(JSON.stringify({ ok: true, claudeCode: state.process !== null }));
205
+ res.end(JSON.stringify({
206
+ terminalServer: true,
207
+ claudeCode: state.process !== null,
208
+ remotionStudio: studioRunning,
209
+ renderProject: RENDER_PROJECT || null,
210
+ ports: {
211
+ terminalServer: PORT,
212
+ remotionStudio: STUDIO_PORT,
213
+ },
214
+ }));
215
+ return;
216
+ }
217
+ // POST /services/studio/start
218
+ if (req.method === 'POST' && req.url === '/services/studio/start') {
219
+ startStudio(res);
220
+ return;
221
+ }
222
+ // POST /services/studio/stop
223
+ if (req.method === 'POST' && req.url === '/services/studio/stop') {
224
+ stopStudio(res);
101
225
  return;
102
226
  }
103
227
  res.writeHead(200, { 'Content-Type': 'text/plain' });
@@ -154,6 +278,12 @@ httpServer.listen(PORT, () => {
154
278
  console.log('');
155
279
  console.log(' PostForMe Terminal Server running');
156
280
  console.log(` → ws://localhost:${PORT}`);
281
+ if (RENDER_PROJECT) {
282
+ console.log(` → Remotion render project: ${RENDER_PROJECT}`);
283
+ }
284
+ else {
285
+ console.log(' → Remotion: not found (set POSTFORME_RENDER_PATH or place postforme-render nearby)');
286
+ }
157
287
  console.log('');
158
288
  console.log(' Open postforme.ca and click the Terminal button.');
159
289
  console.log(' Press Ctrl+C to stop.');
@@ -166,6 +296,10 @@ function shutdown(signal) {
166
296
  state.process.kill();
167
297
  state.process = null;
168
298
  }
299
+ if (studioProcess && studioProcess.exitCode === null) {
300
+ studioProcess.kill('SIGTERM');
301
+ studioProcess = null;
302
+ }
169
303
  for (const client of state.clients) {
170
304
  client.close();
171
305
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postforme-terminal",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "PostForMe terminal server — connects your browser to Claude Code",
5
5
  "type": "module",
6
6
  "bin": {