@hacksmith/doraval 0.2.43 → 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.
Files changed (3) hide show
  1. package/README.md +12 -1
  2. package/bin/doraval.js +103 -16
  3. package/package.json +1 -1
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
 
@@ -168,7 +179,7 @@ Exits with code `1` when errors are found. Pipe `--format json` output to `jq` o
168
179
 
169
180
  ## Providers
170
181
 
171
- Claude Code validators built in. Cursor, Codex, and Windsurf coming next.
182
+ Claude Code, Cursor, Codex, and Copilot CLI validators and scaffolding built in. OpenCode support is experimental.
172
183
 
173
184
  ## Links
174
185
 
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.43",
602
+ version: "0.2.45",
603
603
  author: "Saif",
604
604
  repository: {
605
605
  type: "git",
@@ -1719,19 +1719,17 @@ var init_list = __esm(() => {
1719
1719
  if (!args.all) {
1720
1720
  allEntries = allEntries.filter((e) => e.status === "active");
1721
1721
  }
1722
- const staged = [];
1722
+ let staged = [];
1723
1723
  try {
1724
1724
  const pdir = getPendingProjectDir(project);
1725
1725
  if (existsSync4(pdir)) {
1726
1726
  const files = readdirSync(pdir).filter((f) => f.endsWith(".md") && f !== ".gitkeep");
1727
- for (const f of files) {
1727
+ const stagedResults = await Promise.all(files.map(async (f) => {
1728
1728
  const txt = await Bun.file(join3(pdir, f)).text();
1729
1729
  const parsed = parseJournalEntries(txt);
1730
- for (const e of parsed) {
1731
- e._staged = true;
1732
- staged.push(e);
1733
- }
1734
- }
1730
+ return parsed.map((e) => ({ ...e, _staged: true }));
1731
+ }));
1732
+ staged = stagedResults.flat();
1735
1733
  }
1736
1734
  } catch {}
1737
1735
  if (args.format === "json") {
@@ -4369,7 +4367,7 @@ var exports_ui = {};
4369
4367
  __export(exports_ui, {
4370
4368
  default: () => ui_default
4371
4369
  });
4372
- 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";
4373
4371
  import { join as join18 } from "path";
4374
4372
  import { spawn } from "child_process";
4375
4373
  function slugify2(title) {
@@ -4459,6 +4457,34 @@ async function killPort(port) {
4459
4457
  await new Promise((r) => setTimeout(r, 400));
4460
4458
  } catch {}
4461
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
+ }
4462
4488
  async function getDashboardHtml() {
4463
4489
  const isSource = import.meta.url.includes("/src/");
4464
4490
  const htmlPath = isSource ? new URL("../../ui/index.html", import.meta.url) : new URL("./ui/index.html", import.meta.url);
@@ -4469,7 +4495,7 @@ async function getDashboardHtml() {
4469
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>`;
4470
4496
  }
4471
4497
  }
4472
- var import_picocolors16, DEFAULT_PORT = 3737, ui_default;
4498
+ var import_picocolors16, DEFAULT_PORT = 3737, getPidFile = () => join18(getDoravalDir(), "ui.pid"), ui_default;
4473
4499
  var init_ui = __esm(() => {
4474
4500
  init_journal_config();
4475
4501
  init_journal_parse();
@@ -4481,7 +4507,42 @@ var init_ui = __esm(() => {
4481
4507
  const port = Number(args.port) || DEFAULT_PORT;
4482
4508
  const host = args.host || "127.0.0.1";
4483
4509
  const shouldOpen = args.open !== false;
4484
- 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
+ }
4485
4546
  const config = await readConfig();
4486
4547
  let project = resolveProjectName(config) ?? undefined;
4487
4548
  if (project) {
@@ -4590,6 +4651,7 @@ var init_ui = __esm(() => {
4590
4651
  }
4591
4652
  });
4592
4653
  const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${server.port}`;
4654
+ writePid(process.pid);
4593
4655
  const msg = `
4594
4656
  ${import_picocolors16.default.blue("\u25C9")} dora local dashboard
4595
4657
  ${import_picocolors16.default.dim("Project:")} ${project ? import_picocolors16.default.white(project) : import_picocolors16.default.yellow("none (run dora init)")}
@@ -4606,12 +4668,15 @@ var init_ui = __esm(() => {
4606
4668
  console.error(import_picocolors16.default.dim(` Could not auto-open. Visit ${url}`));
4607
4669
  }
4608
4670
  }
4609
- process.on("SIGINT", () => {
4671
+ const cleanup = () => {
4672
+ removePid();
4610
4673
  console.error(`
4611
4674
  Stopping dashboard...`);
4612
4675
  server.stop();
4613
4676
  process.exit(0);
4614
- });
4677
+ };
4678
+ process.on("SIGINT", cleanup);
4679
+ process.on("SIGTERM", cleanup);
4615
4680
  }
4616
4681
  };
4617
4682
  });
@@ -7735,7 +7800,7 @@ var exports_completion = {};
7735
7800
  __export(exports_completion, {
7736
7801
  default: () => completion_default
7737
7802
  });
7738
- var commands, subCommands, completion_default;
7803
+ var commands, uiFlags, subCommands, completion_default;
7739
7804
  var init_completion = __esm(() => {
7740
7805
  init_dist();
7741
7806
  commands = [
@@ -7746,11 +7811,13 @@ var init_completion = __esm(() => {
7746
7811
  "providers",
7747
7812
  "skill",
7748
7813
  "journal",
7814
+ "ui",
7749
7815
  "claude",
7750
7816
  "codex",
7751
7817
  "cursor",
7752
7818
  "copilot"
7753
7819
  ];
7820
+ uiFlags = ["--port", "--open", "--no-open", "--host", "--status", "--force"];
7754
7821
  subCommands = {
7755
7822
  skill: ["validate", "drift", "judge"],
7756
7823
  journal: ["init", "list", "context", "hook", "update", "add", "sync"],
@@ -7789,6 +7856,7 @@ _doraval_completions() {
7789
7856
  skill) COMPREPLY=( $(compgen -W "${subCommands.skill.join(" ")}" -- "$cur") ) ;;
7790
7857
  journal) COMPREPLY=( $(compgen -W "${subCommands.journal.join(" ")}" -- "$cur") ) ;;
7791
7858
  hook) COMPREPLY=( $(compgen -W "${subCommands.hook.join(" ")}" -- "$cur") ) ;;
7859
+ ui) COMPREPLY=( $(compgen -W "${uiFlags.join(" ")}" -- "$cur") ) ;;
7792
7860
  claude|codex|cursor|copilot) COMPREPLY=( $(compgen -W "${subCommands.claude.join(" ")}" -- "$cur") ) ;;
7793
7861
  esac
7794
7862
  fi
@@ -7801,7 +7869,7 @@ complete -F _doraval_completions doraval
7801
7869
 
7802
7870
  _doraval() {
7803
7871
  local -a commands sub
7804
- commands=(validate init bump update providers skill journal claude codex cursor copilot)
7872
+ commands=(validate init bump update providers skill journal ui claude codex cursor copilot)
7805
7873
  _arguments -C \\
7806
7874
  '1: :->cmd' \\
7807
7875
  '*::arg:->args'
@@ -7821,6 +7889,9 @@ _doraval() {
7821
7889
  hook)
7822
7890
  _describe 'subcommand' (enable disable status)
7823
7891
  ;;
7892
+ ui)
7893
+ _describe 'flag' (${uiFlags})
7894
+ ;;
7824
7895
  claude|codex|cursor|copilot)
7825
7896
  _describe 'subcommand' (new bump)
7826
7897
  ;;
@@ -7834,11 +7905,17 @@ _doraval "$@"
7834
7905
  } else if (shell === "fish") {
7835
7906
  console.log(`# doraval fish completion
7836
7907
  complete -c doraval -f
7837
- complete -c doraval -n '__fish_use_subcommand' -a 'validate init bump update providers skill journal claude codex cursor copilot'
7908
+ complete -c doraval -n '__fish_use_subcommand' -a 'validate init bump update providers skill journal ui claude codex cursor copilot'
7838
7909
 
7839
7910
  complete -c doraval -n '__fish_seen_subcommand_from skill' -a 'validate drift judge'
7840
7911
  complete -c doraval -n '__fish_seen_subcommand_from journal' -a 'init list context hook update add sync'
7841
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'
7842
7919
  complete -c doraval -n '__fish_seen_subcommand_from claude codex cursor copilot' -a 'new bump'
7843
7920
  `);
7844
7921
  } else {
@@ -7976,6 +8053,16 @@ var ui2 = defineCommand({
7976
8053
  type: "string",
7977
8054
  description: "Host to bind (default 127.0.0.1 for local only)",
7978
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
7979
8066
  }
7980
8067
  },
7981
8068
  async run({ args }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hacksmith/doraval",
3
- "version": "0.2.43",
3
+ "version": "0.2.45",
4
4
  "author": "Saif",
5
5
  "repository": {
6
6
  "type": "git",