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 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 at localhost:3847 |
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
- # Opens http://localhost:3847
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 now auto-updates itself before opening the dashboard unless you opt out.',
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. By default it now upgrades itself first when a newer release is available.',
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 at localhost:3847. Before launch, HelixEvo checks npm for a newer version and auto-updates itself unless you opt out.',
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 HELIX_DASHBOARD_DIR = join17(homedir4(), ".helix", "dashboard");
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(" cd helixevo/dashboard && npm install && npx next dev --port 3847");
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 (err) {
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
- if (options.background) {
12953
- const logFile = join17(homedir4(), ".helix", "dashboard.log");
12954
- const out = __require("fs").openSync(logFile, "a");
12955
- const err = __require("fs").openSync(logFile, "a");
12956
- let currentVersion2 = VERSION;
12957
- try {
12958
- currentVersion2 = execSync2("helixevo --version 2>/dev/null", { encoding: "utf-8" }).trim() || VERSION;
12959
- } catch {}
12960
- const child = spawn2("npx", ["next", "dev", "--port", "3847"], {
12961
- cwd: dir,
12962
- stdio: ["ignore", out, err],
12963
- env: { ...process.env, HELIXEVO_VERSION: currentVersion2 },
12964
- detached: true
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
- setTimeout(() => {
12974
- try {
12975
- const platform = process.platform;
12976
- const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
12977
- execSync2(`${cmd} http://localhost:3847`, { stdio: "ignore" });
12978
- } catch {}
12979
- }, 2000);
12980
- process.exit(0);
12980
+ } else {
12981
+ console.log("");
12982
+ }
12983
+ openDashboardUrl(target.state.port);
12984
+ return;
12981
12985
  }
12982
- let currentVersion = VERSION;
12983
- try {
12984
- currentVersion = execSync2("helixevo --version 2>/dev/null", { encoding: "utf-8" }).trim() || VERSION;
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
- launchDashboard(dir, true);
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
- await new Promise((resolve, reject) => {
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
- var RESTART_EXIT_CODE = 75;
13031
- function launchDashboard(dir, openBrowser) {
13032
- let currentVersion = VERSION;
13033
- try {
13034
- currentVersion = execSync2("helixevo --version 2>/dev/null", { encoding: "utf-8" }).trim() || VERSION;
13035
- } catch {}
13036
- const child = spawn2("npx", ["next", "dev", "--port", "3847"], {
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
- try {
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
- let updatedVersion = currentVersion;
13064
- try {
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 at http://localhost:3847").option("--background", "Run in background (detach from terminal)").option("--stop", "Stop a background dashboard").option("--no-auto-update", "Skip automatic update check before launching the dashboard").action(async (options) => {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helixevo",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Co-evolving skill and project brain for AI agents, with ontology-aware learning, pressure modeling, and a premium dashboard.",
5
5
  "type": "module",
6
6
  "bin": {