dev3000 0.0.49 → 0.0.51

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 (36) hide show
  1. package/README.md +55 -6
  2. package/dist/cdp-monitor.d.ts.map +1 -1
  3. package/dist/cdp-monitor.js +54 -48
  4. package/dist/cdp-monitor.js.map +1 -1
  5. package/dist/cli.js +39 -33
  6. package/dist/cli.js.map +1 -1
  7. package/dist/dev-environment.d.ts +2 -0
  8. package/dist/dev-environment.d.ts.map +1 -1
  9. package/dist/dev-environment.js +212 -181
  10. package/dist/dev-environment.js.map +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/mcp-server/app/api/config/route.ts +7 -7
  16. package/mcp-server/app/api/logs/append/route.ts +59 -51
  17. package/mcp-server/app/api/logs/head/route.ts +22 -22
  18. package/mcp-server/app/api/logs/list/route.ts +39 -42
  19. package/mcp-server/app/api/logs/rotate/route.ts +28 -38
  20. package/mcp-server/app/api/logs/stream/route.ts +35 -35
  21. package/mcp-server/app/api/logs/tail/route.ts +22 -22
  22. package/mcp-server/app/api/mcp/[transport]/route.ts +189 -188
  23. package/mcp-server/app/api/replay/route.ts +217 -202
  24. package/mcp-server/app/layout.tsx +9 -8
  25. package/mcp-server/app/logs/LogsClient.test.ts +123 -99
  26. package/mcp-server/app/logs/LogsClient.tsx +724 -562
  27. package/mcp-server/app/logs/page.tsx +71 -72
  28. package/mcp-server/app/logs/utils.ts +99 -28
  29. package/mcp-server/app/page.tsx +10 -14
  30. package/mcp-server/app/replay/ReplayClient.tsx +120 -119
  31. package/mcp-server/app/replay/page.tsx +3 -3
  32. package/mcp-server/next.config.ts +2 -0
  33. package/mcp-server/package.json +5 -2
  34. package/mcp-server/pnpm-lock.yaml +37 -5
  35. package/mcp-server/tsconfig.json +4 -17
  36. package/package.json +16 -13
@@ -1,11 +1,11 @@
1
- import { spawn } from 'child_process';
2
- import { writeFileSync, appendFileSync, mkdirSync, existsSync, copyFileSync, readFileSync, cpSync, lstatSync, symlinkSync, unlinkSync, readdirSync, statSync } from 'fs';
3
- import { join, dirname, basename } from 'path';
4
- import { fileURLToPath } from 'url';
5
- import { tmpdir } from 'os';
6
- import chalk from 'chalk';
7
- import ora from 'ora';
8
- import { CDPMonitor } from './cdp-monitor.js';
1
+ import chalk from "chalk";
2
+ import { spawn } from "child_process";
3
+ import { appendFileSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, statSync, symlinkSync, unlinkSync, writeFileSync, } from "fs";
4
+ import ora from "ora";
5
+ import { tmpdir } from "os";
6
+ import { basename, dirname, join } from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { CDPMonitor } from "./cdp-monitor.js";
9
9
  class Logger {
10
10
  logFile;
11
11
  constructor(logFile) {
@@ -16,7 +16,7 @@ class Logger {
16
16
  mkdirSync(logDir, { recursive: true });
17
17
  }
18
18
  // Clear log file
19
- writeFileSync(this.logFile, '');
19
+ writeFileSync(this.logFile, "");
20
20
  }
21
21
  log(source, message) {
22
22
  const timestamp = new Date().toISOString();
@@ -25,25 +25,25 @@ class Logger {
25
25
  }
26
26
  }
27
27
  function detectPackageManagerForRun() {
28
- if (existsSync('pnpm-lock.yaml'))
29
- return 'pnpm';
30
- if (existsSync('yarn.lock'))
31
- return 'yarn';
32
- if (existsSync('package-lock.json'))
33
- return 'npm';
34
- return 'npm'; // fallback
28
+ if (existsSync("pnpm-lock.yaml"))
29
+ return "pnpm";
30
+ if (existsSync("yarn.lock"))
31
+ return "yarn";
32
+ if (existsSync("package-lock.json"))
33
+ return "npm";
34
+ return "npm"; // fallback
35
35
  }
36
36
  export function createPersistentLogFile() {
37
37
  // Create /var/log/dev3000 directory
38
- const logBaseDir = '/var/log/dev3000';
38
+ const logBaseDir = "/var/log/dev3000";
39
39
  try {
40
40
  if (!existsSync(logBaseDir)) {
41
41
  mkdirSync(logBaseDir, { recursive: true });
42
42
  }
43
43
  }
44
- catch (error) {
44
+ catch (_error) {
45
45
  // Fallback to user's temp directory if /var/log is not writable
46
- const fallbackDir = join(tmpdir(), 'dev3000-logs');
46
+ const fallbackDir = join(tmpdir(), "dev3000-logs");
47
47
  if (!existsSync(fallbackDir)) {
48
48
  mkdirSync(fallbackDir, { recursive: true });
49
49
  }
@@ -53,26 +53,28 @@ export function createPersistentLogFile() {
53
53
  }
54
54
  function createLogFileInDir(baseDir) {
55
55
  // Get current working directory name
56
- const cwdName = basename(process.cwd()).replace(/[^a-zA-Z0-9-_]/g, '_');
56
+ const cwdName = basename(process.cwd()).replace(/[^a-zA-Z0-9-_]/g, "_");
57
57
  // Create timestamp
58
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
58
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
59
59
  // Create log file path
60
60
  const logFileName = `dev3000-${cwdName}-${timestamp}.log`;
61
61
  const logFilePath = join(baseDir, logFileName);
62
62
  // Prune old logs for this project (keep only 10 most recent)
63
63
  pruneOldLogs(baseDir, cwdName);
64
64
  // Create the log file
65
- writeFileSync(logFilePath, '');
66
- // Create or update symlink to /tmp/dev3000.log
67
- const symlinkPath = '/tmp/dev3000.log';
68
- try {
69
- if (existsSync(symlinkPath)) {
70
- unlinkSync(symlinkPath);
65
+ writeFileSync(logFilePath, "");
66
+ // Create or update symlinks to /tmp/dev3000.log and /tmp/d3k.log
67
+ const symlinkPaths = ["/tmp/dev3000.log", "/tmp/d3k.log"];
68
+ for (const symlinkPath of symlinkPaths) {
69
+ try {
70
+ if (existsSync(symlinkPath)) {
71
+ unlinkSync(symlinkPath);
72
+ }
73
+ symlinkSync(logFilePath, symlinkPath);
74
+ }
75
+ catch (error) {
76
+ console.warn(chalk.yellow(`āš ļø Could not create symlink ${symlinkPath}: ${error}`));
71
77
  }
72
- symlinkSync(logFilePath, symlinkPath);
73
- }
74
- catch (error) {
75
- console.warn(chalk.yellow(`āš ļø Could not create symlink ${symlinkPath}: ${error}`));
76
78
  }
77
79
  return logFilePath;
78
80
  }
@@ -80,11 +82,11 @@ function pruneOldLogs(baseDir, cwdName) {
80
82
  try {
81
83
  // Find all log files for this project
82
84
  const files = readdirSync(baseDir)
83
- .filter(file => file.startsWith(`dev3000-${cwdName}-`) && file.endsWith('.log'))
84
- .map(file => ({
85
+ .filter((file) => file.startsWith(`dev3000-${cwdName}-`) && file.endsWith(".log"))
86
+ .map((file) => ({
85
87
  name: file,
86
88
  path: join(baseDir, file),
87
- mtime: statSync(join(baseDir, file)).mtime
89
+ mtime: statSync(join(baseDir, file)).mtime,
88
90
  }))
89
91
  .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); // Most recent first
90
92
  // Keep only the 10 most recent, delete the rest
@@ -94,7 +96,7 @@ function pruneOldLogs(baseDir, cwdName) {
94
96
  try {
95
97
  unlinkSync(file.path);
96
98
  }
97
- catch (error) {
99
+ catch (_error) {
98
100
  // Silently ignore deletion errors
99
101
  }
100
102
  }
@@ -124,35 +126,36 @@ export class DevEnvironment {
124
126
  const packageRoot = dirname(dirname(currentFile));
125
127
  // Always use MCP server's public directory for screenshots to ensure they're web-accessible
126
128
  // and avoid permission issues with /var/log paths
127
- this.screenshotDir = join(packageRoot, 'mcp-server', 'public', 'screenshots');
128
- this.pidFile = join(tmpdir(), 'dev3000.pid');
129
- this.mcpPublicDir = join(packageRoot, 'mcp-server', 'public', 'screenshots');
129
+ this.screenshotDir = join(packageRoot, "mcp-server", "public", "screenshots");
130
+ this.pidFile = join(tmpdir(), "dev3000.pid");
131
+ this.mcpPublicDir = join(packageRoot, "mcp-server", "public", "screenshots");
130
132
  // Read version from package.json for startup message
131
- this.version = '0.0.0';
133
+ this.version = "0.0.0";
132
134
  try {
133
- const packageJsonPath = join(packageRoot, 'package.json');
134
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
135
+ const packageJsonPath = join(packageRoot, "package.json");
136
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
135
137
  this.version = packageJson.version;
136
138
  // Use git to detect if we're in the dev3000 source repository
137
139
  try {
138
- const { execSync } = require('child_process');
139
- const gitRemote = execSync('git remote get-url origin 2>/dev/null', {
140
+ const { execSync } = require("child_process");
141
+ const gitRemote = execSync("git remote get-url origin 2>/dev/null", {
140
142
  cwd: packageRoot,
141
- encoding: 'utf8'
143
+ encoding: "utf8",
142
144
  }).trim();
143
- if (gitRemote.includes('vercel-labs/dev3000') && !this.version.includes('canary')) {
144
- this.version += '-local';
145
+ if (gitRemote.includes("vercel-labs/dev3000") &&
146
+ !this.version.includes("canary")) {
147
+ this.version += "-local";
145
148
  }
146
149
  }
147
150
  catch {
148
151
  // Not in git repo or no git - use version as-is
149
152
  }
150
153
  }
151
- catch (error) {
154
+ catch (_error) {
152
155
  // Use fallback version
153
156
  }
154
157
  // Initialize spinner for clean output management
155
- this.spinner = ora({ text: 'Initializing...', spinner: 'dots' });
158
+ this.spinner = ora({ text: "Initializing...", spinner: "dots" });
156
159
  // Ensure directories exist
157
160
  if (!existsSync(this.screenshotDir)) {
158
161
  mkdirSync(this.screenshotDir, { recursive: true });
@@ -166,15 +169,16 @@ export class DevEnvironment {
166
169
  for (const port of ports) {
167
170
  try {
168
171
  const result = await new Promise((resolve) => {
169
- const proc = spawn('lsof', ['-ti', `:${port}`], { stdio: 'pipe' });
170
- let output = '';
171
- proc.stdout?.on('data', (data) => output += data.toString());
172
- proc.on('exit', () => resolve(output.trim()));
172
+ const proc = spawn("lsof", ["-ti", `:${port}`], { stdio: "pipe" });
173
+ let output = "";
174
+ // biome-ignore lint/suspicious/noAssignInExpressions: whatever
175
+ proc.stdout?.on("data", (data) => (output += data.toString()));
176
+ proc.on("exit", () => resolve(output.trim()));
173
177
  });
174
178
  if (result) {
175
- result.split('\n').filter(line => line.trim());
179
+ result.split("\n").filter((line) => line.trim());
176
180
  // Stop spinner and show error
177
- if (this.spinner && this.spinner.isSpinning) {
181
+ if (this.spinner?.isSpinning) {
178
182
  this.spinner.fail(`Port ${port} is already in use`);
179
183
  }
180
184
  console.log(chalk.yellow(`šŸ’” To free up port ${port}, run: lsof -ti:${port} | xargs kill -9`));
@@ -182,7 +186,7 @@ export class DevEnvironment {
182
186
  }
183
187
  }
184
188
  catch (error) {
185
- if (error instanceof Error && error.message.includes('Port')) {
189
+ if (error instanceof Error && error.message.includes("Port")) {
186
190
  throw error; // Re-throw our custom error
187
191
  }
188
192
  // Ignore other errors - port might just be free
@@ -191,77 +195,86 @@ export class DevEnvironment {
191
195
  }
192
196
  async start() {
193
197
  // Show startup message first
194
- console.log(chalk.blue(`Starting dev3000 (v${this.version})`));
198
+ console.log(chalk.greenBright(`Starting ${this.options.commandName} (v${this.version})`));
195
199
  // Start spinner
196
- this.spinner.start('Checking ports...');
200
+ this.spinner.start("Checking ports...");
197
201
  // Check if ports are available first
198
202
  await this.checkPortsAvailable();
199
- this.spinner.text = 'Setting up environment...';
203
+ this.spinner.text = "Setting up environment...";
200
204
  // Write our process group ID to PID file for cleanup
201
205
  writeFileSync(this.pidFile, process.pid.toString());
202
206
  // Setup cleanup handlers
203
207
  this.setupCleanupHandlers();
204
208
  // Start user's dev server
205
- this.spinner.text = 'Starting your dev server...';
209
+ this.spinner.text = "Starting your dev server...";
206
210
  await this.startServer();
207
211
  // Start MCP server
208
- this.spinner.text = 'Starting dev3000 services...';
212
+ this.spinner.text = `Starting ${this.options.commandName} services...`;
209
213
  await this.startMcpServer();
210
214
  // Wait for servers to be ready
211
- this.spinner.text = 'Waiting for your app server...';
215
+ this.spinner.text = "Waiting for your app server...";
212
216
  await this.waitForServer();
213
- this.spinner.text = 'Waiting for dev3000 services...';
217
+ this.spinner.text = `Waiting for ${this.options.commandName} services...`;
214
218
  await this.waitForMcpServer();
215
- // Start CDP monitoring but don't wait for full setup
216
- this.spinner.text = 'Launching browser monitor...';
217
- this.startCDPMonitoringAsync();
219
+ // Start CDP monitoring if not in servers-only mode
220
+ if (!this.options.serversOnly) {
221
+ this.spinner.text = "Launching browser monitor...";
222
+ this.startCDPMonitoringAsync();
223
+ }
224
+ else {
225
+ this.debugLog("Browser monitoring disabled via --servers-only flag");
226
+ }
218
227
  // Complete startup
219
- this.spinner.succeed('Development environment ready!');
220
- console.log(chalk.blue(`Logs: ${this.options.logFile}`));
221
- console.log(chalk.blue(`Logs symlink: /tmp/dev3000.log`));
222
- console.log(chalk.yellow('ā˜ļø Give this to an AI to auto debug and fix your app\n'));
223
- console.log(chalk.blue(`🌐 Your App: http://localhost:${this.options.port}`));
224
- console.log(chalk.blue(`šŸ¤– MCP Server: http://localhost:${this.options.mcpPort}/api/mcp/mcp`));
225
- console.log(chalk.magenta(`šŸ“ø Visual Timeline: http://localhost:${this.options.mcpPort}/logs`));
226
- console.log(chalk.gray('\nšŸ’” To stop all servers and kill dev3000: Ctrl-C'));
228
+ this.spinner.succeed("Development environment ready!");
229
+ console.log(chalk.cyan(`Logs: /tmp/d3k.log -> ${this.options.logFile}`));
230
+ console.log(chalk.cyan("ā˜ļø Give this to an AI to auto debug and fix your app\n"));
231
+ console.log(chalk.cyan(`🌐 Your App: http://localhost:${this.options.port}`));
232
+ console.log(chalk.cyan(`šŸ¤– MCP Server: http://localhost:${this.options.mcpPort}/api/mcp/mcp`));
233
+ console.log(chalk.cyan(`šŸ“ø Visual Timeline: http://localhost:${this.options.mcpPort}/logs`));
234
+ if (this.options.serversOnly) {
235
+ console.log(chalk.cyan("šŸ–„ļø Servers-only mode - use Chrome extension for browser monitoring"));
236
+ }
237
+ console.log(chalk.gray(`\nšŸ’” To stop all servers and kill ${this.options.commandName}: Ctrl-C`));
227
238
  }
228
239
  async startServer() {
229
- const [command, ...args] = this.options.serverCommand.split(' ');
240
+ const [command, ...args] = this.options.serverCommand.split(" ");
230
241
  this.serverProcess = spawn(command, args, {
231
- stdio: ['ignore', 'pipe', 'pipe'],
242
+ stdio: ["ignore", "pipe", "pipe"],
232
243
  shell: true,
233
244
  detached: true, // Run independently
234
245
  });
235
246
  // Log server output (to file only, reduce stdout noise)
236
- this.serverProcess.stdout?.on('data', (data) => {
247
+ this.serverProcess.stdout?.on("data", (data) => {
237
248
  const message = data.toString().trim();
238
249
  if (message) {
239
- this.logger.log('server', message);
250
+ this.logger.log("server", message);
240
251
  }
241
252
  });
242
- this.serverProcess.stderr?.on('data', (data) => {
253
+ this.serverProcess.stderr?.on("data", (data) => {
243
254
  const message = data.toString().trim();
244
255
  if (message) {
245
- this.logger.log('server', `ERROR: ${message}`);
256
+ this.logger.log("server", `ERROR: ${message}`);
246
257
  // Suppress build errors and common dev errors from console output
247
258
  // They're still logged to file for debugging
248
259
  // Only show truly critical errors that would prevent startup
249
- const isCriticalError = message.includes('EADDRINUSE') ||
250
- message.includes('EACCES') ||
251
- message.includes('ENOENT') ||
252
- (message.includes('FATAL') && !message.includes('generateStaticParams')) ||
253
- (message.includes('Cannot find module') && !message.includes('.next'));
260
+ const isCriticalError = message.includes("EADDRINUSE") ||
261
+ message.includes("EACCES") ||
262
+ message.includes("ENOENT") ||
263
+ (message.includes("FATAL") &&
264
+ !message.includes("generateStaticParams")) ||
265
+ (message.includes("Cannot find module") &&
266
+ !message.includes(".next"));
254
267
  if (isCriticalError) {
255
- console.error(chalk.red('[CRITICAL ERROR]'), message);
268
+ console.error(chalk.red("[CRITICAL ERROR]"), message);
256
269
  }
257
270
  }
258
271
  });
259
- this.serverProcess.on('exit', (code) => {
272
+ this.serverProcess.on("exit", (code) => {
260
273
  if (this.isShuttingDown)
261
274
  return; // Don't handle exits during shutdown
262
275
  if (code !== 0 && code !== null) {
263
276
  this.debugLog(`Server process exited with code ${code}`);
264
- this.logger.log('server', `Server process exited with code ${code}`);
277
+ this.logger.log("server", `Server process exited with code ${code}`);
265
278
  // Only shutdown for truly fatal exit codes, not build failures or restarts
266
279
  // Common exit codes that indicate temporary issues, not fatal errors:
267
280
  // - Code 1: Generic build failure or restart
@@ -270,19 +283,19 @@ export class DevEnvironment {
270
283
  const isFatalExit = code !== 1 && code !== 130 && code !== 143;
271
284
  if (isFatalExit) {
272
285
  // Stop spinner and show error for fatal exits only
273
- if (this.spinner && this.spinner.isSpinning) {
286
+ if (this.spinner?.isSpinning) {
274
287
  this.spinner.fail(`Server process fatally exited with code ${code}`);
275
288
  }
276
289
  else {
277
290
  console.log(chalk.red(`\nāŒ Server process fatally exited with code ${code}`));
278
291
  }
279
- console.log(chalk.yellow('šŸ’” Check your server command and logs for details'));
292
+ console.log(chalk.yellow("šŸ’” Check your server command and logs for details"));
280
293
  this.gracefulShutdown();
281
294
  }
282
295
  else {
283
296
  // For non-fatal exits (like build failures), just log and continue
284
- if (this.spinner && this.spinner.isSpinning) {
285
- this.spinner.text = 'Server process restarted, waiting...';
297
+ if (this.spinner?.isSpinning) {
298
+ this.spinner.text = "Server process restarted, waiting...";
286
299
  }
287
300
  }
288
301
  }
@@ -290,7 +303,7 @@ export class DevEnvironment {
290
303
  }
291
304
  debugLog(message) {
292
305
  if (this.options.debug) {
293
- if (this.spinner && this.spinner.isSpinning) {
306
+ if (this.spinner?.isSpinning) {
294
307
  // Temporarily stop the spinner, show debug message, then restart
295
308
  const currentText = this.spinner.text;
296
309
  this.spinner.stop();
@@ -303,40 +316,46 @@ export class DevEnvironment {
303
316
  }
304
317
  }
305
318
  async startMcpServer() {
306
- this.debugLog('Starting MCP server setup');
319
+ this.debugLog("Starting MCP server setup");
307
320
  // Get the path to our bundled MCP server
308
321
  const currentFile = fileURLToPath(import.meta.url);
309
322
  const packageRoot = dirname(dirname(currentFile)); // Go up from dist/ to package root
310
- const mcpServerPath = join(packageRoot, 'mcp-server');
323
+ const mcpServerPath = join(packageRoot, "mcp-server");
311
324
  this.debugLog(`MCP server path: ${mcpServerPath}`);
312
325
  if (!existsSync(mcpServerPath)) {
313
326
  throw new Error(`MCP server directory not found at ${mcpServerPath}`);
314
327
  }
315
- this.debugLog('MCP server directory found');
328
+ this.debugLog("MCP server directory found");
316
329
  // Check if MCP server dependencies are installed, install if missing
317
- const isGlobalInstall = mcpServerPath.includes('.pnpm');
330
+ const isGlobalInstall = mcpServerPath.includes(".pnpm");
318
331
  this.debugLog(`Is global install: ${isGlobalInstall}`);
319
- let nodeModulesPath = join(mcpServerPath, 'node_modules');
332
+ let nodeModulesPath = join(mcpServerPath, "node_modules");
320
333
  let actualWorkingDir = mcpServerPath;
321
334
  this.debugLog(`Node modules path: ${nodeModulesPath}`);
322
335
  if (isGlobalInstall) {
323
- const tmpDirPath = join(tmpdir(), 'dev3000-mcp-deps');
324
- nodeModulesPath = join(tmpDirPath, 'node_modules');
336
+ const tmpDirPath = join(tmpdir(), "dev3000-mcp-deps");
337
+ nodeModulesPath = join(tmpDirPath, "node_modules");
325
338
  actualWorkingDir = tmpDirPath;
326
339
  // Update screenshot and MCP public directory to use the temp directory for global installs
327
- this.screenshotDir = join(actualWorkingDir, 'public', 'screenshots');
328
- this.mcpPublicDir = join(actualWorkingDir, 'public', 'screenshots');
340
+ this.screenshotDir = join(actualWorkingDir, "public", "screenshots");
341
+ this.mcpPublicDir = join(actualWorkingDir, "public", "screenshots");
329
342
  if (!existsSync(this.mcpPublicDir)) {
330
343
  mkdirSync(this.mcpPublicDir, { recursive: true });
331
344
  }
332
345
  }
333
346
  // Always install dependencies to ensure they're up to date
334
- this.debugLog('Installing/updating MCP server dependencies');
347
+ this.debugLog("Installing/updating MCP server dependencies");
335
348
  await this.installMcpServerDeps(mcpServerPath);
336
349
  // Use version already read in constructor
337
350
  // For global installs, ensure all necessary files are copied to temp directory
338
351
  if (isGlobalInstall && actualWorkingDir !== mcpServerPath) {
339
- const requiredFiles = ['app', 'public', 'next.config.ts', 'next-env.d.ts', 'tsconfig.json'];
352
+ const requiredFiles = [
353
+ "app",
354
+ "public",
355
+ "next.config.ts",
356
+ "next-env.d.ts",
357
+ "tsconfig.json",
358
+ ];
340
359
  for (const file of requiredFiles) {
341
360
  const srcPath = join(mcpServerPath, file);
342
361
  const destPath = join(actualWorkingDir, file);
@@ -353,7 +372,7 @@ export class DevEnvironment {
353
372
  // Remove existing destination if it exists
354
373
  if (existsSync(destPath)) {
355
374
  if (lstatSync(destPath).isDirectory()) {
356
- cpSync(destPath, destPath + '.bak', { recursive: true });
375
+ cpSync(destPath, `${destPath}.bak`, { recursive: true });
357
376
  cpSync(srcPath, destPath, { recursive: true, force: true });
358
377
  }
359
378
  else {
@@ -378,8 +397,8 @@ export class DevEnvironment {
378
397
  this.debugLog(`Using package manager: ${packageManagerForRun}`);
379
398
  this.debugLog(`MCP server working directory: ${actualWorkingDir}`);
380
399
  this.debugLog(`MCP server port: ${this.options.mcpPort}`);
381
- this.mcpServerProcess = spawn(packageManagerForRun, ['run', 'dev'], {
382
- stdio: ['ignore', 'pipe', 'pipe'],
400
+ this.mcpServerProcess = spawn(packageManagerForRun, ["run", "dev"], {
401
+ stdio: ["ignore", "pipe", "pipe"],
383
402
  shell: true,
384
403
  detached: true, // Run independently
385
404
  cwd: actualWorkingDir,
@@ -390,36 +409,36 @@ export class DevEnvironment {
390
409
  DEV3000_VERSION: this.version, // Pass version to MCP server
391
410
  },
392
411
  });
393
- this.debugLog('MCP server process spawned');
412
+ this.debugLog("MCP server process spawned");
394
413
  // Log MCP server output to separate file for debugging
395
- const mcpLogFile = join(dirname(this.options.logFile), 'dev3000-mcp.log');
396
- writeFileSync(mcpLogFile, ''); // Clear the file
397
- this.mcpServerProcess.stdout?.on('data', (data) => {
414
+ const mcpLogFile = join(dirname(this.options.logFile), "dev3000-mcp.log");
415
+ writeFileSync(mcpLogFile, ""); // Clear the file
416
+ this.mcpServerProcess.stdout?.on("data", (data) => {
398
417
  const message = data.toString().trim();
399
418
  if (message) {
400
419
  const timestamp = new Date().toISOString();
401
420
  appendFileSync(mcpLogFile, `[${timestamp}] [MCP-STDOUT] ${message}\n`);
402
421
  }
403
422
  });
404
- this.mcpServerProcess.stderr?.on('data', (data) => {
423
+ this.mcpServerProcess.stderr?.on("data", (data) => {
405
424
  const message = data.toString().trim();
406
425
  if (message) {
407
426
  const timestamp = new Date().toISOString();
408
427
  appendFileSync(mcpLogFile, `[${timestamp}] [MCP-STDERR] ${message}\n`);
409
428
  // Only show critical errors in stdout for debugging
410
- if (message.includes('FATAL') || message.includes('Error:')) {
411
- console.error(chalk.red('[LOG VIEWER ERROR]'), message);
429
+ if (message.includes("FATAL") || message.includes("Error:")) {
430
+ console.error(chalk.red("[LOG VIEWER ERROR]"), message);
412
431
  }
413
432
  }
414
433
  });
415
- this.mcpServerProcess.on('exit', (code) => {
434
+ this.mcpServerProcess.on("exit", (code) => {
416
435
  this.debugLog(`MCP server process exited with code ${code}`);
417
436
  // Only show exit messages for unexpected failures, not restarts
418
437
  if (code !== 0 && code !== null) {
419
- this.logger.log('server', `MCP server process exited with code ${code}`);
438
+ this.logger.log("server", `MCP server process exited with code ${code}`);
420
439
  }
421
440
  });
422
- this.debugLog('MCP server event handlers setup complete');
441
+ this.debugLog("MCP server event handlers setup complete");
423
442
  }
424
443
  async waitForServer() {
425
444
  const maxAttempts = 30;
@@ -427,18 +446,18 @@ export class DevEnvironment {
427
446
  while (attempts < maxAttempts) {
428
447
  try {
429
448
  const response = await fetch(`http://localhost:${this.options.port}`, {
430
- method: 'HEAD',
431
- signal: AbortSignal.timeout(2000)
449
+ method: "HEAD",
450
+ signal: AbortSignal.timeout(2000),
432
451
  });
433
452
  if (response.ok || response.status === 404) {
434
453
  return;
435
454
  }
436
455
  }
437
- catch (error) {
456
+ catch (_error) {
438
457
  // Server not ready yet, continue waiting
439
458
  }
440
459
  attempts++;
441
- await new Promise(resolve => setTimeout(resolve, 1000));
460
+ await new Promise((resolve) => setTimeout(resolve, 1000));
442
461
  }
443
462
  // Continue anyway if health check fails
444
463
  }
@@ -446,44 +465,46 @@ export class DevEnvironment {
446
465
  return new Promise((resolve, reject) => {
447
466
  // For global installs, we need to install to a writable location
448
467
  // Check if this is a global install by looking for .pnpm in the path
449
- const isGlobalInstall = mcpServerPath.includes('.pnpm');
468
+ const isGlobalInstall = mcpServerPath.includes(".pnpm");
450
469
  let workingDir = mcpServerPath;
451
470
  if (isGlobalInstall) {
452
471
  // Create a writable copy in temp directory for global installs
453
- const tmpDirPath = join(tmpdir(), 'dev3000-mcp-deps');
472
+ const tmpDirPath = join(tmpdir(), "dev3000-mcp-deps");
454
473
  // Ensure tmp directory exists
455
474
  if (!existsSync(tmpDirPath)) {
456
475
  mkdirSync(tmpDirPath, { recursive: true });
457
476
  }
458
- // Copy package.json to temp directory if it doesn't exist
459
- const tmpPackageJson = join(tmpDirPath, 'package.json');
460
- if (!existsSync(tmpPackageJson)) {
461
- const sourcePackageJson = join(mcpServerPath, 'package.json');
462
- copyFileSync(sourcePackageJson, tmpPackageJson);
463
- }
477
+ // Always copy package.json to temp directory to ensure it's up to date
478
+ const tmpPackageJson = join(tmpDirPath, "package.json");
479
+ const sourcePackageJson = join(mcpServerPath, "package.json");
480
+ copyFileSync(sourcePackageJson, tmpPackageJson);
464
481
  workingDir = tmpDirPath;
465
482
  }
466
483
  const packageManager = detectPackageManagerForRun();
467
484
  // Don't show any console output during dependency installation
468
485
  // All status will be handled by the progress bar
469
- const installProcess = spawn(packageManager, ['install'], {
470
- stdio: ['ignore', 'pipe', 'pipe'],
486
+ // For pnpm, use --dev flag to include devDependencies
487
+ const installArgs = packageManager === "pnpm"
488
+ ? ["install", "--prod=false"] // Install both prod and dev dependencies
489
+ : ["install", "--include=dev"]; // npm/yarn syntax
490
+ const installProcess = spawn(packageManager, installArgs, {
491
+ stdio: ["ignore", "pipe", "pipe"],
471
492
  shell: true,
472
493
  cwd: workingDir,
473
494
  });
474
495
  // Add timeout (3 minutes)
475
496
  const timeout = setTimeout(() => {
476
- installProcess.kill('SIGKILL');
477
- reject(new Error('MCP server dependency installation timed out after 3 minutes'));
497
+ installProcess.kill("SIGKILL");
498
+ reject(new Error("MCP server dependency installation timed out after 3 minutes"));
478
499
  }, 3 * 60 * 1000);
479
500
  // Suppress all output to prevent progress bar interference
480
- installProcess.stdout?.on('data', (data) => {
501
+ installProcess.stdout?.on("data", (_data) => {
481
502
  // Silently consume output
482
503
  });
483
- installProcess.stderr?.on('data', (data) => {
504
+ installProcess.stderr?.on("data", (_data) => {
484
505
  // Silently consume output
485
506
  });
486
- installProcess.on('exit', (code) => {
507
+ installProcess.on("exit", (code) => {
487
508
  clearTimeout(timeout);
488
509
  if (code === 0) {
489
510
  resolve();
@@ -492,7 +513,7 @@ export class DevEnvironment {
492
513
  reject(new Error(`MCP server dependency installation failed with exit code ${code}`));
493
514
  }
494
515
  });
495
- installProcess.on('error', (error) => {
516
+ installProcess.on("error", (error) => {
496
517
  clearTimeout(timeout);
497
518
  reject(new Error(`Failed to start MCP server dependency installation: ${error.message}`));
498
519
  });
@@ -505,15 +526,16 @@ export class DevEnvironment {
505
526
  try {
506
527
  // Test the actual MCP endpoint
507
528
  const response = await fetch(`http://localhost:${this.options.mcpPort}`, {
508
- method: 'HEAD',
509
- signal: AbortSignal.timeout(2000)
529
+ method: "HEAD",
530
+ signal: AbortSignal.timeout(2000),
510
531
  });
511
532
  this.debugLog(`MCP server health check: ${response.status}`);
512
533
  if (response.status === 500) {
513
534
  const errorText = await response.text();
514
535
  this.debugLog(`MCP server 500 error: ${errorText}`);
515
536
  }
516
- if (response.ok || response.status === 404) { // 404 is OK - means server is responding
537
+ if (response.ok || response.status === 404) {
538
+ // 404 is OK - means server is responding
517
539
  return;
518
540
  }
519
541
  }
@@ -521,39 +543,48 @@ export class DevEnvironment {
521
543
  this.debugLog(`MCP server not ready (attempt ${attempts}): ${error}`);
522
544
  }
523
545
  attempts++;
524
- await new Promise(resolve => setTimeout(resolve, 1000));
546
+ await new Promise((resolve) => setTimeout(resolve, 1000));
525
547
  }
526
- this.debugLog('MCP server health check failed, terminating');
548
+ this.debugLog("MCP server health check failed, terminating");
527
549
  throw new Error(`MCP server failed to start after ${maxAttempts} seconds. Check the logs for errors.`);
528
550
  }
529
551
  startCDPMonitoringAsync() {
552
+ // Skip if in servers-only mode
553
+ if (this.options.serversOnly) {
554
+ return;
555
+ }
530
556
  // Start CDP monitoring in background without blocking completion
531
- this.startCDPMonitoring().catch(error => {
532
- console.error(chalk.red('āš ļø CDP monitoring setup failed:'), error);
557
+ this.startCDPMonitoring().catch((error) => {
558
+ console.error(chalk.red("āš ļø CDP monitoring setup failed:"), error);
533
559
  // CDP monitoring is critical - shutdown if it fails
534
560
  this.gracefulShutdown();
535
561
  });
536
562
  }
537
563
  async startCDPMonitoring() {
564
+ // Skip if in servers-only mode
565
+ if (this.options.serversOnly) {
566
+ this.debugLog("Browser monitoring disabled via --servers-only flag");
567
+ return;
568
+ }
538
569
  // Ensure profile directory exists
539
570
  if (!existsSync(this.options.profileDir)) {
540
571
  mkdirSync(this.options.profileDir, { recursive: true });
541
572
  }
542
573
  // Initialize CDP monitor with enhanced logging - use MCP public directory for screenshots
543
- this.cdpMonitor = new CDPMonitor(this.options.profileDir, this.mcpPublicDir, (source, message) => {
544
- this.logger.log('browser', message);
574
+ this.cdpMonitor = new CDPMonitor(this.options.profileDir, this.mcpPublicDir, (_source, message) => {
575
+ this.logger.log("browser", message);
545
576
  }, this.options.debug);
546
577
  try {
547
578
  // Start CDP monitoring
548
579
  await this.cdpMonitor.start();
549
- this.logger.log('browser', '[CDP] Chrome launched with DevTools Protocol monitoring');
580
+ this.logger.log("browser", "[CDP] Chrome launched with DevTools Protocol monitoring");
550
581
  // Navigate to the app
551
582
  await this.cdpMonitor.navigateToApp(this.options.port);
552
- this.logger.log('browser', `[CDP] Navigated to http://localhost:${this.options.port}`);
583
+ this.logger.log("browser", `[CDP] Navigated to http://localhost:${this.options.port}`);
553
584
  }
554
585
  catch (error) {
555
586
  // Log error and throw to trigger graceful shutdown
556
- this.logger.log('browser', `[CDP ERROR] Failed to start CDP monitoring: ${error}`);
587
+ this.logger.log("browser", `[CDP ERROR] Failed to start CDP monitoring: ${error}`);
557
588
  throw error;
558
589
  }
559
590
  }
@@ -562,17 +593,17 @@ export class DevEnvironment {
562
593
  return; // Prevent multiple shutdown attempts
563
594
  this.isShuttingDown = true;
564
595
  // Stop spinner if it's running
565
- if (this.spinner && this.spinner.isSpinning) {
566
- this.spinner.fail('Critical failure detected');
596
+ if (this.spinner?.isSpinning) {
597
+ this.spinner.fail("Critical failure detected");
567
598
  }
568
- console.log(chalk.yellow('šŸ›‘ Shutting down dev3000 due to critical failure...'));
599
+ console.log(chalk.yellow(`šŸ›‘ Shutting down ${this.options.commandName} due to critical failure...`));
569
600
  // Kill processes on both ports
570
601
  const killPortProcess = async (port, name) => {
571
602
  try {
572
- const { spawn } = await import('child_process');
573
- const killProcess = spawn('sh', ['-c', `lsof -ti:${port} | xargs kill -9`], { stdio: 'inherit' });
603
+ const { spawn } = await import("child_process");
604
+ const killProcess = spawn("sh", ["-c", `lsof -ti:${port} | xargs kill -9`], { stdio: "inherit" });
574
605
  return new Promise((resolve) => {
575
- killProcess.on('exit', (code) => {
606
+ killProcess.on("exit", (code) => {
576
607
  if (code === 0) {
577
608
  console.log(chalk.green(`āœ… Killed ${name} on port ${port}`));
578
609
  }
@@ -580,48 +611,48 @@ export class DevEnvironment {
580
611
  });
581
612
  });
582
613
  }
583
- catch (error) {
614
+ catch (_error) {
584
615
  console.log(chalk.gray(`āš ļø Could not kill ${name} on port ${port}`));
585
616
  }
586
617
  };
587
618
  // Kill servers
588
- console.log(chalk.blue('šŸ”„ Killing servers...'));
619
+ console.log(chalk.cyan("šŸ”„ Killing servers..."));
589
620
  await Promise.all([
590
- killPortProcess(this.options.port, 'your app server'),
591
- killPortProcess(this.options.mcpPort, 'dev3000 MCP server')
621
+ killPortProcess(this.options.port, "your app server"),
622
+ killPortProcess(this.options.mcpPort, `${this.options.commandName} MCP server`),
592
623
  ]);
593
- // Shutdown CDP monitor
624
+ // Shutdown CDP monitor if it was started
594
625
  if (this.cdpMonitor) {
595
626
  try {
596
- console.log(chalk.blue('šŸ”„ Closing CDP monitor...'));
627
+ console.log(chalk.cyan("šŸ”„ Closing CDP monitor..."));
597
628
  await this.cdpMonitor.shutdown();
598
- console.log(chalk.green('āœ… CDP monitor closed'));
629
+ console.log(chalk.green("āœ… CDP monitor closed"));
599
630
  }
600
- catch (error) {
601
- console.log(chalk.gray('āš ļø CDP monitor shutdown failed'));
631
+ catch (_error) {
632
+ console.log(chalk.gray("āš ļø CDP monitor shutdown failed"));
602
633
  }
603
634
  }
604
- console.log(chalk.red('āŒ Dev3000 exited due to server failure'));
635
+ console.log(chalk.red(`āŒ ${this.options.commandName} exited due to server failure`));
605
636
  process.exit(1);
606
637
  }
607
638
  setupCleanupHandlers() {
608
639
  // Handle Ctrl+C to kill all processes
609
- process.on('SIGINT', async () => {
640
+ process.on("SIGINT", async () => {
610
641
  if (this.isShuttingDown)
611
642
  return; // Prevent multiple shutdown attempts
612
643
  this.isShuttingDown = true;
613
644
  // Stop spinner if it's running
614
- if (this.spinner && this.spinner.isSpinning) {
615
- this.spinner.fail('Interrupted');
645
+ if (this.spinner?.isSpinning) {
646
+ this.spinner.fail("Interrupted");
616
647
  }
617
- console.log(chalk.yellow('\nšŸ›‘ Received interrupt signal. Cleaning up processes...'));
648
+ console.log(chalk.yellow("\nšŸ›‘ Received interrupt signal. Cleaning up processes..."));
618
649
  // Kill processes on both ports FIRST - this is most important
619
650
  const killPortProcess = async (port, name) => {
620
651
  try {
621
- const { spawn } = await import('child_process');
622
- const killProcess = spawn('sh', ['-c', `lsof -ti:${port} | xargs kill -9`], { stdio: 'inherit' });
652
+ const { spawn } = await import("child_process");
653
+ const killProcess = spawn("sh", ["-c", `lsof -ti:${port} | xargs kill -9`], { stdio: "inherit" });
623
654
  return new Promise((resolve) => {
624
- killProcess.on('exit', (code) => {
655
+ killProcess.on("exit", (code) => {
625
656
  if (code === 0) {
626
657
  console.log(chalk.green(`āœ… Killed ${name} on port ${port}`));
627
658
  }
@@ -629,28 +660,28 @@ export class DevEnvironment {
629
660
  });
630
661
  });
631
662
  }
632
- catch (error) {
663
+ catch (_error) {
633
664
  console.log(chalk.gray(`āš ļø Could not kill ${name} on port ${port}`));
634
665
  }
635
666
  };
636
667
  // Kill servers immediately - don't wait for browser cleanup
637
- console.log(chalk.blue('šŸ”„ Killing servers...'));
668
+ console.log(chalk.yellow("šŸ”„ Killing servers..."));
638
669
  await Promise.all([
639
- killPortProcess(this.options.port, 'your app server'),
640
- killPortProcess(this.options.mcpPort, 'dev3000 MCP server')
670
+ killPortProcess(this.options.port, "your app server"),
671
+ killPortProcess(this.options.mcpPort, `${this.options.commandName} MCP server`),
641
672
  ]);
642
- // Shutdown CDP monitor
673
+ // Shutdown CDP monitor if it was started
643
674
  if (this.cdpMonitor) {
644
675
  try {
645
- console.log(chalk.blue('šŸ”„ Closing CDP monitor...'));
676
+ console.log(chalk.cyan("šŸ”„ Closing CDP monitor..."));
646
677
  await this.cdpMonitor.shutdown();
647
- console.log(chalk.green('āœ… CDP monitor closed'));
678
+ console.log(chalk.green("āœ… CDP monitor closed"));
648
679
  }
649
- catch (error) {
650
- console.log(chalk.gray('āš ļø CDP monitor shutdown failed'));
680
+ catch (_error) {
681
+ console.log(chalk.gray("āš ļø CDP monitor shutdown failed"));
651
682
  }
652
683
  }
653
- console.log(chalk.green('āœ… Cleanup complete'));
684
+ console.log(chalk.green("āœ… Cleanup complete"));
654
685
  process.exit(0);
655
686
  });
656
687
  }