gnhf 0.1.35 → 0.1.37

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 +28 -9
  2. package/dist/cli.mjs +285 -40
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -45,7 +45,7 @@ gnhf is a [ralph](https://ghuntley.com/ralph/), [autoresearch](https://github.co
45
45
  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 request stop or a configured runtime cap is reached
48
- - **Long running** — each iteration is committed on success, rolled back on failure, with sensible retries; retryable hard agent errors back off exponentially while agent-reported failures continue immediately
48
+ - **Long running** — each iteration is committed on success, rolled back on failure except commit failures preserved for repair, with sensible retries; retryable hard agent errors back off exponentially while agent-reported failures continue immediately
49
49
  - **Live terminal title** — interactive runs keep your terminal title updated with live status, token totals, and commit count, then clear or restore it on exit depending on terminal support; token totals prefixed with `~` are estimates
50
50
  - **Exit summary**: every run ends with a permanent summary covering elapsed time, branch, iterations, tokens, branch diff stats, local notes/log paths, and review commands
51
51
  - **Agent-agnostic**: works with Claude Code, Codex, Rovo Dev, OpenCode, GitHub Copilot CLI, Pi, or ACP targets out of the box
@@ -71,6 +71,11 @@ $ gnhf --worktree "add tests for module Y" &
71
71
  $ gnhf --worktree "refactor the API layer" &
72
72
  ```
73
73
 
74
+ ```sh
75
+ # Commit directly on the current branch and push after each successful iteration
76
+ $ gnhf --current-branch --push "keep improving this app"
77
+ ```
78
+
74
79
  Run `gnhf` from inside a Git repository with a clean working tree. If you are starting from a plain directory, run `git init` first.
75
80
  `gnhf` supports macOS, Linux, and Windows.
76
81
 
@@ -101,7 +106,7 @@ npm link
101
106
 
102
107
  ┌──────────────────────┐
103
108
  │ validate clean git │
104
- │ create gnhf/ branch │
109
+ │ create or use branch │
105
110
  │ write prompt.md │
106
111
  └──────────┬───────────┘
107
112
 
@@ -121,8 +126,8 @@ npm link
121
126
  yes │ │ no │
122
127
  ▼ ▼ │
123
128
  ┌──────────┐ ┌───────────┐ │
124
- │ commit │ │ git reset │ │
125
- │ append │ │ --hard │ │
129
+ │ commit │ │ reset or │ │
130
+ │ append │ │ repair │ │
126
131
  │ notes.md │ │ maybe wait│ │
127
132
  └────┬─────┘ └─────┬─────┘ │
128
133
  │ │ │
@@ -138,9 +143,9 @@ npm link
138
143
  └──────────────────────────────────────┘
139
144
  ```
140
145
 
141
- - **Incremental commits** - each successful iteration is a separate unsigned git commit, so you can cherry-pick or revert individual changes without GPG or SSH signing prompts blocking the run; if the first commit attempt fails, gnhf re-stages changes and retries with `--no-verify` so hook-mutated work is not stranded
142
- - **Failure handling** - all failed iterations are rolled back with `git reset --hard`; agent-reported failures proceed to the next iteration immediately, retryable hard agent errors use exponential backoff, and permanent agent errors such as Claude low credit balance abort immediately and print the run log path. Complete no-op iterations are reported as failures and count toward the consecutive-failure abort limit.
143
- - **Runtime caps** - `--max-iterations` stops before the next iteration begins, `--max-tokens` can abort mid-iteration once reported usage reaches the cap, and `--stop-when` ends the loop after an iteration whose agent output reports the natural-language condition is met; resumed runs reuse the saved stop condition unless you pass a new value, or `--stop-when ""` to clear it; uncommitted work is rolled back in either case, and in the interactive TUI the final state remains visible until you press Ctrl+C to exit
146
+ - **Incremental commits** - each successful iteration is a separate unsigned git commit, so you can cherry-pick or revert individual changes without GPG or SSH signing prompts blocking the run; if `git commit` fails, gnhf preserves the uncommitted work and asks the next agent iteration to repair it
147
+ - **Failure handling** - failed iterations are rolled back with `git reset --hard` except commit failures, which preserve uncommitted work for repair; agent-reported failures proceed to the next iteration immediately, retryable hard agent errors use exponential backoff, and permanent agent errors such as Claude low credit balance abort immediately and print the run log path. Complete no-op iterations are reported as failures and count toward the consecutive-failure abort limit. If the run exits with a pending commit failure, the exit summary warns that uncommitted changes were left for repair.
148
+ - **Runtime caps** - `--max-iterations` stops before the next iteration begins, `--max-tokens` can abort mid-iteration once reported usage reaches the cap, and `--stop-when` ends the loop after an iteration whose agent output reports the natural-language condition is met unless a commit failure needs repair first; resumed runs reuse the saved stop condition unless you pass a new value, or `--stop-when ""` to clear it; pending commit-failure repair work is preserved and other uncommitted work is rolled back, and in the interactive TUI the final state remains visible until you press Ctrl+C to exit
144
149
  - **Iteration finalization** - agents are expected to finish validation, stop any background processes they started, and only then emit the final JSON result for the iteration
145
150
  - **Graceful interrupts** - in the interactive TUI, the first Ctrl+C requests a graceful stop and lets the current iteration finish (or ends backoff early), the second Ctrl+C force-stops immediately, and `SIGTERM` also force-stops immediately
146
151
  - **Exit summary** - after shutdown cleanup, gnhf prints a permanent stdout summary with the final branch, elapsed time, iteration and token totals, branch diff stats, notes/debug-log paths, and review commands
@@ -148,6 +153,17 @@ npm link
148
153
  - **Local run metadata** — gnhf stores prompt, notes, stop conditions, and commit-message convention metadata under `.gnhf/runs/` and ignores it locally, so your branch only contains intentional work
149
154
  - **Resume support** — run `gnhf` while on an existing `gnhf/` branch to pick up where a previous run left off; if you provide a different prompt, gnhf asks whether to update the saved prompt and continue with the existing history, start a new branch, or quit. New runs whose generated branch already exists use a numeric suffix such as `gnhf/<slug>-1`.
150
155
 
156
+ ### Live Branch Mode
157
+
158
+ Pass `--current-branch` to run on the branch you are already on instead of creating a `gnhf/` branch.
159
+ Pass `--push` to push the current branch after each successful iteration.
160
+ Together, `--current-branch --push` is useful for loose projects where you want a deployed or locally watched branch to update throughout the run.
161
+
162
+ - Push failures abort the run after preserving the successful local commit.
163
+ - gnhf never force-pushes or auto-pulls for this mode.
164
+ - `--push` also works with the default `gnhf/` branch mode and sets `origin` as the upstream when needed.
165
+ - Do not combine `--current-branch` with `--worktree`; gnhf exits with an error because those modes choose different working directories.
166
+
151
167
  ### Worktree Mode
152
168
 
153
169
  Pass `--worktree` to run each agent in an isolated [git worktree](https://git-scm.com/docs/git-worktree). This lets you launch multiple agents on the same repo simultaneously — each gets its own working directory and branch without interfering with the others or your main checkout.
@@ -161,7 +177,7 @@ Pass `--worktree` to run each agent in an isolated [git worktree](https://git-sc
161
177
 
162
178
  - Worktrees with commits are **preserved** after the run so you can review, merge, or cherry-pick the work. gnhf prints the path and cleanup command.
163
179
  - Re-running the same prompt with `--worktree` resumes a preserved matching worktree when possible; otherwise gnhf creates a suffixed worktree such as `<run-slug>-1` if the original name is unavailable.
164
- - Worktrees with **no commits** are automatically removed on exit.
180
+ - Worktrees with **no commits** are automatically removed on exit unless a pending commit failure left uncommitted work to inspect or repair.
165
181
  - `--worktree` must be run from a non-gnhf branch (typically `main`).
166
182
 
167
183
  ## CLI Reference
@@ -182,9 +198,12 @@ If you run `gnhf` on an existing `gnhf/` branch with a different prompt, gnhf as
182
198
  | `--agent <agent>` | Agent to use (`claude`, `codex`, `rovodev`, `opencode`, `copilot`, `pi`, or `acp:<target-or-command>`) | config file (`claude`) |
183
199
  | `--max-iterations <n>` | Abort after `n` total iterations | unlimited |
184
200
  | `--max-tokens <n>` | Abort after `n` total input+output tokens | unlimited |
185
- | `--stop-when <cond>` | End the loop when the agent reports this condition; persists across resume | unlimited |
201
+ | `--stop-when <cond>` | End when the agent reports this condition, after any commit-failure repair; persists across resume | unlimited |
186
202
  | `--prevent-sleep <mode>` | Prevent system sleep during the run (`on`/`off` or `true`/`false`) | config file (`on`) |
187
203
  | `--worktree` | Run in a separate git worktree (enables multiple agents concurrently) | `false` |
204
+ | `--current-branch` | Run on the current branch instead of creating a `gnhf/` branch | `false` |
205
+ | `--push` | Push the current branch after each successful iteration | `false` |
206
+ | `--meteor-frequency <n>` | Set TUI meteor frequency from 0 to 5 (`0` disables meteors) | `3` |
188
207
  | `--version` | Show version | |
189
208
 
190
209
  ## Configuration
package/dist/cli.mjs CHANGED
@@ -416,6 +416,33 @@ const NOT_GIT_REPOSITORY_MESSAGE = "This command must be run inside a Git reposi
416
416
  function translateGitError(error) {
417
417
  return error instanceof Error ? error : new Error(String(error));
418
418
  }
419
+ function outputText(value) {
420
+ if (typeof value === "string") return value;
421
+ if (Buffer.isBuffer(value)) return value.toString("utf-8");
422
+ if (value instanceof Uint8Array) return Buffer.from(value).toString("utf-8");
423
+ return "";
424
+ }
425
+ function formatGitError(error) {
426
+ const parts = [
427
+ error instanceof Error ? error.message : String(error),
428
+ outputText(error.stdout),
429
+ outputText(error.stderr)
430
+ ].map((part) => part.trim()).filter(Boolean);
431
+ return [...new Set(parts)].join("\n");
432
+ }
433
+ function firstLine(text) {
434
+ return text.split("\n").find((line) => line.trim())?.trim() ?? text;
435
+ }
436
+ var CommitFailedError = class extends Error {
437
+ detail;
438
+ constructor(error) {
439
+ const detail = formatGitError(error);
440
+ super(`git commit failed: ${firstLine(detail)}`);
441
+ this.name = "CommitFailedError";
442
+ this.detail = detail;
443
+ this.cause = error;
444
+ }
445
+ };
419
446
  function git(args, cwd, options = {}) {
420
447
  const env = {
421
448
  ...options.env ?? process.env,
@@ -572,18 +599,52 @@ function commitAll(message, cwd) {
572
599
  message
573
600
  ];
574
601
  git(["add", "-A"], cwd);
575
- let firstError;
576
602
  try {
577
- git(commitArgs, cwd);
603
+ git([
604
+ "diff",
605
+ "--cached",
606
+ "--quiet"
607
+ ], cwd);
578
608
  return;
609
+ } catch {}
610
+ try {
611
+ git(commitArgs, cwd);
579
612
  } catch (error) {
580
- firstError = error;
613
+ const commitError = new CommitFailedError(error);
614
+ appendDebugLog("git:commit:failed", {
615
+ error: serializeError(error),
616
+ detail: commitError.detail
617
+ });
618
+ throw commitError;
581
619
  }
582
- git(["add", "-A"], cwd);
620
+ }
621
+ function pushCurrentBranch(cwd) {
622
+ let hasUpstream = true;
583
623
  try {
584
- git([...commitArgs, "--no-verify"], cwd);
585
- appendDebugLog("git:commit:no-verify-fallback", { firstError: serializeError(firstError) });
586
- } catch {}
624
+ git([
625
+ "rev-parse",
626
+ "--abbrev-ref",
627
+ "--symbolic-full-name",
628
+ "@{upstream}"
629
+ ], cwd);
630
+ } catch {
631
+ hasUpstream = false;
632
+ }
633
+ if (hasUpstream) {
634
+ git(["push"], cwd);
635
+ return;
636
+ }
637
+ git([
638
+ "remote",
639
+ "get-url",
640
+ "origin"
641
+ ], cwd);
642
+ git([
643
+ "push",
644
+ "-u",
645
+ "origin",
646
+ "HEAD"
647
+ ], cwd);
587
648
  }
588
649
  function resetHard(cwd) {
589
650
  git([
@@ -16387,6 +16448,7 @@ var Orchestrator = class extends EventEmitter {
16387
16448
  activeIterationPromise = null;
16388
16449
  activeAbortController = null;
16389
16450
  pendingAbortReason = null;
16451
+ pendingCommitFailure = null;
16390
16452
  activeIterationTokensEstimated = false;
16391
16453
  loopDone = false;
16392
16454
  stoppedEventEmitted = false;
@@ -16423,7 +16485,8 @@ var Orchestrator = class extends EventEmitter {
16423
16485
  return {
16424
16486
  ...this.state,
16425
16487
  tokensEstimated: this.state.tokensEstimated || this.activeIterationTokensEstimated,
16426
- interruptHint: getInterruptHint(this.state)
16488
+ interruptHint: getInterruptHint(this.state),
16489
+ hasPendingCommitFailure: this.pendingCommitFailure !== null
16427
16490
  };
16428
16491
  }
16429
16492
  requestGracefulStop() {
@@ -16476,6 +16539,7 @@ var Orchestrator = class extends EventEmitter {
16476
16539
  await iterationPromise;
16477
16540
  } else await this.closeAgent();
16478
16541
  resetHard(this.cwd);
16542
+ this.pendingCommitFailure = null;
16479
16543
  this.state.status = "stopped";
16480
16544
  this.emit("state", this.getState());
16481
16545
  this.emitStopped();
@@ -16491,6 +16555,7 @@ var Orchestrator = class extends EventEmitter {
16491
16555
  startIteration: this.state.currentIteration,
16492
16556
  maxIterations: this.limits.maxIterations,
16493
16557
  maxTokens: this.limits.maxTokens,
16558
+ push: this.limits.push === true,
16494
16559
  maxConsecutiveFailures: this.config.maxConsecutiveFailures,
16495
16560
  baseCommit: this.runInfo.baseCommit,
16496
16561
  initialCommitCount: this.state.commitCount
@@ -16507,13 +16572,14 @@ var Orchestrator = class extends EventEmitter {
16507
16572
  this.state.status = "running";
16508
16573
  this.emit("iteration:start", this.state.currentIteration);
16509
16574
  this.emit("state", this.getState());
16510
- const iterationPrompt = buildIterationPrompt({
16575
+ const baseIterationPrompt = buildIterationPrompt({
16511
16576
  n: this.state.currentIteration,
16512
16577
  runId: this.runInfo.runId,
16513
16578
  prompt: this.prompt,
16514
16579
  stopWhen: this.limits.stopWhen,
16515
16580
  commitMessage: this.config.commitMessage
16516
16581
  });
16582
+ const iterationPrompt = this.pendingCommitFailure ? this.buildCommitRepairPrompt(baseIterationPrompt) : baseIterationPrompt;
16517
16583
  appendDebugLog("iteration:start", {
16518
16584
  iteration: this.state.currentIteration,
16519
16585
  promptLength: iterationPrompt.length,
@@ -16560,6 +16626,10 @@ var Orchestrator = class extends EventEmitter {
16560
16626
  tokensEstimated: this.state.tokensEstimated,
16561
16627
  commitCount: this.state.commitCount
16562
16628
  });
16629
+ if (result.abortReason) {
16630
+ this.abort(result.abortReason);
16631
+ break;
16632
+ }
16563
16633
  if (this.stopForGracefulShutdown()) break;
16564
16634
  if (this.limits.stopWhen !== void 0 && result.shouldFullyStop) {
16565
16635
  this.abort("stop condition met");
@@ -16669,11 +16739,16 @@ var Orchestrator = class extends EventEmitter {
16669
16739
  });
16670
16740
  if (this.stopRequested) return { type: "stopped" };
16671
16741
  const shouldFullyStop = result.output.should_fully_stop === true;
16672
- if (result.output.success) return {
16673
- type: "completed",
16674
- record: this.recordSuccess(result.output),
16675
- shouldFullyStop
16676
- };
16742
+ if (result.output.success) {
16743
+ const record = this.recordSuccess(result.output);
16744
+ const abortReason = record.success && this.limits.push === true ? this.pushAfterSuccess() : void 0;
16745
+ return {
16746
+ type: "completed",
16747
+ record,
16748
+ shouldFullyStop: record.success ? shouldFullyStop : false,
16749
+ ...abortReason === void 0 ? {} : { abortReason }
16750
+ };
16751
+ }
16677
16752
  return {
16678
16753
  type: "completed",
16679
16754
  record: this.recordFailure(`[FAIL] ${result.output.summary}`, result.output.summary, toStringArray(result.output.key_learnings), "reported"),
@@ -16691,7 +16766,7 @@ var Orchestrator = class extends EventEmitter {
16691
16766
  elapsedMs,
16692
16767
  reason: this.pendingAbortReason
16693
16768
  });
16694
- resetHard(this.cwd);
16769
+ if (this.pendingCommitFailure === null) resetHard(this.cwd);
16695
16770
  return {
16696
16771
  type: "aborted",
16697
16772
  reason: this.pendingAbortReason
@@ -16710,7 +16785,7 @@ var Orchestrator = class extends EventEmitter {
16710
16785
  error: serializeError(err)
16711
16786
  });
16712
16787
  if (err instanceof PermanentAgentError) {
16713
- resetHard(this.cwd);
16788
+ if (this.pendingCommitFailure === null) resetHard(this.cwd);
16714
16789
  this.state.lastAgentError = err.detail;
16715
16790
  return {
16716
16791
  type: "aborted",
@@ -16729,8 +16804,16 @@ var Orchestrator = class extends EventEmitter {
16729
16804
  }
16730
16805
  }
16731
16806
  recordSuccess(output) {
16732
- appendNotes(this.runInfo.notesPath, this.state.currentIteration, output.summary, toStringArray(output.key_changes_made), toStringArray(output.key_learnings));
16733
- commitAll(buildCommitMessage(this.config.commitMessage, output, { iteration: this.state.currentIteration }), this.cwd);
16807
+ const keyChanges = toStringArray(output.key_changes_made);
16808
+ const keyLearnings = toStringArray(output.key_learnings);
16809
+ try {
16810
+ commitAll(buildCommitMessage(this.config.commitMessage, output, { iteration: this.state.currentIteration }), this.cwd);
16811
+ } catch (error) {
16812
+ if (error instanceof CommitFailedError) return this.recordCommitFailure(error);
16813
+ throw error;
16814
+ }
16815
+ this.pendingCommitFailure = null;
16816
+ appendNotes(this.runInfo.notesPath, this.state.currentIteration, output.summary, keyChanges, keyLearnings);
16734
16817
  this.state.commitCount = getBranchCommitCount(this.runInfo.baseCommit, this.cwd);
16735
16818
  this.state.successCount++;
16736
16819
  this.state.consecutiveFailures = 0;
@@ -16740,14 +16823,60 @@ var Orchestrator = class extends EventEmitter {
16740
16823
  number: this.state.currentIteration,
16741
16824
  success: true,
16742
16825
  summary: output.summary,
16743
- keyChanges: toStringArray(output.key_changes_made),
16744
- keyLearnings: toStringArray(output.key_learnings),
16826
+ keyChanges,
16827
+ keyLearnings,
16745
16828
  timestamp: /* @__PURE__ */ new Date()
16746
16829
  };
16747
16830
  }
16831
+ buildCommitRepairPrompt(basePrompt) {
16832
+ return `${basePrompt}
16833
+
16834
+ ## Previous Commit Failure
16835
+
16836
+ The previous iteration made workspace changes, but gnhf could not commit them because git commit failed.
16837
+ Do not start unrelated work.
16838
+ Inspect and fix the existing uncommitted changes so the commit can pass, then report success.
16839
+
16840
+ Git commit output:
16841
+
16842
+ \`\`\`
16843
+ ${this.pendingCommitFailure}
16844
+ \`\`\``;
16845
+ }
16846
+ recordCommitFailure(error) {
16847
+ this.pendingCommitFailure = error.detail;
16848
+ const summary = "git commit failed; asking agent to repair the workspace";
16849
+ appendNotes(this.runInfo.notesPath, this.state.currentIteration, `[ERROR] ${summary}`, [], [error.detail]);
16850
+ this.state.failCount++;
16851
+ this.state.consecutiveFailures++;
16852
+ this.state.consecutiveErrors = 0;
16853
+ this.state.lastAgentError = error.detail;
16854
+ return {
16855
+ number: this.state.currentIteration,
16856
+ success: false,
16857
+ summary,
16858
+ keyChanges: [],
16859
+ keyLearnings: [error.detail],
16860
+ timestamp: /* @__PURE__ */ new Date()
16861
+ };
16862
+ }
16863
+ pushAfterSuccess() {
16864
+ try {
16865
+ pushCurrentBranch(this.cwd);
16866
+ appendDebugLog("git:push:success", { iteration: this.state.currentIteration });
16867
+ return;
16868
+ } catch (err) {
16869
+ appendDebugLog("git:push:error", {
16870
+ iteration: this.state.currentIteration,
16871
+ error: serializeError(err)
16872
+ });
16873
+ return `push failed: ${err instanceof Error ? err.message : String(err)}`;
16874
+ }
16875
+ }
16748
16876
  recordFailure(notesSummary, recordSummary, learnings, kind) {
16877
+ const hadPendingCommitFailure = this.pendingCommitFailure !== null;
16749
16878
  appendNotes(this.runInfo.notesPath, this.state.currentIteration, notesSummary, [], toStringArray(learnings));
16750
- resetHard(this.cwd);
16879
+ if (!hadPendingCommitFailure) resetHard(this.cwd);
16751
16880
  this.state.failCount++;
16752
16881
  this.state.consecutiveFailures++;
16753
16882
  if (kind === "error") {
@@ -16961,7 +17090,7 @@ function renderExitSummary(options) {
16961
17090
  const title = stopped ? `${s.red("×")} ${s.bold("gnhf stopped")}` : `${s.cyan("✦")} ${s.bold("gnhf wrapped")}`;
16962
17091
  const subtitle = stopped ? `${s.cyan(options.agentName)} ran for ${s.yellow(elapsed)} before: ${options.abortReason ?? options.status}` : `${s.cyan(options.agentName)} worked for ${s.yellow(elapsed)} on ${s.magenta(options.branchName)}`;
16963
17092
  const cardWidth = resolveCardWidth([title, ` ${subtitle}`], options.terminalColumns);
16964
- const rolledBack = `${options.failCount} rolled back`;
17093
+ const failed = `${options.failCount} failed`;
16965
17094
  const inputTokens = formatTokenCount$1(options.totalInputTokens, "in", options.tokensEstimated);
16966
17095
  const outputTokens = formatTokenCount$1(options.totalOutputTokens, "out", options.tokensEstimated);
16967
17096
  const commits = plural(options.commitCount, "commit");
@@ -16976,7 +17105,7 @@ function renderExitSummary(options) {
16976
17105
  metricLine(s.dim("iterations"), [
16977
17106
  `${s.bold(String(options.iterations))} total`,
16978
17107
  s.green(`${options.successCount} good`),
16979
- stopped ? s.red(rolledBack) : s.yellow(rolledBack)
17108
+ stopped ? s.red(failed) : s.yellow(failed)
16980
17109
  ]),
16981
17110
  metricLine(s.dim("tokens"), [s.bold(inputTokens), s.bold(outputTokens)]),
16982
17111
  metricLine(s.dim("branch diff"), [
@@ -16992,6 +17121,7 @@ function renderExitSummary(options) {
16992
17121
  "",
16993
17122
  commandLine(s.dim("notes"), options.notesPath),
16994
17123
  commandLine(s.dim("debug log"), options.logPath),
17124
+ ...options.hasPendingCommitFailure ? [commandLine(s.yellow("uncommitted"), "commit failed; changes were left for repair")] : [],
16995
17125
  "",
16996
17126
  commandLine(s.dim("next steps"), s.cyan(`git log --oneline ${options.baseRef}..HEAD`)),
16997
17127
  continuationLine(s.cyan(`git diff --stat ${options.baseRef}..HEAD`)),
@@ -17157,6 +17287,7 @@ const STAR_CHARS = [
17157
17287
  "°",
17158
17288
  "°"
17159
17289
  ];
17290
+ const MIN_METEOR_START_GAP_MS = 500;
17160
17291
  function generateStarField(width, height, density, seed) {
17161
17292
  const stars = [];
17162
17293
  let s = seed;
@@ -17191,6 +17322,47 @@ function getStarState(star, now) {
17191
17322
  if (t > .025) return "bright";
17192
17323
  return "dim";
17193
17324
  }
17325
+ function generateMeteorShower(width, height, count, seed) {
17326
+ if (width <= 0 || height <= 0 || count <= 0) return [];
17327
+ const meteors = [];
17328
+ let s = seed % 2147483647;
17329
+ if (s <= 0) s += 2147483646;
17330
+ const rand = () => {
17331
+ s = s * 16807 % 2147483647;
17332
+ return s / 2147483647;
17333
+ };
17334
+ for (let i = 0; i < count; i++) {
17335
+ const length = Math.min(height, 2 + (i + Math.floor(rand() * 6)) % 6);
17336
+ const xMax = Math.max(1, width - length);
17337
+ const yMin = Math.max(0, length - 1);
17338
+ const yMaxExclusive = Math.max(yMin + 1, Math.ceil(height * .75));
17339
+ const ySpan = Math.max(1, yMaxExclusive - yMin);
17340
+ const period = 16e3 + rand() * 2e4;
17341
+ const duration = count >= 8 ? 3e3 + rand() * 600 : 900 + rand() * 500;
17342
+ meteors.push({
17343
+ x: Math.floor(rand() * xMax),
17344
+ y: yMin + Math.floor(rand() * ySpan),
17345
+ length,
17346
+ phase: i === 0 ? 0 : i * (MIN_METEOR_START_GAP_MS + 80) + rand() * 60,
17347
+ period,
17348
+ duration
17349
+ });
17350
+ }
17351
+ return meteors;
17352
+ }
17353
+ function getMeteorTrail(meteor, now) {
17354
+ const cycleTime = ((now - meteor.phase) % meteor.period + meteor.period) % meteor.period;
17355
+ if (cycleTime >= meteor.duration) return [];
17356
+ const step = Math.floor(cycleTime / 120);
17357
+ const cells = [];
17358
+ for (let i = 0; i < meteor.length; i++) cells.push({
17359
+ x: meteor.x - step + i,
17360
+ y: meteor.y + step - i,
17361
+ state: i === 0 ? "bright" : "dim",
17362
+ char: "╱"
17363
+ });
17364
+ return cells;
17365
+ }
17194
17366
  //#endregion
17195
17367
  //#region src/utils/moon.ts
17196
17368
  const MOON_PHASES = [
@@ -17414,6 +17586,8 @@ const CONTENT_WIDTH = 63;
17414
17586
  const MAX_PROMPT_LINES = 3;
17415
17587
  const BASE_CONTENT_ROWS = 24;
17416
17588
  const STAR_DENSITY = .035;
17589
+ const DEFAULT_METEOR_FREQUENCY = 3;
17590
+ const METEOR_SEED_OFFSET = 101;
17417
17591
  const TICK_MS = 200;
17418
17592
  const MOONS_PER_ROW = 30;
17419
17593
  const MOON_PHASE_PERIOD = 1600;
@@ -17517,6 +17691,17 @@ function starStyle(state) {
17517
17691
  if (state === "dim") return "dim";
17518
17692
  return "normal";
17519
17693
  }
17694
+ function meteorCountForFrequency(frequency) {
17695
+ if (frequency <= 0) return 0;
17696
+ if (frequency === 1) return 1;
17697
+ if (frequency === 2) return 2;
17698
+ if (frequency === 3) return 4;
17699
+ if (frequency === 4) return 6;
17700
+ return 28;
17701
+ }
17702
+ function meteorsStartingBefore(meteors, rowOffset, maxStartRow) {
17703
+ return meteors.filter((meteor) => rowOffset + meteor.y < maxStartRow);
17704
+ }
17520
17705
  function placeStarsInCells(cells, stars, row, xMin, xMax, xOffset, now) {
17521
17706
  for (const star of stars) {
17522
17707
  if (star.y !== row || star.x < xMin || star.x >= xMax) continue;
@@ -17533,15 +17718,28 @@ function placeStarsInCells(cells, stars, row, xMin, xMax, xOffset, now) {
17533
17718
  };
17534
17719
  }
17535
17720
  }
17536
- function renderStarLineCells(stars, width, y, now) {
17721
+ function placeMeteorsInCells(cells, meteors, row, xMin, xMax, xOffset, now) {
17722
+ for (const meteor of meteors) for (const trail of getMeteorTrail(meteor, now)) {
17723
+ if (trail.y !== row || trail.x < xMin || trail.x >= xMax) continue;
17724
+ const localX = trail.x - xOffset;
17725
+ cells[localX] = {
17726
+ char: trail.char,
17727
+ style: trail.state === "bright" ? "bold" : "dim",
17728
+ width: 1
17729
+ };
17730
+ }
17731
+ }
17732
+ function renderStarLineCells(stars, meteors, width, y, now) {
17537
17733
  const cells = emptyCells(width);
17538
17734
  placeStarsInCells(cells, stars, y, 0, width, 0, now);
17735
+ placeMeteorsInCells(cells, meteors, y, 0, width, 0, now);
17539
17736
  return cells;
17540
17737
  }
17541
- function renderSideStarsCells(stars, rowIndex, xOffset, sideWidth, now) {
17738
+ function renderSideStarsCells(stars, meteors, rowIndex, xOffset, sideWidth, now) {
17542
17739
  if (sideWidth <= 0) return [];
17543
17740
  const cells = emptyCells(sideWidth);
17544
17741
  placeStarsInCells(cells, stars, rowIndex, xOffset, xOffset + sideWidth, xOffset, now);
17742
+ placeMeteorsInCells(cells, meteors, rowIndex, xOffset, xOffset + sideWidth, xOffset, now);
17545
17743
  return cells;
17546
17744
  }
17547
17745
  function clampCellsToWidth(content, width) {
@@ -17658,7 +17856,7 @@ function buildContentCells(prompt, agentName, state, elapsed, now, availableHeig
17658
17856
  }
17659
17857
  return rows;
17660
17858
  }
17661
- function buildFrameCells(prompt, agentName, state, topStars, bottomStars, sideStars, now, terminalWidth, terminalHeight) {
17859
+ function buildFrameCells(prompt, agentName, state, topStars, bottomStars, sideStars, now, terminalWidth, terminalHeight, topMeteors = [], bottomMeteors = [], sideMeteors = []) {
17662
17860
  const elapsed = formatElapsed(now - state.startTime.getTime());
17663
17861
  const availableHeight = Math.max(0, terminalHeight - 2);
17664
17862
  const contentRows = buildContentCells(prompt, agentName, state, elapsed, now, availableHeight);
@@ -17667,20 +17865,24 @@ function buildFrameCells(prompt, agentName, state, topStars, bottomStars, sideSt
17667
17865
  const remaining = Math.max(0, availableHeight - contentCount);
17668
17866
  const topHeight = Math.max(0, Math.ceil(remaining / 2));
17669
17867
  const bottomHeight = remaining - topHeight;
17868
+ const maxMeteorStartRow = Math.ceil(availableHeight * .75);
17869
+ const visibleTopMeteors = meteorsStartingBefore(topMeteors, 0, maxMeteorStartRow);
17870
+ const visibleSideMeteors = meteorsStartingBefore(sideMeteors, topHeight, maxMeteorStartRow);
17871
+ const visibleBottomMeteors = meteorsStartingBefore(bottomMeteors, topHeight + contentCount, maxMeteorStartRow);
17670
17872
  const sideWidth = Math.max(0, Math.floor((terminalWidth - CONTENT_WIDTH) / 2));
17671
17873
  const frame = [];
17672
- for (let y = 0; y < topHeight; y++) frame.push(renderStarLineCells(topStars, terminalWidth, y, now));
17874
+ for (let y = 0; y < topHeight; y++) frame.push(renderStarLineCells(topStars, visibleTopMeteors, terminalWidth, y, now));
17673
17875
  for (let i = 0; i < contentRows.length; i++) {
17674
- const left = renderSideStarsCells(sideStars, i, 0, sideWidth, now);
17876
+ const left = renderSideStarsCells(sideStars, visibleSideMeteors, i, 0, sideWidth, now);
17675
17877
  const center = centerLineCells(contentRows[i], CONTENT_WIDTH);
17676
- const right = renderSideStarsCells(sideStars, i, terminalWidth - sideWidth, sideWidth, now);
17878
+ const right = renderSideStarsCells(sideStars, visibleSideMeteors, i, terminalWidth - sideWidth, sideWidth, now);
17677
17879
  frame.push([
17678
17880
  ...left,
17679
17881
  ...center,
17680
17882
  ...right
17681
17883
  ]);
17682
17884
  }
17683
- for (let y = 0; y < bottomHeight; y++) frame.push(renderStarLineCells(bottomStars, terminalWidth, y, now));
17885
+ for (let y = 0; y < bottomHeight; y++) frame.push(renderStarLineCells(bottomStars, visibleBottomMeteors, terminalWidth, y, now));
17684
17886
  frame.push(renderResumeHintCells(terminalWidth, state.interruptHint));
17685
17887
  frame.push(emptyCells(terminalWidth));
17686
17888
  return frame;
@@ -17696,8 +17898,11 @@ var Renderer = class {
17696
17898
  topStars = [];
17697
17899
  bottomStars = [];
17698
17900
  sideStars = [];
17901
+ topMeteors = [];
17902
+ bottomMeteors = [];
17699
17903
  cachedWidth = 0;
17700
17904
  cachedHeight = 0;
17905
+ meteorFrequency;
17701
17906
  prevCells = [];
17702
17907
  prevTitle = null;
17703
17908
  titleSaved = false;
@@ -17716,11 +17921,12 @@ var Renderer = class {
17716
17921
  handleStopped = () => {
17717
17922
  this.stop("stopped");
17718
17923
  };
17719
- constructor(orchestrator, prompt, agentName, onInterrupt) {
17924
+ constructor(orchestrator, prompt, agentName, onInterrupt, options = {}) {
17720
17925
  this.orchestrator = orchestrator;
17721
17926
  this.prompt = prompt;
17722
17927
  this.agentName = agentName;
17723
17928
  this.onInterrupt = onInterrupt;
17929
+ this.meteorFrequency = Math.max(0, Math.floor(options.meteorFrequency ?? DEFAULT_METEOR_FREQUENCY));
17724
17930
  this.state = orchestrator.getState();
17725
17931
  this.seedTop = Math.floor(Math.random() * 2147483646) + 1;
17726
17932
  this.seedBottom = Math.floor(Math.random() * 2147483646) + 1;
@@ -17773,6 +17979,7 @@ var Renderer = class {
17773
17979
  const availableHeight = Math.max(0, h - 2);
17774
17980
  const remaining = Math.max(0, availableHeight - BASE_CONTENT_ROWS);
17775
17981
  const topHeight = Math.max(0, Math.ceil(remaining / 2));
17982
+ const bottomHeight = Math.max(0, remaining - topHeight);
17776
17983
  const proximityRows = 8;
17777
17984
  const shrinkBig = (s, nearContentRow) => {
17778
17985
  if (!nearContentRow || s.x < contentStart || s.x >= contentEnd) return s;
@@ -17788,6 +17995,8 @@ var Renderer = class {
17788
17995
  this.topStars = generateStarField(w, h, STAR_DENSITY, this.seedTop).map((s) => shrinkBig(s, s.y >= topHeight - proximityRows));
17789
17996
  this.bottomStars = generateStarField(w, h, STAR_DENSITY, this.seedBottom).map((s) => shrinkBig(s, s.y < proximityRows));
17790
17997
  this.sideStars = generateStarField(w, Math.max(BASE_CONTENT_ROWS, availableHeight), STAR_DENSITY, this.seedSide);
17998
+ this.topMeteors = generateMeteorShower(w, topHeight, topHeight > 0 ? meteorCountForFrequency(this.meteorFrequency) : 0, this.seedTop + METEOR_SEED_OFFSET);
17999
+ this.bottomMeteors = generateMeteorShower(w, bottomHeight, bottomHeight > 0 ? meteorCountForFrequency(this.meteorFrequency) : 0, this.seedBottom + METEOR_SEED_OFFSET);
17791
18000
  return true;
17792
18001
  }
17793
18002
  return false;
@@ -17798,7 +18007,7 @@ var Renderer = class {
17798
18007
  const h = process$1.stdout.rows || 24;
17799
18008
  const resized = this.ensureStarFields(w, h);
17800
18009
  this.updateTerminalTitle(now);
17801
- const nextCells = buildFrameCells(this.prompt, this.agentName, this.state, this.topStars, this.bottomStars, this.sideStars, now, w, h);
18010
+ const nextCells = buildFrameCells(this.prompt, this.agentName, this.state, this.topStars, this.bottomStars, this.sideStars, now, w, h, this.topMeteors, this.bottomMeteors);
17802
18011
  if (this.isFirstFrame || resized) {
17803
18012
  process$1.stdout.write("\x1B[H" + nextCells.map(rowToString).join("\n"));
17804
18013
  this.isFirstFrame = false;
@@ -17829,6 +18038,7 @@ function slugifyPrompt(prompt) {
17829
18038
  //#region src/cli.ts
17830
18039
  const packageVersion = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8")).version;
17831
18040
  const FORCE_EXIT_TIMEOUT_MS = 5e3;
18041
+ const MAX_METEOR_FREQUENCY = 5;
17832
18042
  const GNHF_REEXEC_STDIN_PROMPT = "GNHF_REEXEC_STDIN_PROMPT";
17833
18043
  const GNHF_REEXEC_STDIN_PROMPT_FILE = "GNHF_REEXEC_STDIN_PROMPT_FILE";
17834
18044
  const GNHF_REEXEC_STDIN_PROMPT_DIR_PREFIX = "gnhf-stdin-";
@@ -17847,6 +18057,11 @@ function parseNonNegativeInteger(value) {
17847
18057
  if (!Number.isSafeInteger(parsed)) throw new InvalidArgumentError("must be a safe integer");
17848
18058
  return parsed;
17849
18059
  }
18060
+ function parseMeteorFrequency(value) {
18061
+ const parsed = parseNonNegativeInteger(value);
18062
+ if (parsed > MAX_METEOR_FREQUENCY) throw new InvalidArgumentError(`must be between 0 and ${MAX_METEOR_FREQUENCY}`);
18063
+ return parsed;
18064
+ }
17850
18065
  function parseOnOffBoolean(value) {
17851
18066
  if (value === "on" || value === "true") return true;
17852
18067
  if (value === "off" || value === "false") return false;
@@ -17923,6 +18138,11 @@ function initializeNewBranch(prompt, cwd, schemaOptions) {
17923
18138
  const runId = createBranchWithSuffix(slugifyPrompt(prompt), cwd).split("/")[1];
17924
18139
  return setupRun(runId, prompt, baseCommit, cwd, schemaOptions);
17925
18140
  }
18141
+ function initializeCurrentBranchRun(prompt, cwd, schemaOptions) {
18142
+ ensureCleanWorkingTree(cwd);
18143
+ const baseCommit = getHeadCommit(cwd);
18144
+ return setupRun(createRunIdWithSuffix(slugifyPrompt(prompt).split("/")[1], cwd), prompt, baseCommit, cwd, schemaOptions);
18145
+ }
17926
18146
  function branchNameWithSuffix(branchName, suffix) {
17927
18147
  return suffix === 0 ? branchName : `${branchName}-${suffix}`;
17928
18148
  }
@@ -17942,6 +18162,16 @@ function createBranchWithSuffix(branchName, cwd) {
17942
18162
  }
17943
18163
  throw new Error(`Unable to create a unique branch name for ${branchName}`);
17944
18164
  }
18165
+ function runIdWithSuffix(runId, suffix) {
18166
+ return suffix === 0 ? runId : `${runId}-${suffix}`;
18167
+ }
18168
+ function createRunIdWithSuffix(runId, cwd) {
18169
+ for (let suffix = 0; suffix < 100; suffix += 1) {
18170
+ const candidate = runIdWithSuffix(runId, suffix);
18171
+ if (!existsSync(join(cwd, ".gnhf", "runs", candidate))) return candidate;
18172
+ }
18173
+ throw new Error(`Unable to create a unique run id for ${runId}`);
18174
+ }
17945
18175
  function initializeWorktreeRun(prompt, cwd, schemaOptions, resumeSchemaOptions) {
17946
18176
  const repoRoot = getRepoRootDir(cwd);
17947
18177
  const baseCommit = getHeadCommit(cwd);
@@ -18119,13 +18349,13 @@ function readReexecStdinPrompt(env) {
18119
18349
  }
18120
18350
  }
18121
18351
  const program = new Command();
18122
- program.name("gnhf").description("Before I go to bed, I tell my agents: good night, have fun").version(packageVersion).argument("[prompt]", "The objective for the coding agent").option("--agent <agent>", `Agent to use (${AGENT_NAMES.join(", ")}, or acp:<target-or-command>)`).option("--max-iterations <n>", "Abort after N total iterations", parseNonNegativeInteger).option("--max-tokens <n>", "Abort after N total input+output tokens", parseNonNegativeInteger).option("--stop-when <condition>", "End when the agent reports this condition; resumes reuse it, pass a new value to overwrite or \"\" to clear").option("--prevent-sleep <mode>", "Prevent system sleep during the run (\"on\" or \"off\")", parseOnOffBoolean).option("--worktree", "Run in a separate git worktree (enables multiple agents on the same repo)", false).option("--mock", "", false).action(async (promptArg, options) => {
18352
+ program.name("gnhf").description("Before I go to bed, I tell my agents: good night, have fun").version(packageVersion).argument("[prompt]", "The objective for the coding agent").option("--agent <agent>", `Agent to use (${AGENT_NAMES.join(", ")}, or acp:<target-or-command>)`).option("--max-iterations <n>", "Abort after N total iterations", parseNonNegativeInteger).option("--max-tokens <n>", "Abort after N total input+output tokens", parseNonNegativeInteger).option("--stop-when <condition>", "End when the agent reports this condition, after any commit-failure repair; resumes reuse it, pass a new value to overwrite or \"\" to clear").option("--prevent-sleep <mode>", "Prevent system sleep during the run (\"on\" or \"off\")", parseOnOffBoolean).option("--worktree", "Run in a separate git worktree (enables multiple agents on the same repo)", false).option("--current-branch", "Run on the current branch instead of creating a gnhf branch", false).option("--push", "Push the current branch after each successful iteration", false).option("--meteor-frequency <n>", "Meteor frequency from 0 to 5 (0 disables, 3 is default)", parseMeteorFrequency, 3).option("--mock", "", false).action(async (promptArg, options) => {
18123
18353
  if (options.mock) {
18124
18354
  const mock = new MockOrchestrator();
18125
18355
  enterAltScreen();
18126
- const renderer = new Renderer(mock, "let's minimize app startup latency without sacrificing any functionality", "claude", () => {
18356
+ const renderer = new Renderer(mock, "let's minimize app startup latency without sacrificing any functionality", "codex", () => {
18127
18357
  mock.handleInterrupt();
18128
- });
18358
+ }, { meteorFrequency: options.meteorFrequency });
18129
18359
  renderer.start();
18130
18360
  mock.start();
18131
18361
  await renderer.waitUntilExit();
@@ -18168,6 +18398,10 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
18168
18398
  let worktreeCleanup = null;
18169
18399
  const currentBranch = getCurrentBranch(cwd);
18170
18400
  const onGnhfBranch = currentBranch.startsWith("gnhf/");
18401
+ if (options.currentBranch && options.worktree) {
18402
+ console.error("Cannot combine --current-branch and --worktree.");
18403
+ process$1.exit(1);
18404
+ }
18171
18405
  const cliStopWhen = options.stopWhen === "" ? void 0 : options.stopWhen;
18172
18406
  let effectiveStopWhen = cliStopWhen;
18173
18407
  let effectiveCommitMessage = config.commitMessage;
@@ -18204,6 +18438,12 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
18204
18438
  if (worktreeCleanup === exitCleanup) exitCleanup();
18205
18439
  });
18206
18440
  }
18441
+ } else if (options.currentBranch) {
18442
+ if (!prompt) {
18443
+ program.help();
18444
+ return;
18445
+ }
18446
+ runInfo = initializeCurrentBranchRun(prompt, cwd, schemaOptions);
18207
18447
  } else if (onGnhfBranch) {
18208
18448
  const existingRunId = currentBranch.slice(5);
18209
18449
  const existingMetadata = peekRunMetadata(existingRunId, cwd);
@@ -18260,7 +18500,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
18260
18500
  if (!reexeced) persistedPrompt?.cleanup();
18261
18501
  }
18262
18502
  }
18263
- const runMode = options.worktree ? "worktree" : startIteration > 0 ? "resume" : "new";
18503
+ const runMode = options.worktree ? "worktree" : options.currentBranch ? "current-branch" : startIteration > 0 ? "resume" : "new";
18264
18504
  const telemetryAgent = getTelemetryAgent(config.agent);
18265
18505
  telemetry.pageview("/run", {
18266
18506
  agent: telemetryAgent,
@@ -18283,6 +18523,8 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
18283
18523
  agentArgsOverride: getNativeAgentName(config.agent) ? config.agentArgsOverride?.[getNativeAgentName(config.agent)] : void 0,
18284
18524
  worktree: options.worktree,
18285
18525
  worktreePath,
18526
+ currentBranch: options.currentBranch,
18527
+ push: options.push,
18286
18528
  platform: process$1.platform,
18287
18529
  nodeVersion: process$1.version,
18288
18530
  gnhfVersion: packageVersion
@@ -18298,7 +18540,8 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
18298
18540
  }, agent, runInfo, prompt, effectiveCwd, startIteration, {
18299
18541
  maxIterations: options.maxIterations,
18300
18542
  maxTokens: options.maxTokens,
18301
- stopWhen: effectiveStopWhen
18543
+ stopWhen: effectiveStopWhen,
18544
+ ...options.push ? { push: true } : {}
18302
18545
  });
18303
18546
  let shutdownSignal = null;
18304
18547
  let forceShutdownRequested = false;
@@ -18329,7 +18572,7 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
18329
18572
  requestForceShutdown("SIGTERM");
18330
18573
  };
18331
18574
  enterAltScreen();
18332
- const renderer = new Renderer(orchestrator, prompt, config.agent, handleSigInt);
18575
+ const renderer = new Renderer(orchestrator, prompt, config.agent, handleSigInt, { meteorFrequency: options.meteorFrequency });
18333
18576
  renderer.start();
18334
18577
  process$1.on("SIGINT", handleSigInt);
18335
18578
  process$1.on("SIGTERM", handleSigTerm);
@@ -18390,7 +18633,8 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
18390
18633
  baseRef: runInfo.baseCommit.slice(0, 12) || runInfo.baseCommit,
18391
18634
  diffStats,
18392
18635
  color: shouldUseColor(),
18393
- terminalColumns: process$1.stdout.columns
18636
+ terminalColumns: process$1.stdout.columns,
18637
+ hasPendingCommitFailure: finalState.hasPendingCommitFailure
18394
18638
  });
18395
18639
  appendDebugLog("run:complete", {
18396
18640
  signal: shutdownSignal,
@@ -18416,12 +18660,13 @@ program.name("gnhf").description("Before I go to bed, I tell my agents: good nig
18416
18660
  total_output_tokens: finalState.totalOutputTokens,
18417
18661
  duration_ms: Date.now() - runStartedAt,
18418
18662
  prevent_sleep: config.preventSleep === true,
18663
+ push_each_iteration: options.push === true,
18419
18664
  commit_message_preset: effectiveCommitMessage?.preset ?? "default",
18420
18665
  stop_when_set: effectiveStopWhen !== void 0
18421
18666
  });
18422
18667
  await telemetry.close(1e3);
18423
18668
  if (finalState.status === "aborted") console.error(`\n gnhf: Run log: ${runInfo.logPath}\n`);
18424
- if (worktreePath) if (finalState.commitCount > 0) {
18669
+ if (worktreePath) if (finalState.commitCount > 0 || finalState.hasPendingCommitFailure) {
18425
18670
  worktreeCleanup = null;
18426
18671
  console.error(`\n gnhf: worktree preserved at ${worktreePath}\n gnhf: merge the branch and remove with: git worktree remove "${worktreePath}"\n`);
18427
18672
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnhf",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "Before I go to bed, I tell my agents: good night, have fun",
5
5
  "type": "module",
6
6
  "bin": {