helixevo 0.3.0 → 0.3.1
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/CHANGELOG.md +7 -0
- package/README.md +5 -2
- package/dashboard/app/commands/page.tsx +6 -4
- package/dashboard/app/guide/page.tsx +8 -2
- package/dist/cli.js +209 -77
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to HelixEvo are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.3.1] - 2026-03-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- `helixevo dashboard` now recovers cleanly when the preferred port is occupied: it reuses a known managed dashboard if one is already running there, otherwise it falls forward to the next open port instead of failing with `EADDRINUSE`
|
|
9
|
+
- Added `helixevo dashboard --port <port>` so operators can choose a preferred starting port while preserving automatic fallback behavior
|
|
10
|
+
- Background dashboard stop/reuse logic now tracks the actual assigned port, so `helixevo dashboard --stop` works correctly even after fallback to a non-default port
|
|
11
|
+
|
|
5
12
|
## [0.3.0] - 2026-03-23
|
|
6
13
|
|
|
7
14
|
### Added
|
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ helixevo dashboard
|
|
|
90
90
|
| `helixevo specialize --project <name>` | Create project-specific skills ↓ |
|
|
91
91
|
| `helixevo graph` | View skill network in terminal |
|
|
92
92
|
| `helixevo research` | Proactive web research for skill improvement |
|
|
93
|
-
| `helixevo dashboard` | Open web dashboard
|
|
93
|
+
| `helixevo dashboard [--port <n>]` | Open web dashboard, preferring localhost:3847 and falling forward if occupied |
|
|
94
94
|
| `helixevo status` | Show system health |
|
|
95
95
|
| `helixevo report` | Generate evolution report |
|
|
96
96
|
|
|
@@ -149,7 +149,10 @@ The dashboard provides an interactive view of your skill ecosystem:
|
|
|
149
149
|
|
|
150
150
|
```bash
|
|
151
151
|
helixevo dashboard
|
|
152
|
-
#
|
|
152
|
+
# Prefers http://localhost:3847 and falls forward if that port is occupied
|
|
153
|
+
|
|
154
|
+
helixevo dashboard --port 3900
|
|
155
|
+
# Prefer port 3900 first
|
|
153
156
|
```
|
|
154
157
|
|
|
155
158
|
**Tabs:**
|
|
@@ -242,21 +242,23 @@ const COMMANDS: CommandInfo[] = [
|
|
|
242
242
|
},
|
|
243
243
|
{
|
|
244
244
|
name: 'dashboard',
|
|
245
|
-
description: 'Launch the web dashboard at http://localhost:3847. When a newer npm version is available, HelixEvo
|
|
245
|
+
description: 'Launch the web dashboard at http://localhost:3847 by default. When that port is occupied, HelixEvo now reuses a known managed dashboard or falls forward to the next open port instead of failing. When a newer npm version is available, HelixEvo also auto-updates itself before opening the dashboard unless you opt out.',
|
|
246
246
|
usage: 'helixevo dashboard [options]',
|
|
247
247
|
examples: [
|
|
248
|
-
{ cmd: 'helixevo dashboard', desc: 'Auto-update to the latest version if needed, then open the dashboard' },
|
|
248
|
+
{ cmd: 'helixevo dashboard', desc: 'Auto-update to the latest version if needed, then open the dashboard on 3847 or the next open port' },
|
|
249
249
|
{ cmd: 'helixevo dashboard --background', desc: 'Run the dashboard in the background after the same update check' },
|
|
250
|
+
{ cmd: 'helixevo dashboard --port 3900', desc: 'Prefer port 3900 first, then fall forward if that port is occupied' },
|
|
250
251
|
{ cmd: 'helixevo dashboard --no-auto-update', desc: 'Open the dashboard immediately without checking npm for updates' },
|
|
251
252
|
],
|
|
252
253
|
options: [
|
|
253
254
|
{ flag: '--background', desc: 'Run the dashboard detached from the terminal' },
|
|
254
|
-
{ flag: '--stop', desc: 'Stop a background dashboard process' },
|
|
255
|
+
{ flag: '--stop', desc: 'Stop a managed background dashboard process' },
|
|
256
|
+
{ flag: '--port <port>', desc: 'Preferred port to try first (default: 3847)' },
|
|
255
257
|
{ flag: '--no-auto-update', desc: 'Skip the automatic update check before launch' },
|
|
256
258
|
],
|
|
257
259
|
category: 'system',
|
|
258
260
|
needsLLM: false,
|
|
259
|
-
note: 'You are currently using the dashboard.
|
|
261
|
+
note: 'You are currently using the dashboard. It now prefers 3847, reuses a known managed dashboard when one is already running there, and otherwise falls forward to the next open port.',
|
|
260
262
|
},
|
|
261
263
|
]
|
|
262
264
|
|
|
@@ -395,8 +395,14 @@ helixevo graph
|
|
|
395
395
|
# Open this dashboard
|
|
396
396
|
helixevo dashboard
|
|
397
397
|
|
|
398
|
+
# Prefer a different port if you want
|
|
399
|
+
helixevo dashboard --port 3900
|
|
400
|
+
|
|
398
401
|
# Check system health
|
|
399
402
|
helixevo status`}</Code>
|
|
403
|
+
<p className="guide-text-sm">
|
|
404
|
+
The dashboard prefers port <code>3847</code>. If that port is already occupied, HelixEvo now reuses a known managed dashboard or falls forward to the next open port instead of failing immediately.
|
|
405
|
+
</p>
|
|
400
406
|
</Step>
|
|
401
407
|
</Section>
|
|
402
408
|
|
|
@@ -456,8 +462,8 @@ helixevo status`}</Code>
|
|
|
456
462
|
},
|
|
457
463
|
{
|
|
458
464
|
cmd: 'helixevo dashboard',
|
|
459
|
-
desc: 'Open the interactive web dashboard
|
|
460
|
-
flags: ['--background', '--stop', '--no-auto-update'],
|
|
465
|
+
desc: 'Open the interactive web dashboard. HelixEvo prefers localhost:3847, reuses a known managed dashboard if one is already there, and otherwise falls forward to the next open port. Before launch, it also checks npm for a newer version and auto-updates itself unless you opt out.',
|
|
466
|
+
flags: ['--background', '--stop', '--port <port>', '--no-auto-update'],
|
|
461
467
|
},
|
|
462
468
|
{
|
|
463
469
|
cmd: 'helixevo status',
|
package/dist/cli.js
CHANGED
|
@@ -12809,9 +12809,10 @@ ${replay.slice(0, 800)}`
|
|
|
12809
12809
|
// src/commands/dashboard.ts
|
|
12810
12810
|
import { execSync as execSync2, spawn as spawn2 } from "node:child_process";
|
|
12811
12811
|
import { join as join17, dirname as dirname3 } from "node:path";
|
|
12812
|
-
import { existsSync as existsSync11, cpSync as cpSync3, mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync10 } from "node:fs";
|
|
12812
|
+
import { existsSync as existsSync11, cpSync as cpSync3, mkdirSync as mkdirSync5, openSync, readdirSync as readdirSync3, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync10 } from "node:fs";
|
|
12813
12813
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12814
12814
|
import { homedir as homedir4 } from "node:os";
|
|
12815
|
+
import { createServer } from "node:net";
|
|
12815
12816
|
init_skills();
|
|
12816
12817
|
init_data();
|
|
12817
12818
|
|
|
@@ -12920,11 +12921,23 @@ function printUpdateBanner(currentVersion, latestVersion) {
|
|
|
12920
12921
|
|
|
12921
12922
|
// src/commands/dashboard.ts
|
|
12922
12923
|
var __filename = "/Users/tianchichen/Documents/GitHub/helixevo/src/commands/dashboard.ts";
|
|
12923
|
-
var
|
|
12924
|
+
var HELIX_HOME_DIR = join17(homedir4(), ".helix");
|
|
12925
|
+
var HELIX_DASHBOARD_DIR = join17(HELIX_HOME_DIR, "dashboard");
|
|
12926
|
+
var DASHBOARD_LOG_FILE = join17(HELIX_HOME_DIR, "dashboard.log");
|
|
12927
|
+
var DASHBOARD_PID_FILE = join17(HELIX_HOME_DIR, "dashboard.pid");
|
|
12928
|
+
var DASHBOARD_STATE_FILE = join17(HELIX_HOME_DIR, "dashboard-state.json");
|
|
12924
12929
|
var DASHBOARD_SKIP_AUTO_UPDATE_ENV = "HELIXEVO_DASHBOARD_SKIP_AUTO_UPDATE";
|
|
12930
|
+
var DEFAULT_DASHBOARD_PORT = 3847;
|
|
12931
|
+
var MAX_PORT_SCAN_ATTEMPTS = 25;
|
|
12932
|
+
var RESTART_EXIT_CODE = 75;
|
|
12925
12933
|
async function dashboardCommand(options) {
|
|
12934
|
+
if (options.stop) {
|
|
12935
|
+
stopDashboard();
|
|
12936
|
+
return;
|
|
12937
|
+
}
|
|
12938
|
+
const preferredPort = parseDashboardPort(options.port);
|
|
12926
12939
|
if (options.autoUpdate !== false && process.env[DASHBOARD_SKIP_AUTO_UPDATE_ENV] !== "1" && !findDevDashboard()) {
|
|
12927
|
-
const didRelaunch = await maybeAutoUpdateAndRelaunch(options);
|
|
12940
|
+
const didRelaunch = await maybeAutoUpdateAndRelaunch({ ...options, port: preferredPort });
|
|
12928
12941
|
if (didRelaunch)
|
|
12929
12942
|
return;
|
|
12930
12943
|
}
|
|
@@ -12934,14 +12947,14 @@ async function dashboardCommand(options) {
|
|
|
12934
12947
|
`);
|
|
12935
12948
|
console.error(" You can install and run it manually:");
|
|
12936
12949
|
console.error(" git clone https://github.com/danielchen26/helixevo.git");
|
|
12937
|
-
console.error(
|
|
12950
|
+
console.error(` cd helixevo/dashboard && npm install && npx next dev --port ${preferredPort}`);
|
|
12938
12951
|
process.exit(1);
|
|
12939
12952
|
}
|
|
12940
12953
|
if (!existsSync11(join17(dir, "node_modules"))) {
|
|
12941
12954
|
console.log(" Installing dashboard dependencies...");
|
|
12942
12955
|
try {
|
|
12943
12956
|
execSync2("npm install --no-audit --no-fund", { cwd: dir, stdio: "inherit" });
|
|
12944
|
-
} catch
|
|
12957
|
+
} catch {
|
|
12945
12958
|
console.error(" Failed to install dashboard dependencies.");
|
|
12946
12959
|
console.error(" Try running manually:");
|
|
12947
12960
|
console.error(` cd ${dir} && npm install`);
|
|
@@ -12949,43 +12962,39 @@ async function dashboardCommand(options) {
|
|
|
12949
12962
|
}
|
|
12950
12963
|
}
|
|
12951
12964
|
ensureSkillGraph();
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
12963
|
-
|
|
12964
|
-
|
|
12965
|
-
|
|
12966
|
-
child.unref();
|
|
12967
|
-
writeFileSync10(join17(homedir4(), ".helix", "dashboard.pid"), String(child.pid));
|
|
12968
|
-
console.log(` \uD83C\uDF10 HelixEvo Dashboard v${currentVersion2} running in background`);
|
|
12969
|
-
console.log(` http://localhost:3847`);
|
|
12970
|
-
console.log(` Logs: ${logFile}`);
|
|
12971
|
-
console.log(` Stop: helixevo dashboard --stop
|
|
12965
|
+
let target;
|
|
12966
|
+
try {
|
|
12967
|
+
target = await resolveLaunchTarget(preferredPort);
|
|
12968
|
+
} catch (error) {
|
|
12969
|
+
console.error(` ${error.message}`);
|
|
12970
|
+
process.exit(1);
|
|
12971
|
+
}
|
|
12972
|
+
const currentVersion = readCurrentVersion();
|
|
12973
|
+
if (target.source === "existing" && target.state) {
|
|
12974
|
+
const url = getDashboardUrl(target.state.port);
|
|
12975
|
+
console.log(` ✓ HelixEvo Dashboard is already running at ${url}`);
|
|
12976
|
+
if (options.background) {
|
|
12977
|
+
console.log(` Logs: ${DASHBOARD_LOG_FILE}`);
|
|
12978
|
+
console.log(` Stop: helixevo dashboard --stop
|
|
12972
12979
|
`);
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
} catch {}
|
|
12979
|
-
}, 2000);
|
|
12980
|
-
process.exit(0);
|
|
12980
|
+
} else {
|
|
12981
|
+
console.log("");
|
|
12982
|
+
}
|
|
12983
|
+
openDashboardUrl(target.state.port);
|
|
12984
|
+
return;
|
|
12981
12985
|
}
|
|
12982
|
-
|
|
12983
|
-
|
|
12984
|
-
|
|
12985
|
-
} catch {}
|
|
12986
|
-
console.log(` \uD83C\uDF10 Starting HelixEvo Dashboard v${currentVersion} at http://localhost:3847
|
|
12986
|
+
if (target.source === "fallback") {
|
|
12987
|
+
console.log(` Port ${preferredPort} is already in use.`);
|
|
12988
|
+
console.log(` Falling forward to ${getDashboardUrl(target.port)} instead.
|
|
12987
12989
|
`);
|
|
12988
|
-
|
|
12990
|
+
}
|
|
12991
|
+
if (options.background) {
|
|
12992
|
+
launchBackgroundDashboard(dir, target.port, currentVersion);
|
|
12993
|
+
return;
|
|
12994
|
+
}
|
|
12995
|
+
console.log(` \uD83C\uDF10 Starting HelixEvo Dashboard v${currentVersion} at ${getDashboardUrl(target.port)}
|
|
12996
|
+
`);
|
|
12997
|
+
launchDashboard(dir, true, target.port);
|
|
12989
12998
|
}
|
|
12990
12999
|
async function maybeAutoUpdateAndRelaunch(options) {
|
|
12991
13000
|
const update = await getUpdateInfo(VERSION);
|
|
@@ -13015,7 +13024,9 @@ async function maybeAutoUpdateAndRelaunch(options) {
|
|
|
13015
13024
|
args.push("--background");
|
|
13016
13025
|
if (options.autoUpdate === false)
|
|
13017
13026
|
args.push("--no-auto-update");
|
|
13018
|
-
|
|
13027
|
+
if (options.port !== undefined)
|
|
13028
|
+
args.push("--port", String(options.port));
|
|
13029
|
+
await new Promise((_resolve, reject) => {
|
|
13019
13030
|
const child = spawn2(executable, args, {
|
|
13020
13031
|
stdio: "inherit",
|
|
13021
13032
|
env: { ...process.env, [DASHBOARD_SKIP_AUTO_UPDATE_ENV]: "1" }
|
|
@@ -13027,30 +13038,49 @@ async function maybeAutoUpdateAndRelaunch(options) {
|
|
|
13027
13038
|
});
|
|
13028
13039
|
return true;
|
|
13029
13040
|
}
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13041
|
+
function launchBackgroundDashboard(dir, port, version) {
|
|
13042
|
+
mkdirSync5(HELIX_HOME_DIR, { recursive: true });
|
|
13043
|
+
const out = openSync(DASHBOARD_LOG_FILE, "a");
|
|
13044
|
+
const err = openSync(DASHBOARD_LOG_FILE, "a");
|
|
13045
|
+
const child = spawn2("npx", ["next", "dev", "--port", String(port)], {
|
|
13046
|
+
cwd: dir,
|
|
13047
|
+
stdio: ["ignore", out, err],
|
|
13048
|
+
env: { ...process.env, HELIXEVO_VERSION: version },
|
|
13049
|
+
detached: true
|
|
13050
|
+
});
|
|
13051
|
+
child.unref();
|
|
13052
|
+
writeDashboardState({
|
|
13053
|
+
pid: child.pid ?? -1,
|
|
13054
|
+
port,
|
|
13055
|
+
version,
|
|
13056
|
+
startedAt: new Date().toISOString()
|
|
13057
|
+
});
|
|
13058
|
+
console.log(` \uD83C\uDF10 HelixEvo Dashboard v${version} running in background`);
|
|
13059
|
+
console.log(` ${getDashboardUrl(port)}`);
|
|
13060
|
+
console.log(` Logs: ${DASHBOARD_LOG_FILE}`);
|
|
13061
|
+
console.log(` Stop: helixevo dashboard --stop
|
|
13062
|
+
`);
|
|
13063
|
+
setTimeout(() => {
|
|
13064
|
+
openDashboardUrl(port);
|
|
13065
|
+
}, 2000);
|
|
13066
|
+
process.exit(0);
|
|
13067
|
+
}
|
|
13068
|
+
function launchDashboard(dir, openBrowser, port) {
|
|
13069
|
+
const currentVersion = readCurrentVersion();
|
|
13070
|
+
const child = spawn2("npx", ["next", "dev", "--port", String(port)], {
|
|
13037
13071
|
cwd: dir,
|
|
13038
13072
|
stdio: "inherit",
|
|
13039
13073
|
env: { ...process.env, HELIXEVO_VERSION: currentVersion }
|
|
13040
13074
|
});
|
|
13041
13075
|
if (openBrowser) {
|
|
13042
13076
|
setTimeout(() => {
|
|
13043
|
-
|
|
13044
|
-
const platform = process.platform;
|
|
13045
|
-
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
13046
|
-
execSync2(`${cmd} http://localhost:3847`, { stdio: "ignore" });
|
|
13047
|
-
} catch {}
|
|
13077
|
+
openDashboardUrl(port);
|
|
13048
13078
|
}, 3000);
|
|
13049
13079
|
}
|
|
13050
13080
|
child.on("close", (code) => {
|
|
13051
13081
|
if (code === RESTART_EXIT_CODE) {
|
|
13052
13082
|
console.log(`
|
|
13053
|
-
\uD83D\uDD04 Restarting dashboard after update...
|
|
13083
|
+
\uD83D\uDD04 Restarting dashboard after update on ${getDashboardUrl(port)}...
|
|
13054
13084
|
`);
|
|
13055
13085
|
setTimeout(() => {
|
|
13056
13086
|
const nextCache = join17(dir, ".next");
|
|
@@ -13060,19 +13090,138 @@ function launchDashboard(dir, openBrowser) {
|
|
|
13060
13090
|
} catch {}
|
|
13061
13091
|
}
|
|
13062
13092
|
ensureSkillGraph();
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
updatedVersion = execSync2("helixevo --version 2>/dev/null", { encoding: "utf-8" }).trim() || currentVersion;
|
|
13066
|
-
} catch {}
|
|
13067
|
-
console.log(` \uD83C\uDF10 HelixEvo Dashboard v${updatedVersion} at http://localhost:3847
|
|
13093
|
+
const updatedVersion = readCurrentVersion(currentVersion);
|
|
13094
|
+
console.log(` \uD83C\uDF10 HelixEvo Dashboard v${updatedVersion} at ${getDashboardUrl(port)}
|
|
13068
13095
|
`);
|
|
13069
|
-
launchDashboard(dir, false);
|
|
13096
|
+
launchDashboard(dir, false, port);
|
|
13070
13097
|
}, 2000);
|
|
13071
13098
|
} else {
|
|
13072
13099
|
process.exit(code ?? 0);
|
|
13073
13100
|
}
|
|
13074
13101
|
});
|
|
13075
13102
|
}
|
|
13103
|
+
async function resolveLaunchTarget(preferredPort) {
|
|
13104
|
+
const existingState = readDashboardState();
|
|
13105
|
+
if (existingState && existingState.port === preferredPort) {
|
|
13106
|
+
return { port: preferredPort, source: "existing", state: existingState };
|
|
13107
|
+
}
|
|
13108
|
+
if (await isPortAvailable(preferredPort)) {
|
|
13109
|
+
return { port: preferredPort, source: "preferred" };
|
|
13110
|
+
}
|
|
13111
|
+
const fallbackPort = await findAvailablePort(preferredPort + 1);
|
|
13112
|
+
return { port: fallbackPort, source: "fallback" };
|
|
13113
|
+
}
|
|
13114
|
+
function stopDashboard() {
|
|
13115
|
+
const state = readDashboardState();
|
|
13116
|
+
if (!state) {
|
|
13117
|
+
console.log(" No background dashboard running.");
|
|
13118
|
+
return;
|
|
13119
|
+
}
|
|
13120
|
+
try {
|
|
13121
|
+
process.kill(state.pid, "SIGTERM");
|
|
13122
|
+
} catch {}
|
|
13123
|
+
clearDashboardState();
|
|
13124
|
+
console.log(` Dashboard stopped (${getDashboardUrl(state.port)}).`);
|
|
13125
|
+
}
|
|
13126
|
+
function parseDashboardPort(port) {
|
|
13127
|
+
if (port === undefined)
|
|
13128
|
+
return DEFAULT_DASHBOARD_PORT;
|
|
13129
|
+
const parsed = typeof port === "number" ? port : Number.parseInt(String(port), 10);
|
|
13130
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
13131
|
+
console.error(` Invalid dashboard port: ${port}`);
|
|
13132
|
+
process.exit(1);
|
|
13133
|
+
}
|
|
13134
|
+
return parsed;
|
|
13135
|
+
}
|
|
13136
|
+
function readCurrentVersion(fallback = VERSION) {
|
|
13137
|
+
try {
|
|
13138
|
+
return execSync2("helixevo --version 2>/dev/null", { encoding: "utf-8" }).trim() || fallback;
|
|
13139
|
+
} catch {
|
|
13140
|
+
return fallback;
|
|
13141
|
+
}
|
|
13142
|
+
}
|
|
13143
|
+
function getDashboardUrl(port) {
|
|
13144
|
+
return `http://localhost:${port}`;
|
|
13145
|
+
}
|
|
13146
|
+
function openDashboardUrl(port) {
|
|
13147
|
+
try {
|
|
13148
|
+
const platform = process.platform;
|
|
13149
|
+
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
13150
|
+
execSync2(`${cmd} ${getDashboardUrl(port)}`, { stdio: "ignore" });
|
|
13151
|
+
} catch {}
|
|
13152
|
+
}
|
|
13153
|
+
function writeDashboardState(state) {
|
|
13154
|
+
mkdirSync5(HELIX_HOME_DIR, { recursive: true });
|
|
13155
|
+
writeFileSync10(DASHBOARD_PID_FILE, String(state.pid));
|
|
13156
|
+
writeFileSync10(DASHBOARD_STATE_FILE, JSON.stringify(state, null, 2));
|
|
13157
|
+
}
|
|
13158
|
+
function readDashboardState() {
|
|
13159
|
+
let state = null;
|
|
13160
|
+
if (existsSync11(DASHBOARD_STATE_FILE)) {
|
|
13161
|
+
try {
|
|
13162
|
+
const parsed = JSON.parse(readFileSync9(DASHBOARD_STATE_FILE, "utf-8"));
|
|
13163
|
+
if (Number.isInteger(parsed.pid) && Number.isInteger(parsed.port)) {
|
|
13164
|
+
state = parsed;
|
|
13165
|
+
}
|
|
13166
|
+
} catch {}
|
|
13167
|
+
}
|
|
13168
|
+
if (!state && existsSync11(DASHBOARD_PID_FILE)) {
|
|
13169
|
+
try {
|
|
13170
|
+
const pid = Number.parseInt(readFileSync9(DASHBOARD_PID_FILE, "utf-8").trim(), 10);
|
|
13171
|
+
if (Number.isInteger(pid)) {
|
|
13172
|
+
state = {
|
|
13173
|
+
pid,
|
|
13174
|
+
port: DEFAULT_DASHBOARD_PORT,
|
|
13175
|
+
version: VERSION,
|
|
13176
|
+
startedAt: ""
|
|
13177
|
+
};
|
|
13178
|
+
}
|
|
13179
|
+
} catch {}
|
|
13180
|
+
}
|
|
13181
|
+
if (!state)
|
|
13182
|
+
return null;
|
|
13183
|
+
if (state.pid <= 0 || !isProcessAlive(state.pid)) {
|
|
13184
|
+
clearDashboardState();
|
|
13185
|
+
return null;
|
|
13186
|
+
}
|
|
13187
|
+
return state;
|
|
13188
|
+
}
|
|
13189
|
+
function clearDashboardState() {
|
|
13190
|
+
try {
|
|
13191
|
+
rmSync2(DASHBOARD_PID_FILE, { force: true });
|
|
13192
|
+
} catch {}
|
|
13193
|
+
try {
|
|
13194
|
+
rmSync2(DASHBOARD_STATE_FILE, { force: true });
|
|
13195
|
+
} catch {}
|
|
13196
|
+
}
|
|
13197
|
+
function isProcessAlive(pid) {
|
|
13198
|
+
try {
|
|
13199
|
+
process.kill(pid, 0);
|
|
13200
|
+
return true;
|
|
13201
|
+
} catch {
|
|
13202
|
+
return false;
|
|
13203
|
+
}
|
|
13204
|
+
}
|
|
13205
|
+
async function isPortAvailable(port) {
|
|
13206
|
+
return await new Promise((resolve) => {
|
|
13207
|
+
const server = createServer();
|
|
13208
|
+
server.unref();
|
|
13209
|
+
server.once("error", () => {
|
|
13210
|
+
resolve(false);
|
|
13211
|
+
});
|
|
13212
|
+
server.listen(port, () => {
|
|
13213
|
+
server.close(() => resolve(true));
|
|
13214
|
+
});
|
|
13215
|
+
});
|
|
13216
|
+
}
|
|
13217
|
+
async function findAvailablePort(startPort) {
|
|
13218
|
+
for (let attempt = 0;attempt < MAX_PORT_SCAN_ATTEMPTS; attempt += 1) {
|
|
13219
|
+
const candidate = startPort + attempt;
|
|
13220
|
+
if (await isPortAvailable(candidate))
|
|
13221
|
+
return candidate;
|
|
13222
|
+
}
|
|
13223
|
+
throw new Error(`Unable to find an open dashboard port after checking ${MAX_PORT_SCAN_ATTEMPTS} ports starting at ${startPort}.`);
|
|
13224
|
+
}
|
|
13076
13225
|
function prepareDashboard() {
|
|
13077
13226
|
const devDir = findDevDashboard();
|
|
13078
13227
|
if (devDir)
|
|
@@ -14007,9 +14156,6 @@ async function projectSetupCommand(projectPath, options) {
|
|
|
14007
14156
|
}
|
|
14008
14157
|
|
|
14009
14158
|
// src/cli.ts
|
|
14010
|
-
import { join as join21 } from "node:path";
|
|
14011
|
-
import { homedir as homedir6 } from "node:os";
|
|
14012
|
-
import { existsSync as existsSync16, readFileSync as readFileSync13, rmSync as rmSync3 } from "node:fs";
|
|
14013
14159
|
var program2 = new Command;
|
|
14014
14160
|
program2.name("helixevo").description("Self-evolving skill ecosystem for AI agents").version(VERSION).addHelpText("after", `
|
|
14015
14161
|
Examples:
|
|
@@ -14037,21 +14183,7 @@ program2.command("generalize").description("Promote cross-skill patterns to high
|
|
|
14037
14183
|
program2.command("specialize").description("Create project-specific skills ↓ --project <name> [--dry-run] [--verbose]").requiredOption("--project <name>", "Project name").option("--dry-run", "Show candidates without applying").option("--verbose", "Show detailed analysis").action(specializeCommand);
|
|
14038
14184
|
program2.command("graph").description("Skill network [--mermaid] [--obsidian <path>] [--rebuild] [--optimize]").option("--mermaid", "Render as Mermaid diagram in browser").option("--rebuild", "Force rebuild (re-infer relationships via LLM)").option("--obsidian <path>", "Sync to Obsidian vault at path").option("--optimize", "Run network optimization (merge/split/conflict detection)").option("--verbose", "Show detailed analysis").action(graphCommand);
|
|
14039
14185
|
program2.command("research").description("Proactive research via web [--project <path>] [--dry-run] [--verbose]").option("--project <path>", "Project path for goal extraction").option("--dry-run", "Show discoveries without creating skills").option("--verbose", "Show detailed research steps").option("--max-hypotheses <n>", "Max hypotheses to test", "3").action(researchCommand);
|
|
14040
|
-
program2.command("dashboard").description("Open web dashboard
|
|
14041
|
-
if (options.stop) {
|
|
14042
|
-
const pidFile = join21(homedir6(), ".helix", "dashboard.pid");
|
|
14043
|
-
if (existsSync16(pidFile)) {
|
|
14044
|
-
const pid = parseInt(readFileSync13(pidFile, "utf-8").trim());
|
|
14045
|
-
try {
|
|
14046
|
-
process.kill(pid, "SIGTERM");
|
|
14047
|
-
} catch {}
|
|
14048
|
-
rmSync3(pidFile);
|
|
14049
|
-
console.log(" Dashboard stopped.");
|
|
14050
|
-
} else {
|
|
14051
|
-
console.log(" No background dashboard running.");
|
|
14052
|
-
}
|
|
14053
|
-
return;
|
|
14054
|
-
}
|
|
14186
|
+
program2.command("dashboard").description("Open web dashboard (default: http://localhost:3847, falls forward if occupied)").option("--background", "Run in background (detach from terminal)").option("--stop", "Stop a background dashboard").option("--port <port>", "Preferred port to try first (default: 3847)").option("--no-auto-update", "Skip automatic update check before launching the dashboard").action(async (options) => {
|
|
14055
14187
|
await dashboardCommand(options);
|
|
14056
14188
|
});
|
|
14057
14189
|
program2.command("status").description("Show frontier, skills, failures, and network health").action(statusCommand);
|
package/package.json
CHANGED