nstantpage-agent 0.3.3 → 0.3.5

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.
@@ -19,6 +19,7 @@ import chalk from 'chalk';
19
19
  import path from 'path';
20
20
  import fs from 'fs';
21
21
  import os from 'os';
22
+ import { execSync } from 'child_process';
22
23
  import { getConfig } from '../config.js';
23
24
  import { TunnelClient } from '../tunnel.js';
24
25
  import { LocalServer } from '../localServer.js';
@@ -97,6 +98,54 @@ async function fetchProjectFiles(backendUrl, projectId, projectDir, token) {
97
98
  console.log(chalk.green(` ✓ ${written} files downloaded (version ${data.version})`));
98
99
  return { fileCount: written, isNew: true };
99
100
  }
101
+ /**
102
+ * Kill any process listening on the given port.
103
+ */
104
+ function killPort(port) {
105
+ try {
106
+ // Works on macOS and Linux
107
+ const pids = execSync(`lsof -ti :${port} 2>/dev/null`, { encoding: 'utf-8' }).trim();
108
+ if (pids) {
109
+ for (const pid of pids.split('\n')) {
110
+ const p = parseInt(pid.trim(), 10);
111
+ if (p && p !== process.pid) {
112
+ try {
113
+ process.kill(p, 'SIGTERM');
114
+ }
115
+ catch { }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ catch {
121
+ // lsof not available or no process found — that's fine
122
+ try {
123
+ // Fallback for systems without lsof
124
+ execSync(`fuser -k ${port}/tcp 2>/dev/null`, { encoding: 'utf-8' });
125
+ }
126
+ catch { }
127
+ }
128
+ }
129
+ /**
130
+ * Clean up any previous agent instance (kill old PID + free ports).
131
+ */
132
+ function cleanupPreviousAgent(conf, apiPort, devPort) {
133
+ // 1. Try to kill the previously stored agent PID
134
+ const oldPid = conf.get('agentPid');
135
+ if (oldPid && oldPid !== process.pid) {
136
+ try {
137
+ process.kill(oldPid, 'SIGTERM');
138
+ console.log(chalk.gray(` Stopped previous agent (PID ${oldPid})`));
139
+ }
140
+ catch {
141
+ // Already dead
142
+ }
143
+ conf.delete('agentPid');
144
+ }
145
+ // 2. Free the ports in case orphaned processes are still holding them
146
+ killPort(apiPort);
147
+ killPort(devPort);
148
+ }
100
149
  export async function startCommand(directory, options) {
101
150
  const conf = getConfig();
102
151
  // If --token was passed, persist it
@@ -134,6 +183,10 @@ export async function startCommand(directory, options) {
134
183
  // Save project ID
135
184
  conf.set('projectId', projectId);
136
185
  const backendUrl = resolveBackendUrl(options);
186
+ // Kill any leftover agent / free ports from a previous run
187
+ cleanupPreviousAgent(conf, apiPort, devPort);
188
+ // Small delay to let ports release
189
+ await new Promise(r => setTimeout(r, 300));
137
190
  console.log(chalk.blue(`\n🚀 nstantpage agent v${VERSION}\n`));
138
191
  console.log(chalk.gray(` Project ID: ${projectId}`));
139
192
  console.log(chalk.gray(` Directory: ${projectDir}`));
@@ -290,6 +290,8 @@ export class LocalServer {
290
290
  async handleContainerStatus(_req, res, _body, url) {
291
291
  this.json(res, {
292
292
  running: this.devServer.isRunning,
293
+ status: this.devServer.isRunning ? 'running' : 'stopped',
294
+ projectId: this.options.projectId,
293
295
  mode: 'agent',
294
296
  agentMode: true,
295
297
  hostname: os.hostname(),
@@ -298,32 +300,45 @@ export class LocalServer {
298
300
  }
299
301
  // ─── /live/container-stats ───────────────────────────────────
300
302
  async handleContainerStats(_req, res) {
301
- const stats = this.devServer.getStats();
302
- const totalMem = os.totalmem() / (1024 * 1024);
303
- const freeMem = os.freemem() / (1024 * 1024);
303
+ const devStats = this.devServer.getStats();
304
+ const totalMemMb = Math.round(os.totalmem() / (1024 * 1024));
305
+ const freeMemMb = Math.round(os.freemem() / (1024 * 1024));
306
+ const usedMemMb = totalMemMb - freeMemMb;
307
+ const memPercent = totalMemMb > 0 ? Math.round((usedMemMb / totalMemMb) * 100) : 0;
308
+ // Return in the same format as the container stats (ContainerStatsSnapshot)
309
+ // so the frontend terminal panel can display it unchanged.
304
310
  this.json(res, {
305
311
  success: true,
312
+ running: this.devServer.isRunning,
306
313
  agentMode: true,
307
- cpu: { percent: stats.cpuPercent, cores: os.cpus().length },
308
- memory: {
309
- usedMb: stats.memoryMb,
310
- totalMb: Math.round(totalMem),
311
- freeMb: Math.round(freeMem),
312
- percent: totalMem > 0 ? Math.round((stats.memoryMb / totalMem) * 100) : 0,
314
+ stats: {
315
+ cpuPercent: devStats.cpuPercent,
316
+ memoryUsageBytes: usedMemMb * 1024 * 1024,
317
+ memoryLimitBytes: totalMemMb * 1024 * 1024,
318
+ memoryUsageMb: usedMemMb,
319
+ memoryLimitMb: totalMemMb,
320
+ memoryPercent: memPercent,
321
+ diskUsageMb: null,
322
+ diskUsageBytes: null,
323
+ },
324
+ agentInfo: {
325
+ hostname: os.hostname(),
326
+ platform: `${os.platform()} ${os.arch()}`,
327
+ cpuCores: os.cpus().length,
328
+ pid: devStats.pid,
329
+ uptime: this.devServer.uptime,
313
330
  },
314
- pid: stats.pid,
315
- uptime: this.devServer.uptime,
316
331
  });
317
332
  }
318
333
  // ─── /live/logs ──────────────────────────────────────────────
319
334
  async handleLogs(_req, res, _body, url) {
320
- const limit = parseInt(url.searchParams.get('limit') || '100', 10);
335
+ const limit = parseInt(url.searchParams.get('limit') || url.searchParams.get('tail') || '100', 10);
321
336
  const logs = this.devServer.getLogs(limit);
322
337
  this.json(res, {
323
338
  success: true,
324
339
  logs: logs.map(l => ({
325
340
  timestamp: new Date(l.timestamp).toISOString(),
326
- type: l.type,
341
+ stream: l.type, // 'stdout' | 'stderr' — matches what the terminal panel expects
327
342
  message: l.message,
328
343
  source: 'frontend',
329
344
  })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nstantpage-agent",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Local development agent for nstantpage.com — run your projects locally, preview in the cloud. Replaces cloud containers for faster builds.",
5
5
  "type": "module",
6
6
  "bin": {