gnhf 0.1.0 → 0.1.3
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 +20 -14
- package/dist/cli.mjs +32 -18
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
# gnhf
|
|
2
|
+
|
|
3
|
+
<p align="center">Before I go to bed, I tell my agents:</p>
|
|
4
|
+
<h1 align="center">good night, have fun</h1>
|
|
5
|
+
|
|
2
6
|
<p align="center">
|
|
3
7
|
<a href="https://www.npmjs.com/package/gnhf"
|
|
4
8
|
><img
|
|
@@ -33,14 +37,15 @@
|
|
|
33
37
|
/></a>
|
|
34
38
|
</p>
|
|
35
39
|
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
<p align="center">
|
|
41
|
+
<img src="docs/splash.png" alt="gnhf — Good Night, Have Fun" width="800">
|
|
42
|
+
</p>
|
|
39
43
|
|
|
40
|
-
gnhf is
|
|
44
|
+
gnhf is a [ralph loop](https://ghuntley.com/ralph/), autoresearch-style orchestrator that keeps your agents running while you sleep — each iteration makes one small, committed, documented change towards an objective.
|
|
45
|
+
You wake up to a branch full of clean work and a log of everything that happened.
|
|
41
46
|
|
|
42
|
-
- **
|
|
43
|
-
- **
|
|
47
|
+
- **Dead simple** — one command starts an autonomous loop that runs until you Ctrl+C
|
|
48
|
+
- **Long running** — each iteration is committed on success, rolled back on failure, with sensible retries and exponential backoff
|
|
44
49
|
- **Agent-agnostic** — works with Claude Code or Codex out of the box
|
|
45
50
|
|
|
46
51
|
## Quick Start
|
|
@@ -105,7 +110,7 @@ npm link
|
|
|
105
110
|
│ ┌──────────┘ │
|
|
106
111
|
▼ ▼ │
|
|
107
112
|
┌────────────┐ yes ┌──────────┐ │
|
|
108
|
-
│
|
|
113
|
+
│ 3 consec. ├─────────►│ abort │ │
|
|
109
114
|
│ failures? │ └──────────┘ │
|
|
110
115
|
└─────┬──────┘ │
|
|
111
116
|
no │ │
|
|
@@ -118,11 +123,12 @@ npm link
|
|
|
118
123
|
|
|
119
124
|
## CLI Reference
|
|
120
125
|
|
|
121
|
-
| Command
|
|
122
|
-
|
|
|
123
|
-
| `gnhf "<prompt>"`
|
|
124
|
-
| `gnhf`
|
|
125
|
-
| `echo "prompt" \| gnhf` | Pipe prompt via stdin |
|
|
126
|
+
| Command | Description |
|
|
127
|
+
| ------------------------- | ----------------------------------------------- |
|
|
128
|
+
| `gnhf "<prompt>"` | Start a new run with the given objective |
|
|
129
|
+
| `gnhf` | Resume a run (when on an existing gnhf/ branch) |
|
|
130
|
+
| `echo "<prompt>" \| gnhf` | Pipe prompt via stdin |
|
|
131
|
+
| `cat prd.md \| gnhf` | Pipe a large spec or PRD via stdin |
|
|
126
132
|
|
|
127
133
|
### Flags
|
|
128
134
|
|
|
@@ -140,7 +146,7 @@ Config lives at `~/.gnhf/config.yml`:
|
|
|
140
146
|
agent: claude
|
|
141
147
|
|
|
142
148
|
# Abort after this many consecutive failures
|
|
143
|
-
maxConsecutiveFailures:
|
|
149
|
+
maxConsecutiveFailures: 3
|
|
144
150
|
```
|
|
145
151
|
|
|
146
152
|
CLI flags override config file values.
|
package/dist/cli.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { createHash } from "node:crypto";
|
|
|
12
12
|
//#region src/core/config.ts
|
|
13
13
|
const DEFAULT_CONFIG = {
|
|
14
14
|
agent: "claude",
|
|
15
|
-
maxConsecutiveFailures:
|
|
15
|
+
maxConsecutiveFailures: 3
|
|
16
16
|
};
|
|
17
17
|
function loadConfig(overrides) {
|
|
18
18
|
const configPath = join(homedir(), ".gnhf", "config.yml");
|
|
@@ -401,11 +401,18 @@ This is iteration ${params.n} of an ongoing loop to fully accomplish the objecti
|
|
|
401
401
|
## Instructions
|
|
402
402
|
|
|
403
403
|
1. Read .gnhf/runs/${params.runId}/notes.md first to understand what has been done in previous iterations.
|
|
404
|
-
2. Focus on
|
|
405
|
-
3.
|
|
404
|
+
2. Focus on the next smallest logical unit of work that's individually testable and would make incremental progress towards the objective - that's the scope of this iteration.
|
|
405
|
+
3. If you made code changes, run build/tests/linters/formatters if available to validate your work.
|
|
406
406
|
4. Do NOT make any git commits. Commits will be handled automatically by the gnhf orchestrator.
|
|
407
407
|
5. When you are done, respond with a JSON object according to the provided schema.
|
|
408
408
|
|
|
409
|
+
## Output
|
|
410
|
+
|
|
411
|
+
- success: whether you were able to complete your iteration. set to false only if something made it impossible for you to do your work
|
|
412
|
+
- summary: a concise one-sentence summary of the accomplishment in this iteration
|
|
413
|
+
- key_changes_made: an array of descriptions for key changes you made. don't group this by file - group by logical units of work. don't describe activities - describe material outcomes
|
|
414
|
+
- key_learnings: an array of new learnings that were surprising and weren't captured by previous notes
|
|
415
|
+
|
|
409
416
|
## Objective
|
|
410
417
|
|
|
411
418
|
${params.prompt}`;
|
|
@@ -767,24 +774,30 @@ function formatTokens(count) {
|
|
|
767
774
|
//#region src/utils/wordwrap.ts
|
|
768
775
|
function wordWrap(text, width, maxLines) {
|
|
769
776
|
if (!text) return [];
|
|
770
|
-
const words = text.split(/\s+/);
|
|
771
777
|
const lines = [];
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
if (
|
|
775
|
-
|
|
776
|
-
lines.push(current);
|
|
777
|
-
current = "";
|
|
778
|
-
}
|
|
779
|
-
for (let i = 0; i < word.length; i += width) lines.push(word.slice(i, i + width));
|
|
778
|
+
for (const paragraph of text.split("\n")) {
|
|
779
|
+
const words = paragraph.split(/\s+/).filter(Boolean);
|
|
780
|
+
if (words.length === 0) {
|
|
781
|
+
lines.push("");
|
|
780
782
|
continue;
|
|
781
783
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
784
|
+
let current = "";
|
|
785
|
+
for (const word of words) {
|
|
786
|
+
if (word.length > width) {
|
|
787
|
+
if (current) {
|
|
788
|
+
lines.push(current);
|
|
789
|
+
current = "";
|
|
790
|
+
}
|
|
791
|
+
for (let i = 0; i < word.length; i += width) lines.push(word.slice(i, i + width));
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
if (current && current.length + 1 + word.length > width) {
|
|
795
|
+
lines.push(current);
|
|
796
|
+
current = word;
|
|
797
|
+
} else current = current ? current + " " + word : word;
|
|
798
|
+
}
|
|
799
|
+
if (current) lines.push(current);
|
|
786
800
|
}
|
|
787
|
-
if (current) lines.push(current);
|
|
788
801
|
if (maxLines && lines.length > maxLines) {
|
|
789
802
|
const capped = lines.slice(0, maxLines);
|
|
790
803
|
const last = capped[maxLines - 1];
|
|
@@ -1142,6 +1155,7 @@ function slugifyPrompt(prompt) {
|
|
|
1142
1155
|
}
|
|
1143
1156
|
//#endregion
|
|
1144
1157
|
//#region src/cli.ts
|
|
1158
|
+
const packageVersion = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8")).version;
|
|
1145
1159
|
function ask(question) {
|
|
1146
1160
|
const rl = createInterface({
|
|
1147
1161
|
input: process$1.stdin,
|
|
@@ -1155,7 +1169,7 @@ function ask(question) {
|
|
|
1155
1169
|
});
|
|
1156
1170
|
}
|
|
1157
1171
|
const program = new Command();
|
|
1158
|
-
program.name("gnhf").description("Before I go to bed, I tell my agents: good night, have fun").version(
|
|
1172
|
+
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 (claude or codex)", "claude").option("--mock", "", false).action(async (promptArg, options) => {
|
|
1159
1173
|
if (options.mock) {
|
|
1160
1174
|
const mock = new MockOrchestrator();
|
|
1161
1175
|
enterAltScreen();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gnhf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Before I go to bed, I tell my agents: good night, have fun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/kunchenguid/gnhf"
|
|
46
|
+
},
|
|
43
47
|
"engines": {
|
|
44
48
|
"node": ">=20"
|
|
45
49
|
}
|