gnhf 0.1.20 → 0.1.21

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 +9 -8
  2. package/dist/cli.mjs +54 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -46,6 +46,7 @@ You wake up to a branch full of clean work and a log of everything that happened
46
46
 
47
47
  - **Dead simple** — one command starts an autonomous loop that runs until you Ctrl+C or a configured runtime cap is reached
48
48
  - **Long running** — each iteration is committed on success, rolled back on failure, with sensible retries and exponential backoff
49
+ - **Live terminal title** — interactive runs keep your terminal title updated with live status, token totals, and commit count, then restore the previous title on exit
49
50
  - **Agent-agnostic** — works with Claude Code, Codex, Rovo Dev, or OpenCode out of the box
50
51
 
51
52
  ## Quick Start
@@ -166,14 +167,14 @@ Pass `--worktree` to run each agent in an isolated [git worktree](https://git-sc
166
167
 
167
168
  ### Flags
168
169
 
169
- | Flag | Description | Default |
170
- | ------------------------ | ------------------------------------------------------------------ | ---------------------- |
171
- | `--agent <agent>` | Agent to use (`claude`, `codex`, `rovodev`, or `opencode`) | config file (`claude`) |
172
- | `--max-iterations <n>` | Abort after `n` total iterations | unlimited |
173
- | `--max-tokens <n>` | Abort after `n` total input+output tokens | unlimited |
174
- | `--prevent-sleep <mode>` | Prevent system sleep during the run (`on`/`off` or `true`/`false`) | config file (`on`) |
175
- | `--worktree` | Run in a separate git worktree (enables multiple agents concurrently) | `false` |
176
- | `--version` | Show version | |
170
+ | Flag | Description | Default |
171
+ | ------------------------ | --------------------------------------------------------------------- | ---------------------- |
172
+ | `--agent <agent>` | Agent to use (`claude`, `codex`, `rovodev`, or `opencode`) | config file (`claude`) |
173
+ | `--max-iterations <n>` | Abort after `n` total iterations | unlimited |
174
+ | `--max-tokens <n>` | Abort after `n` total input+output tokens | unlimited |
175
+ | `--prevent-sleep <mode>` | Prevent system sleep during the run (`on`/`off` or `true`/`false`) | config file (`on`) |
176
+ | `--worktree` | Run in a separate git worktree (enables multiple agents concurrently) | `false` |
177
+ | `--version` | Show version | |
177
178
 
178
179
  ## Configuration
179
180
 
package/dist/cli.mjs CHANGED
@@ -3530,6 +3530,24 @@ const DONE_HINT = "[ctrl+c to exit]";
3530
3530
  function spacedLabel(text) {
3531
3531
  return text.split("").join(" ");
3532
3532
  }
3533
+ function formatTokenCount(tokens, direction) {
3534
+ return `${formatTokens(tokens)} ${direction}`;
3535
+ }
3536
+ function formatCommitCount(commitCount) {
3537
+ return `${commitCount} ${commitCount === 1 ? "commit" : "commits"}`;
3538
+ }
3539
+ function buildTerminalTitle(state, now) {
3540
+ return `gnhf ${state.status === "running" || state.status === "waiting" ? getMoonPhase("active", now, MOON_PHASE_PERIOD) : state.status} · ${formatTokenCount(state.totalInputTokens, "in")} · ${formatTokenCount(state.totalOutputTokens, "out")} · ${formatCommitCount(state.commitCount)}`;
3541
+ }
3542
+ function emitTerminalTitle(title) {
3543
+ return `\x1b]2;${title}\x07`;
3544
+ }
3545
+ function saveTerminalTitle() {
3546
+ return "\x1B[22;0t";
3547
+ }
3548
+ function restoreTerminalTitle() {
3549
+ return "\x1B[23;0t";
3550
+ }
3533
3551
  function renderTitleCells(agentName) {
3534
3552
  return [
3535
3553
  [...textToCells(spacedLabel("gnhf"), "dim"), ...agentName ? [
@@ -3545,21 +3563,20 @@ function renderTitleCells(agentName) {
3545
3563
  ];
3546
3564
  }
3547
3565
  function renderStatsCells(elapsed, inputTokens, outputTokens, commitCount) {
3548
- const commitLabel = commitCount === 1 ? "commit" : "commits";
3549
3566
  return [
3550
3567
  ...textToCells(elapsed, "bold"),
3551
3568
  ...textToCells(" ", "normal"),
3552
3569
  ...textToCells("·", "dim"),
3553
3570
  ...textToCells(" ", "normal"),
3554
- ...textToCells(`${formatTokens(inputTokens)} in`, "normal"),
3571
+ ...textToCells(formatTokenCount(inputTokens, "in"), "normal"),
3555
3572
  ...textToCells(" ", "normal"),
3556
3573
  ...textToCells("·", "dim"),
3557
3574
  ...textToCells(" ", "normal"),
3558
- ...textToCells(`${formatTokens(outputTokens)} out`, "normal"),
3575
+ ...textToCells(formatTokenCount(outputTokens, "out"), "normal"),
3559
3576
  ...textToCells(" ", "normal"),
3560
3577
  ...textToCells("·", "dim"),
3561
3578
  ...textToCells(" ", "normal"),
3562
- ...textToCells(`${commitCount} ${commitLabel}`, "normal")
3579
+ ...textToCells(formatCommitCount(commitCount), "normal")
3563
3580
  ];
3564
3581
  }
3565
3582
  function renderAgentMessageCells(message, status) {
@@ -3775,10 +3792,22 @@ var Renderer = class {
3775
3792
  cachedWidth = 0;
3776
3793
  cachedHeight = 0;
3777
3794
  prevCells = [];
3795
+ prevTitle = null;
3796
+ titleSaved = false;
3778
3797
  isFirstFrame = true;
3779
3798
  seedTop;
3780
3799
  seedBottom;
3781
3800
  seedSide;
3801
+ handleState = (newState) => {
3802
+ this.state = {
3803
+ ...newState,
3804
+ iterations: [...newState.iterations]
3805
+ };
3806
+ this.updateTerminalTitle();
3807
+ };
3808
+ handleStopped = () => {
3809
+ this.stop("stopped");
3810
+ };
3782
3811
  constructor(orchestrator, prompt, agentName) {
3783
3812
  this.orchestrator = orchestrator;
3784
3813
  this.prompt = prompt;
@@ -3792,15 +3821,8 @@ var Renderer = class {
3792
3821
  });
3793
3822
  }
3794
3823
  start() {
3795
- this.orchestrator.on("state", (newState) => {
3796
- this.state = {
3797
- ...newState,
3798
- iterations: [...newState.iterations]
3799
- };
3800
- });
3801
- this.orchestrator.on("stopped", () => {
3802
- this.stop("stopped");
3803
- });
3824
+ this.orchestrator.on("state", this.handleState);
3825
+ this.orchestrator.on("stopped", this.handleStopped);
3804
3826
  if (process$1.stdin.isTTY) {
3805
3827
  process$1.stdin.setRawMode(true);
3806
3828
  process$1.stdin.resume();
@@ -3819,11 +3841,18 @@ var Renderer = class {
3819
3841
  clearInterval(this.interval);
3820
3842
  this.interval = null;
3821
3843
  }
3844
+ this.orchestrator.off("state", this.handleState);
3845
+ this.orchestrator.off("stopped", this.handleStopped);
3822
3846
  if (process$1.stdin.isTTY) {
3823
3847
  process$1.stdin.setRawMode(false);
3824
3848
  process$1.stdin.pause();
3825
3849
  process$1.stdin.removeAllListeners("data");
3826
3850
  }
3851
+ if (this.titleSaved) {
3852
+ process$1.stdout.write(restoreTerminalTitle());
3853
+ this.titleSaved = false;
3854
+ this.prevTitle = null;
3855
+ }
3827
3856
  this.exitResolve(reason);
3828
3857
  }
3829
3858
  waitUntilExit() {
@@ -3862,6 +3891,7 @@ var Renderer = class {
3862
3891
  const w = process$1.stdout.columns || 80;
3863
3892
  const h = process$1.stdout.rows || 24;
3864
3893
  const resized = this.ensureStarFields(w, h);
3894
+ this.updateTerminalTitle(now);
3865
3895
  const nextCells = buildFrameCells(this.prompt, this.agentName, this.state, this.topStars, this.bottomStars, this.sideStars, now, w, h);
3866
3896
  if (this.isFirstFrame || resized) {
3867
3897
  process$1.stdout.write("\x1B[H" + nextCells.map(rowToString).join("\n"));
@@ -3872,6 +3902,17 @@ var Renderer = class {
3872
3902
  }
3873
3903
  this.prevCells = nextCells;
3874
3904
  }
3905
+ updateTerminalTitle(now = Date.now()) {
3906
+ if (!process$1.stdout.isTTY) return;
3907
+ const nextTitle = buildTerminalTitle(this.state, now);
3908
+ if (!this.titleSaved) {
3909
+ process$1.stdout.write(saveTerminalTitle());
3910
+ this.titleSaved = true;
3911
+ }
3912
+ if (nextTitle === this.prevTitle) return;
3913
+ process$1.stdout.write(emitTerminalTitle(nextTitle));
3914
+ this.prevTitle = nextTitle;
3915
+ }
3875
3916
  };
3876
3917
  //#endregion
3877
3918
  //#region src/utils/slugify.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnhf",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Before I go to bed, I tell my agents: good night, have fun",
5
5
  "type": "module",
6
6
  "bin": {