@hacksmith/doraval 0.2.44 → 0.2.45

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/README.md CHANGED
@@ -144,6 +144,17 @@ doraval journal update # pull latest from remote
144
144
 
145
145
  Requires the GitHub CLI (`gh`). Journal lives in a private GitHub repo you control.
146
146
 
147
+ ### `ui` — Local dashboard (avoid typing repetitive commands)
148
+
149
+ ```bash
150
+ doraval ui # start the web dashboard (opens browser)
151
+ doraval ui --port 4921 # different port
152
+ doraval ui --status # check if running + show URL
153
+ doraval ui --force # force restart
154
+ ```
155
+
156
+ Re-running `doraval ui` is now idempotent (uses PID tracking). Use `--force` to restart.
157
+
147
158
  doraval update # self-update doraval to the latest version
148
159
  doraval claude new # interactive wizard for skills/plugins (follows official table)
149
160
 
package/bin/doraval.js CHANGED
@@ -599,7 +599,7 @@ var init_dist = __esm(() => {
599
599
  var require_package = __commonJS((exports, module) => {
600
600
  module.exports = {
601
601
  name: "@hacksmith/doraval",
602
- version: "0.2.44",
602
+ version: "0.2.45",
603
603
  author: "Saif",
604
604
  repository: {
605
605
  type: "git",
@@ -4367,7 +4367,7 @@ var exports_ui = {};
4367
4367
  __export(exports_ui, {
4368
4368
  default: () => ui_default
4369
4369
  });
4370
- import { existsSync as existsSync19, readdirSync as readdirSync9 } from "fs";
4370
+ import { existsSync as existsSync19, readdirSync as readdirSync9, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync2 } from "fs";
4371
4371
  import { join as join18 } from "path";
4372
4372
  import { spawn } from "child_process";
4373
4373
  function slugify2(title) {
@@ -4457,6 +4457,34 @@ async function killPort(port) {
4457
4457
  await new Promise((r) => setTimeout(r, 400));
4458
4458
  } catch {}
4459
4459
  }
4460
+ function readPid() {
4461
+ const file = getPidFile();
4462
+ if (!existsSync19(file))
4463
+ return null;
4464
+ try {
4465
+ const raw = readFileSync2(file, "utf8").trim();
4466
+ const pid = parseInt(raw, 10);
4467
+ if (isNaN(pid))
4468
+ return null;
4469
+ process.kill(pid, 0);
4470
+ return pid;
4471
+ } catch {
4472
+ try {
4473
+ unlinkSync2(file);
4474
+ } catch {}
4475
+ return null;
4476
+ }
4477
+ }
4478
+ function writePid(pid) {
4479
+ ensureDoravalDirs();
4480
+ writeFileSync6(getPidFile(), String(pid) + `
4481
+ `);
4482
+ }
4483
+ function removePid() {
4484
+ try {
4485
+ unlinkSync2(getPidFile());
4486
+ } catch {}
4487
+ }
4460
4488
  async function getDashboardHtml() {
4461
4489
  const isSource = import.meta.url.includes("/src/");
4462
4490
  const htmlPath = isSource ? new URL("../../ui/index.html", import.meta.url) : new URL("./ui/index.html", import.meta.url);
@@ -4467,7 +4495,7 @@ async function getDashboardHtml() {
4467
4495
  return `<!doctype html><meta charset="utf-8"><body style="font-family:monospace;background:#111;color:#ddd;padding:2rem"><h1>doraval ui</h1><p>Dashboard HTML missing.</p><pre>${String(err)}</pre></body>`;
4468
4496
  }
4469
4497
  }
4470
- var import_picocolors16, DEFAULT_PORT = 3737, ui_default;
4498
+ var import_picocolors16, DEFAULT_PORT = 3737, getPidFile = () => join18(getDoravalDir(), "ui.pid"), ui_default;
4471
4499
  var init_ui = __esm(() => {
4472
4500
  init_journal_config();
4473
4501
  init_journal_parse();
@@ -4479,7 +4507,42 @@ var init_ui = __esm(() => {
4479
4507
  const port = Number(args.port) || DEFAULT_PORT;
4480
4508
  const host = args.host || "127.0.0.1";
4481
4509
  const shouldOpen = args.open !== false;
4482
- await killPort(port);
4510
+ const showStatusOnly = !!args.status;
4511
+ const force = !!args.force;
4512
+ ensureDoravalDirs();
4513
+ const existingPid = readPid();
4514
+ if (showStatusOnly) {
4515
+ if (existingPid) {
4516
+ const url2 = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
4517
+ console.error(` Dashboard running (pid ${existingPid})`);
4518
+ console.error(` URL: ${import_picocolors16.default.underline(import_picocolors16.default.cyan(url2))}`);
4519
+ } else {
4520
+ console.error(` No dashboard running.`);
4521
+ }
4522
+ return;
4523
+ }
4524
+ if (existingPid && !force) {
4525
+ const url2 = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
4526
+ console.error(` Dashboard already running (pid ${existingPid}).`);
4527
+ console.error(` URL: ${import_picocolors16.default.underline(import_picocolors16.default.cyan(url2))}`);
4528
+ if (shouldOpen && process.stdout.isTTY) {
4529
+ try {
4530
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
4531
+ spawn(opener, [url2], { stdio: "ignore", detached: true }).unref();
4532
+ } catch {}
4533
+ }
4534
+ return;
4535
+ }
4536
+ if (existingPid && force) {
4537
+ console.error(` Force restarting (killing pid ${existingPid})...`);
4538
+ try {
4539
+ process.kill(existingPid, "SIGTERM");
4540
+ } catch {}
4541
+ await new Promise((r) => setTimeout(r, 400));
4542
+ removePid();
4543
+ } else if (!existingPid) {
4544
+ await killPort(port);
4545
+ }
4483
4546
  const config = await readConfig();
4484
4547
  let project = resolveProjectName(config) ?? undefined;
4485
4548
  if (project) {
@@ -4588,6 +4651,7 @@ var init_ui = __esm(() => {
4588
4651
  }
4589
4652
  });
4590
4653
  const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${server.port}`;
4654
+ writePid(process.pid);
4591
4655
  const msg = `
4592
4656
  ${import_picocolors16.default.blue("\u25C9")} dora local dashboard
4593
4657
  ${import_picocolors16.default.dim("Project:")} ${project ? import_picocolors16.default.white(project) : import_picocolors16.default.yellow("none (run dora init)")}
@@ -4604,12 +4668,15 @@ var init_ui = __esm(() => {
4604
4668
  console.error(import_picocolors16.default.dim(` Could not auto-open. Visit ${url}`));
4605
4669
  }
4606
4670
  }
4607
- process.on("SIGINT", () => {
4671
+ const cleanup = () => {
4672
+ removePid();
4608
4673
  console.error(`
4609
4674
  Stopping dashboard...`);
4610
4675
  server.stop();
4611
4676
  process.exit(0);
4612
- });
4677
+ };
4678
+ process.on("SIGINT", cleanup);
4679
+ process.on("SIGTERM", cleanup);
4613
4680
  }
4614
4681
  };
4615
4682
  });
@@ -7733,7 +7800,7 @@ var exports_completion = {};
7733
7800
  __export(exports_completion, {
7734
7801
  default: () => completion_default
7735
7802
  });
7736
- var commands, subCommands, completion_default;
7803
+ var commands, uiFlags, subCommands, completion_default;
7737
7804
  var init_completion = __esm(() => {
7738
7805
  init_dist();
7739
7806
  commands = [
@@ -7750,6 +7817,7 @@ var init_completion = __esm(() => {
7750
7817
  "cursor",
7751
7818
  "copilot"
7752
7819
  ];
7820
+ uiFlags = ["--port", "--open", "--no-open", "--host", "--status", "--force"];
7753
7821
  subCommands = {
7754
7822
  skill: ["validate", "drift", "judge"],
7755
7823
  journal: ["init", "list", "context", "hook", "update", "add", "sync"],
@@ -7788,6 +7856,7 @@ _doraval_completions() {
7788
7856
  skill) COMPREPLY=( $(compgen -W "${subCommands.skill.join(" ")}" -- "$cur") ) ;;
7789
7857
  journal) COMPREPLY=( $(compgen -W "${subCommands.journal.join(" ")}" -- "$cur") ) ;;
7790
7858
  hook) COMPREPLY=( $(compgen -W "${subCommands.hook.join(" ")}" -- "$cur") ) ;;
7859
+ ui) COMPREPLY=( $(compgen -W "${uiFlags.join(" ")}" -- "$cur") ) ;;
7791
7860
  claude|codex|cursor|copilot) COMPREPLY=( $(compgen -W "${subCommands.claude.join(" ")}" -- "$cur") ) ;;
7792
7861
  esac
7793
7862
  fi
@@ -7820,6 +7889,9 @@ _doraval() {
7820
7889
  hook)
7821
7890
  _describe 'subcommand' (enable disable status)
7822
7891
  ;;
7892
+ ui)
7893
+ _describe 'flag' (${uiFlags})
7894
+ ;;
7823
7895
  claude|codex|cursor|copilot)
7824
7896
  _describe 'subcommand' (new bump)
7825
7897
  ;;
@@ -7838,6 +7910,12 @@ complete -c doraval -n '__fish_use_subcommand' -a 'validate init bump update pro
7838
7910
  complete -c doraval -n '__fish_seen_subcommand_from skill' -a 'validate drift judge'
7839
7911
  complete -c doraval -n '__fish_seen_subcommand_from journal' -a 'init list context hook update add sync'
7840
7912
  complete -c doraval -n '__fish_seen_subcommand_from hook' -a 'enable disable status'
7913
+ complete -c doraval -n '__fish_seen_subcommand_from ui' -l port -d 'Port'
7914
+ complete -c doraval -n '__fish_seen_subcommand_from ui' -l open -d 'Open browser'
7915
+ complete -c doraval -n '__fish_seen_subcommand_from ui' -l no-open -d 'Do not open browser'
7916
+ complete -c doraval -n '__fish_seen_subcommand_from ui' -l host -d 'Host'
7917
+ complete -c doraval -n '__fish_seen_subcommand_from ui' -l status -d 'Show status only'
7918
+ complete -c doraval -n '__fish_seen_subcommand_from ui' -l force -d 'Force restart'
7841
7919
  complete -c doraval -n '__fish_seen_subcommand_from claude codex cursor copilot' -a 'new bump'
7842
7920
  `);
7843
7921
  } else {
@@ -7975,6 +8053,16 @@ var ui2 = defineCommand({
7975
8053
  type: "string",
7976
8054
  description: "Host to bind (default 127.0.0.1 for local only)",
7977
8055
  default: "127.0.0.1"
8056
+ },
8057
+ status: {
8058
+ type: "boolean",
8059
+ description: "Check if a dashboard is running and print its URL (no start)",
8060
+ default: false
8061
+ },
8062
+ force: {
8063
+ type: "boolean",
8064
+ description: "Force start/restart even if one is already running",
8065
+ default: false
7978
8066
  }
7979
8067
  },
7980
8068
  async run({ args }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hacksmith/doraval",
3
- "version": "0.2.44",
3
+ "version": "0.2.45",
4
4
  "author": "Saif",
5
5
  "repository": {
6
6
  "type": "git",