dev3000 0.0.131 → 0.0.134
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/bin/dev3000 +90 -0
- package/dist/cli.js +202 -33
- package/dist/cli.js.map +1 -1
- package/dist/commands/cloud-fix.js +8 -8
- package/dist/commands/cloud-fix.js.map +1 -1
- package/dist/components/AgentSelector.d.ts +12 -0
- package/dist/components/AgentSelector.d.ts.map +1 -0
- package/dist/components/AgentSelector.js +99 -0
- package/dist/components/AgentSelector.js.map +1 -0
- package/dist/components/SkillSelector.d.ts +9 -0
- package/dist/components/SkillSelector.d.ts.map +1 -0
- package/dist/components/SkillSelector.js +70 -0
- package/dist/components/SkillSelector.js.map +1 -0
- package/dist/dev-environment.d.ts +37 -0
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +245 -106
- package/dist/dev-environment.js.map +1 -1
- package/dist/src/tui-interface-impl.tsx +32 -20
- package/dist/tui-interface-impl.d.ts +10 -6
- package/dist/tui-interface-impl.d.ts.map +1 -1
- package/dist/tui-interface-impl.js +20 -14
- package/dist/tui-interface-impl.js.map +1 -1
- package/dist/tui-interface.d.ts +11 -7
- package/dist/tui-interface.d.ts.map +1 -1
- package/dist/tui-interface.js +6 -6
- package/dist/tui-interface.js.map +1 -1
- package/dist/utils/agent-selection.d.ts +24 -0
- package/dist/utils/agent-selection.d.ts.map +1 -0
- package/dist/utils/agent-selection.js +34 -0
- package/dist/utils/agent-selection.js.map +1 -0
- package/dist/utils/project-name.d.ts +6 -0
- package/dist/utils/project-name.d.ts.map +1 -1
- package/dist/utils/project-name.js +10 -0
- package/dist/utils/project-name.js.map +1 -1
- package/dist/utils/skill-installer.d.ts +29 -0
- package/dist/utils/skill-installer.d.ts.map +1 -0
- package/dist/utils/skill-installer.js +185 -0
- package/dist/utils/skill-installer.js.map +1 -0
- package/dist/utils/tmux-helpers.d.ts.map +1 -1
- package/dist/utils/tmux-helpers.js +4 -2
- package/dist/utils/tmux-helpers.js.map +1 -1
- package/dist/utils/user-config.d.ts +6 -0
- package/dist/utils/user-config.d.ts.map +1 -1
- package/dist/utils/user-config.js +30 -5
- package/dist/utils/user-config.js.map +1 -1
- package/dist/utils/version-check.d.ts +10 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +60 -2
- package/dist/utils/version-check.js.map +1 -1
- package/mcp-server/.next/BUILD_ID +1 -1
- package/mcp-server/.next/build-manifest.json +2 -2
- package/mcp-server/.next/fallback-build-manifest.json +2 -2
- package/mcp-server/.next/next-minimal-server.js.nft.json +1 -1
- package/mcp-server/.next/next-server.js.nft.json +1 -1
- package/mcp-server/.next/prerender-manifest.json +3 -3
- package/mcp-server/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/_global-error.html +2 -2
- package/mcp-server/.next/server/app/_global-error.rsc +1 -1
- package/mcp-server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/_not-found.html +1 -1
- package/mcp-server/.next/server/app/_not-found.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/mcp-server/.next/server/app/api/jank/[session]/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/logs/append/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/logs/head/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/logs/list/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/logs/rotate/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/logs/stream/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/logs/tail/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/orchestrator/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/screenshots/[filename]/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/screenshots/capture/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/screenshots/clear/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/screenshots/list/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/teams/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/api/tools/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/index.html +1 -1
- package/mcp-server/.next/server/app/index.rsc +1 -1
- package/mcp-server/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/mcp-server/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/mcp-server/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/mcp-server/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/mcp-server/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/mcp-server/.next/server/app/logs/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/mcp/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/video/[session]/page.js.nft.json +1 -1
- package/mcp-server/.next/server/pages/404.html +1 -1
- package/mcp-server/.next/server/pages/500.html +2 -2
- package/mcp-server/.next/server/server-reference-manifest.js +1 -1
- package/mcp-server/.next/server/server-reference-manifest.json +1 -1
- package/package.json +11 -4
- package/src/tui-interface-impl.tsx +32 -20
- /package/mcp-server/.next/static/{i5SOhj7cf3JSMPksTBNpr → Fekb6bDxTd8hH8dFQNlIK}/_buildManifest.js +0 -0
- /package/mcp-server/.next/static/{i5SOhj7cf3JSMPksTBNpr → Fekb6bDxTd8hH8dFQNlIK}/_clientMiddlewareManifest.json +0 -0
- /package/mcp-server/.next/static/{i5SOhj7cf3JSMPksTBNpr → Fekb6bDxTd8hH8dFQNlIK}/_ssgManifest.js +0 -0
package/dist/dev-environment.js
CHANGED
|
@@ -10,9 +10,9 @@ import { ScreencastManager } from "./screencast-manager.js";
|
|
|
10
10
|
import { NextJsErrorDetector, OutputProcessor, StandardLogParser } from "./services/parsers/index.js";
|
|
11
11
|
import { DevTUI } from "./tui-interface.js";
|
|
12
12
|
import { formatMcpConfigTargets, MCP_CONFIG_TARGETS } from "./utils/mcp-configs.js";
|
|
13
|
-
import { getProjectDisplayName, getProjectName } from "./utils/project-name.js";
|
|
13
|
+
import { getProjectDir, getProjectDisplayName, getProjectName } from "./utils/project-name.js";
|
|
14
14
|
import { formatTimestamp } from "./utils/timestamp.js";
|
|
15
|
-
import { checkForUpdates } from "./utils/version-check.js";
|
|
15
|
+
import { checkForUpdates, performUpgradeAsync } from "./utils/version-check.js";
|
|
16
16
|
// MCP names
|
|
17
17
|
const MCP_NAMES = {
|
|
18
18
|
DEV3000: "dev3000",
|
|
@@ -41,6 +41,66 @@ export const ORPHANED_PROCESS_CLEANUP_PATTERNS = [
|
|
|
41
41
|
function hasVercelProject() {
|
|
42
42
|
return existsSync(join(process.cwd(), ".vercel"));
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Gracefully terminate a process by first trying SIGTERM, waiting for graceful
|
|
46
|
+
* shutdown, then falling back to SIGKILL if needed.
|
|
47
|
+
*
|
|
48
|
+
* This is important for processes like Next.js dev server that need to clean up
|
|
49
|
+
* resources (like .next/dev/lock) before exiting.
|
|
50
|
+
*
|
|
51
|
+
* @param options - Kill options including PID and optional overrides for testing
|
|
52
|
+
* @returns Result indicating how the process was terminated
|
|
53
|
+
*/
|
|
54
|
+
export async function gracefulKillProcess(options) {
|
|
55
|
+
const { pid, gracePeriodMs = 500, killFn = (p, s) => process.kill(p, s), delayFn = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), debugLog = () => { } } = options;
|
|
56
|
+
const result = {
|
|
57
|
+
terminated: false,
|
|
58
|
+
graceful: false,
|
|
59
|
+
forcedKill: false
|
|
60
|
+
};
|
|
61
|
+
// Try process group first (negative PID), fall back to direct PID
|
|
62
|
+
const pgid = -pid;
|
|
63
|
+
// Step 1: Send SIGTERM for graceful shutdown
|
|
64
|
+
debugLog(`Sending SIGTERM to process group ${pgid} (PID: ${pid})`);
|
|
65
|
+
try {
|
|
66
|
+
killFn(pgid, "SIGTERM");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Process group may not exist, try direct kill
|
|
70
|
+
try {
|
|
71
|
+
killFn(pid, "SIGTERM");
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Process may already be dead
|
|
75
|
+
debugLog(`Process ${pid} not found for SIGTERM`);
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Step 2: Wait for graceful shutdown
|
|
80
|
+
await delayFn(gracePeriodMs);
|
|
81
|
+
// Step 3: Check if process is still running
|
|
82
|
+
try {
|
|
83
|
+
// Signal 0 checks if process exists without killing it
|
|
84
|
+
killFn(pid, 0);
|
|
85
|
+
// Process still running, need to force kill
|
|
86
|
+
debugLog(`Process still running after SIGTERM, sending SIGKILL`);
|
|
87
|
+
try {
|
|
88
|
+
killFn(pgid, "SIGKILL");
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
killFn(pid, "SIGKILL");
|
|
92
|
+
}
|
|
93
|
+
result.terminated = true;
|
|
94
|
+
result.forcedKill = true;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Process already dead - graceful shutdown succeeded
|
|
98
|
+
debugLog(`Process terminated gracefully after SIGTERM`);
|
|
99
|
+
result.terminated = true;
|
|
100
|
+
result.graceful = true;
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
44
104
|
class Logger {
|
|
45
105
|
logFile;
|
|
46
106
|
tail;
|
|
@@ -436,7 +496,11 @@ async function ensureCursorMcpServers(mcpPort, _appPort, _enableChromeDevtools)
|
|
|
436
496
|
}
|
|
437
497
|
/**
|
|
438
498
|
* Ensure MCP server configurations are added to project's opencode.json
|
|
439
|
-
* OpenCode uses a different structure: "mcp" instead of "mcpServers"
|
|
499
|
+
* OpenCode uses a different structure: "mcp" instead of "mcpServers"
|
|
500
|
+
*
|
|
501
|
+
* IMPORTANT: OpenCode has issues with "type": "remote" for HTTP MCP servers.
|
|
502
|
+
* The workaround is to use "type": "local" with mcp-remote package to proxy requests.
|
|
503
|
+
* See: https://github.com/sst/opencode/issues/1595
|
|
440
504
|
*/
|
|
441
505
|
async function ensureOpenCodeMcpServers(mcpPort, _appPort, _enableChromeDevtools) {
|
|
442
506
|
try {
|
|
@@ -454,30 +518,43 @@ async function ensureOpenCodeMcpServers(mcpPort, _appPort, _enableChromeDevtools
|
|
|
454
518
|
if (!settings.mcp) {
|
|
455
519
|
settings.mcp = {};
|
|
456
520
|
}
|
|
457
|
-
let
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
521
|
+
let changed = false;
|
|
522
|
+
// Always update dev3000 MCP server config to ensure correct format
|
|
523
|
+
// Try simple remote type first - no OAuth needed for local dev3000
|
|
524
|
+
const expectedDev3000Config = {
|
|
525
|
+
type: "remote",
|
|
526
|
+
url: `http://localhost:${mcpPort}/mcp`,
|
|
527
|
+
enabled: true
|
|
528
|
+
};
|
|
529
|
+
const currentDev3000 = settings.mcp[MCP_NAMES.DEV3000];
|
|
530
|
+
if (!currentDev3000 ||
|
|
531
|
+
currentDev3000.type !== expectedDev3000Config.type ||
|
|
532
|
+
currentDev3000.url !== expectedDev3000Config.url) {
|
|
533
|
+
settings.mcp[MCP_NAMES.DEV3000] = expectedDev3000Config;
|
|
534
|
+
changed = true;
|
|
535
|
+
}
|
|
536
|
+
// Always update Vercel MCP if this is a Vercel project (.vercel directory exists)
|
|
537
|
+
// Vercel MCP requires OAuth, so use OpenCode's native remote type with oauth: {}
|
|
538
|
+
// This triggers OpenCode's built-in OAuth flow instead of mcp-remote
|
|
539
|
+
// See: https://github.com/sst/opencode/issues/5444
|
|
540
|
+
if (hasVercelProject()) {
|
|
541
|
+
const expectedVercelConfig = {
|
|
473
542
|
type: "remote",
|
|
474
543
|
url: VERCEL_MCP_URL,
|
|
544
|
+
oauth: {},
|
|
475
545
|
enabled: true
|
|
476
546
|
};
|
|
477
|
-
|
|
547
|
+
const currentVercel = settings.mcp[MCP_NAMES.VERCEL];
|
|
548
|
+
if (!currentVercel ||
|
|
549
|
+
currentVercel.type !== expectedVercelConfig.type ||
|
|
550
|
+
currentVercel.url !== expectedVercelConfig.url ||
|
|
551
|
+
!currentVercel.oauth) {
|
|
552
|
+
settings.mcp[MCP_NAMES.VERCEL] = expectedVercelConfig;
|
|
553
|
+
changed = true;
|
|
554
|
+
}
|
|
478
555
|
}
|
|
479
|
-
// Write if we
|
|
480
|
-
if (
|
|
556
|
+
// Write if we changed anything
|
|
557
|
+
if (changed) {
|
|
481
558
|
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf-8");
|
|
482
559
|
}
|
|
483
560
|
}
|
|
@@ -521,8 +598,8 @@ async function ensureD3kSkill() {
|
|
|
521
598
|
export function createPersistentLogFile() {
|
|
522
599
|
// Get unique project name
|
|
523
600
|
const projectName = getProjectName();
|
|
524
|
-
// Use ~/.d3k/logs directory for persistent, accessible logs
|
|
525
|
-
const logBaseDir = join(
|
|
601
|
+
// Use ~/.d3k/{projectName}/logs directory for persistent, accessible logs
|
|
602
|
+
const logBaseDir = join(getProjectDir(), "logs");
|
|
526
603
|
try {
|
|
527
604
|
if (!existsSync(logBaseDir)) {
|
|
528
605
|
mkdirSync(logBaseDir, { recursive: true });
|
|
@@ -540,11 +617,11 @@ export function createPersistentLogFile() {
|
|
|
540
617
|
}
|
|
541
618
|
// Write session info for MCP server to discover
|
|
542
619
|
function writeSessionInfo(projectName, logFilePath, appPort, mcpPort, cdpUrl, chromePids, serverCommand, framework, serverPid) {
|
|
543
|
-
const
|
|
620
|
+
const projectDir = getProjectDir();
|
|
544
621
|
try {
|
|
545
|
-
// Create
|
|
546
|
-
if (!existsSync(
|
|
547
|
-
mkdirSync(
|
|
622
|
+
// Create project directory if it doesn't exist
|
|
623
|
+
if (!existsSync(projectDir)) {
|
|
624
|
+
mkdirSync(projectDir, { recursive: true });
|
|
548
625
|
}
|
|
549
626
|
// Session file contains project info
|
|
550
627
|
const sessionInfo = {
|
|
@@ -561,8 +638,8 @@ function writeSessionInfo(projectName, logFilePath, appPort, mcpPort, cdpUrl, ch
|
|
|
561
638
|
framework: framework || null,
|
|
562
639
|
serverPid: serverPid || null
|
|
563
640
|
};
|
|
564
|
-
// Write session file
|
|
565
|
-
const sessionFile = join(
|
|
641
|
+
// Write session file in project directory
|
|
642
|
+
const sessionFile = join(projectDir, "session.json");
|
|
566
643
|
writeFileSync(sessionFile, JSON.stringify(sessionInfo, null, 2));
|
|
567
644
|
}
|
|
568
645
|
catch (error) {
|
|
@@ -572,8 +649,7 @@ function writeSessionInfo(projectName, logFilePath, appPort, mcpPort, cdpUrl, ch
|
|
|
572
649
|
}
|
|
573
650
|
// Get Chrome PIDs for this instance
|
|
574
651
|
function getSessionChromePids(projectName) {
|
|
575
|
-
const
|
|
576
|
-
const sessionFile = join(sessionDir, `${projectName}.json`);
|
|
652
|
+
const sessionFile = join(homedir(), ".d3k", projectName, "session.json");
|
|
577
653
|
try {
|
|
578
654
|
if (existsSync(sessionFile)) {
|
|
579
655
|
const sessionInfo = JSON.parse(readFileSync(sessionFile, "utf8"));
|
|
@@ -587,8 +663,7 @@ function getSessionChromePids(projectName) {
|
|
|
587
663
|
}
|
|
588
664
|
// Get server PID for this instance
|
|
589
665
|
function getSessionServerPid(projectName) {
|
|
590
|
-
const
|
|
591
|
-
const sessionFile = join(sessionDir, `${projectName}.json`);
|
|
666
|
+
const sessionFile = join(homedir(), ".d3k", projectName, "session.json");
|
|
592
667
|
try {
|
|
593
668
|
if (existsSync(sessionFile)) {
|
|
594
669
|
const sessionInfo = JSON.parse(readFileSync(sessionFile, "utf8"));
|
|
@@ -600,23 +675,31 @@ function getSessionServerPid(projectName) {
|
|
|
600
675
|
}
|
|
601
676
|
return null;
|
|
602
677
|
}
|
|
603
|
-
function createLogFileInDir(baseDir,
|
|
604
|
-
// Create timestamp
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
678
|
+
function createLogFileInDir(baseDir, _projectName) {
|
|
679
|
+
// Create short timestamp: MMDD-HHmmss (e.g., 0106-171301)
|
|
680
|
+
const now = new Date();
|
|
681
|
+
const timestamp = [
|
|
682
|
+
String(now.getMonth() + 1).padStart(2, "0"),
|
|
683
|
+
String(now.getDate()).padStart(2, "0"),
|
|
684
|
+
"-",
|
|
685
|
+
String(now.getHours()).padStart(2, "0"),
|
|
686
|
+
String(now.getMinutes()).padStart(2, "0"),
|
|
687
|
+
String(now.getSeconds()).padStart(2, "0")
|
|
688
|
+
].join("");
|
|
689
|
+
// Create log file path (project name already in directory path)
|
|
690
|
+
const logFileName = `${timestamp}.log`;
|
|
608
691
|
const logFilePath = join(baseDir, logFileName);
|
|
609
|
-
// Prune old logs
|
|
610
|
-
pruneOldLogs(baseDir
|
|
692
|
+
// Prune old logs (keep only 10 most recent)
|
|
693
|
+
pruneOldLogs(baseDir);
|
|
611
694
|
// Create the log file
|
|
612
695
|
writeFileSync(logFilePath, "");
|
|
613
696
|
return logFilePath;
|
|
614
697
|
}
|
|
615
|
-
function pruneOldLogs(baseDir
|
|
698
|
+
function pruneOldLogs(baseDir) {
|
|
616
699
|
try {
|
|
617
|
-
// Find all log files
|
|
700
|
+
// Find all log files in directory
|
|
618
701
|
const files = readdirSync(baseDir)
|
|
619
|
-
.filter((file) => file.
|
|
702
|
+
.filter((file) => file.endsWith(".log"))
|
|
620
703
|
.map((file) => ({
|
|
621
704
|
name: file,
|
|
622
705
|
path: join(baseDir, file),
|
|
@@ -636,8 +719,8 @@ function pruneOldLogs(baseDir, projectName) {
|
|
|
636
719
|
}
|
|
637
720
|
}
|
|
638
721
|
}
|
|
639
|
-
catch (
|
|
640
|
-
|
|
722
|
+
catch (_error) {
|
|
723
|
+
// Silently ignore prune errors
|
|
641
724
|
}
|
|
642
725
|
}
|
|
643
726
|
export class DevEnvironment {
|
|
@@ -673,9 +756,20 @@ export class DevEnvironment {
|
|
|
673
756
|
this.disabledMcpConfigSet = new Set(this.options.disabledMcpConfigs);
|
|
674
757
|
this.logger = new Logger(options.logFile, options.tail || false, options.dateTimeFormat || "local");
|
|
675
758
|
this.outputProcessor = new OutputProcessor(new StandardLogParser(), new NextJsErrorDetector());
|
|
676
|
-
//
|
|
677
|
-
const
|
|
678
|
-
const
|
|
759
|
+
// Detect if running from compiled binary
|
|
760
|
+
const execPath = process.execPath;
|
|
761
|
+
const isCompiledBinary = execPath.includes("@d3k/darwin-") || execPath.includes("d3k-darwin-") || execPath.endsWith("/dev3000");
|
|
762
|
+
let packageRoot;
|
|
763
|
+
if (isCompiledBinary) {
|
|
764
|
+
// For compiled binaries: bin/dev3000 -> package root
|
|
765
|
+
const binDir = dirname(execPath);
|
|
766
|
+
packageRoot = dirname(binDir);
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
// Normal install: dist/dev-environment.js -> package root
|
|
770
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
771
|
+
packageRoot = dirname(dirname(currentFile));
|
|
772
|
+
}
|
|
679
773
|
// Always use MCP server's public directory for screenshots to ensure they're web-accessible
|
|
680
774
|
// and avoid permission issues with /var/log paths
|
|
681
775
|
this.screenshotDir = join(packageRoot, "mcp-server", "public", "screenshots");
|
|
@@ -684,30 +778,36 @@ export class DevEnvironment {
|
|
|
684
778
|
this.pidFile = join(tmpdir(), `dev3000-${projectName}.pid`);
|
|
685
779
|
this.lockFile = join(tmpdir(), `dev3000-${projectName}.lock`);
|
|
686
780
|
this.mcpPublicDir = join(packageRoot, "mcp-server", "public", "screenshots");
|
|
687
|
-
// Read version from package.json
|
|
781
|
+
// Read version - for compiled binaries, use injected version; otherwise read from package.json
|
|
688
782
|
this.version = "0.0.0";
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
783
|
+
// Check for compile-time injected version first
|
|
784
|
+
if (typeof __D3K_VERSION__ !== "undefined") {
|
|
785
|
+
this.version = __D3K_VERSION__;
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
694
788
|
try {
|
|
695
|
-
const
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
789
|
+
const packageJsonPath = join(packageRoot, "package.json");
|
|
790
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
791
|
+
this.version = packageJson.version;
|
|
792
|
+
// Use git to detect if we're in the dev3000 source repository
|
|
793
|
+
try {
|
|
794
|
+
const { execSync } = require("child_process");
|
|
795
|
+
const gitRemote = execSync("git remote get-url origin 2>/dev/null", {
|
|
796
|
+
cwd: packageRoot,
|
|
797
|
+
encoding: "utf8"
|
|
798
|
+
}).trim();
|
|
799
|
+
if (gitRemote.includes("vercel-labs/dev3000") && !this.version.includes("canary")) {
|
|
800
|
+
this.version += "-local";
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
// Not in git repo or no git - use version as-is
|
|
702
805
|
}
|
|
703
806
|
}
|
|
704
|
-
catch {
|
|
705
|
-
//
|
|
807
|
+
catch (_error) {
|
|
808
|
+
// Use fallback version
|
|
706
809
|
}
|
|
707
810
|
}
|
|
708
|
-
catch (_error) {
|
|
709
|
-
// Use fallback version
|
|
710
|
-
}
|
|
711
811
|
// Initialize spinner for clean output management (only if not in TUI mode)
|
|
712
812
|
this.spinner = ora({
|
|
713
813
|
text: "Initializing...",
|
|
@@ -825,7 +925,7 @@ export class DevEnvironment {
|
|
|
825
925
|
this.debugLog(`Waiting ${waitTime}ms for port ${this.options.mcpPort} to be released...`);
|
|
826
926
|
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
827
927
|
// Check if port is now free
|
|
828
|
-
const available = await isPortAvailable(this.options.mcpPort
|
|
928
|
+
const available = await isPortAvailable(this.options.mcpPort?.toString() ?? "");
|
|
829
929
|
if (available) {
|
|
830
930
|
this.debugLog(`Port ${this.options.mcpPort} released successfully`);
|
|
831
931
|
return;
|
|
@@ -945,14 +1045,26 @@ export class DevEnvironment {
|
|
|
945
1045
|
serversOnly: this.options.serversOnly,
|
|
946
1046
|
version: this.version,
|
|
947
1047
|
projectName: projectDisplayName,
|
|
948
|
-
|
|
1048
|
+
updateInfo: null // Will be updated async after auto-upgrade
|
|
949
1049
|
});
|
|
950
1050
|
await this.tui.start();
|
|
951
|
-
//
|
|
952
|
-
updateCheckPromise.then((versionInfo) => {
|
|
1051
|
+
// Auto-upgrade if update available (non-blocking)
|
|
1052
|
+
updateCheckPromise.then(async (versionInfo) => {
|
|
953
1053
|
if (versionInfo?.updateAvailable && versionInfo.latestVersion && this.tui) {
|
|
954
|
-
this.debugLog(`Update available: ${versionInfo.currentVersion} -> ${versionInfo.latestVersion}
|
|
955
|
-
|
|
1054
|
+
this.debugLog(`Update available: ${versionInfo.currentVersion} -> ${versionInfo.latestVersion}, auto-upgrading...`);
|
|
1055
|
+
// Perform upgrade in background
|
|
1056
|
+
const result = await performUpgradeAsync();
|
|
1057
|
+
if (result.success) {
|
|
1058
|
+
const newVersion = result.newVersion || versionInfo.latestVersion;
|
|
1059
|
+
this.debugLog(`Auto-upgrade successful: ${newVersion}`);
|
|
1060
|
+
// Show "Updated to vX.X.X" message (auto-hides after 10s)
|
|
1061
|
+
this.tui.updateUpdateInfo({ type: "updated", newVersion });
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
// Upgrade failed - show update available instead
|
|
1065
|
+
this.debugLog(`Auto-upgrade failed: ${result.error}, showing update available`);
|
|
1066
|
+
this.tui.updateUpdateInfo({ type: "available", latestVersion: versionInfo.latestVersion });
|
|
1067
|
+
}
|
|
956
1068
|
}
|
|
957
1069
|
});
|
|
958
1070
|
// Check ports in background after TUI is visible
|
|
@@ -989,7 +1101,7 @@ export class DevEnvironment {
|
|
|
989
1101
|
if (!serverStarted) {
|
|
990
1102
|
await this.tui.updateStatus("❌ Server failed to start");
|
|
991
1103
|
console.error(chalk.red("\n❌ Your app server failed to start after 30 seconds."));
|
|
992
|
-
console.error(chalk.yellow(`Check the logs at ~/.d3k
|
|
1104
|
+
console.error(chalk.yellow(`Check the logs at ~/.d3k/${getProjectName()}/logs/ for errors.`));
|
|
993
1105
|
console.error(chalk.yellow("Exiting without launching browser."));
|
|
994
1106
|
process.exit(1);
|
|
995
1107
|
}
|
|
@@ -1052,7 +1164,7 @@ export class DevEnvironment {
|
|
|
1052
1164
|
if (!serverStarted) {
|
|
1053
1165
|
this.spinner.fail("Server failed to start");
|
|
1054
1166
|
console.error(chalk.red("\n❌ Your app server failed to start after 30 seconds."));
|
|
1055
|
-
console.error(chalk.yellow(`Check the logs at ~/.d3k
|
|
1167
|
+
console.error(chalk.yellow(`Check the logs at ~/.d3k/${getProjectName()}/logs/ for errors.`));
|
|
1056
1168
|
console.error(chalk.yellow("Exiting without launching browser."));
|
|
1057
1169
|
process.exit(1);
|
|
1058
1170
|
}
|
|
@@ -1100,12 +1212,21 @@ export class DevEnvironment {
|
|
|
1100
1212
|
console.log(chalk.cyan("🖥️ Servers-only mode - use Chrome extension for browser monitoring"));
|
|
1101
1213
|
}
|
|
1102
1214
|
console.log(chalk.cyan("\nUse Ctrl-C to stop.\n"));
|
|
1103
|
-
//
|
|
1215
|
+
// Auto-upgrade in non-TUI mode (non-blocking)
|
|
1104
1216
|
checkForUpdates()
|
|
1105
|
-
.then((versionInfo) => {
|
|
1217
|
+
.then(async (versionInfo) => {
|
|
1106
1218
|
if (versionInfo?.updateAvailable && versionInfo.latestVersion) {
|
|
1107
|
-
|
|
1108
|
-
|
|
1219
|
+
this.debugLog(`Update available: ${versionInfo.currentVersion} -> ${versionInfo.latestVersion}, auto-upgrading...`);
|
|
1220
|
+
const result = await performUpgradeAsync();
|
|
1221
|
+
if (result.success) {
|
|
1222
|
+
const newVersion = result.newVersion || versionInfo.latestVersion;
|
|
1223
|
+
console.log(chalk.green(`\n✓ Updated to v${newVersion}`));
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
// Upgrade failed - show update available
|
|
1227
|
+
console.log(chalk.yellow(`\n↑ Update available: v${versionInfo.currentVersion} → v${versionInfo.latestVersion}`));
|
|
1228
|
+
console.log(chalk.gray(` Run 'd3k upgrade' to update\n`));
|
|
1229
|
+
}
|
|
1109
1230
|
}
|
|
1110
1231
|
})
|
|
1111
1232
|
.catch(() => {
|
|
@@ -1331,11 +1452,11 @@ export class DevEnvironment {
|
|
|
1331
1452
|
}
|
|
1332
1453
|
// Always write to d3k debug log file (even when not in debug mode)
|
|
1333
1454
|
try {
|
|
1334
|
-
const
|
|
1335
|
-
if (!existsSync(
|
|
1336
|
-
mkdirSync(
|
|
1455
|
+
const projectDir = getProjectDir();
|
|
1456
|
+
if (!existsSync(projectDir)) {
|
|
1457
|
+
mkdirSync(projectDir, { recursive: true });
|
|
1337
1458
|
}
|
|
1338
|
-
const debugLogFile = join(
|
|
1459
|
+
const debugLogFile = join(projectDir, "debug.log");
|
|
1339
1460
|
const logEntry = `[${timestamp}] [DEBUG] ${message}\n`;
|
|
1340
1461
|
appendFileSync(debugLogFile, logEntry);
|
|
1341
1462
|
}
|
|
@@ -1399,9 +1520,27 @@ export class DevEnvironment {
|
|
|
1399
1520
|
// Note: MCP server cleanup now happens earlier in checkPortsAvailable()
|
|
1400
1521
|
// to ensure the port is free before we check availability
|
|
1401
1522
|
// Get the path to our bundled MCP server
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1523
|
+
// Handle both normal npm install and compiled binary cases
|
|
1524
|
+
let mcpServerPath;
|
|
1525
|
+
// Check if we're running from a compiled binary
|
|
1526
|
+
// Compiled binaries have process.execPath pointing to the binary itself
|
|
1527
|
+
const execPath = process.execPath;
|
|
1528
|
+
const isCompiledBinary = execPath.includes("@d3k/darwin-") || execPath.includes("d3k-darwin-") || execPath.endsWith("/dev3000");
|
|
1529
|
+
if (isCompiledBinary) {
|
|
1530
|
+
// For compiled binaries, mcp-server is a sibling to the bin directory
|
|
1531
|
+
// Structure: packages/d3k-darwin-arm64/bin/dev3000 -> packages/d3k-darwin-arm64/mcp-server
|
|
1532
|
+
const binDir = dirname(execPath);
|
|
1533
|
+
const packageDir = dirname(binDir);
|
|
1534
|
+
mcpServerPath = join(packageDir, "mcp-server");
|
|
1535
|
+
this.debugLog(`Compiled binary detected, MCP server path: ${mcpServerPath}`);
|
|
1536
|
+
}
|
|
1537
|
+
else {
|
|
1538
|
+
// Normal npm install - mcp-server is in the package root
|
|
1539
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
1540
|
+
const packageRoot = dirname(dirname(currentFile)); // Go up from dist/ to package root
|
|
1541
|
+
mcpServerPath = join(packageRoot, "mcp-server");
|
|
1542
|
+
this.debugLog(`Standard install detected, MCP server path: ${mcpServerPath}`);
|
|
1543
|
+
}
|
|
1405
1544
|
this.debugLog(`Initial MCP server path: ${mcpServerPath}`);
|
|
1406
1545
|
// For pnpm global installs, resolve symlinks to get the real path
|
|
1407
1546
|
if (existsSync(mcpServerPath)) {
|
|
@@ -1832,13 +1971,12 @@ export class DevEnvironment {
|
|
|
1832
1971
|
}
|
|
1833
1972
|
initializeD3KLog() {
|
|
1834
1973
|
try {
|
|
1835
|
-
const
|
|
1836
|
-
if (!existsSync(
|
|
1837
|
-
mkdirSync(
|
|
1974
|
+
const projectDir = getProjectDir();
|
|
1975
|
+
if (!existsSync(projectDir)) {
|
|
1976
|
+
mkdirSync(projectDir, { recursive: true });
|
|
1838
1977
|
}
|
|
1839
|
-
// Create
|
|
1840
|
-
const
|
|
1841
|
-
const d3kLogFile = join(debugLogDir, `${projectName}-d3k.log`);
|
|
1978
|
+
// Create D3K log file and clear it for new session
|
|
1979
|
+
const d3kLogFile = join(projectDir, "d3k.log");
|
|
1842
1980
|
writeFileSync(d3kLogFile, "");
|
|
1843
1981
|
}
|
|
1844
1982
|
catch {
|
|
@@ -1851,13 +1989,11 @@ export class DevEnvironment {
|
|
|
1851
1989
|
const timestamp = formatTimestamp(new Date(), this.options.dateTimeFormat || "local");
|
|
1852
1990
|
const logEntry = `[${timestamp}] [D3K] ${message}\n`;
|
|
1853
1991
|
try {
|
|
1854
|
-
const
|
|
1855
|
-
if (!existsSync(
|
|
1856
|
-
mkdirSync(
|
|
1992
|
+
const projectDir = getProjectDir();
|
|
1993
|
+
if (!existsSync(projectDir)) {
|
|
1994
|
+
mkdirSync(projectDir, { recursive: true });
|
|
1857
1995
|
}
|
|
1858
|
-
|
|
1859
|
-
const projectName = getProjectName();
|
|
1860
|
-
const d3kLogFile = join(debugLogDir, `${projectName}-d3k.log`);
|
|
1996
|
+
const d3kLogFile = join(projectDir, "d3k.log");
|
|
1861
1997
|
appendFileSync(d3kLogFile, logEntry);
|
|
1862
1998
|
}
|
|
1863
1999
|
catch {
|
|
@@ -1952,7 +2088,7 @@ export class DevEnvironment {
|
|
|
1952
2088
|
const savedServerPid = getSessionServerPid(projectName);
|
|
1953
2089
|
// Clean up session file
|
|
1954
2090
|
try {
|
|
1955
|
-
const sessionFile = join(homedir(), ".d3k",
|
|
2091
|
+
const sessionFile = join(homedir(), ".d3k", projectName, "session.json");
|
|
1956
2092
|
if (existsSync(sessionFile)) {
|
|
1957
2093
|
unlinkSync(sessionFile);
|
|
1958
2094
|
}
|
|
@@ -2127,7 +2263,7 @@ export class DevEnvironment {
|
|
|
2127
2263
|
const savedServerPid = getSessionServerPid(projectName);
|
|
2128
2264
|
// Clean up session file
|
|
2129
2265
|
try {
|
|
2130
|
-
const sessionFile = join(homedir(), ".d3k",
|
|
2266
|
+
const sessionFile = join(homedir(), ".d3k", projectName, "session.json");
|
|
2131
2267
|
if (existsSync(sessionFile)) {
|
|
2132
2268
|
unlinkSync(sessionFile);
|
|
2133
2269
|
}
|
|
@@ -2256,12 +2392,15 @@ export class DevEnvironment {
|
|
|
2256
2392
|
// Next.js's next-server and webpack workers) are also killed.
|
|
2257
2393
|
if (this.serverProcess?.pid) {
|
|
2258
2394
|
try {
|
|
2259
|
-
//
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2395
|
+
// Use graceful kill: SIGTERM first, wait, then SIGKILL if needed
|
|
2396
|
+
// This allows Next.js to clean up .next/dev/lock before terminating
|
|
2397
|
+
const result = await gracefulKillProcess({
|
|
2398
|
+
pid: this.serverProcess.pid,
|
|
2399
|
+
debugLog: (msg) => this.debugLog(msg)
|
|
2400
|
+
});
|
|
2401
|
+
if (!this.options.tui && result.terminated) {
|
|
2402
|
+
const method = result.graceful ? "gracefully" : "forcefully";
|
|
2403
|
+
console.log(chalk.green(`✅ Killed server process group ${method} (PID: ${this.serverProcess.pid})`));
|
|
2265
2404
|
}
|
|
2266
2405
|
}
|
|
2267
2406
|
catch (error) {
|